From 9dcd88b8c12b2ef8eaa05ffe5f89e096a738d735 Mon Sep 17 00:00:00 2001 From: dkaraush Date: Fri, 6 Dec 2024 21:05:26 +0400 Subject: [PATCH] update to 11.5.3 (5511) --- TMessagesProj/jni/audio.c | 40 +- TMessagesProj/jni/gifvideo.cpp | 7 +- .../src/main/assets/models/coin_stars.binobj | Bin 34532 -> 45840 bytes .../src/main/assets/models/deal_logo.binobj | Bin 0 -> 247148 bytes .../exoplayer2/DefaultRenderersFactory.java | 5 + .../google/android/exoplayer2/ExoPlayer.java | 2 + .../android/exoplayer2/ExoPlayerImpl.java | 1 + .../android/exoplayer2/RenderersFactory.java | 2 + .../audio/MediaCodecAudioRenderer.java | 5 +- .../mediacodec/MediaCodecRenderer.java | 8 +- .../exoplayer2/offline/DownloadHelper.java | 1 + .../upstream/DefaultBandwidthMeter.java | 4 +- .../exoplayer2/util/EGLSurfaceTexture.java | 14 +- .../video/MediaCodecVideoRenderer.java | 18 +- .../exoplayer2/video/PlaceholderSurface.java | 21 +- .../telegram/messenger/AndroidUtilities.java | 8 + .../telegram/messenger/LocaleController.java | 8 +- .../telegram/messenger/MediaController.java | 23 +- .../messenger/MediaDataController.java | 96 +- .../org/telegram/messenger/MessageObject.java | 58 +- .../messenger/MessagesController.java | 85 +- .../messenger/NotificationCenter.java | 2 + .../messenger/NotificationsController.java | 4 +- .../telegram/messenger/UserNameResolver.java | 32 +- .../telegram/messenger/VideoEditedInfo.java | 149 +- .../telegram/messenger/camera/CameraView.java | 305 ++- .../ExtendedDefaultDataSource.java | 99 - .../messenger/video/InputSurface.java | 4 + .../messenger/video/MediaCodecPlayer.java | 139 ++ .../video/MediaCodecVideoConvertor.java | 38 +- .../messenger/video/OutputSurface.java | 175 +- .../messenger/video/TextureRenderer.java | 316 ++- .../video/VideoPlayerHolderBase.java | 97 +- .../telegram/tgnet/ConnectionsManager.java | 28 +- .../main/java/org/telegram/tgnet/TLRPC.java | 477 +++- .../java/org/telegram/tgnet/tl/TL_bots.java | 43 + .../org/telegram/tgnet/tl/TL_payments.java | 383 ++++ .../java/org/telegram/tgnet/tl/TL_stars.java | 381 +++- .../ui/ActionBar/ActionBarMenuSubItem.java | 6 +- .../telegram/ui/ActionBar/AlertDialog.java | 3 +- .../telegram/ui/ActionBar/BottomSheet.java | 1 + .../ui/ActionBar/FloatingToolbar.java | 65 +- .../java/org/telegram/ui/ActionBar/Theme.java | 35 + .../telegram/ui/Adapters/DialogsAdapter.java | 11 + .../telegram/ui/Adapters/MentionsAdapter.java | 61 + .../org/telegram/ui/CachedStaticLayout.java | 1 - .../telegram/ui/Cells/ChatMessageCell.java | 3 +- .../org/telegram/ui/Cells/DialogCell.java | 122 +- .../telegram/ui/Cells/ProfileSearchCell.java | 119 +- .../telegram/ui/Cells/SlideIntChooseView.java | 66 +- .../ui/ChannelMonetizationLayout.java | 80 +- .../java/org/telegram/ui/ChatActivity.java | 67 +- .../org/telegram/ui/ChatEditActivity.java | 74 +- .../org/telegram/ui/ChatUsersActivity.java | 6 +- .../telegram/ui/Components/AlertsCreator.java | 12 +- .../ui/Components/AnimatedFileDrawable.java | 61 +- .../ui/Components/BlurringShader.java | 179 +- .../org/telegram/ui/Components/Bulletin.java | 9 + .../ui/Components/BulletinFactory.java | 7 + .../ui/Components/CaptionPhotoViewer.java | 198 +- .../ui/Components/ChatAttachAlert.java | 770 +++++-- .../ChatAttachAlertAudioLayout.java | 4 +- .../ChatAttachAlertContactsLayout.java | 2 +- .../ChatAttachAlertDocumentLayout.java | 2 +- .../ChatAttachAlertPhotoLayout.java | 216 +- .../ChatAttachAlertPhotoLayoutPreview.java | 2 +- .../ui/Components/DialogsBotsAdapter.java | 2 +- .../ui/Components/EditTextCaption.java | 4 + .../telegram/ui/Components/EditTextEmoji.java | 9 +- .../ui/Components/EmojiPacksAlert.java | 80 +- .../org/telegram/ui/Components/EmojiView.java | 52 +- .../telegram/ui/Components/ItemOptions.java | 244 ++- .../telegram/ui/Components/LayoutHelper.java | 6 + .../ui/Components/MentionsContainerView.java | 13 +- .../Premium/GLIcon/GLIconTextureView.java | 6 +- .../ui/Components/Premium/GLIcon/Icon3D.java | 9 + .../cells/selector/SelectorUserCell.java | 2 +- .../telegram/ui/Components/SeekBarView.java | 43 +- .../Components/SizeNotifierFrameLayout.java | 13 +- .../SizeNotifierFrameLayoutPhoto.java | 5 +- .../ui/Components/SlideChooseView.java | 19 +- .../ui/Components/SuggestEmojiView.java | 49 +- .../java/org/telegram/ui/Components/Text.java | 17 +- .../org/telegram/ui/Components/UItem.java | 14 +- .../ui/Components/UniversalAdapter.java | 5 +- .../telegram/ui/Components/VideoPlayer.java | 22 +- .../java/org/telegram/ui/DialogsActivity.java | 41 +- .../org/telegram/ui/FilterCreateActivity.java | 16 +- .../java/org/telegram/ui/GradientClip.java | 1 - .../telegram/ui/GradientHeaderActivity.java | 7 +- .../java/org/telegram/ui/LaunchActivity.java | 86 +- .../java/org/telegram/ui/PhotoViewer.java | 473 +++- .../java/org/telegram/ui/ProfileActivity.java | 84 +- .../ui/SelectAnimatedEmojiDialog.java | 16 +- .../telegram/ui/Stars/BotStarsActivity.java | 39 +- .../telegram/ui/Stars/BotStarsController.java | 366 +++- .../telegram/ui/Stars/StarsController.java | 51 +- .../telegram/ui/Stars/StarsIntroActivity.java | 244 ++- .../ui/Stars/StarsReactionsSheet.java | 2 +- .../org/telegram/ui/StatisticActivity.java | 1 + .../telegram/ui/Stories/PeerStoriesView.java | 4 +- .../telegram/ui/Stories/StoriesViewPager.java | 2 +- .../org/telegram/ui/Stories/StoryViewer.java | 2 +- .../recorder/CaptionContainerView.java | 123 +- .../ui/Stories/recorder/CollageLayout.java | 126 ++ .../Stories/recorder/CollageLayoutButton.java | 303 +++ .../Stories/recorder/CollageLayoutView2.java | 1317 +++++++++++ .../ui/Stories/recorder/DraftsController.java | 43 +- .../recorder/FfmpegAudioWaveformLoader.java | 5 + .../ui/Stories/recorder/FlashViews.java | 4 + .../ui/Stories/recorder/HintView2.java | 5 + .../ui/Stories/recorder/MuteButtonHint.java | 119 - .../ui/Stories/recorder/PaintView.java | 68 +- .../recorder/PhotoVideoSwitcherView.java | 7 + .../ui/Stories/recorder/PreviewView.java | 133 +- .../ui/Stories/recorder/RecordControl.java | 133 +- .../recorder/RecordControlRenderer.java | 314 --- .../ui/Stories/recorder/SliderView.java | 7 +- .../ui/Stories/recorder/StoryEntry.java | 191 +- .../recorder/StoryPrivacyBottomSheet.java | 16 +- .../ui/Stories/recorder/StoryRecorder.java | 802 +++++-- .../ui/Stories/recorder/TimelineView.java | 1951 ++++++++++++----- .../ui/Stories/recorder/ToggleButton2.java | 61 +- .../java/org/telegram/ui/TopicsFragment.java | 2 +- .../ui/bots/AffiliateProgramFragment.java | 767 +++++++ .../org/telegram/ui/bots/BotWebViewSheet.java | 19 +- .../ChannelAffiliateProgramsFragment.java | 1107 ++++++++++ .../bots/ChatAttachAlertBotWebViewLayout.java | 58 +- .../SuggestedAffiliateProgramsFragment.java | 121 + .../telegram/ui/web/BotWebViewContainer.java | 8 +- .../res/drawable-hdpi/filled_affiliate.png | Bin 0 -> 771 bytes .../res/drawable-hdpi/filled_earn_stars.png | Bin 0 -> 773 bytes .../main/res/drawable-hdpi/menu_bots_add.png | Bin 0 -> 1103 bytes .../res/drawable-hdpi/menu_camera_retake.png | Bin 0 -> 1099 bytes .../drawable-hdpi/menu_feature_premium.png | Bin .../drawable-hdpi/menu_feature_reliable.png | Bin 0 -> 1121 bytes .../res/drawable-hdpi/menu_feature_simple.png | Bin 0 -> 904 bytes .../menu_feature_transparent.png | Bin 0 -> 1108 bytes .../main/res/drawable-hdpi/menu_lightbulb.png | Bin 0 -> 927 bytes .../res/drawable-mdpi/filled_affiliate.png | Bin 0 -> 549 bytes .../res/drawable-mdpi/filled_earn_stars.png | Bin 0 -> 578 bytes .../main/res/drawable-mdpi/menu_bots_add.png | Bin 0 -> 747 bytes .../res/drawable-mdpi/menu_camera_retake.png | Bin 0 -> 722 bytes .../drawable-mdpi/menu_feature_premium.png | Bin .../drawable-mdpi/menu_feature_reliable.png | Bin 0 -> 747 bytes .../res/drawable-mdpi/menu_feature_simple.png | Bin 0 -> 631 bytes .../menu_feature_transparent.png | Bin 0 -> 731 bytes .../main/res/drawable-mdpi/menu_lightbulb.png | Bin 0 -> 626 bytes .../res/drawable-xhdpi/filled_affiliate.png | Bin 0 -> 974 bytes .../res/drawable-xhdpi/filled_earn_stars.png | Bin 0 -> 986 bytes .../main/res/drawable-xhdpi/menu_bots_add.png | Bin 0 -> 1323 bytes .../res/drawable-xhdpi/menu_camera_retake.png | Bin 0 -> 1477 bytes .../drawable-xhdpi/menu_feature_premium.png | Bin .../drawable-xhdpi/menu_feature_reliable.png | Bin 0 -> 1456 bytes .../drawable-xhdpi/menu_feature_simple.png | Bin 0 -> 1154 bytes .../menu_feature_transparent.png | Bin 0 -> 1597 bytes .../res/drawable-xhdpi/menu_lightbulb.png | Bin 0 -> 1303 bytes .../res/drawable-xxhdpi/filled_affiliate.png | Bin 0 -> 1392 bytes .../res/drawable-xxhdpi/filled_earn_stars.png | Bin 0 -> 1410 bytes .../res/drawable-xxhdpi/menu_bots_add.png | Bin 0 -> 2051 bytes .../drawable-xxhdpi/menu_camera_retake.png | Bin 0 -> 2167 bytes .../drawable-xxhdpi/menu_feature_premium.png | Bin .../drawable-xxhdpi/menu_feature_reliable.png | Bin 0 -> 2286 bytes .../drawable-xxhdpi/menu_feature_simple.png | Bin 0 -> 1738 bytes .../menu_feature_transparent.png | Bin 0 -> 2333 bytes .../res/drawable-xxhdpi/menu_lightbulb.png | Bin 0 -> 1921 bytes .../src/main/res/drawable/arrows_select.xml | 19 + .../src/main/res/drawable/menu_link_above.xml | 24 + .../src/main/res/drawable/menu_link_below.xml | 24 + .../src/main/res/drawable/timeline.xml | 11 + .../src/main/res/raw/camera_blur_frag.glsl | 22 + .../src/main/res/raw/camera_blur_frag2.glsl | 26 + .../src/main/res/raw/camera_blur_vert.glsl | 11 + .../src/main/res/raw/caption_down.json | 1 + .../src/main/res/raw/caption_up.json | 1 + TMessagesProj/src/main/res/values/strings.xml | 125 ++ gradle.properties | 4 +- 177 files changed, 12835 insertions(+), 2552 deletions(-) create mode 100644 TMessagesProj/src/main/assets/models/deal_logo.binobj create mode 100644 TMessagesProj/src/main/java/org/telegram/messenger/video/MediaCodecPlayer.java create mode 100644 TMessagesProj/src/main/java/org/telegram/tgnet/tl/TL_payments.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/CollageLayout.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/CollageLayoutButton.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/CollageLayoutView2.java delete mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/MuteButtonHint.java delete mode 100644 TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/RecordControlRenderer.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/bots/AffiliateProgramFragment.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/bots/ChannelAffiliateProgramsFragment.java create mode 100644 TMessagesProj/src/main/java/org/telegram/ui/bots/SuggestedAffiliateProgramsFragment.java create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/filled_affiliate.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/filled_earn_stars.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/menu_bots_add.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/menu_camera_retake.png rename {TMessagesProj_AppStandalone => TMessagesProj}/src/main/res/drawable-hdpi/menu_feature_premium.png (100%) create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/menu_feature_reliable.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/menu_feature_simple.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/menu_feature_transparent.png create mode 100644 TMessagesProj/src/main/res/drawable-hdpi/menu_lightbulb.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/filled_affiliate.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/filled_earn_stars.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/menu_bots_add.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/menu_camera_retake.png rename {TMessagesProj_AppStandalone => TMessagesProj}/src/main/res/drawable-mdpi/menu_feature_premium.png (100%) create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/menu_feature_reliable.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/menu_feature_simple.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/menu_feature_transparent.png create mode 100644 TMessagesProj/src/main/res/drawable-mdpi/menu_lightbulb.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/filled_affiliate.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/filled_earn_stars.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/menu_bots_add.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/menu_camera_retake.png rename {TMessagesProj_AppStandalone => TMessagesProj}/src/main/res/drawable-xhdpi/menu_feature_premium.png (100%) create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/menu_feature_reliable.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/menu_feature_simple.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/menu_feature_transparent.png create mode 100644 TMessagesProj/src/main/res/drawable-xhdpi/menu_lightbulb.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/filled_affiliate.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/filled_earn_stars.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/menu_bots_add.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/menu_camera_retake.png rename {TMessagesProj_AppStandalone => TMessagesProj}/src/main/res/drawable-xxhdpi/menu_feature_premium.png (100%) create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/menu_feature_reliable.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/menu_feature_simple.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/menu_feature_transparent.png create mode 100644 TMessagesProj/src/main/res/drawable-xxhdpi/menu_lightbulb.png create mode 100644 TMessagesProj/src/main/res/drawable/arrows_select.xml create mode 100644 TMessagesProj/src/main/res/drawable/menu_link_above.xml create mode 100644 TMessagesProj/src/main/res/drawable/menu_link_below.xml create mode 100644 TMessagesProj/src/main/res/drawable/timeline.xml create mode 100644 TMessagesProj/src/main/res/raw/camera_blur_frag.glsl create mode 100644 TMessagesProj/src/main/res/raw/camera_blur_frag2.glsl create mode 100644 TMessagesProj/src/main/res/raw/camera_blur_vert.glsl create mode 100644 TMessagesProj/src/main/res/raw/caption_down.json create mode 100644 TMessagesProj/src/main/res/raw/caption_up.json diff --git a/TMessagesProj/jni/audio.c b/TMessagesProj/jni/audio.c index f5202f3e1..218ca1ae4 100644 --- a/TMessagesProj/jni/audio.c +++ b/TMessagesProj/jni/audio.c @@ -917,6 +917,8 @@ JNIEXPORT void JNICALL Java_org_telegram_ui_Stories_recorder_FfmpegAudioWaveform int skip = 4; int barWidth = (int) round((double) duration_in_seconds * sampleRate / count / (1 + skip)); // Assuming you have 'duration' and 'count' defined somewhere + int channels = codecContext->channels; + short peak = 0; int currentCount = 0; int index = 0; @@ -937,9 +939,43 @@ JNIEXPORT void JNICALL Java_org_telegram_ui_Stories_recorder_FfmpegAudioWaveform break; } - int16_t* samples = (int16_t*) frame->data[0]; + const int is_planar = av_sample_fmt_is_planar(codecContext->sample_fmt); + const int sample_size = av_get_bytes_per_sample(codecContext->sample_fmt); for (int i = 0; i < frame->nb_samples; i++) { - short value = samples[i]; // Read the 16-bit PCM sample + int sum = 0; + for (int channel = 0; channel < channels; channel++) { + uint8_t *data; + if (is_planar) { + data = frame->data[channel] + i * sample_size; + } else { + data = frame->data[0] + (i * channels + channel) * sample_size; + } + short sample_value = 0; + switch (codecContext->sample_fmt) { + case AV_SAMPLE_FMT_S16: + case AV_SAMPLE_FMT_S16P: + // Signed 16-bit PCM + sample_value = *(int16_t *)data; + break; + + case AV_SAMPLE_FMT_FLT: + case AV_SAMPLE_FMT_FLTP: + // 32-bit float, scale to 16-bit PCM range + sample_value = (short)(*(float *)data * 32767.0f); + break; + + case AV_SAMPLE_FMT_U8: + case AV_SAMPLE_FMT_U8P: + // Unsigned 8-bit PCM, scale to 16-bit PCM range + sample_value = (*(uint8_t *)data - 128) * 256; + break; + + default: + break; + } + sum += sample_value; + } + short value = sum / channels; if (currentCount >= barWidth) { waveformChunkData[index - chunkIndex] = peak; diff --git a/TMessagesProj/jni/gifvideo.cpp b/TMessagesProj/jni/gifvideo.cpp index ffe2f8e5b..eaf1910f9 100644 --- a/TMessagesProj/jni/gifvideo.cpp +++ b/TMessagesProj/jni/gifvideo.cpp @@ -176,7 +176,7 @@ int open_codec_context(int *stream_idx, AVCodecContext **dec_ctx, AVFormatContex dec = avcodec_find_decoder(st->codecpar->codec_id); if (!dec) { - LOGE("failed to find %s codec", av_get_media_type_string(type)); + LOGE("failed to find %d codec", st->codecpar->codec_id); return AVERROR(EINVAL); } @@ -857,7 +857,7 @@ extern "C" JNIEXPORT int JNICALL Java_org_telegram_ui_Components_AnimatedFileDra } extern "C" JNIEXPORT jint JNICALL Java_org_telegram_ui_Components_AnimatedFileDrawable_getVideoFrame(JNIEnv *env, jclass clazz, jlong ptr, jobject bitmap, jintArray data, jint stride, jboolean preview, jfloat start_time, jfloat end_time, jboolean loop) { - if (ptr == NULL || bitmap == nullptr) { + if (ptr == NULL) { return 0; } //int64_t time = ConnectionsManager::getInstance(0).getCurrentTimeMonotonicMillis(); @@ -946,10 +946,11 @@ extern "C" JNIEXPORT jint JNICALL Java_org_telegram_ui_Components_AnimatedFileDr } if (got_frame) { //LOGD("decoded frame with w = %d, h = %d, format = %d", info->frame->width, info->frame->height, info->frame->format); - if (info->frame->format == AV_PIX_FMT_YUV420P || info->frame->format == AV_PIX_FMT_BGRA || info->frame->format == AV_PIX_FMT_YUVJ420P || info->frame->format == AV_PIX_FMT_YUV444P || info->frame->format == AV_PIX_FMT_YUVA420P) { + if (bitmap != nullptr && (info->frame->format == AV_PIX_FMT_YUV420P || info->frame->format == AV_PIX_FMT_BGRA || info->frame->format == AV_PIX_FMT_YUVJ420P || info->frame->format == AV_PIX_FMT_YUV444P || info->frame->format == AV_PIX_FMT_YUVA420P)) { writeFrameToBitmap(env, info, data, bitmap, stride); } info->has_decoded_frames = true; + push_time(env, info, data); av_frame_unref(info->frame); return 1; } diff --git a/TMessagesProj/src/main/assets/models/coin_stars.binobj b/TMessagesProj/src/main/assets/models/coin_stars.binobj index 61a562721051e0a32b5fa503360655d05326ee98..a3f49980a11a30d8041a1f8b1706d1c4695a2264 100644 GIT binary patch literal 45840 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

jnG`DNe z+^#`$y9Uke8Z@_S(A=&e8|@leW3+3~+^#`$y9Uke8Z@_S(A=&;bGru3?HV+jnG`DNe+^#`$y9Uke8Z@_S(A=&;bGru3?HV+jnG`DNe+^#`$y9Uke8Z@_S(A=&;bGru3?HV+jnG`DNe+^#`$y9Uke8Z@_S$VR({Y_x00M!N>h?HV+< zYtY=TAsg))G`DNWM!N>h?HV+jnG`DNe+^#`$y9Uke8Z@_S(A=&e8|@mh(XJsI?HaPtt|1%k8nV%@ zAsg))veB+VbGwFYv}?#lyM}DEYtY=TL36tX&FvaAw`n%gyKZr6~Fb`9BR*PyvwgXVS(n%gyKZr7l>U4!O!4cTbdpt)Ux=5`I)XxE^* zU4!O!4Vv3EXl~b_xm|jn-8tT|+k7 zHE3?vpt)V6??)52YtY=TL36tX&FvaAw`jnG`DNe+^#`$y9Uke8nV%@ zAsg))veB*~8|@mh(XJsI?HaPtu0eCVhHSKJ(A=&;bGwFYv}@4Zu0eCV2F>jnG`DNe z+^#`$y9Uke8Z@_S(A=&;bGwFYv}@4Zu0eCV2F>jnG`DNe+^#`$y9Uke8Z@_S(A=&; zbGru3?HV+jn-8tjnG`DNe+^#`$y9Uke z8r?bV8Z@_S(A=&;bGru3?HV+jnG`DNe+^#`$y9Uke8Z@_S(A=&;bGru3?HV+jnG`DMX=d^3k z+^#`$y9Uke8Z@_S(A=&;bGru3?HV+jnG`DNe z+^#`$y9Uke8Z@_S(A=&e8|@mjYS%QS-(7IK2It(aL36tX&FvaAw`jn zG`DNe+^#`$y9Uke8vQ;C+^#`$y9Uke8Z@_S(A=&;bGru3?HV+jn-8tXl~b_ zxm|U4!O!4Vv3EXl~b_ zxm|U4!O!4cTbdpt)Ux z=5`I5+cjuz*PyvwLpItqXl~b#jdl&$XxGqsqg_Kb+BIly*Purj?HV+U4!O!4Vv3EXl~b_xm|jnveB+VbGwFYv}@4Zt|1%k8Z@_S(A=&;bGru3?HV+jnG`DNe+^#`$ zy9Uke8Z@_S(A=&;bGru3?HW8zxLt$hb`6@_HE3?vkd1Z?n%gyGqg_Kb+BLM^XxEU9 zb`6@_HE3?vpt)Ux=5`I5+cjjPU4!O!4Vv3EXl~b_xm|U4!O!4Vv3EXl~b_xm|N*M@_@m=WcaZyx|1w3tR1A6{{Qc)P3Fqzz3)oM0`n)MMSPbaNN-nI z_j-{h?>7k0|9`jWwsf@57kzYKite>g`$B>!U>%6-VfD8)VnSsf@)1ufv98(<^2QP_ zV(*{zXi>qGpjkOr)xBPPfx=L+`di)GO*XZjK&@zRa)BC6q4}S8Q zK;zK8x_6uE{`et6k5Z^HGDka8a^H>KB_Hlzica(I-B(M)pD%A zN73QkQgOP)VtuaVxJ~9}t==zd@z&m1pGRr+{(Dcx?aarOK8H=~hM9`z_SDyeJ>%StX=O{Tgp^?9)F?PNSYHS-ixZ02{GYvx4q z|37YVJs-E|I*T8hOpUT=eVxTm$+*73%$fS{gErEN-rVy)ZYQFzEna&Zn>89W| zxQ_y5E4P_sn)kb43Va!@=KU^`X}%%bRQl&_HQ(@#O!M6lbl%bP80Q_j@x7|z(yB(Z zzVW?iGOn*RDZj8Cj<2V*Zd)?WqpDwoQcJtioQ?C~H^1Rg)=z!zlVhCwHTX*BHO`A4 zJ|ttr23o_$^WFXD8(Ht4{xo<0nN7xdq4s@cvzQ|EvkJBEPsZnTs8X1&5BS>hWS*ew zgYKN`1DdZ7`g;VvKA;zF>L;reJE}kD|ExbJ@O48!9>7)qH(3Gq!}T@)v%Y_Gt|$6& zm~%bRt!*srC3BgJ>FdLCtRKUo?B1F1JWg+1J_^e*ULW}SqaXibQjsmNY2bBz&Hs#R zh6FZ*Sv|aI&c^37_5BCBKMkZg8`tx749~UcofcZ|U0H~0a*XF4U*GUt49_z)%ajvw6jcX21=_$6>x~tFi=P``)L+;KZweTC7 zvvIzE%22Ue@ATn%HqQ6tD>a;(oSu4%hO@SB!6Ⓢ7zW(EW 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¥7+VL)84o=*eUI3iu1h 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 XgsBVZVJG-Un0z+Zbi5Y)jr9Kq50X@- literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java b/TMessagesProj/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java index 5baba4595..29d81197b 100644 --- a/TMessagesProj/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java +++ b/TMessagesProj/src/main/java/com/google/android/exoplayer2/DefaultRenderersFactory.java @@ -20,6 +20,7 @@ import static java.lang.annotation.ElementType.TYPE_USE; import android.content.Context; import android.media.MediaCodec; import android.media.PlaybackParams; +import android.opengl.EGLContext; import android.os.Handler; import android.os.Looper; import androidx.annotation.IntDef; @@ -286,6 +287,7 @@ public class DefaultRenderersFactory implements RenderersFactory { @Override public Renderer[] createRenderers( Handler eventHandler, + EGLContext parentContext, VideoRendererEventListener videoRendererEventListener, AudioRendererEventListener audioRendererEventListener, TextOutput textRendererOutput, @@ -293,6 +295,7 @@ public class DefaultRenderersFactory implements RenderersFactory { ArrayList renderersList = new ArrayList<>(); buildVideoRenderers( context, + parentContext, extensionRendererMode, mediaCodecSelector, enableDecoderFallback, @@ -348,6 +351,7 @@ public class DefaultRenderersFactory implements RenderersFactory { */ protected void buildVideoRenderers( Context context, + EGLContext parentContext, @ExtensionRendererMode int extensionRendererMode, MediaCodecSelector mediaCodecSelector, boolean enableDecoderFallback, @@ -358,6 +362,7 @@ public class DefaultRenderersFactory implements RenderersFactory { MediaCodecVideoRenderer videoRenderer = new MediaCodecVideoRenderer( context, + parentContext, getCodecAdapterFactory(), mediaCodecSelector, allowedVideoJoiningTimeMs, diff --git a/TMessagesProj/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/TMessagesProj/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index 5f113dd75..a4904736b 100644 --- a/TMessagesProj/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/TMessagesProj/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -23,6 +23,7 @@ import android.content.Context; import android.media.AudioDeviceInfo; import android.media.AudioTrack; import android.media.MediaCodec; +import android.opengl.EGLContext; import android.os.Looper; import android.os.Process; import android.view.Surface; @@ -452,6 +453,7 @@ public interface ExoPlayer extends Player { final class Builder { /* package */ final Context context; + public EGLContext eglContext; /* package */ Clock clock; /* package */ long foregroundModeTimeoutMs; diff --git a/TMessagesProj/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/TMessagesProj/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 393cd79a6..bcdbd1a11 100644 --- a/TMessagesProj/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/TMessagesProj/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -249,6 +249,7 @@ import java.util.concurrent.TimeoutException; .get() .createRenderers( eventHandler, + builder.eglContext, componentListener, componentListener, componentListener, diff --git a/TMessagesProj/src/main/java/com/google/android/exoplayer2/RenderersFactory.java b/TMessagesProj/src/main/java/com/google/android/exoplayer2/RenderersFactory.java index d012bfaae..80e3d117b 100644 --- a/TMessagesProj/src/main/java/com/google/android/exoplayer2/RenderersFactory.java +++ b/TMessagesProj/src/main/java/com/google/android/exoplayer2/RenderersFactory.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2; +import android.opengl.EGLContext; import android.os.Handler; import com.google.android.exoplayer2.audio.AudioRendererEventListener; import com.google.android.exoplayer2.metadata.MetadataOutput; @@ -36,6 +37,7 @@ public interface RenderersFactory { */ Renderer[] createRenderers( Handler eventHandler, + EGLContext parentContext, VideoRendererEventListener videoRendererEventListener, AudioRendererEventListener audioRendererEventListener, TextOutput textRendererOutput, diff --git a/TMessagesProj/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/TMessagesProj/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index d76edd884..02e247a1a 100644 --- a/TMessagesProj/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/TMessagesProj/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -28,6 +28,7 @@ import android.media.AudioFormat; import android.media.MediaCodec; import android.media.MediaCrypto; import android.media.MediaFormat; +import android.opengl.EGLContext; import android.os.Handler; import androidx.annotation.CallSuper; import androidx.annotation.DoNotInline; @@ -418,7 +419,9 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media MediaCodecInfo codecInfo, Format format, @Nullable MediaCrypto crypto, - float codecOperatingRate) { + float codecOperatingRate, + EGLContext parentContext + ) { codecMaxInputSize = getCodecMaxInputSize(codecInfo, format, getStreamFormats()); codecNeedsDiscardChannelsWorkaround = codecNeedsDiscardChannelsWorkaround(codecInfo.name); MediaFormat mediaFormat = diff --git a/TMessagesProj/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java b/TMessagesProj/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java index bf9d7b3f6..01b6914c4 100644 --- a/TMessagesProj/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java +++ b/TMessagesProj/src/main/java/com/google/android/exoplayer2/mediacodec/MediaCodecRenderer.java @@ -39,6 +39,7 @@ import android.media.MediaCrypto; import android.media.MediaCryptoException; import android.media.MediaFormat; import android.media.metrics.LogSessionId; +import android.opengl.EGLContext; import android.os.Bundle; import android.os.SystemClock; import androidx.annotation.CallSuper; @@ -75,6 +76,8 @@ import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.TimedValueQueue; import com.google.android.exoplayer2.util.TraceUtil; import com.google.android.exoplayer2.util.Util; +import com.google.android.exoplayer2.video.MediaCodecVideoRenderer; + import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -487,7 +490,8 @@ public abstract class MediaCodecRenderer extends BaseRenderer { MediaCodecInfo codecInfo, Format format, @Nullable MediaCrypto crypto, - float codecOperatingRate); + float codecOperatingRate, + EGLContext context); protected final void maybeInitCodecOrBypass() throws ExoPlaybackException { if (codec != null || bypassEnabled || inputFormat == null) { @@ -1099,7 +1103,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer { } codecInitializingTimestamp = SystemClock.elapsedRealtime(); MediaCodecAdapter.Configuration configuration = - getMediaCodecConfiguration(codecInfo, inputFormat, crypto, codecOperatingRate); + getMediaCodecConfiguration(codecInfo, inputFormat, crypto, codecOperatingRate, this instanceof MediaCodecVideoRenderer ? ((MediaCodecVideoRenderer) this).eglContext : null); if (Util.SDK_INT >= 31) { Api31.setLogSessionIdToMediaCodecFormat(configuration, getPlayerId()); } diff --git a/TMessagesProj/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java b/TMessagesProj/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java index 6575c8fc2..f90e4c929 100644 --- a/TMessagesProj/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java +++ b/TMessagesProj/src/main/java/com/google/android/exoplayer2/offline/DownloadHelper.java @@ -152,6 +152,7 @@ public final class DownloadHelper { Renderer[] renderers = renderersFactory.createRenderers( Util.createHandlerForCurrentOrMainLooper(), + null, new VideoRendererEventListener() {}, new AudioRendererEventListener() {}, (cues) -> {}, diff --git a/TMessagesProj/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java b/TMessagesProj/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java index 970df0f33..eb4db349a 100644 --- a/TMessagesProj/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java +++ b/TMessagesProj/src/main/java/com/google/android/exoplayer2/upstream/DefaultBandwidthMeter.java @@ -292,7 +292,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList private @C.NetworkType int networkType; private long totalElapsedTimeMs; private long totalBytesTransferred; - private long bitrateEstimate; + private volatile long bitrateEstimate; private long lastReportedBitrateEstimate; private boolean networkTypeOverrideSet; @@ -420,7 +420,7 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList streamCount--; } - public void onTransfer(long bytes, long duration) { + public synchronized void onTransfer(long bytes, long duration) { long nowMs = clock.elapsedRealtime(); int sampleElapsedTimeMs = (int) (nowMs - sampleStartTimeMs); totalElapsedTimeMs += sampleElapsedTimeMs; diff --git a/TMessagesProj/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java b/TMessagesProj/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java index 7855a4ca3..6be0e0a8d 100644 --- a/TMessagesProj/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java +++ b/TMessagesProj/src/main/java/com/google/android/exoplayer2/util/EGLSurfaceTexture.java @@ -90,7 +90,7 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL /** * @param handler The {@link Handler} that will be used to call {@link * SurfaceTexture#updateTexImage()} to update images on the {@link SurfaceTexture}. Note that - * {@link #init(int)} has to be called on the same looper thread as the {@link Handler}'s + * {@link #init(int, EGLContext)} has to be called on the same looper thread as the {@link Handler}'s * looper. */ public EGLSurfaceTexture(Handler handler) { @@ -100,7 +100,7 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL /** * @param handler The {@link Handler} that will be used to call {@link * SurfaceTexture#updateTexImage()} to update images on the {@link SurfaceTexture}. Note that - * {@link #init(int)} has to be called on the same looper thread as the looper of the {@link + * {@link #init(int, EGLContext)} has to be called on the same looper thread as the looper of the {@link * Handler}. * @param callback The {@link TextureImageListener} to be called when the texture image on {@link * SurfaceTexture} has been updated. This callback will be called on the same handler thread @@ -117,10 +117,10 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL * * @param secureMode The {@link SecureMode} to be used for EGL surface. */ - public void init(@SecureMode int secureMode) throws GlUtil.GlException { + public void init(@SecureMode int secureMode, EGLContext parentContext) throws GlUtil.GlException { display = getDefaultDisplay(); EGLConfig config = chooseEGLConfig(display); - context = createEGLContext(display, config, secureMode); + context = createEGLContext(display, config, secureMode, parentContext); surface = createEGLSurface(display, config, context, secureMode); generateTextureIds(textureIdHolder); texture = new SurfaceTexture(textureIdHolder[0]); @@ -164,7 +164,7 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL } /** - * Returns the wrapped {@link SurfaceTexture}. This can only be called after {@link #init(int)}. + * Returns the wrapped {@link SurfaceTexture}. This can only be called after {@link #init(int, EGLContext)}. */ public SurfaceTexture getSurfaceTexture() { return Assertions.checkNotNull(texture); @@ -232,7 +232,7 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL } private static EGLContext createEGLContext( - EGLDisplay display, EGLConfig config, @SecureMode int secureMode) throws GlUtil.GlException { + EGLDisplay display, EGLConfig config, @SecureMode int secureMode, EGLContext eglContext) throws GlUtil.GlException { int[] glAttributes; if (secureMode == SECURE_MODE_NONE) { glAttributes = new int[] {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE}; @@ -248,7 +248,7 @@ public final class EGLSurfaceTexture implements SurfaceTexture.OnFrameAvailableL } EGLContext context = EGL14.eglCreateContext( - display, config, android.opengl.EGL14.EGL_NO_CONTEXT, glAttributes, 0); + display, config, eglContext == null ? android.opengl.EGL14.EGL_NO_CONTEXT : eglContext, glAttributes, 0); GlUtil.checkGlException(context != null, "eglCreateContext failed"); return context; } diff --git a/TMessagesProj/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java b/TMessagesProj/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java index 37b3a7da0..c34f8d83e 100644 --- a/TMessagesProj/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java +++ b/TMessagesProj/src/main/java/com/google/android/exoplayer2/video/MediaCodecVideoRenderer.java @@ -34,6 +34,7 @@ import android.media.MediaCodecInfo.CodecCapabilities; import android.media.MediaCodecInfo.CodecProfileLevel; import android.media.MediaCrypto; import android.media.MediaFormat; +import android.opengl.EGLContext; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -123,6 +124,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { private static boolean deviceNeedsSetOutputSurfaceWorkaround; private final Context context; + public EGLContext eglContext; private final VideoFrameReleaseHelper frameReleaseHelper; private final EventDispatcher eventDispatcher; private final long allowedJoiningTimeMs; @@ -207,6 +209,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { int maxDroppedFramesToNotify) { this( context, + null, MediaCodecAdapter.Factory.DEFAULT, mediaCodecSelector, allowedJoiningTimeMs, @@ -241,6 +244,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { int maxDroppedFramesToNotify) { this( context, + null, MediaCodecAdapter.Factory.DEFAULT, mediaCodecSelector, allowedJoiningTimeMs, @@ -269,6 +273,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { */ public MediaCodecVideoRenderer( Context context, + EGLContext parentContext, MediaCodecAdapter.Factory codecAdapterFactory, MediaCodecSelector mediaCodecSelector, long allowedJoiningTimeMs, @@ -279,6 +284,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { this( context, + parentContext, codecAdapterFactory, mediaCodecSelector, allowedJoiningTimeMs, @@ -312,6 +318,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { */ public MediaCodecVideoRenderer( Context context, + EGLContext parentContext, MediaCodecAdapter.Factory codecAdapterFactory, MediaCodecSelector mediaCodecSelector, long allowedJoiningTimeMs, @@ -329,6 +336,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { this.allowedJoiningTimeMs = allowedJoiningTimeMs; this.maxDroppedFramesToNotify = maxDroppedFramesToNotify; this.context = context.getApplicationContext(); + this.eglContext = parentContext; frameReleaseHelper = new VideoFrameReleaseHelper(this.context); eventDispatcher = new EventDispatcher(eventHandler, eventListener); deviceNeedsNoPostProcessWorkaround = deviceNeedsNoPostProcessWorkaround(); @@ -674,7 +682,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { } else { MediaCodecInfo codecInfo = getCodecInfo(); if (codecInfo != null && shouldUsePlaceholderSurface(codecInfo)) { - placeholderSurface = PlaceholderSurface.newInstanceV17(context, codecInfo.secure); + placeholderSurface = PlaceholderSurface.newInstanceV17(context, codecInfo.secure, eglContext); surface = placeholderSurface; } } @@ -739,8 +747,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { MediaCodecInfo codecInfo, Format format, @Nullable MediaCrypto crypto, - float codecOperatingRate) { - if (placeholderSurface != null && placeholderSurface.secure != codecInfo.secure) { + float codecOperatingRate, + EGLContext parentContext + ) { + if (placeholderSurface != null && (placeholderSurface.secure != codecInfo.secure || placeholderSurface.parentContext != parentContext)) { // We can't re-use the current DummySurface instance with the new decoder. releasePlaceholderSurface(); } @@ -759,7 +769,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { throw new IllegalStateException(); } if (placeholderSurface == null) { - placeholderSurface = PlaceholderSurface.newInstanceV17(context, codecInfo.secure); + placeholderSurface = PlaceholderSurface.newInstanceV17(context, codecInfo.secure, parentContext); } surface = placeholderSurface; } diff --git a/TMessagesProj/src/main/java/com/google/android/exoplayer2/video/PlaceholderSurface.java b/TMessagesProj/src/main/java/com/google/android/exoplayer2/video/PlaceholderSurface.java index 885e34b4d..87171f082 100644 --- a/TMessagesProj/src/main/java/com/google/android/exoplayer2/video/PlaceholderSurface.java +++ b/TMessagesProj/src/main/java/com/google/android/exoplayer2/video/PlaceholderSurface.java @@ -21,6 +21,7 @@ import static com.google.android.exoplayer2.util.EGLSurfaceTexture.SECURE_MODE_S import android.content.Context; import android.graphics.SurfaceTexture; +import android.opengl.EGLContext; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; @@ -43,6 +44,7 @@ public final class PlaceholderSurface extends Surface { /** Whether the surface is secure. */ public final boolean secure; + public final EGLContext parentContext; private static @SecureMode int secureMode; private static boolean secureModeInitialized; @@ -76,17 +78,18 @@ public final class PlaceholderSurface extends Surface { * @throws IllegalStateException If a secure surface is requested on a device for which {@link * #isSecureSupported(Context)} returns {@code false}. */ - public static PlaceholderSurface newInstanceV17(Context context, boolean secure) { + public static PlaceholderSurface newInstanceV17(Context context, boolean secure, EGLContext parentContext) { Assertions.checkState(!secure || isSecureSupported(context)); PlaceholderSurfaceThread thread = new PlaceholderSurfaceThread(); - return thread.init(secure ? secureMode : SECURE_MODE_NONE); + return thread.init(secure ? secureMode : SECURE_MODE_NONE, parentContext); } private PlaceholderSurface( - PlaceholderSurfaceThread thread, SurfaceTexture surfaceTexture, boolean secure) { + PlaceholderSurfaceThread thread, SurfaceTexture surfaceTexture, boolean secure, EGLContext parentContext) { super(surfaceTexture); this.thread = thread; this.secure = secure; + this.parentContext = parentContext; } @Override @@ -135,13 +138,13 @@ public final class PlaceholderSurface extends Surface { super("ExoPlayer:PlaceholderSurface"); } - public PlaceholderSurface init(@SecureMode int secureMode) { + public PlaceholderSurface init(@SecureMode int secureMode, EGLContext parentContext) { start(); handler = new Handler(getLooper(), /* callback= */ this); eglSurfaceTexture = new EGLSurfaceTexture(handler); boolean wasInterrupted = false; synchronized (this) { - handler.obtainMessage(MSG_INIT, secureMode, 0).sendToTarget(); + handler.obtainMessage(MSG_INIT, secureMode, 0, parentContext).sendToTarget(); while (surface == null && initException == null && initError == null) { try { wait(); @@ -173,7 +176,7 @@ public final class PlaceholderSurface extends Surface { switch (msg.what) { case MSG_INIT: try { - initInternal(/* secureMode= */ msg.arg1); + initInternal(/* secureMode= */ msg.arg1, msg.obj == null ? null : (EGLContext) msg.obj); } catch (RuntimeException e) { Log.e(TAG, "Failed to initialize placeholder surface", e); initException = e; @@ -203,12 +206,12 @@ public final class PlaceholderSurface extends Surface { } } - private void initInternal(@SecureMode int secureMode) throws GlUtil.GlException { + private void initInternal(@SecureMode int secureMode, EGLContext parentContext) throws GlUtil.GlException { Assertions.checkNotNull(eglSurfaceTexture); - eglSurfaceTexture.init(secureMode); + eglSurfaceTexture.init(secureMode, parentContext); this.surface = new PlaceholderSurface( - this, eglSurfaceTexture.getSurfaceTexture(), secureMode != SECURE_MODE_NONE); + this, eglSurfaceTexture.getSurfaceTexture(), secureMode != SECURE_MODE_NONE, parentContext); } private void releaseInternal() { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java b/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java index 73d5f6b4e..6d8cae3fe 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/AndroidUtilities.java @@ -4881,6 +4881,10 @@ public class AndroidUtilities { return -1; } + public static float distance(float x1, float y1, float x2, float y2) { + return (float) Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); + } + public static int lerp(int a, int b, float f) { return (int) (a + f * (b - a)); } @@ -5861,6 +5865,10 @@ public class AndroidUtilities { return Math.max(x1, x2) > Math.min(y1, y2) && Math.max(y1, y2) > Math.min(x1, x2); } + public static boolean intersect1d(float x1, float x2, float y1, float y2) { + return Math.max(x1, x2) > Math.min(y1, y2) && Math.max(y1, y2) > Math.min(x1, x2); + } + public static boolean intersect1dInclusive(int x1, int x2, int y1, int y2) { return Math.max(x1, x2) >= Math.min(y1, y2) && Math.max(y1, y2) >= Math.min(x1, x2); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java b/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java index 3228ee6f9..d882a7c67 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/LocaleController.java @@ -2271,13 +2271,13 @@ public class LocaleController { int dateYear = rightNow.get(Calendar.YEAR); if (dateDay == day && year == dateYear && useToday) { - return LocaleController.formatString("TodayAtFormattedWithToday", R.string.TodayAtFormattedWithToday, getInstance().getFormatterDay().format(new Date(date))); + return LocaleController.formatString(R.string.TodayAtFormattedWithToday, getInstance().getFormatterDay().format(new Date(date))); } else if (dateDay + 1 == day && year == dateYear && useToday) { - return LocaleController.formatString("YesterdayAtFormatted", R.string.YesterdayAtFormatted, getInstance().getFormatterDay().format(new Date(date))); + return LocaleController.formatString(R.string.YesterdayAtFormatted, getInstance().getFormatterDay().format(new Date(date))); } else if (Math.abs(System.currentTimeMillis() - date) < 31536000000L) { - return LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, getInstance().getChatDate().format(new Date(date)), getInstance().getFormatterDay().format(new Date(date))); + return LocaleController.formatString(R.string.formatDateAtTime, getInstance().getChatDate().format(new Date(date)), getInstance().getFormatterDay().format(new Date(date))); } else { - return LocaleController.formatString("formatDateAtTime", R.string.formatDateAtTime, getInstance().getChatFullDate().format(new Date(date)), getInstance().getFormatterDay().format(new Date(date))); + return LocaleController.formatString(R.string.formatDateAtTime, getInstance().getChatFullDate().format(new Date(date)), getInstance().getFormatterDay().format(new Date(date))); } } catch (Exception e) { FileLog.e(e); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java index 62420c6b4..a5e0668ed 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MediaController.java @@ -48,6 +48,9 @@ import android.media.MediaRecorder; import android.net.Uri; import android.os.Build; import android.os.Environment; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; import android.os.PowerManager; import android.os.SystemClock; import android.provider.MediaStore; @@ -5602,24 +5605,30 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, private static class VideoConvertRunnable implements Runnable { - private VideoConvertMessage convertMessage; + private final VideoConvertMessage convertMessage; + private final Handler handler; - private VideoConvertRunnable(VideoConvertMessage message) { - convertMessage = message; + private VideoConvertRunnable(VideoConvertMessage message, Handler handler) { + this.convertMessage = message; + this.handler = handler; } @Override public void run() { - MediaController.getInstance().convertVideo(convertMessage); + MediaController.getInstance().convertVideo(convertMessage, handler); } public static void runConversion(final VideoConvertMessage obj) { + HandlerThread handlerThread = new HandlerThread("VideoConvertRunnableThread"); + handlerThread.start(); + Handler handler = new Handler(handlerThread.getLooper()); new Thread(() -> { try { - VideoConvertRunnable wrapper = new VideoConvertRunnable(obj); + VideoConvertRunnable wrapper = new VideoConvertRunnable(obj, handler); Thread th = new Thread(wrapper, "VideoConvertRunnable"); th.start(); th.join(); + handlerThread.join(); } catch (Exception e) { FileLog.e(e); } @@ -5628,7 +5637,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, } - private boolean convertVideo(final VideoConvertMessage convertMessage) { + private boolean convertVideo(final VideoConvertMessage convertMessage, final Handler handler) { MessageObject messageObject = convertMessage.messageObject; VideoEditedInfo info = convertMessage.videoEditedInfo; if (messageObject == null || info == null) { @@ -5734,7 +5743,7 @@ public class MediaController implements AudioManager.OnAudioFocusChangeListener, callback, info); convertVideoParams.soundInfos.addAll(info.mixedSoundInfos); - boolean error = videoConvertor.convertVideo(convertVideoParams); + boolean error = videoConvertor.convertVideo(convertVideoParams, handler); boolean canceled = info.canceled; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java index 5e2b92614..b5afa38e9 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java @@ -6220,7 +6220,9 @@ public class MediaDataController extends BaseController { } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionGameScore) { messageObject.generateGameMessageText(null); } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSent) { - messageObject.generatePaymentSentMessageText(null); + messageObject.generatePaymentSentMessageText(null, false); + } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSentMe) { + messageObject.generatePaymentSentMessageText(null, true); } break; } @@ -6690,7 +6692,9 @@ public class MediaDataController extends BaseController { } else if (m.messageOwner.action instanceof TLRPC.TL_messageActionGameScore) { m.generateGameMessageText(null); } else if (m.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSent) { - m.generatePaymentSentMessageText(null); + m.generatePaymentSentMessageText(null, false); + } else if (m.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSentMe) { + m.generatePaymentSentMessageText(null, true); } } changed = true; @@ -9648,4 +9652,92 @@ public class MediaDataController extends BaseController { } } } + + public static class SearchStickersKey { + public final boolean emojis; + public final String lang_code; + public final String q; + + public SearchStickersKey(boolean emojis, String lang_code, String q) { + this.emojis = emojis; + this.lang_code = lang_code; + this.q = q; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SearchStickersKey that = (SearchStickersKey) o; + return emojis == that.emojis && Objects.equals(lang_code, that.lang_code) && Objects.equals(q, that.q); + } + + @Override + public int hashCode() { + return Objects.hash(emojis, lang_code, q); + } + } + + private static class SearchStickersResult { + public final ArrayList documents = new ArrayList<>(); + public Integer next_offset; + public void apply(TLRPC.TL_messages_foundStickers r) { + documents.addAll(r.stickers); + next_offset = (r.flags & 1) != 0 ? r.next_offset : null; + } + } + + private final HashMap loadingSearchStickersKeys = new HashMap<>(); + private final android.util.LruCache searchStickerResults = new android.util.LruCache<>(25); + public SearchStickersKey searchStickers(boolean emojis, String lang_code, String q, Utilities.Callback> whenDone) { + return searchStickers(emojis, lang_code, q, whenDone, false); + } + public SearchStickersKey searchStickers(boolean emojis, String lang_code, String q, Utilities.Callback> whenDone, boolean next) { + if (whenDone == null) return null; + final SearchStickersKey key = new SearchStickersKey(emojis, lang_code, q); + SearchStickersResult cached = searchStickerResults.get(key); + if ((cached == null || cached.next_offset != null && next) && !loadingSearchStickersKeys.containsKey(key)) { + loadingSearchStickersKeys.put(key, 0); + MediaDataController.getInstance(currentAccount).getEmojiSuggestions(new String[]{lang_code}, q, true, (result, a) -> { + if (!loadingSearchStickersKeys.containsKey(key)) return; + StringBuilder s = new StringBuilder(); + for (KeywordResult r : result) { + if (!TextUtils.isEmpty(r.emoji) && !r.emoji.startsWith("animated_")) { + s.append(r.emoji); + } + } + TLRPC.TL_messages_searchStickers req = new TLRPC.TL_messages_searchStickers(); + req.emojis = key.emojis; + if (!TextUtils.isEmpty(key.lang_code)) { + req.lang_code.add(key.lang_code); + } + req.emoticon = s.toString(); + req.q = key.q; + req.limit = 50; + req.offset = cached == null ? 0 : cached.next_offset; + final int reqId = getConnectionsManager().sendRequest(req, (res, err) -> AndroidUtilities.runOnUIThread(() -> { + loadingSearchStickersKeys.remove(key); + SearchStickersResult finalResult = cached != null ? cached : new SearchStickersResult(); + if (res instanceof TLRPC.TL_messages_foundStickers) { + finalResult.apply((TLRPC.TL_messages_foundStickers) res); + } + searchStickerResults.put(key, finalResult); + whenDone.run(finalResult.documents); + })); + loadingSearchStickersKeys.put(key, reqId); + }, false); + } else if (cached != null) { + whenDone.run(cached.documents); + } else { + whenDone.run(new ArrayList<>()); + } + return key; + } + public void cancelSearchStickers(SearchStickersKey key) { + if (key == null) return; + final Integer reqId = loadingSearchStickersKeys.remove(key); + if (reqId != null && reqId != 0) { + getConnectionsManager().cancelRequest(reqId, true); + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java index b820249fd..8e6986d69 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessageObject.java @@ -3533,16 +3533,11 @@ public class MessageObject { return !(replyMessageObject == null || replyMessageObject.messageOwner instanceof TLRPC.TL_messageEmpty || replyMessageObject.messageOwner.action instanceof TLRPC.TL_messageActionHistoryClear || replyMessageObject.messageOwner.action instanceof TLRPC.TL_messageActionTopicCreate); } - public void generatePaymentSentMessageText(TLRPC.User fromUser) { + public void generatePaymentSentMessageText(TLRPC.User fromUser, boolean me) { if (fromUser == null) { fromUser = MessagesController.getInstance(currentAccount).getUser(getDialogId()); } - String name; - if (fromUser != null) { - name = UserObject.getFirstName(fromUser); - } else { - name = ""; - } + final String name = fromUser != null ? UserObject.getFirstName(fromUser) : ""; String currency; try { if (StarsController.currency.equals(messageOwner.action.currency)) { @@ -3555,16 +3550,28 @@ public class MessageObject { FileLog.e(e); } if (replyMessageObject != null && getMedia(replyMessageObject) instanceof TLRPC.TL_messageMediaInvoice) { - if (messageOwner.action.recurring_init) { + if (messageOwner.action.subscription_until_date != 0) { + if (me) { + messageText = formatString(R.string.PaymentSuccessfullyPaidMeSubscription, name, currency, getMedia(replyMessageObject).title, LocaleController.formatDateTime(messageOwner.action.subscription_until_date, false)); + } else { + messageText = formatString(R.string.PaymentSuccessfullyPaidSubscription, currency, name, getMedia(replyMessageObject).title, LocaleController.formatDateTime(messageOwner.action.subscription_until_date, false)); + } + } else if (messageOwner.action.recurring_init && !me) { messageText = formatString(R.string.PaymentSuccessfullyPaidRecurrent, currency, name, getMedia(replyMessageObject).title); } else { - messageText = formatString("PaymentSuccessfullyPaid", R.string.PaymentSuccessfullyPaid, currency, name, getMedia(replyMessageObject).title); + messageText = formatString(R.string.PaymentSuccessfullyPaid, currency, name, getMedia(replyMessageObject).title); } } else { - if (messageOwner.action.recurring_init) { + if (messageOwner.action.subscription_until_date != 0) { + if (me) { + messageText = formatString(R.string.PaymentSuccessfullyPaidMeNoItemSubscription, name, currency, LocaleController.formatDateTime(messageOwner.action.subscription_until_date, false)); + } else { + messageText = formatString(R.string.PaymentSuccessfullyPaidSubscriptionNoItem, currency, name, LocaleController.formatDateTime(messageOwner.action.subscription_until_date, false)); + } + } else if (messageOwner.action.recurring_init && !me) { messageText = formatString(R.string.PaymentSuccessfullyPaidNoItemRecurrent, currency, name); } else { - messageText = formatString("PaymentSuccessfullyPaidNoItem", R.string.PaymentSuccessfullyPaidNoItem, currency, name); + messageText = formatString(R.string.PaymentSuccessfullyPaidNoItem, currency, name); } } messageText = StarsIntroActivity.replaceStars(messageText); @@ -4414,17 +4421,31 @@ public class MessageObject { messageText = replaceWithLink(AndroidUtilities.replaceTags(LocaleController.formatPluralStringComma("ActionStarGiveawayPrize", (int) action.stars)), "un1", chat); } else if (messageOwner.action instanceof TLRPC.TL_messageActionStarGift) { TLRPC.TL_messageActionStarGift action = (TLRPC.TL_messageActionStarGift) messageOwner.action; + int stars = 0; + if (action.gift != null) { + stars = (int) action.gift.stars; + } if (fromObject instanceof TLRPC.User && ((TLRPC.User) fromObject).self && !action.forceIn) { TLRPC.User user = getUser(users, sUsers, messageOwner.peer_id.user_id); messageText = replaceWithLink(AndroidUtilities.replaceTags(getString(R.string.ActionGiftOutbound)), "un1", user); + if (action.message != null && !TextUtils.isEmpty(action.message.text)) { + SpannableStringBuilder stringBuilder = new SpannableStringBuilder(action.message.text); + addEntitiesToText(stringBuilder, action.message.entities, isOutOwner(), false, false, false); + messageTextShort = stringBuilder; + } else { + messageTextShort = getString(R.string.ActionStarGift); + } } else if (fromObject instanceof TLRPC.User && UserObject.isService(((TLRPC.User) fromObject).id)) { messageText = TextUtils.replace(AndroidUtilities.replaceTags(getString(R.string.ActionGiftInbound)), new String[] {"un1"}, new CharSequence[]{ getString(R.string.StarsTransactionUnknown) }); } else { messageText = replaceWithLink(AndroidUtilities.replaceTags(getString(R.string.ActionGiftInbound)), "un1", fromObject); - } - int stars = 0; - if (action.gift != null) { - stars = (int) action.gift.stars; + if (action.message != null && !TextUtils.isEmpty(action.message.text)) { + SpannableStringBuilder stringBuilder = new SpannableStringBuilder(action.message.text); + addEntitiesToText(stringBuilder, action.message.entities, isOutOwner(), false, false, false); + messageTextShort = stringBuilder; + } else { + messageTextShort = getString(R.string.ActionStarGift); + } } int i = messageText.toString().indexOf("un2"); if (i != -1) { @@ -4840,8 +4861,11 @@ public class MessageObject { } } } else if (messageOwner.action instanceof TLRPC.TL_messageActionPaymentSent) { - TLRPC.User user = getUser(users, sUsers, getDialogId()); - generatePaymentSentMessageText(user); + final TLRPC.User user = getUser(users, sUsers, getDialogId()); + generatePaymentSentMessageText(user, false); + } else if (messageOwner.action instanceof TLRPC.TL_messageActionPaymentSentMe) { + final TLRPC.User user = getUser(users, sUsers, getDialogId()); + generatePaymentSentMessageText(user, true); } else if (messageOwner.action instanceof TLRPC.TL_messageActionBotAllowed) { String domain = ((TLRPC.TL_messageActionBotAllowed) messageOwner.action).domain; TLRPC.BotApp botApp = ((TLRPC.TL_messageActionBotAllowed) messageOwner.action).app; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java index 8921b854e..8a0c411fa 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/MessagesController.java @@ -639,6 +639,11 @@ public class MessagesController extends BaseController implements NotificationCe public float starsUsdSellRate1000; public float starsUsdWithdrawRate1000; public boolean sponsoredLinksInappAllow; + public Set starrefStartParamPrefixes = new HashSet<>(); + public boolean starrefProgramAllowed; + public boolean starrefConnectAllowed; + public int starrefMinCommissionPermille; + public int starrefMaxCommissionPermille; public long paidReactionsAnonymousTime; public Boolean paidReactionsAnonymous; @@ -1605,6 +1610,11 @@ public class MessagesController extends BaseController implements NotificationCe starsUsdSellRate1000 = mainPreferences.getFloat("starsUsdSellRate1000", 2000); starsUsdWithdrawRate1000 = mainPreferences.getFloat("starsUsdWithdrawRate1000", 1200); sponsoredLinksInappAllow = mainPreferences.getBoolean("sponsoredLinksInappAllow", false); + starrefProgramAllowed = mainPreferences.getBoolean("starrefProgramAllowed", false); + starrefConnectAllowed = mainPreferences.getBoolean("starrefConnectAllowed", false); + starrefStartParamPrefixes = mainPreferences.getStringSet("starrefStartParamPrefixes", new HashSet<>(Arrays.asList("_tgr_"))); + starrefMinCommissionPermille = mainPreferences.getInt("starrefMinCommissionPermille", 1); + starrefMaxCommissionPermille = mainPreferences.getInt("starrefMaxCommissionPermille", 400); paidReactionsAnonymousTime = mainPreferences.getLong("paidReactionsAnonymousTime", 0); paidReactionsAnonymous = mainPreferences.contains("paidReactionsAnonymous") && (System.currentTimeMillis() - paidReactionsAnonymousTime) < 1000 * 60 * 60 * 2 ? mainPreferences.getBoolean("paidReactionsAnonymous", false) : null; scheduleTranscriptionUpdate(); @@ -4288,6 +4298,25 @@ public class MessagesController extends BaseController implements NotificationCe } break; } + case "starref_start_param_prefixes": { + HashSet newPrefixes = new HashSet<>(); + if (value.value instanceof TLRPC.TL_jsonArray) { + TLRPC.TL_jsonArray array = (TLRPC.TL_jsonArray) value.value; + for (int b = 0, N2 = array.value.size(); b < N2; b++) { + TLRPC.JSONValue val = array.value.get(b); + if (val instanceof TLRPC.TL_jsonString) { + TLRPC.TL_jsonString string = (TLRPC.TL_jsonString) val; + newPrefixes.add(string.value.toLowerCase()); + } + } + } + if (!starrefStartParamPrefixes.equals(newPrefixes)) { + starrefStartParamPrefixes = newPrefixes; + editor.putStringSet("starrefStartParamPrefixes", starrefStartParamPrefixes); + changed = true; + } + break; + } case "weather_search_username": { if (value.value instanceof TLRPC.TL_jsonString) { TLRPC.TL_jsonString str = (TLRPC.TL_jsonString) value.value; @@ -4406,6 +4435,50 @@ public class MessagesController extends BaseController implements NotificationCe } break; } + case "starref_program_allowed": { + if (value.value instanceof TLRPC.TL_jsonBool) { + TLRPC.TL_jsonBool bool = (TLRPC.TL_jsonBool) value.value; + if (bool.value != starrefProgramAllowed) { + starrefProgramAllowed = bool.value; + editor.putBoolean("starrefProgramAllowed", starrefProgramAllowed); + changed = true; + } + } + break; + } + case "starref_connect_allowed": { + if (value.value instanceof TLRPC.TL_jsonBool) { + TLRPC.TL_jsonBool bool = (TLRPC.TL_jsonBool) value.value; + if (bool.value != starrefConnectAllowed) { + starrefConnectAllowed = bool.value; + editor.putBoolean("starrefConnectAllowed", starrefConnectAllowed); + changed = true; + } + } + break; + } + case "starref_min_commission_permille": { + if (value.value instanceof TLRPC.TL_jsonNumber) { + TLRPC.TL_jsonNumber num = (TLRPC.TL_jsonNumber) value.value; + if (num.value != starrefMinCommissionPermille) { + starrefMinCommissionPermille = (int) num.value; + editor.putInt("starrefMinCommissionPermille", starrefMinCommissionPermille); + changed = true; + } + } + break; + } + case "starref_max_commission_permille": { + if (value.value instanceof TLRPC.TL_jsonNumber) { + TLRPC.TL_jsonNumber num = (TLRPC.TL_jsonNumber) value.value; + if (num.value != starrefMaxCommissionPermille) { + starrefMaxCommissionPermille = (int) num.value; + editor.putInt("starrefMaxCommissionPermille", starrefMaxCommissionPermille); + changed = true; + } + } + break; + } } } @@ -10765,7 +10838,9 @@ public class MessagesController extends BaseController implements NotificationCe } else if (msg.messageOwner.action instanceof TLRPC.TL_messageActionGameScore) { msg.generateGameMessageText(null); } else if (msg.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSent) { - msg.generatePaymentSentMessageText(null); + msg.generatePaymentSentMessageText(null, false); + } else if (msg.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSentMe) { + msg.generatePaymentSentMessageText(null, true); } break; } @@ -22128,4 +22203,12 @@ public class MessagesController extends BaseController implements NotificationCe return paidReactionsAnonymous; } + public boolean shouldShowMoveCaptionHint() { + return getMainSettings().getInt("movecaptionhint", 0) < 24 || BuildVars.DEBUG_PRIVATE_VERSION; + } + + public void incrementMoveCaptionHint() { + getMainSettings().edit().putInt("movecaptionhint", getMainSettings().getInt("movecaptionhint", 0) + 1).apply(); + } + } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java index 631ecb8a6..bd65258d7 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationCenter.java @@ -263,6 +263,8 @@ public class NotificationCenter { public static final int starGiftSoldOut = totalEvents++; public static final int updateStories = totalEvents++; public static final int botDownloadsUpdate = totalEvents++; + public static final int channelSuggestedBotsUpdate = totalEvents++; + public static final int channelConnectedBotsUpdate = totalEvents++; //global public static final int pushMessagesUpdated = totalEvents++; diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java index 9b3ce3460..f3a7cd8b3 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/NotificationsController.java @@ -1802,7 +1802,7 @@ public class NotificationsController extends BaseController { } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionLoginUnknownLocation) { String date = LocaleController.formatString(R.string.formatDateAtTime, LocaleController.getInstance().getFormatterYear().format(((long) messageObject.messageOwner.date) * 1000), LocaleController.getInstance().getFormatterDay().format(((long) messageObject.messageOwner.date) * 1000)); return LocaleController.formatString(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) { + } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionGameScore || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSent || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSentMe) { return messageObject.messageText.toString(); } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionStarGift || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionGiftPremium) { return messageObject.messageText.toString(); @@ -2431,7 +2431,7 @@ public class NotificationsController extends BaseController { } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionLoginUnknownLocation) { String date = LocaleController.formatString(R.string.formatDateAtTime, LocaleController.getInstance().getFormatterYear().format(((long) messageObject.messageOwner.date) * 1000), LocaleController.getInstance().getFormatterDay().format(((long) messageObject.messageOwner.date) * 1000)); msg = LocaleController.formatString(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) { + } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionGameScore || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSent || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSentMe) { msg = messageObject.messageText.toString(); } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionStarGift || messageObject.messageOwner.action instanceof TLRPC.TL_messageActionGiftPremium) { msg = messageObject.messageText.toString(); diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/UserNameResolver.java b/TMessagesProj/src/main/java/org/telegram/messenger/UserNameResolver.java index f1c9744ee..2af61ee4e 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/UserNameResolver.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/UserNameResolver.java @@ -28,14 +28,20 @@ public class UserNameResolver { HashMap>> resolvingConsumers = new HashMap<>(); public int resolve(String username, Consumer resolveConsumer) { - CachedPeer cachedPeer = resolvedCache.get(username); - if (cachedPeer != null) { - if (System.currentTimeMillis() - cachedPeer.time < CACHE_TIME) { - resolveConsumer.accept(cachedPeer.peerId); - FileLog.d("resolve username from cache " + username + " " + cachedPeer.peerId); - return -1; - } else { - resolvedCache.remove(username); + return resolve(username, null, resolveConsumer); + } + + public int resolve(String username, String referrer, Consumer resolveConsumer) { + if (TextUtils.isEmpty(referrer)) { + CachedPeer cachedPeer = resolvedCache.get(username); + if (cachedPeer != null) { + if (System.currentTimeMillis() - cachedPeer.time < CACHE_TIME) { + resolveConsumer.accept(cachedPeer.peerId); + FileLog.d("resolve username from cache " + username + " " + cachedPeer.peerId); + return -1; + } else { + resolvedCache.remove(username); + } } } @@ -57,6 +63,10 @@ public class UserNameResolver { } else { TLRPC.TL_contacts_resolveUsername resolveUsername = new TLRPC.TL_contacts_resolveUsername(); resolveUsername.username = username; + if (!TextUtils.isEmpty(referrer)) { + resolveUsername.flags |= 1; + resolveUsername.referer = referrer; + } req = resolveUsername; } return ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { @@ -65,6 +75,12 @@ public class UserNameResolver { return; } if (error != null) { + if (error != null && error.text != null && "STARREF_EXPIRED".equals(error.text)) { + for (int i = 0; i < finalConsumers.size(); i++) { + finalConsumers.get(i).accept(Long.MAX_VALUE); + } + return; + } for (int i = 0; i < finalConsumers.size(); i++) { finalConsumers.get(i).accept(null); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/VideoEditedInfo.java b/TMessagesProj/src/main/java/org/telegram/messenger/VideoEditedInfo.java index 257537b87..1fad084b2 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/VideoEditedInfo.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/VideoEditedInfo.java @@ -11,12 +11,16 @@ package org.telegram.messenger; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Matrix; +import android.graphics.SurfaceTexture; import android.text.TextUtils; import android.view.View; +import org.telegram.messenger.video.MediaCodecPlayer; import org.telegram.messenger.video.MediaCodecVideoConvertor; +import org.telegram.messenger.video.VideoPlayerHolderBase; import org.telegram.tgnet.AbstractSerializedData; import org.telegram.tgnet.SerializedData; +import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.tgnet.tl.TL_stories; import org.telegram.ui.Components.AnimatedFileDrawable; @@ -25,11 +29,17 @@ import org.telegram.ui.Components.Paint.Views.LinkPreview; import org.telegram.ui.Components.PhotoFilterView; import org.telegram.ui.Components.Point; import org.telegram.ui.Components.Reactions.ReactionsLayoutInBubble; +import org.telegram.ui.Components.VideoPlayer; +import org.telegram.ui.Stories.recorder.CollageLayout; import org.telegram.ui.Stories.recorder.StoryEntry; import org.telegram.ui.Stories.recorder.Weather; +import java.io.File; +import java.nio.FloatBuffer; import java.util.ArrayList; import java.util.Locale; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; public class VideoEditedInfo { @@ -66,6 +76,9 @@ public class VideoEditedInfo { public boolean isStory; public StoryEntry.HDRInfo hdrInfo; + public CollageLayout collage; + public ArrayList collageParts; + public boolean isSticker; public Bitmap thumb; @@ -83,7 +96,6 @@ public class VideoEditedInfo { public boolean videoConvertFirstWrite; public boolean needUpdateProgress = false; public boolean shouldLimitFps = true; - public boolean tryUseHevc = false; public boolean fromCamera; public ArrayList mixedSoundInfos = new ArrayList<>(); @@ -416,7 +428,7 @@ public class VideoEditedInfo { blurPathBytes = null; } SerializedData serializedData = new SerializedData(len); - serializedData.writeInt32(10); + serializedData.writeInt32(11); serializedData.writeInt64(avatarStartTime); serializedData.writeInt32(originalBitrate); if (filterState != null) { @@ -509,6 +521,15 @@ public class VideoEditedInfo { } serializedData.writeFloat(volume); serializedData.writeBool(isSticker); + if (collage != null && collageParts != null && collage.parts.size() > 1 && !collageParts.isEmpty()) { + serializedData.writeInt32(0xdeadbeef); + serializedData.writeString(collage.toString()); + for (int i = 0; i < collageParts.size(); ++i) { + collageParts.get(i).serializeToStream(serializedData); + } + } else { + serializedData.writeInt32(TLRPC.TL_null.constructor); + } filters = Utilities.bytesToHex(serializedData.toByteArray()); serializedData.cleanup(); } else { @@ -642,6 +663,19 @@ public class VideoEditedInfo { if (version >= 10) { isSticker = serializedData.readBool(false); } + if (version >= 11) { + int magic = serializedData.readInt32(false); + if (magic == 0xdeadbeef) { + collage = new CollageLayout(serializedData.readString(false)); + collageParts = new ArrayList<>(); + for (int i = 0; i < collage.parts.size(); ++i) { + Part part = new Part(); + part.part = collage.parts.get(i); + part.readParams(serializedData, false); + collageParts.add(part); + } + } + } serializedData.cleanup(); } } else { @@ -676,4 +710,115 @@ public class VideoEditedInfo { public boolean canAutoPlaySourceVideo() { return roundVideo; } + + public static class Part extends TLObject { + + public int flags; + public boolean isVideo; + public boolean muted; + public String path; + public float volume = 1.0f; + public long offset = 0; + public boolean loop = true; + public float left, right; + public int width, height; + public long duration; + + public CollageLayout.Part part; + + public Part() {} + public Part(StoryEntry entry) { + isVideo = entry.isVideo; + muted = entry.muted; + path = entry.file.getAbsolutePath(); + volume = entry.videoVolume; + loop = entry.videoLoop; + offset = entry.videoOffset; + left = entry.videoLeft; + right = entry.videoRight; + width = entry.width; + height = entry.height; + duration = entry.duration; + } + + public static ArrayList toParts(StoryEntry collageEntry) { + if (collageEntry == null || collageEntry.collageContent == null) + return null; + final ArrayList parts = new ArrayList<>(); + for (int i = 0; i < collageEntry.collageContent.size(); ++i) { + final StoryEntry entry = collageEntry.collageContent.get(i); + Part part = new Part(entry); + part.part = collageEntry.collage.parts.get(i); + parts.add(part); + } + return parts; + } + + public static ArrayList toStoryEntries(ArrayList parts) { + if (parts == null) return null; + final ArrayList entries = new ArrayList<>(); + for (Part part : parts) { + final StoryEntry entry = new StoryEntry(); + entry.isVideo = part.isVideo; + entry.muted = part.muted; + entry.file = new File(part.path); + entry.videoVolume = part.volume; + entry.videoLoop = part.loop; + entry.videoOffset = part.offset; + entry.videoLeft = part.left; + entry.videoRight = part.right; + entry.width = part.width; + entry.height = part.height; + entry.duration = part.duration; + entries.add(entry); + } + return entries; + } + + @Override + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + isVideo = (flags & 1) != 0; + loop = (flags & 2) != 0; + muted = (flags & 4) != 0; + path = stream.readString(exception); + volume = stream.readFloat(exception); + offset = stream.readInt64(exception); + left = stream.readFloat(exception); + right = stream.readFloat(exception); + width = stream.readInt32(exception); + height = stream.readInt32(exception); + duration = stream.readInt64(exception); + } + + @Override + public void serializeToStream(AbstractSerializedData stream) { + flags = isVideo ? flags | 1 : flags &~ 1; + flags = loop ? flags | 2 : flags &~ 2; + flags = muted ? flags | 4 : flags &~ 4; + stream.writeInt32(flags); + stream.writeString(path); + stream.writeFloat(volume); + stream.writeInt64(offset); + stream.writeFloat(left); + stream.writeFloat(right); + stream.writeInt32(width); + stream.writeInt32(height); + stream.writeInt64(duration); + } + + public FloatBuffer posBuffer; + public FloatBuffer uvBuffer; + + // software rendering + public AnimatedFileDrawable animatedFileDrawable; + public float currentFrame; + public float framesPerDraw; + public float msPerFrame; + + // hardware rendering + public SurfaceTexture surfaceTexture; + public MediaCodecPlayer player; + + } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraView.java b/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraView.java index a6dc295ff..76f0a0f09 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraView.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/camera/CameraView.java @@ -8,6 +8,8 @@ package org.telegram.messenger.camera; +import static org.telegram.messenger.AndroidUtilities.dp; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; @@ -21,6 +23,9 @@ import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.RenderEffect; +import android.graphics.RenderNode; +import android.graphics.Shader; import android.graphics.SurfaceTexture; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; @@ -89,7 +94,6 @@ import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; -import javax.microedition.khronos.opengles.GL; @SuppressLint("NewApi") public class CameraView extends FrameLayout implements TextureView.SurfaceTextureListener, CameraController.ICameraView, CameraController.ErrorCallback { @@ -97,6 +101,7 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur public boolean WRITE_TO_FILE_IN_BACKGROUND = false; public boolean isStory; + public boolean recordHevc; private float scaleX, scaleY; private Size[] previewSize = new Size[2]; private Size[] pictureSize = new Size[2]; @@ -104,7 +109,7 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur private boolean mirror; private boolean lazy; private TextureView textureView; - private ImageView blurredStubView; + public ImageView blurredStubView; private boolean inited; private CameraViewDelegate delegate; private int clipTop; @@ -398,10 +403,10 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur blurredStubView = new ImageView(context); addView(blurredStubView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.CENTER)); blurredStubView.setVisibility(View.GONE); - focusAreaSize = AndroidUtilities.dp(96); + focusAreaSize = dp(96); outerPaint.setColor(0xffffffff); outerPaint.setStyle(Paint.Style.STROKE); - outerPaint.setStrokeWidth(AndroidUtilities.dp(2)); + outerPaint.setStrokeWidth(dp(2)); innerPaint.setColor(0x7fffffff); } @@ -671,6 +676,9 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur FileLog.d("CameraView " + "start create thread"); } cameraThread = new CameraGLThread(surface); + if (blurTextureView != null) { + cameraThread.setBlurSurfaceTexture(blurTextureView.getSurfaceTexture()); + } checkPreviewMatrix(); } } @@ -960,9 +968,30 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { - boolean result = super.drawChild(canvas, child, drawingTime); + Canvas c = canvas; + if (child == textureView && canvas.isHardwareAccelerated()) { + if (renderNode != null) { + RenderNode node = (RenderNode) renderNode; + node.setPosition(0, 0, getWidth(), getHeight()); + c = node.beginRecording(); + } + } + boolean result = super.drawChild(c, child, drawingTime); + if (child == textureView && canvas.isHardwareAccelerated()) { + if (renderNode != null) { + RenderNode node = (RenderNode) renderNode; + node.endRecording(); + canvas.drawRenderNode(node); + if (blurRenderNode != null) { + RenderNode blurNode = (RenderNode) blurRenderNode; + blurNode.setPosition(0, 0, getWidth(), getHeight()); + blurNode.beginRecording().drawRenderNode(node); + blurNode.endRecording();; + } + } + } if (focusProgress != 1.0f || innerAlpha != 0.0f || outerAlpha != 0.0f) { - int baseRad = AndroidUtilities.dp(30); + int baseRad = dp(30); long newTime = System.currentTimeMillis(); long dt = newTime - lastDrawTime; if (dt < 0 || dt > 17) { @@ -1020,6 +1049,51 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur } } + private Object renderNode; + private Object blurRenderNode; + + public Object getBlurRenderNode() { + if (renderNode == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + renderNode = new RenderNode("CameraViewRenderNode"); + blurRenderNode = new RenderNode("CameraViewRenderNodeBlur"); + ((RenderNode) blurRenderNode).setRenderEffect(RenderEffect.createBlurEffect(dp(32), dp(32), Shader.TileMode.DECAL)); + } + return blurRenderNode; + } + + private TextureView blurTextureView; + public TextureView makeBlurTextureView() { + if (blurTextureView == null) { + blurTextureView = new TextureView(getContext()); + blurTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() { + @Override + public void onSurfaceTextureAvailable(@NonNull SurfaceTexture surface, int width, int height) { + if (cameraThread != null) { + cameraThread.setBlurSurfaceTexture(surface); + } + } + + @Override + public void onSurfaceTextureSizeChanged(@NonNull SurfaceTexture surface, int width, int height) { + + } + + @Override + public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) { + if (cameraThread != null) { + cameraThread.setBlurSurfaceTexture(null); + } + return false; + } + + @Override + public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) { + + } + }); + } + return blurTextureView; + } @Override protected void dispatchDraw(Canvas canvas) { @@ -1028,7 +1102,7 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur } super.dispatchDraw(canvas); if (takePictureProgress != 1f) { - takePictureProgress += 16 / 150f; + takePictureProgress += 16 / 250f; if (takePictureProgress > 1f) { takePictureProgress = 1f; } else { @@ -1038,6 +1112,38 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur } } + private final ArrayList invalidateListeners = new ArrayList<>(); + public void listenDraw(Runnable listener) { + invalidateListeners.add(listener); + } + public void unlistenDraw(Runnable listener) { + invalidateListeners.remove(listener); + } + + @Override + public void invalidate() { + super.invalidate(); + for (Runnable l : invalidateListeners) { + l.run(); + } + } + + @Override + public void invalidate(Rect dirty) { + super.invalidate(dirty); + for (Runnable l : invalidateListeners) { + l.run(); + } + } + + @Override + public void invalidate(int l, int t, int r, int b) { + super.invalidate(l, t, r, b); + for (Runnable i : invalidateListeners) { + i.run(); + } + } + private int videoWidth; private int videoHeight; @@ -1048,9 +1154,7 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur return videoHeight; } - private int[] position = new int[2]; - private int[][] cameraTexture = new int[2][1]; - private int[] oldCameraTexture = new int[1]; + private final int[][] cameraTexture = new int[2][1]; private VideoRecorder videoEncoder; private volatile float pixelW, pixelH; @@ -1070,6 +1174,19 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur private EGLConfig eglConfig; private boolean initied; + private SurfaceTexture blurSurfaceTexture; + private EGLContext eglBlurContext; + private EGLSurface eglBlurSurface; + private boolean blurInited; + + private int drawBlurProgram; + private int blurVertexMatrixHandle; + private int blurTextureMatrixHandle; + private int blurCameraMatrixHandle; + private int blurPositionHandle; + private int blurTextureHandle; + private int blurPixelHandle; + private final CameraSessionWrapper currentSession[] = new CameraSessionWrapper[2]; private final SurfaceTexture[] cameraSurface = new SurfaceTexture[2]; @@ -1088,6 +1205,8 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur private final int DO_DUAL_END = 10; private final int BLUR_CAMERA1 = 11; + private final int DO_BLUR_TEXTURE = 12; + private int drawProgram; private int vertexMatrixHandle; private int textureMatrixHandle; @@ -1111,7 +1230,7 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur private boolean recording; private boolean needRecord; - private int cameraId[] = new int[] { -1, -1 }; + private final int cameraId[] = new int[] { -1, -1 }; private final float[] verticesData = { -1.0f, -1.0f, 0, @@ -1120,8 +1239,6 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur 1.0f, 1.0f, 0 }; - //private InstantCameraView.VideoRecorder videoEncoder; - public CameraGLThread(SurfaceTexture surface) { super("CameraGLThread"); surfaceTexture = surface; @@ -1214,7 +1331,6 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur finish(); return false; } - GL gl = eglContext.getGL(); android.opengl.Matrix.setIdentityM(mSTMatrix[0], 0); @@ -1334,6 +1450,65 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur return true; } + private boolean initBlurGL() { + if (!initied) { + return false; + } + int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE}; + eglBlurContext = egl10.eglCreateContext(eglDisplay, eglConfig, eglContext, attrib_list); + if (eglBlurContext == null || eglBlurContext == EGL10.EGL_NO_CONTEXT) { + eglBlurContext = null; + if (BuildVars.LOGS_ENABLED) { + FileLog.e("eglCreateContext (blur) failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); + } + return false; + } + if (blurSurfaceTexture != null) { + eglBlurSurface = egl10.eglCreateWindowSurface(eglDisplay, eglConfig, blurSurfaceTexture, null); + } else { + finishBlur(); + return false; + } + if (eglBlurSurface == null || eglBlurSurface == EGL10.EGL_NO_SURFACE) { + if (BuildVars.LOGS_ENABLED) { + FileLog.e("createWindowSurface failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); + } + finishBlur(); + return false; + } + if (!egl10.eglMakeCurrent(eglDisplay, eglBlurSurface, eglBlurSurface, eglBlurContext)) { + if (BuildVars.LOGS_ENABLED) { + FileLog.e("eglMakeCurrent failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); + } + finishBlur(); + egl10.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext); + return false; + } + + int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, AndroidUtilities.readRes(R.raw.camera_blur_vert)); + int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, AndroidUtilities.readRes(R.raw.camera_blur_frag)); + if (vertexShader != 0 && fragmentShader != 0) { + drawBlurProgram = GLES20.glCreateProgram(); + GLES20.glAttachShader(drawBlurProgram, vertexShader); + GLES20.glAttachShader(drawBlurProgram, fragmentShader); + GLES20.glLinkProgram(drawBlurProgram); + int[] linkStatus = new int[1]; + GLES20.glGetProgramiv(drawBlurProgram, GLES20.GL_LINK_STATUS, linkStatus, 0); + if (linkStatus[0] == 0) { + GLES20.glDeleteProgram(drawBlurProgram); + drawBlurProgram = 0; + } else { + blurPositionHandle = GLES20.glGetAttribLocation(drawBlurProgram, "aPosition"); + blurTextureHandle = GLES20.glGetAttribLocation(drawBlurProgram, "aTextureCoord"); + blurVertexMatrixHandle = GLES20.glGetUniformLocation(drawBlurProgram, "uMVPMatrix"); + blurTextureMatrixHandle = GLES20.glGetUniformLocation(drawBlurProgram, "uSTMatrix"); + blurCameraMatrixHandle = GLES20.glGetUniformLocation(drawBlurProgram, "cameraMatrix"); + blurPixelHandle = GLES20.glGetUniformLocation(drawBlurProgram, "pixelWH"); + } + } + return true; + } + private void updTex(SurfaceTexture surfaceTexture) { if (surfaceTexture == cameraSurface[0]) { if (!ignoreCamera1Upd && System.currentTimeMillis() > camera1AppearedUntil) { @@ -1369,6 +1544,7 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur } } } + finishBlur(); if (eglSurface != null) { egl10.eglMakeCurrent(eglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); egl10.eglDestroySurface(eglDisplay, eglSurface); @@ -1384,6 +1560,19 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur } } + public void finishBlur() { + if (eglBlurSurface != null) { + egl10.eglMakeCurrent(eglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + egl10.eglDestroySurface(eglDisplay, eglBlurSurface); + eglBlurSurface = null; + } + if (eglBlurContext != null) { + egl10.eglDestroyContext(eglDisplay, eglBlurContext); + eglBlurContext = null; + } + blurInited = false; + } + public void setCurrentSession(CameraSessionWrapper session, int i) { Handler handler = getHandler(); if (handler != null) { @@ -1492,6 +1681,7 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur if (crossfade <= 0) { crossfading = false; } + int drawnCameraTexture = -1; for (int a = -1; a < 2; ++a) { if (a == -1 && !crossfading) { continue; @@ -1511,6 +1701,9 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur GLES20.glUseProgram(drawProgram); GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, cameraTexture[i][0]); + if (drawnCameraTexture == -1) { + drawnCameraTexture = cameraTexture[i][0]; + } GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 12, vertexBuffer); GLES20.glEnableVertexAttribArray(positionHandle); @@ -1541,14 +1734,14 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur GLES20.glUniform1f(shapeHandle, 0); GLES20.glUniform1f(crossfadeHandle, 1); } else if (!crossfading) { - GLES20.glUniform1f(roundRadiusHandle, AndroidUtilities.dp(16)); + GLES20.glUniform1f(roundRadiusHandle, dp(16)); GLES20.glUniform1f(scaleHandle, dualScale); GLES20.glUniform1f(shapeFromHandle, (float) Math.floor(shapeValue)); GLES20.glUniform1f(shapeToHandle, (float) Math.ceil(shapeValue)); GLES20.glUniform1f(shapeHandle, shapeValue - (float) Math.floor(shapeValue)); GLES20.glUniform1f(crossfadeHandle, 0); } else { - GLES20.glUniform1f(roundRadiusHandle, AndroidUtilities.dp(16)); + GLES20.glUniform1f(roundRadiusHandle, dp(16)); GLES20.glUniform1f(scaleHandle, 1f - crossfade); GLES20.glUniform1f(shapeFromHandle, (float) Math.floor(shapeValue)); GLES20.glUniform1f(shapeToHandle, (float) Math.ceil(shapeValue)); @@ -1559,7 +1752,7 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur } else { GLES20.glUniform1f(alphaHandle, 1f); if (crossfading) { - GLES20.glUniform1f(roundRadiusHandle, AndroidUtilities.lerp(AndroidUtilities.dp(12), AndroidUtilities.dp(16), crossfade)); + GLES20.glUniform1f(roundRadiusHandle, AndroidUtilities.lerp(dp(12), dp(16), crossfade)); GLES20.glUniform1f(scaleHandle, 1f); GLES20.glUniform1f(shapeFromHandle, shapeTo); GLES20.glUniform1f(shapeToHandle, 2); @@ -1585,6 +1778,42 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur egl10.eglSwapBuffers(eglDisplay, eglSurface); + if (blurSurfaceTexture != null && blurInited) { + boolean drawBlur = true; + if (!eglBlurContext.equals(egl10.eglGetCurrentContext()) || !eglBlurSurface.equals(egl10.eglGetCurrentSurface(EGL10.EGL_DRAW))) { + if (!egl10.eglMakeCurrent(eglDisplay, eglBlurSurface, eglBlurSurface, eglBlurContext)) { + if (BuildVars.LOGS_ENABLED) { + FileLog.e("eglMakeCurrent failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); + } + drawBlur = false; + } + } + if (drawBlur && cameraSurface[0] != null) { + GLES20.glUseProgram(drawBlurProgram); + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, cameraTexture[0][0]); + + GLES20.glVertexAttribPointer(blurPositionHandle, 3, GLES20.GL_FLOAT, false, 12, vertexBuffer); + GLES20.glEnableVertexAttribArray(blurPositionHandle); + + GLES20.glVertexAttribPointer(blurTextureHandle, 2, GLES20.GL_FLOAT, false, 8, textureBuffer); + GLES20.glEnableVertexAttribArray(blurTextureHandle); + + GLES20.glUniformMatrix4fv(blurCameraMatrixHandle, 1, false, cameraMatrix[0], 0); + + GLES20.glUniformMatrix4fv(blurTextureMatrixHandle, 1, false, mSTMatrix[0], 0); + GLES20.glUniformMatrix4fv(blurVertexMatrixHandle, 1, false, mMVPMatrix[0], 0); + + GLES20.glUniform2f(blurPixelHandle, pixelW, pixelH); + + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + + GLES20.glDisableVertexAttribArray(blurPositionHandle); + GLES20.glDisableVertexAttribArray(blurTextureHandle); + egl10.eglSwapBuffers(eglDisplay, eglBlurSurface); + } + } + synchronized (layoutLock) { if (!firstFrameRendered && !waitingForCamera1) { firstFrameRendered = true; @@ -1604,6 +1833,9 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur @Override public void run() { initied = initGL(); + if (blurSurfaceTexture != null) { + blurInited = initBlurGL(); + } super.run(); } @@ -1616,6 +1848,7 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur onDraw(inputMessage.arg1, inputMessage.arg2, inputMessage.obj == updateTexBoth || inputMessage.obj == updateTex1, inputMessage.obj == updateTexBoth || inputMessage.obj == updateTex2); break; case DO_SHUTDOWN_MESSAGE: + finishBlur(); finish(); if (recording) { videoEncoder.stopRecording(inputMessage.arg1); @@ -1808,6 +2041,18 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur requestRender(false, false); break; } + case DO_BLUR_TEXTURE: { + if (blurSurfaceTexture != inputMessage.obj) { + finishBlur(); + blurSurfaceTexture = null; + } + if (inputMessage.obj != null && blurSurfaceTexture != inputMessage.obj) { + blurSurfaceTexture = (SurfaceTexture) inputMessage.obj; + blurInited = initBlurGL(); + } + requestRender(false, false); + break; + } } } @@ -1838,16 +2083,7 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur FileLog.d("CameraView camera scaleX = " + scaleX + " scaleY = " + scaleY); } -// private final float[] tempVertices = new float[6]; private void applyDualMatrix(Matrix matrix) { -// tempVertices[0] = tempVertices[1] = 0; -// tempVertices[2] = pixelW; -// tempVertices[3] = 0; -// tempVertices[4] = 0; -// tempVertices[5] = pixelH; -// matrix.mapPoints(tempVertices); -// pixelDualW = MathUtils.distance(tempVertices[0], tempVertices[1], tempVertices[2], tempVertices[3]); -// pixelDualH = MathUtils.distance(tempVertices[0], tempVertices[1], tempVertices[4], tempVertices[5]); getValues(matrix, cameraMatrix[1]); } @@ -1934,6 +2170,15 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur sendMessage(handler.obtainMessage(DO_STOP_RECORDING), 0); } } + + public void setBlurSurfaceTexture(SurfaceTexture blurSurfaceTexture) { + Handler handler = getHandler(); + if (handler != null) { + sendMessage(handler.obtainMessage(DO_BLUR_TEXTURE, blurSurfaceTexture), 0); + } else { + this.blurSurfaceTexture = blurSurfaceTexture; + } + } } private void onFirstFrameRendered(int i) { @@ -2510,14 +2755,14 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur GLES20.glUniform1f(shapeHandle, 0); GLES20.glUniform1f(crossfadeHandle, 1); } else if (!crossfading) { - GLES20.glUniform1f(roundRadiusHandle, AndroidUtilities.dp(16)); + GLES20.glUniform1f(roundRadiusHandle, dp(16)); GLES20.glUniform1f(scaleHandle, 1f); GLES20.glUniform1f(shapeFromHandle, (float) Math.floor(shapeValue)); GLES20.glUniform1f(shapeToHandle, (float) Math.ceil(shapeValue)); GLES20.glUniform1f(shapeHandle, shapeValue - (float) Math.floor(shapeValue)); GLES20.glUniform1f(crossfadeHandle, 0); } else { - GLES20.glUniform1f(roundRadiusHandle, AndroidUtilities.dp(16)); + GLES20.glUniform1f(roundRadiusHandle, dp(16)); GLES20.glUniform1f(scaleHandle, 1f - crossfade); GLES20.glUniform1f(shapeFromHandle, (float) Math.floor(shapeValue)); GLES20.glUniform1f(shapeToHandle, (float) Math.ceil(shapeValue)); @@ -2528,7 +2773,7 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur } else { GLES20.glUniform1f(alphaHandle, 1f); if (crossfading) { - GLES20.glUniform1f(roundRadiusHandle, AndroidUtilities.lerp(AndroidUtilities.dp(12), AndroidUtilities.dp(16), crossfade)); + GLES20.glUniform1f(roundRadiusHandle, AndroidUtilities.lerp(dp(12), dp(16), crossfade)); GLES20.glUniform1f(scaleHandle, 1f); GLES20.glUniform1f(shapeFromHandle, lastShapeTo); GLES20.glUniform1f(shapeToHandle, 2); @@ -2677,7 +2922,7 @@ public class CameraView extends FrameLayout implements TextureView.SurfaceTextur audioEncoder.configure(audioFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); audioEncoder.start(); - boolean shouldUseHevc = isStory; + boolean shouldUseHevc = recordHevc; outputMimeType = shouldUseHevc ? "video/hevc" : "video/avc"; try { if (shouldUseHevc) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/secretmedia/ExtendedDefaultDataSource.java b/TMessagesProj/src/main/java/org/telegram/messenger/secretmedia/ExtendedDefaultDataSource.java index c42a62197..42d647680 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/secretmedia/ExtendedDefaultDataSource.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/secretmedia/ExtendedDefaultDataSource.java @@ -321,103 +321,4 @@ public final class ExtendedDefaultDataSource implements DataSource { dataSource.addTransferListener(listener); } } - - - private final Cache cache = new Cache() { - @Override - public long getUid() { - return 0; - } - - @Override - public void release() { - - } - - @Override - public NavigableSet addListener(String key, Listener listener) { - return null; - } - - @Override - public void removeListener(String key, Listener listener) { - - } - - @Override - public NavigableSet getCachedSpans(String key) { - return null; - } - - @Override - public Set getKeys() { - return null; - } - - @Override - public long getCacheSpace() { - return 0; - } - - @Override - public CacheSpan startReadWrite(String key, long position, long length) throws InterruptedException, CacheException { - return null; - } - - @Nullable - @Override - public CacheSpan startReadWriteNonBlocking(String key, long position, long length) throws CacheException { - return null; - } - - @Override - public File startFile(String key, long position, long length) throws CacheException { - return null; - } - - @Override - public void commitFile(File file, long length) throws CacheException { - - } - - @Override - public void releaseHoleSpan(CacheSpan holeSpan) { - - } - - @Override - public void removeResource(String key) { - - } - - @Override - public void removeSpan(CacheSpan span) { - - } - - @Override - public boolean isCached(String key, long position, long length) { - return false; - } - - @Override - public long getCachedLength(String key, long position, long length) { - return 0; - } - - @Override - public long getCachedBytes(String key, long position, long length) { - return 0; - } - - @Override - public void applyContentMetadataMutations(String key, ContentMetadataMutations mutations) throws CacheException { - - } - - @Override - public ContentMetadata getContentMetadata(String key) { - return null; - } - }; } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/video/InputSurface.java b/TMessagesProj/src/main/java/org/telegram/messenger/video/InputSurface.java index 159db80c6..4013a48df 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/video/InputSurface.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/video/InputSurface.java @@ -111,6 +111,10 @@ public class InputSurface { } } + public EGLContext getContext() { + return mEGLContext; + } + public boolean swapBuffers() { return EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface); } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/video/MediaCodecPlayer.java b/TMessagesProj/src/main/java/org/telegram/messenger/video/MediaCodecPlayer.java new file mode 100644 index 000000000..8c57f3681 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/messenger/video/MediaCodecPlayer.java @@ -0,0 +1,139 @@ +package org.telegram.messenger.video; + +import android.media.MediaCodec; +import android.media.MediaExtractor; +import android.media.MediaFormat; +import android.util.Log; +import android.view.Surface; + +import java.io.IOException; +import java.nio.ByteBuffer; + +public class MediaCodecPlayer { + + private final MediaExtractor extractor; + private final MediaCodec codec; + private final Surface outputSurface; + + private final int w, h, o; + + public MediaCodecPlayer(String videoPath, Surface surface) throws IOException { + this.outputSurface = surface; + this.extractor = new MediaExtractor(); + + // Set up the extractor to read the video file + extractor.setDataSource(videoPath); + MediaFormat videoFormat = null; + int videoTrackIndex = -1; + + // Find the video track + for (int i = 0; i < extractor.getTrackCount(); i++) { + MediaFormat format = extractor.getTrackFormat(i); + String mimeType = format.getString(MediaFormat.KEY_MIME); + if (mimeType.startsWith("video/")) { + videoTrackIndex = i; + videoFormat = format; + break; + } + } + + if (videoTrackIndex == -1 || videoFormat == null) { + throw new IllegalArgumentException("No video track found in file."); + } + + extractor.selectTrack(videoTrackIndex); + w = videoFormat.getInteger(MediaFormat.KEY_WIDTH); + h = videoFormat.getInteger(MediaFormat.KEY_HEIGHT); + if (videoFormat.containsKey(MediaFormat.KEY_ROTATION)) { + o = videoFormat.getInteger(MediaFormat.KEY_ROTATION); + } else { + o = 0; + } + + codec = MediaCodec.createDecoderByType(videoFormat.getString(MediaFormat.KEY_MIME)); + codec.configure(videoFormat, surface, null, 0); + codec.start(); + } + + public int getWidth() { + return w; + } + + public int getOrientedWidth() { + return (o / 90) % 2 == 1 ? h : w; + } + + public int getHeight() { + return h; + } + + public int getOrientedHeight() { + return (o / 90) % 2 == 1 ? w : h; + } + + public int getOrientation() { + return o; + } + + private boolean done; + private boolean first = true; + private long lastPositionUs = 0; + + public boolean ensure(long ms) { + if (done) return false; + final boolean first = this.first; + this.first = false; + final long us = ms * 1000; + if (!first && us <= lastPositionUs) { + return false; + } + + if (extractor.getSampleTime() > us || first && us > 1000 * 1000) { + extractor.seekTo(us, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); + } + + while (true) { + int inputBufferIndex = codec.dequeueInputBuffer(10000); + if (inputBufferIndex >= 0) { + ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferIndex); + if (inputBuffer != null) { + int sampleSize = extractor.readSampleData(inputBuffer, 0); + if (sampleSize > 0) { + long sampleTime = extractor.getSampleTime(); + codec.queueInputBuffer(inputBufferIndex, 0, sampleSize, sampleTime, extractor.getSampleFlags()); + extractor.advance(); + } else { + codec.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + release(); + return false; + } + } + } + + MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); + int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 10000); + if (outputBufferIndex >= 0) { + if (bufferInfo.presentationTimeUs >= us - 16 * 1000) { + lastPositionUs = bufferInfo.presentationTimeUs; + codec.releaseOutputBuffer(outputBufferIndex, true); + return true; + } else { + codec.releaseOutputBuffer(outputBufferIndex, false); + } + } + } + } + + public void release() { + if (done) return; + done = true; + if (codec != null) { + codec.stop(); + codec.release(); + } + if (extractor != null) { + extractor.release(); + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/video/MediaCodecVideoConvertor.java b/TMessagesProj/src/main/java/org/telegram/messenger/video/MediaCodecVideoConvertor.java index a453e9f82..5e2beeb10 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/video/MediaCodecVideoConvertor.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/video/MediaCodecVideoConvertor.java @@ -7,6 +7,9 @@ import android.media.MediaExtractor; import android.media.MediaFormat; import android.media.MediaMuxer; import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; import androidx.annotation.NonNull; @@ -21,6 +24,7 @@ import org.telegram.messenger.VideoEditedInfo; import org.telegram.messenger.video.audio_input.AudioInput; import org.telegram.messenger.video.audio_input.BlankAudioInput; import org.telegram.messenger.video.audio_input.GeneralAudioInput; +import org.telegram.ui.Stories.recorder.CollageLayout; import org.telegram.ui.Stories.recorder.StoryEntry; import java.io.File; @@ -48,12 +52,12 @@ public class MediaCodecVideoConvertor { private static final int MEDIACODEC_TIMEOUT_INCREASED = 22000; private String outputMimeType; - public boolean convertVideo(ConvertVideoParams convertVideoParams) { + public boolean convertVideo(ConvertVideoParams convertVideoParams, Handler handler) { if (convertVideoParams.isSticker) { return WebmEncoder.convert(convertVideoParams, 0); } this.callback = convertVideoParams.callback; - return convertVideoInternal(convertVideoParams, false, 0); + return convertVideoInternal(convertVideoParams, false, 0, handler); } public long getLastFrameTimestamp() { @@ -61,9 +65,12 @@ public class MediaCodecVideoConvertor { } @TargetApi(18) - private boolean convertVideoInternal(ConvertVideoParams convertVideoParams, - boolean increaseTimeout, - int triesCount) { + private boolean convertVideoInternal( + ConvertVideoParams convertVideoParams, + boolean increaseTimeout, + int triesCount, + Handler handler + ) { String videoPath = convertVideoParams.videoPath; File cacheFile = convertVideoParams.cacheFile; int rotationValue = convertVideoParams.rotationValue; @@ -178,7 +185,7 @@ public class MediaCodecVideoConvertor { inputSurface.makeCurrent(); encoder.start(); - outputSurface = new OutputSurface(savedFilterState, videoPath, paintPath, blurPath, mediaEntities, cropState != null && cropState.useMatrix != null ? cropState : null, resultWidth, resultHeight, originalWidth, originalHeight, rotationValue, framerate, true, gradientTopColor, gradientBottomColor, null, convertVideoParams); + outputSurface = new OutputSurface(inputSurface.getContext(), savedFilterState, videoPath, paintPath, blurPath, mediaEntities, cropState != null && cropState.useMatrix != null ? cropState : null, resultWidth, resultHeight, originalWidth, originalHeight, rotationValue, framerate, true, gradientTopColor, gradientBottomColor, null, convertVideoParams, handler); ByteBuffer[] encoderOutputBuffers = null; ByteBuffer[] encoderInputBuffers = null; @@ -511,7 +518,7 @@ public class MediaCodecVideoConvertor { } } - outputSurface = new OutputSurface(savedFilterState, null, paintPath, blurPath, mediaEntities, cropState, resultWidth, resultHeight, originalWidth, originalHeight, rotationValue, framerate, false, gradientTopColor, gradientBottomColor, hdrInfo, convertVideoParams); + outputSurface = new OutputSurface(inputSurface.getContext(), savedFilterState, null, paintPath, blurPath, mediaEntities, cropState, resultWidth, resultHeight, originalWidth, originalHeight, rotationValue, framerate, false, gradientTopColor, gradientBottomColor, hdrInfo, convertVideoParams, handler); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && hdrInfo != null && hdrInfo.getHDRType() != 0) { outputSurface.changeFragmentShader( hdrFragmentShader(originalWidth, originalHeight, resultWidth, resultHeight, true, hdrInfo), @@ -956,18 +963,17 @@ public class MediaCodecVideoConvertor { } if (repeatWithIncreasedTimeout) { - return convertVideoInternal(convertVideoParams, true, triesCount + 1); + return convertVideoInternal(convertVideoParams, true, triesCount + 1, handler); } if (error && canBeBrokenEncoder && triesCount < 3) { - return convertVideoInternal(convertVideoParams, increaseTimeout, triesCount + 1); + return convertVideoInternal(convertVideoParams, increaseTimeout, triesCount + 1, handler); } long timeLeft = System.currentTimeMillis() - time; if (BuildVars.LOGS_ENABLED) { FileLog.d("compression completed time=" + timeLeft + " needCompress=" + needCompress + " w=" + resultWidth + " h=" + resultHeight + " bitrate=" + bitrate + " file size=" + AndroidUtilities.formatFileSize(cacheFile.length()) + " encoder_name=" + selectedEncoderName); } - return error; } @@ -975,7 +981,13 @@ public class MediaCodecVideoConvertor { if (soundInfos == null) return; for (int i = 0; i < soundInfos.size(); i++) { MixedSoundInfo soundInfo = soundInfos.get(i); - GeneralAudioInput secondAudio = new GeneralAudioInput(soundInfo.audioFile); + GeneralAudioInput secondAudio; + try { + secondAudio = new GeneralAudioInput(soundInfo.audioFile); + } catch (Exception e) { + FileLog.e(e); + continue; + } secondAudio.setVolume(soundInfo.volume); long startTimeLocal = 0; if (soundInfo.startTime > 0) { @@ -1444,6 +1456,8 @@ public class MediaCodecVideoConvertor { boolean isDark; long wallpaperPeerId; boolean isSticker; + CollageLayout collage; + ArrayList collageParts; private ConvertVideoParams() { @@ -1496,6 +1510,8 @@ public class MediaCodecVideoConvertor { params.messageVideoMaskPath = info.messageVideoMaskPath; params.backgroundPath = info.backgroundPath; params.isSticker = info.isSticker; + params.collage = info.collage; + params.collageParts = info.collageParts; return params; } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/video/OutputSurface.java b/TMessagesProj/src/main/java/org/telegram/messenger/video/OutputSurface.java index 6793076ba..0f6f4efcc 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/video/OutputSurface.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/video/OutputSurface.java @@ -9,7 +9,13 @@ package org.telegram.messenger.video; import android.graphics.SurfaceTexture; +import android.opengl.EGL14; +import android.opengl.EGLConfig; +import android.opengl.EGLContext; +import android.opengl.EGLDisplay; +import android.opengl.EGLSurface; import android.opengl.GLES20; +import android.os.Handler; import android.view.Surface; import org.telegram.messenger.FileLog; @@ -18,12 +24,9 @@ import org.telegram.messenger.VideoEditedInfo; import org.telegram.ui.Stories.recorder.StoryEntry; import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; -import javax.microedition.khronos.egl.EGLSurface; public class OutputSurface implements SurfaceTexture.OnFrameAvailableListener { @@ -39,70 +42,130 @@ public class OutputSurface implements SurfaceTexture.OnFrameAvailableListener { private boolean mFrameAvailable; private TextureRenderer mTextureRender; - public OutputSurface(MediaController.SavedFilterState savedFilterState, String imagePath, String paintPath, String blurPath, ArrayList mediaEntities, MediaController.CropState cropState, int w, int h, int originalW, int originalH, int rotation, float fps, boolean photo, Integer gradientTopColor, Integer gradientBottomColor, StoryEntry.HDRInfo hdrInfo, MediaCodecVideoConvertor.ConvertVideoParams params) { - mTextureRender = new TextureRenderer(savedFilterState, imagePath, paintPath, blurPath, mediaEntities, cropState, w, h, originalW, originalH, rotation, fps, photo, gradientTopColor, gradientBottomColor, hdrInfo, params); + private android.opengl.EGLContext parentContext; + private Handler handler; + + public OutputSurface(android.opengl.EGLContext context, MediaController.SavedFilterState savedFilterState, String imagePath, String paintPath, String blurPath, ArrayList mediaEntities, MediaController.CropState cropState, int w, int h, int originalW, int originalH, int rotation, float fps, boolean photo, Integer gradientTopColor, Integer gradientBottomColor, StoryEntry.HDRInfo hdrInfo, MediaCodecVideoConvertor.ConvertVideoParams params, Handler handler) { + this.parentContext = context; + this.handler = handler; + final CountDownLatch latch = new CountDownLatch(1); + handler.post(() -> { + setupBackground(context); + latch.countDown(); + }); + try { + latch.await(); + } catch (Exception e) { + FileLog.e(e); + } + mTextureRender = new TextureRenderer(savedFilterState, imagePath, paintPath, blurPath, mediaEntities, cropState, w, h, originalW, originalH, rotation, fps, photo, gradientTopColor, gradientBottomColor, hdrInfo, params, handler, bgEGLContext); mTextureRender.surfaceCreated(); mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId()); mSurfaceTexture.setOnFrameAvailableListener(this); mSurface = new Surface(mSurfaceTexture); } - private void eglSetup(int width, int height) { - mEGL = (EGL10) EGLContext.getEGL(); - mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); - - if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) { - throw new RuntimeException("unable to get EGL10 display"); + private EGLDisplay bgEGLDisplay; + private EGLContext bgEGLContext; + private void setupBackground(EGLContext ctx) { + bgEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + if (bgEGLDisplay == EGL14.EGL_NO_DISPLAY) { + throw new RuntimeException("unable to get EGL14 display"); } - - if (!mEGL.eglInitialize(mEGLDisplay, null)) { - mEGLDisplay = null; - throw new RuntimeException("unable to initialize EGL10"); + int[] version = new int[2]; + if (!EGL14.eglInitialize(bgEGLDisplay, version, 0, version, 1)) { + bgEGLDisplay = null; + throw new RuntimeException("unable to initialize EGL14"); } int[] attribList = { - EGL10.EGL_RED_SIZE, 8, - EGL10.EGL_GREEN_SIZE, 8, - EGL10.EGL_BLUE_SIZE, 8, - EGL10.EGL_ALPHA_SIZE, 8, - EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT, - EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL10.EGL_NONE + EGL14.EGL_RED_SIZE, 8, + EGL14.EGL_GREEN_SIZE, 8, + EGL14.EGL_BLUE_SIZE, 8, + EGL14.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL14.EGL_NONE }; EGLConfig[] configs = new EGLConfig[1]; int[] numConfigs = new int[1]; - if (!mEGL.eglChooseConfig(mEGLDisplay, attribList, configs, configs.length, numConfigs)) { - throw new RuntimeException("unable to find RGB888+pbuffer EGL config"); + if (!EGL14.eglChooseConfig(bgEGLDisplay, attribList, 0, configs, 0, configs.length, + numConfigs, 0)) { + throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config"); } int[] attrib_list = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL10.EGL_NONE + EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, + EGL14.EGL_NONE }; - mEGLContext = mEGL.eglCreateContext(mEGLDisplay, configs[0], EGL10.EGL_NO_CONTEXT, attrib_list); + bgEGLContext = EGL14.eglCreateContext(bgEGLDisplay, configs[0], ctx, attrib_list, 0); checkEglError("eglCreateContext"); - if (mEGLContext == null) { + if (bgEGLContext == null) { throw new RuntimeException("null context"); } - int[] surfaceAttribs = { - EGL10.EGL_WIDTH, width, - EGL10.EGL_HEIGHT, height, - EGL10.EGL_NONE - }; - mEGLSurface = mEGL.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs); - checkEglError("eglCreatePbufferSurface"); - if (mEGLSurface == null) { - throw new RuntimeException("surface was null"); + checkEglError("before makeCurrent"); + if (!EGL14.eglMakeCurrent(bgEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, bgEGLContext)) { + throw new RuntimeException("eglMakeCurrent failed"); } } +// private void eglSetup(int width, int height) { +// mEGL = (EGL10) EGLContext.getEGL(); +// mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); +// +// if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) { +// throw new RuntimeException("unable to get EGL10 display"); +// } +// +// if (!mEGL.eglInitialize(mEGLDisplay, null)) { +// mEGLDisplay = null; +// throw new RuntimeException("unable to initialize EGL10"); +// } +// +// int[] attribList = { +// EGL10.EGL_RED_SIZE, 8, +// EGL10.EGL_GREEN_SIZE, 8, +// EGL10.EGL_BLUE_SIZE, 8, +// EGL10.EGL_ALPHA_SIZE, 8, +// EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT, +// EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, +// EGL10.EGL_NONE +// }; +// EGLConfig[] configs = new EGLConfig[1]; +// int[] numConfigs = new int[1]; +// if (!mEGL.eglChooseConfig(mEGLDisplay, attribList, configs, configs.length, numConfigs)) { +// throw new RuntimeException("unable to find RGB888+pbuffer EGL config"); +// } +// int[] attrib_list = { +// EGL_CONTEXT_CLIENT_VERSION, 2, +// EGL10.EGL_NONE +// }; +// mEGLContext = mEGL.eglCreateContext(mEGLDisplay, configs[0], EGL10.EGL_NO_CONTEXT, attrib_list); +// checkEglError("eglCreateContext"); +// if (mEGLContext == null) { +// throw new RuntimeException("null context"); +// } +// int[] surfaceAttribs = { +// EGL10.EGL_WIDTH, width, +// EGL10.EGL_HEIGHT, height, +// EGL10.EGL_NONE +// }; +// mEGLSurface = mEGL.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs); +// checkEglError("eglCreatePbufferSurface"); +// if (mEGLSurface == null) { +// throw new RuntimeException("surface was null"); +// } +// } + public void release() { - if (mEGL != null) { - if (mEGL.eglGetCurrentContext().equals(mEGLContext)) { - mEGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); - } - mEGL.eglDestroySurface(mEGLDisplay, mEGLSurface); - mEGL.eglDestroyContext(mEGLDisplay, mEGLContext); - } +// if (mEGL != null) { +// if (EGL14.eglGetCurrentContext().equals(mEGLContext)) { +// EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); +// } +// if (mEGLSurface != null) { +// EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface); +// } +// if (mEGLContext != null) { +// EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); +// } +// } if (mTextureRender != null) { mTextureRender.release(); } @@ -116,15 +179,15 @@ public class OutputSurface implements SurfaceTexture.OnFrameAvailableListener { mSurfaceTexture = null; } - public void makeCurrent() { - if (mEGL == null) { - throw new RuntimeException("not configured for makeCurrent"); - } - checkEglError("before makeCurrent"); - if (!mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { - throw new RuntimeException("eglMakeCurrent failed"); - } - } +// public void makeCurrent() { +// if (mEGL == null) { +// throw new RuntimeException("not configured for makeCurrent"); +// } +// checkEglError("before makeCurrent"); +// if (!mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { +// throw new RuntimeException("eglMakeCurrent failed"); +// } +// } public Surface getSurface() { return mSurface; @@ -164,8 +227,8 @@ public class OutputSurface implements SurfaceTexture.OnFrameAvailableListener { } private void checkEglError(String msg) { - if (mEGL.eglGetError() != EGL10.EGL_SUCCESS) { - throw new RuntimeException("EGL error encountered (see log)"); + if (EGL14.eglGetError() != EGL14.EGL_SUCCESS) { + throw new RuntimeException("EGL error encountered (see log) at: " + msg); } } diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/video/TextureRenderer.java b/TMessagesProj/src/main/java/org/telegram/messenger/video/TextureRenderer.java index 0a54b5487..a8d93959d 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/video/TextureRenderer.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/video/TextureRenderer.java @@ -11,7 +11,6 @@ package org.telegram.messenger.video; import android.annotation.SuppressLint; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.BlendMode; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; @@ -22,21 +21,24 @@ import android.graphics.RectF; import android.graphics.SurfaceTexture; import android.graphics.Typeface; import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.opengl.EGLContext; import android.opengl.GLES11Ext; import android.opengl.GLES20; import android.opengl.GLES30; import android.opengl.GLUtils; import android.opengl.Matrix; import android.os.Build; +import android.os.Handler; import android.text.Layout; import android.text.SpannableString; import android.text.Spanned; import android.text.TextUtils; -import android.text.style.ReplacementSpan; import android.util.Log; import android.util.Pair; import android.util.TypedValue; import android.view.Gravity; +import android.view.Surface; import android.view.View; import android.view.inputmethod.EditorInfo; @@ -51,11 +53,9 @@ import org.telegram.messenger.Emoji; import org.telegram.messenger.FileLog; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaController; -import org.telegram.messenger.MessageObject; import org.telegram.messenger.UserConfig; import org.telegram.messenger.Utilities; import org.telegram.messenger.VideoEditedInfo; -import org.telegram.tgnet.TLRPC; import org.telegram.ui.Components.AnimatedEmojiDrawable; import org.telegram.ui.Components.AnimatedEmojiSpan; import org.telegram.ui.Components.AnimatedFileDrawable; @@ -68,17 +68,18 @@ import org.telegram.ui.Components.Paint.Views.LinkPreview; import org.telegram.ui.Components.Paint.Views.LocationMarker; import org.telegram.ui.Components.Paint.Views.PaintTextOptionsView; import org.telegram.ui.Components.RLottieDrawable; -import org.telegram.ui.Components.Rect; +import org.telegram.ui.Components.VideoPlayer; import org.telegram.ui.Stories.recorder.PreviewView; import org.telegram.ui.Stories.recorder.StoryEntry; import java.io.File; -import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; -import java.nio.channels.FileChannel; import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import javax.microedition.khronos.opengles.GL10; @@ -248,6 +249,11 @@ public class TextureRenderer { private int simpleInputTexCoordHandle; private int simpleSourceImageHandle; + private int simpleShaderProgramOES; + private int simplePositionHandleOES; + private int simpleInputTexCoordHandleOES; + private int simpleSourceImageHandleOES; + private int blurShaderProgram; private int blurPositionHandle; private int blurInputTexCoordHandle; @@ -274,9 +280,14 @@ public class TextureRenderer { private int imageOrientation; private boolean blendEnabled; - private boolean isPhoto; + private int[] collageTextures; + private ArrayList collageParts; + private boolean isCollage() { + return collageParts != null; + } + private boolean firstFrame = true; Path path; Paint xRefPaint; @@ -286,6 +297,8 @@ public class TextureRenderer { private int[] blurTexture; private int gradientTopColor, gradientBottomColor; + private final Handler handler; + private final EGLContext parentContext; public TextureRenderer( MediaController.SavedFilterState savedFilterState, @@ -302,9 +315,14 @@ public class TextureRenderer { Integer gradientTopColor, Integer gradientBottomColor, StoryEntry.HDRInfo hdrInfo, - MediaCodecVideoConvertor.ConvertVideoParams params + MediaCodecVideoConvertor.ConvertVideoParams params, + Handler handler, + EGLContext parentContext ) { isPhoto = photo; + collageParts = params.collageParts; + this.handler = handler; + this.parentContext = parentContext; float[] texData = { 0.f, 0.f, @@ -561,6 +579,11 @@ public class TextureRenderer { } public void drawFrame(SurfaceTexture st, long time) { +// if (isCollage()) { +// for (int i = 0; i < collageParts.size(); ++i) { +// stepCollagePart(i, collageParts.get(i), time); +// } +// } boolean blurred = false; if (isPhoto) { drawBackground(); @@ -680,6 +703,12 @@ public class TextureRenderer { GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); } } + if (isCollage()) { + for (int i = 0; i < collageParts.size(); ++i) { + stepCollagePart(i, collageParts.get(i), time); + drawCollagePart(i, collageParts.get(i), time); + } + } if (isPhoto || paintTexture != null || stickerTexture != null) { GLES20.glUseProgram(simpleShaderProgram); GLES20.glActiveTexture(GLES20.GL_TEXTURE0); @@ -689,7 +718,7 @@ public class TextureRenderer { GLES20.glVertexAttribPointer(simpleInputTexCoordHandle, 2, GLES20.GL_FLOAT, false, 8, textureBuffer); GLES20.glEnableVertexAttribArray(simplePositionHandle); } - if (imagePathIndex >= 0) { + if (imagePathIndex >= 0 && !isCollage()) { drawTexture(true, paintTexture[imagePathIndex], -10000, -10000, -10000, -10000, 0, false, useMatrixForImagePath && isPhoto, -1); } if (paintPathIndex >= 0) { @@ -1086,7 +1115,7 @@ public class TextureRenderer { } } } - if (filterShaders != null || imagePath != null || paintPath != null || messagePath != null || mediaEntities != null) { + if (filterShaders != null || imagePath != null || paintPath != null || messagePath != null || mediaEntities != null || isCollage()) { int vertexShader = FilterShaders.loadShader(GLES20.GL_VERTEX_SHADER, FilterShaders.simpleVertexShaderCode); int fragmentShader = FilterShaders.loadShader(GLES20.GL_FRAGMENT_SHADER, FilterShaders.simpleFragmentShaderCode); if (vertexShader != 0 && fragmentShader != 0) { @@ -1109,6 +1138,29 @@ public class TextureRenderer { } } } + if (isCollage()) { + int vertexShader = FilterShaders.loadShader(GLES20.GL_VERTEX_SHADER, FilterShaders.simpleVertexShaderCode); + int fragmentShader = FilterShaders.loadShader(GLES20.GL_FRAGMENT_SHADER, "#extension GL_OES_EGL_image_external : require\n" + FilterShaders.simpleFragmentShaderCode.replaceAll("sampler2D", "samplerExternalOES")); + if (vertexShader != 0 && fragmentShader != 0) { + simpleShaderProgramOES = GLES20.glCreateProgram(); + GLES20.glAttachShader(simpleShaderProgramOES, vertexShader); + GLES20.glAttachShader(simpleShaderProgramOES, fragmentShader); + GLES20.glBindAttribLocation(simpleShaderProgramOES, 0, "position"); + GLES20.glBindAttribLocation(simpleShaderProgramOES, 1, "inputTexCoord"); + + GLES20.glLinkProgram(simpleShaderProgramOES); + int[] linkStatus = new int[1]; + GLES20.glGetProgramiv(simpleShaderProgramOES, GLES20.GL_LINK_STATUS, linkStatus, 0); + if (linkStatus[0] == 0) { + GLES20.glDeleteProgram(simpleShaderProgramOES); + simpleShaderProgramOES = 0; + } else { + simplePositionHandleOES = GLES20.glGetAttribLocation(simpleShaderProgramOES, "position"); + simpleInputTexCoordHandleOES = GLES20.glGetAttribLocation(simpleShaderProgramOES, "inputTexCoord"); + simpleSourceImageHandleOES = GLES20.glGetUniformLocation(simpleShaderProgramOES, "sTexture"); + } + } + } if (filterShaders != null) { filterShaders.create(); @@ -1185,6 +1237,17 @@ public class TextureRenderer { FileLog.e(e); } } + if (isCollage()) { + try { + collageTextures = new int[collageParts.size()]; + GLES20.glGenTextures(collageTextures.length, collageTextures, 0); + for (int i = 0; i < collageParts.size(); ++i) { + initCollagePart(i, collageParts.get(i)); + } + } catch (Exception e) { + FileLog.e(e); + } + } if (mediaEntities != null || backgroundDrawable != null) { try { stickerBitmap = Bitmap.createBitmap(512, 512, Bitmap.Config.ARGB_8888); @@ -1486,6 +1549,230 @@ public class TextureRenderer { } } + public static final boolean USE_MEDIACODEC = true; + + private void initCollagePart(int i, VideoEditedInfo.Part part) { + AtomicInteger width = new AtomicInteger(part.width); + AtomicInteger height = new AtomicInteger(part.height); + AtomicInteger rotate = new AtomicInteger(0); + if (part.isVideo) { + if (USE_MEDIACODEC) { + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, collageTextures[i]); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + + part.surfaceTexture = new SurfaceTexture(collageTextures[i]); + part.surfaceTexture.setDefaultBufferSize(part.width, part.height); + try { + part.player = new MediaCodecPlayer(part.path, new Surface(part.surfaceTexture)); + } catch (Exception e) { + FileLog.e(e); + part.player = null; + } + + if (part.player != null) { + width.set(part.player.getOrientedWidth()); + height.set(part.player.getOrientedHeight()); + rotate.set(part.player.getOrientation()); + } else { + part.surfaceTexture.release(); + part.surfaceTexture = null; + GLES20.glDeleteTextures(1, collageTextures, i); + GLES20.glGenTextures(1, collageTextures, i); + + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, collageTextures[i]); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + + part.animatedFileDrawable = new AnimatedFileDrawable(new File(part.path), true, 0, 0, null, null, null, 0, UserConfig.selectedAccount, true, 512, 512, null); + if (part.animatedFileDrawable.decoderFailed()) { + throw new RuntimeException("Failed to decode with ffmpeg software codecs"); + } + part.framesPerDraw = part.animatedFileDrawable.getFps() / videoFps; + part.msPerFrame = 1000.0f / part.animatedFileDrawable.getFps(); + part.currentFrame = 1; + Bitmap bitmap = part.animatedFileDrawable.getNextFrame(false); + if (bitmap != null) { + GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0); + } + width.set(part.animatedFileDrawable.getIntrinsicWidth()); + height.set(part.animatedFileDrawable.getIntrinsicHeight()); + rotate.set(part.animatedFileDrawable.getOrientation()); + } + } + } else { + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, collageTextures[i]); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + + final BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inMutable = true; + Bitmap bitmap = BitmapFactory.decodeFile(part.path, opts); + final Pair orientation = AndroidUtilities.getImageOrientation(part.path); + if (orientation.first != 0 || orientation.second != 0) { + android.graphics.Matrix matrix = new android.graphics.Matrix(); + if (orientation.second != 0) + matrix.postScale(orientation.second == 1 ? -1 : 1, orientation.second == 2 ? -1 : 1); + if (orientation.first != 0) + matrix.postRotate(orientation.first); + bitmap = Bitmaps.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); + } + GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); + width.set(bitmap.getWidth()); + height.set(bitmap.getHeight()); + } + + final float[] pos = new float[] { + part.part.l(2.0f) - 1.0f, -(part.part.t(2.0f) - 1.0f), + part.part.r(2.0f) - 1.0f, -(part.part.t(2.0f) - 1.0f), + part.part.l(2.0f) - 1.0f, -(part.part.b(2.0f) - 1.0f), + part.part.r(2.0f) - 1.0f, -(part.part.b(2.0f) - 1.0f) + }; + final float partWidth = part.part.w(transformedWidth); + final float partHeight = part.part.h(transformedHeight); + final int W = width.get(), H = height.get(); + int r = rotate.get(); + final float scale = 1.0f / Math.max(partWidth / W, partHeight / H); + float uvHW = partWidth * scale / W / 2; + float uvHH = partHeight * scale / H / 2; + if ((r / 90) % 2 == 1) { + float x = uvHW; + uvHW = uvHH; + uvHH = x; + } + final float[] uv = new float[] { + 0.5f - uvHW, 0.5f - uvHH, + 0.5f + uvHW, 0.5f - uvHH, + 0.5f - uvHW, 0.5f + uvHH, + 0.5f + uvHW, 0.5f + uvHH + }; + while (r > 0) { + // left top 0 1 + // right top 2 3 + // left bottom 4 5 + // right bottom 6 7 + final float uv0 = uv[0], uv1 = uv[1]; + uv[0] = uv[4]; + uv[1] = uv[5]; + + uv[4] = uv[6]; + uv[5] = uv[7]; + + uv[6] = uv[2]; + uv[7] = uv[3]; + + uv[2] = uv0; + uv[3] = uv1; + r -= 90; + } + while (r < 0) { + // left top 0 1 + // right top 2 3 + // left bottom 4 5 + // right bottom 6 7 + final float uv0 = uv[0], uv1 = uv[1]; + uv[0] = uv[2]; + uv[1] = uv[3]; + + uv[2] = uv[6]; + uv[3] = uv[7]; + + uv[6] = uv[4]; + uv[7] = uv[5]; + + uv[4] = uv0; + uv[5] = uv1; + r += 90; + } + part.posBuffer = floats(pos); + part.uvBuffer = floats(uv); + } + + private void destroyCollagePart(int i, VideoEditedInfo.Part part) { + if (part == null) return; + if (part.animatedFileDrawable != null) { + part.animatedFileDrawable.recycle(); + part.animatedFileDrawable = null; + } + if (part.player != null) { + part.player.release(); + part.player = null; + } + if (part.surfaceTexture != null) { + part.surfaceTexture.release(); + part.surfaceTexture = null; + } + } + + private FloatBuffer floats(float[] values) { + final FloatBuffer buffer = ByteBuffer.allocateDirect(values.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer(); + buffer.put(values).position(0); + return buffer; + } + + private void stepCollagePart(int i, VideoEditedInfo.Part part, long time) { + final long ms = time / 1_000_000L; + final long position = Utilities.clamp(ms - part.offset, (long) (part.right * part.duration), (long) (part.left * part.duration)); + if (part.player != null) { + part.player.ensure(position); + part.surfaceTexture.updateTexImage(); + } else if (part.animatedFileDrawable != null) { + boolean first = part.animatedFileDrawable.getProgressMs() <= 0; + if (position < part.animatedFileDrawable.getProgressMs() || first && position > 1000) { + part.animatedFileDrawable.seekToSync(position); + } + while (part.animatedFileDrawable.getProgressMs() + part.msPerFrame * 2 < position) { + long before = part.animatedFileDrawable.getProgressMs(); + part.animatedFileDrawable.skipNextFrame(false); + long after = part.animatedFileDrawable.getProgressMs(); + if (after == before) { + break; + } + } + if (first || position > part.animatedFileDrawable.getProgressMs() - part.msPerFrame / 2) { + Bitmap bitmap = part.animatedFileDrawable.getNextFrame(false); + if (bitmap != null) { + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, collageTextures[i]); + GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0); + } + } + } + } + + private void drawCollagePart(int i, VideoEditedInfo.Part part, long time) { + if (part.player != null && part.isVideo) { + GLES20.glUseProgram(simpleShaderProgramOES); + GLES20.glActiveTexture(GLES20.GL_TEXTURE3); + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, collageTextures[i]); + GLES20.glUniform1i(simpleSourceImageHandleOES, 3); + + GLES20.glEnableVertexAttribArray(simpleInputTexCoordHandleOES); + GLES20.glVertexAttribPointer(simpleInputTexCoordHandleOES, 2, GLES20.GL_FLOAT, false, 8, part.uvBuffer); + + GLES20.glEnableVertexAttribArray(simplePositionHandleOES); + GLES20.glVertexAttribPointer(simplePositionHandleOES, 2, GLES20.GL_FLOAT, false, 8, part.posBuffer); + } else { + GLES20.glUseProgram(simpleShaderProgram); + GLES20.glActiveTexture(GLES20.GL_TEXTURE2); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, collageTextures[i]); + GLES20.glUniform1i(simpleSourceImageHandle, 2); + + GLES20.glEnableVertexAttribArray(simpleInputTexCoordHandle); + GLES20.glVertexAttribPointer(simpleInputTexCoordHandle, 2, GLES20.GL_FLOAT, false, 8, part.uvBuffer); + + GLES20.glEnableVertexAttribArray(simplePositionHandle); + GLES20.glVertexAttribPointer(simplePositionHandle, 2, GLES20.GL_FLOAT, false, 8, part.posBuffer); + } + + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + } + private int createProgram(String vertexSource, String fragmentSource, boolean is300) { if (is300) { int vertexShader = FilterShaders.loadShader(GLES30.GL_VERTEX_SHADER, vertexSource); @@ -1555,6 +1842,13 @@ public class TextureRenderer { } } } + if (collageParts != null) { + for (VideoEditedInfo.Part part : collageParts) { + for (int i = 0; i < collageParts.size(); ++i) { + destroyCollagePart(i, collageParts.get(i)); + } + } + } } public void changeFragmentShader(String fragmentExternalShader, String fragmentShader, boolean is300) { diff --git a/TMessagesProj/src/main/java/org/telegram/messenger/video/VideoPlayerHolderBase.java b/TMessagesProj/src/main/java/org/telegram/messenger/video/VideoPlayerHolderBase.java index ccca9d828..7b48b99c7 100644 --- a/TMessagesProj/src/main/java/org/telegram/messenger/video/VideoPlayerHolderBase.java +++ b/TMessagesProj/src/main/java/org/telegram/messenger/video/VideoPlayerHolderBase.java @@ -6,7 +6,7 @@ import android.graphics.Paint; import android.graphics.SurfaceTexture; import android.net.Uri; import android.os.Build; -import android.util.Log; +import android.view.Surface; import android.view.SurfaceView; import android.view.TextureView; @@ -33,7 +33,7 @@ public class VideoPlayerHolderBase { public float progress; int lastState; - public long currentPosition; + public volatile long currentPosition; private int currentAccount; long playerDuration; boolean audioDisabled; @@ -41,6 +41,7 @@ public class VideoPlayerHolderBase { private TextureView textureView; private SurfaceView surfaceView; + private Surface surface; public Bitmap playerStubBitmap; public Paint playerStubPaint; public long pendingSeekTo; @@ -53,12 +54,22 @@ public class VideoPlayerHolderBase { public VideoPlayerHolderBase with(SurfaceView surfaceView) { this.surfaceView = surfaceView; this.textureView = null; + this.surface = null; return this; } public VideoPlayerHolderBase with(TextureView textureView) { this.surfaceView = null; this.textureView = textureView; + this.surface = null; + return this; + } + + + public VideoPlayerHolderBase with(Surface surface) { + this.surfaceView = null; + this.textureView = null; + this.surface = surface; return this; } @@ -76,7 +87,7 @@ public class VideoPlayerHolderBase { currentPosition = videoPlayer.getCurrentPosition(); playerDuration = videoPlayer.getDuration(); } - if (lastState == ExoPlayer.STATE_READY) { + if (lastState == ExoPlayer.STATE_READY || lastState == ExoPlayer.STATE_BUFFERING) { dispatchQueue.cancelRunnable(progressRunnable); dispatchQueue.postRunnable(progressRunnable, 16); } @@ -107,7 +118,7 @@ public class VideoPlayerHolderBase { }); } - public void start(boolean paused, Uri uri, long position, boolean audioDisabled, float speed) { + public void start(boolean attach, boolean paused, Uri uri, long position, boolean audioDisabled, float speed) { startTime = System.currentTimeMillis(); this.audioDisabled = audioDisabled; this.paused = paused; @@ -127,22 +138,44 @@ public class VideoPlayerHolderBase { videoPlayer.preparePlayer(uri, "other"); videoPlayer.setWorkerQueue(dispatchQueue); if (!paused) { - if (surfaceView != null) { + if (surface != null) { + videoPlayer.setSurface(surface); + } else if (surfaceView != null) { videoPlayer.setSurfaceView(surfaceView); } else { videoPlayer.setTextureView(textureView); } videoPlayer.setPlayWhenReady(true); + } else if (attach) { + if (surface != null) { + videoPlayer.setSurface(surface); + } else if (surfaceView != null) { + videoPlayer.setSurfaceView(surfaceView); + } else { + videoPlayer.setTextureView(textureView); + } + videoPlayer.setPlayWhenReady(false); } } else { FileLog.d("videoplayerholderbase.start(): player already exist"); if (!paused) { - if (surfaceView != null) { + if (surface != null) { + videoPlayer.setSurface(surface); + } else if (surfaceView != null) { videoPlayer.setSurfaceView(surfaceView); } else { videoPlayer.setTextureView(textureView); } videoPlayer.play(); + } else if (attach) { + if (surface != null) { + videoPlayer.setSurface(surface); + } else if (surfaceView != null) { + videoPlayer.setSurfaceView(surfaceView); + } else { + videoPlayer.setTextureView(textureView); + } + videoPlayer.setPlayWhenReady(false); } } if (position > 0) { @@ -154,6 +187,11 @@ public class VideoPlayerHolderBase { }); } + private boolean allowMultipleInstances; + public void allowMultipleInstances(boolean allow) { + this.allowMultipleInstances = allow; + } + private volatile int triesCount = 3; private void ensurePlayerCreated(boolean audioDisabled) { @@ -161,10 +199,13 @@ public class VideoPlayerHolderBase { videoPlayer.releasePlayer(true); } videoPlayer = new VideoPlayer(false, audioDisabled); + videoPlayer.allowMultipleInstances = allowMultipleInstances; videoPlayer.setDelegate(new VideoPlayer.VideoPlayerDelegate() { @Override public void onStateChanged(boolean playWhenReady, int playbackState) { lastState = playbackState; + currentPosition = videoPlayer.getCurrentPosition(); + playerDuration = videoPlayer.getDuration(); if (playbackState == ExoPlayer.STATE_READY || playbackState == ExoPlayer.STATE_BUFFERING) { dispatchQueue.cancelRunnable(progressRunnable); dispatchQueue.postRunnable(progressRunnable); @@ -193,12 +234,19 @@ public class VideoPlayerHolderBase { videoPlayer.preparePlayer(uri, "other"); videoPlayer.seekTo(positionMs); }); + } else { + AndroidUtilities.runOnUIThread(() -> { + if (onErrorListener != null) { + onErrorListener.run(); + onErrorListener = null; + } + }); } } @Override public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { - + VideoPlayerHolderBase.this.onVideoSizeChanged(width, height, unappliedRotationDegrees, pixelWidthHeightRatio); } @Override @@ -213,7 +261,7 @@ public class VideoPlayerHolderBase { onReadyListener.run(); onReadyListener = null; } - }, surfaceView == null ? 16 : 32); + }, surface != null ? 0 : surfaceView == null ? 16 : 32); } @Override @@ -229,11 +277,18 @@ public class VideoPlayerHolderBase { videoPlayer.setIsStory(); } + protected void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { + + } private Runnable onReadyListener; public void setOnReadyListener(Runnable listener) { onReadyListener = listener; } + private Runnable onErrorListener; + public void setOnErrorListener(Runnable listener) { + onErrorListener = listener; + } public boolean release(Runnable whenReleased) { TLRPC.Document document = this.document; @@ -246,9 +301,11 @@ public class VideoPlayerHolderBase { } released = true; dispatchQueue.cancelRunnable(initRunnable); + dispatchQueue.cancelRunnable(progressRunnable); initRunnable = null; dispatchQueue.postRunnable(() -> { if (videoPlayer != null) { + videoPlayer.setSurface(null); videoPlayer.setTextureView(null); videoPlayer.setSurfaceView(null); videoPlayer.releasePlayer(false); @@ -260,6 +317,7 @@ public class VideoPlayerHolderBase { AndroidUtilities.runOnUIThread(whenReleased); } videoPlayer = null; + dispatchQueue.cancelRunnable(progressRunnable); }); if (playerStubBitmap != null) { AndroidUtilities.recycleBitmap(playerStubBitmap); @@ -321,7 +379,9 @@ public class VideoPlayerHolderBase { paused = false; dispatchQueue.postRunnable(() -> { if (videoPlayer != null) { - if (surfaceView != null) { + if (surface != null) { + videoPlayer.setSurface(surface); + } else if (surfaceView != null) { videoPlayer.setSurfaceView(surfaceView); } else { videoPlayer.setTextureView(textureView); @@ -345,7 +405,9 @@ public class VideoPlayerHolderBase { paused = false; dispatchQueue.postRunnable(() -> { if (videoPlayer != null) { - if (surfaceView != null) { + if (surface != null) { + videoPlayer.setSurface(surface); + } else if (surfaceView != null) { videoPlayer.setSurfaceView(surfaceView); } else { videoPlayer.setTextureView(textureView); @@ -379,11 +441,14 @@ public class VideoPlayerHolderBase { videoPlayer.releasePlayer(false); videoPlayer = null; ensurePlayerCreated(audioDisabled); + final Uri uri = this.uri == null ? contentUri : this.uri; FileLog.d("videoplayerholderbase.setAudioEnabled(): repreparePlayer as audio track is enabled back uri=" + uri); videoPlayer.preparePlayer(uri, "other"); videoPlayer.setWorkerQueue(dispatchQueue); if (!prepared) { - if (surfaceView != null) { + if (surface != null) { + videoPlayer.setSurface(surface); + } else if (surfaceView != null) { videoPlayer.setSurfaceView(surfaceView); } else { videoPlayer.setTextureView(textureView); @@ -484,6 +549,16 @@ public class VideoPlayerHolderBase { }); } + public void seekTo(long position, boolean fast, Runnable done) { + dispatchQueue.postRunnable(() -> { + if (videoPlayer == null) { + pendingSeekTo = position; + return; + } + videoPlayer.seekTo(position, fast, done); + }); + } + public Uri getCurrentUri() { return contentUri; } diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java b/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java index b3eaabd80..953b553c3 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/ConnectionsManager.java @@ -383,7 +383,9 @@ public class ConnectionsManager extends BaseController { } if ((connectionType & ConnectionTypeDownload) != 0 && VideoPlayer.activePlayers.isEmpty()) { long ping_time = native_getCurrentPingTime(currentAccount); - DefaultBandwidthMeter.getSingletonInstance(ApplicationLoader.applicationContext).onTransfer(responseSize, Math.max(0, (System.currentTimeMillis() - finalStartRequestTime) - ping_time)); + final long size = responseSize; + final long delta = Math.max(0, (System.currentTimeMillis() - finalStartRequestTime) - ping_time); + DefaultBandwidthMeter.getSingletonInstance(ApplicationLoader.applicationContext).onTransfer(size, delta); } if (BuildVars.DEBUG_PRIVATE_VERSION && !getUserConfig().isClientActivated() && error != null && error.code == 400 && Objects.equals(error.text, "CONNECTION_NOT_INITED")) { if (BuildVars.LOGS_ENABLED) { @@ -437,16 +439,16 @@ public class ConnectionsManager extends BaseController { private void listen(int requestToken, RequestDelegateInternal onComplete, QuickAckDelegate onQuickAck, WriteToSocketDelegate onWriteToSocket) { requestCallbacks.put(requestToken, new RequestCallbacks(onComplete, onQuickAck, onWriteToSocket)); - FileLog.d("{rc} listen(" + currentAccount + ", " + requestToken + "): " + requestCallbacks.size() + " requests' callbacks"); +// FileLog.d("{rc} listen(" + currentAccount + ", " + requestToken + "): " + requestCallbacks.size() + " requests' callbacks"); } private void listenCancel(int requestToken, Runnable onCancelled) { RequestCallbacks callbacks = requestCallbacks.get(requestToken); if (callbacks != null) { callbacks.onCancelled = onCancelled; - FileLog.d("{rc} listenCancel(" + currentAccount + ", " + requestToken + "): " + requestCallbacks.size() + " requests' callbacks"); +// FileLog.d("{rc} listenCancel(" + currentAccount + ", " + requestToken + "): " + requestCallbacks.size() + " requests' callbacks"); } else { - FileLog.d("{rc} listenCancel(" + currentAccount + ", " + requestToken + "): callback not found, " + requestCallbacks.size() + " requests' callbacks"); +// FileLog.d("{rc} listenCancel(" + currentAccount + ", " + requestToken + "): callback not found, " + requestCallbacks.size() + " requests' callbacks"); } } @@ -460,13 +462,13 @@ public class ConnectionsManager extends BaseController { callbacks.onCancelled.run(); } connectionsManager.requestCallbacks.remove(requestToken); - FileLog.d("{rc} onRequestClear(" + currentAccount + ", " + requestToken + ", " + cancelled + "): request to cancel is found " + connectionsManager.requestCallbacks.size() + " requests' callbacks"); +// FileLog.d("{rc} onRequestClear(" + currentAccount + ", " + requestToken + ", " + cancelled + "): request to cancel is found " + connectionsManager.requestCallbacks.size() + " requests' callbacks"); } else { - FileLog.d("{rc} onRequestClear(" + currentAccount + ", " + requestToken + ", " + cancelled + "): request to cancel is not found " + connectionsManager.requestCallbacks.size() + " requests' callbacks"); +// FileLog.d("{rc} onRequestClear(" + currentAccount + ", " + requestToken + ", " + cancelled + "): request to cancel is not found " + connectionsManager.requestCallbacks.size() + " requests' callbacks"); } } else if (callbacks != null) { connectionsManager.requestCallbacks.remove(requestToken); - FileLog.d("{rc} onRequestClear(" + currentAccount + ", " + requestToken + ", " + cancelled + "): " + connectionsManager.requestCallbacks.size() + " requests' callbacks"); +// FileLog.d("{rc} onRequestClear(" + currentAccount + ", " + requestToken + ", " + cancelled + "): " + connectionsManager.requestCallbacks.size() + " requests' callbacks"); } } @@ -479,9 +481,9 @@ public class ConnectionsManager extends BaseController { if (callbacks.onComplete != null) { callbacks.onComplete.run(response, errorCode, errorText, networkType, timestamp, requestMsgId, dcId); } - FileLog.d("{rc} onRequestComplete(" + currentAccount + ", " + requestToken + "): found request " + requestToken + ", " + connectionsManager.requestCallbacks.size() + " requests' callbacks"); +// FileLog.d("{rc} onRequestComplete(" + currentAccount + ", " + requestToken + "): found request " + requestToken + ", " + connectionsManager.requestCallbacks.size() + " requests' callbacks"); } else { - FileLog.d("{rc} onRequestComplete(" + currentAccount + ", " + requestToken + "): not found request " + requestToken + "! " + connectionsManager.requestCallbacks.size() + " requests' callbacks"); +// FileLog.d("{rc} onRequestComplete(" + currentAccount + ", " + requestToken + "): not found request " + requestToken + "! " + connectionsManager.requestCallbacks.size() + " requests' callbacks"); } } @@ -493,9 +495,9 @@ public class ConnectionsManager extends BaseController { if (callbacks.onQuickAck != null) { callbacks.onQuickAck.run(); } - FileLog.d("{rc} onRequestQuickAck(" + currentAccount + ", " + requestToken + "): found request " + requestToken + ", " + connectionsManager.requestCallbacks.size() + " requests' callbacks"); +// FileLog.d("{rc} onRequestQuickAck(" + currentAccount + ", " + requestToken + "): found request " + requestToken + ", " + connectionsManager.requestCallbacks.size() + " requests' callbacks"); } else { - FileLog.d("{rc} onRequestQuickAck(" + currentAccount + ", " + requestToken + "): not found request " + requestToken + "! " + connectionsManager.requestCallbacks.size() + " requests' callbacks"); +// FileLog.d("{rc} onRequestQuickAck(" + currentAccount + ", " + requestToken + "): not found request " + requestToken + "! " + connectionsManager.requestCallbacks.size() + " requests' callbacks"); } } @@ -507,9 +509,9 @@ public class ConnectionsManager extends BaseController { if (callbacks.onWriteToSocket != null) { callbacks.onWriteToSocket.run(); } - FileLog.d("{rc} onRequestWriteToSocket(" + currentAccount + ", " + requestToken + "): found request " + requestToken + ", " + connectionsManager.requestCallbacks.size() + " requests' callbacks"); +// FileLog.d("{rc} onRequestWriteToSocket(" + currentAccount + ", " + requestToken + "): found request " + requestToken + ", " + connectionsManager.requestCallbacks.size() + " requests' callbacks"); } else { - FileLog.d("{rc} onRequestWriteToSocket(" + currentAccount + ", " + requestToken + "): not found request " + requestToken + "! " + connectionsManager.requestCallbacks.size() + " requests' callbacks"); +// FileLog.d("{rc} onRequestWriteToSocket(" + currentAccount + ", " + requestToken + "): not found request " + requestToken + "! " + connectionsManager.requestCallbacks.size() + " requests' callbacks"); } } diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java b/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java index 6385f9ec6..029bd47c9 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/TLRPC.java @@ -28,6 +28,7 @@ import org.telegram.messenger.MessagesController; import org.telegram.messenger.SvgHelper; import org.telegram.messenger.Utilities; import org.telegram.tgnet.tl.TL_bots; +import org.telegram.tgnet.tl.TL_payments; import org.telegram.tgnet.tl.TL_stars; import org.telegram.tgnet.tl.TL_stats; import org.telegram.tgnet.tl.TL_stories; @@ -81,7 +82,7 @@ public class TLRPC { public static final int MESSAGE_FLAG_HAS_BOT_ID = 0x00000800; public static final int MESSAGE_FLAG_EDITED = 0x00008000; - public static final int LAYER = 193; + public static final int LAYER = 195; public static abstract class EmailVerifyPurpose extends TLObject { @@ -28087,6 +28088,7 @@ public class TLRPC { public WallPaper wallpaper; public Peer peer; public byte[] payload; + public int subscription_until_date; public static MessageAction TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { MessageAction result = null; @@ -28268,6 +28270,9 @@ public class TLRPC { case TL_messageActionPaymentSent.constructor: result = new TL_messageActionPaymentSent(); break; + case TL_messageActionPaymentSent_layer193.constructor: + result = new TL_messageActionPaymentSent_layer193(); + break; case TL_messageActionPaymentSent_layer140.constructor: result = new TL_messageActionPaymentSent_layer140(); break; @@ -28286,6 +28291,9 @@ public class TLRPC { case TL_messageActionPaymentSentMe.constructor: result = new TL_messageActionPaymentSentMe(); break; + case TL_messageActionPaymentSentMe_layer193.constructor: + result = new TL_messageActionPaymentSentMe_layer193(); + break; case TL_messageActionGiftPremium.constructor: result = new TL_messageActionGiftPremium(); break; @@ -29284,6 +29292,39 @@ public class TLRPC { } public static class TL_messageActionPaymentSent extends MessageAction { + public static final int constructor = 0xc624b16e; + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + recurring_init = (flags & 4) != 0; + recurring_used = (flags & 8) != 0; + currency = stream.readString(exception); + total_amount = stream.readInt64(exception); + if ((flags & 1) != 0) { + invoice_slug = stream.readString(exception); + } + if ((flags & 16) != 0) { + subscription_until_date = stream.readInt32(exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = recurring_init ? (flags | 4) : (flags &~ 4); + flags = recurring_used ? (flags | 8) : (flags &~ 8); + stream.writeInt32(flags); + stream.writeString(currency); + stream.writeInt64(total_amount); + if ((flags & 1) != 0) { + stream.writeString(invoice_slug); + } + if ((flags & 16) != 0) { + stream.writeInt32(subscription_until_date); + } + } + } + + public static class TL_messageActionPaymentSent_layer193 extends TL_messageActionPaymentSent { public static final int constructor = 0x96163f56; public void readParams(AbstractSerializedData stream, boolean exception) { @@ -29477,7 +29518,7 @@ public class TLRPC { } public static class TL_messageActionPaymentSentMe extends MessageAction { - public static final int constructor = 0x8f31b327; + public static final int constructor = 0xffa00ccc; public int flags; public byte[] payload; @@ -29485,6 +29526,49 @@ public class TLRPC { public String shipping_option_id; public TL_paymentCharge charge; + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + recurring_init = (flags & 4) != 0; + recurring_used = (flags & 8) != 0; + currency = stream.readString(exception); + total_amount = stream.readInt64(exception); + payload = stream.readByteArray(exception); + if ((flags & 1) != 0) { + info = TL_paymentRequestedInfo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 2) != 0) { + shipping_option_id = stream.readString(exception); + } + charge = TL_paymentCharge.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 16) != 0) { + subscription_until_date = stream.readInt32(exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = recurring_init ? (flags | 4) : (flags &~ 4); + flags = recurring_used ? (flags | 8) : (flags &~ 8); + stream.writeInt32(flags); + stream.writeString(currency); + stream.writeInt64(total_amount); + stream.writeByteArray(payload); + if ((flags & 1) != 0) { + info.serializeToStream(stream); + } + if ((flags & 2) != 0) { + stream.writeString(shipping_option_id); + } + charge.serializeToStream(stream); + if ((flags & 16) != 0) { + stream.writeInt32(subscription_until_date); + } + } + } + + public static class TL_messageActionPaymentSentMe_layer193 extends TL_messageActionPaymentSentMe { + public static final int constructor = 0x8f31b327; + public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); recurring_init = (flags & 4) != 0; @@ -54406,6 +54490,7 @@ public class TLRPC { public long personal_channel_id; public int personal_channel_message; public int stargifts_count; + public TL_payments.starRefProgram starref_program; public static UserFull TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { UserFull result = null; @@ -54413,6 +54498,9 @@ public class TLRPC { case TL_userFull.constructor: result = new TL_userFull(); break; + case TL_userFull_layer194.constructor: + result = new TL_userFull_layer194(); + break; case TL_userFull_layer188.constructor: result = new TL_userFull_layer188(); break; @@ -54476,6 +54564,226 @@ public class TLRPC { } public static class TL_userFull extends UserFull { + public static final int constructor = 0x979d2376; + + 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; + has_scheduled = (flags & 4096) != 0; + video_calls_available = (flags & 8192) != 0; + voice_messages_forbidden = (flags & 1048576) != 0; + translations_disabled = (flags & 8388608) != 0; + stories_pinned_available = (flags & 67108864) != 0; + blocked_my_stories_from = (flags & 134217728) != 0; + wallpaper_overridden = (flags & 268435456) != 0; + contact_require_premium = (flags & 536870912) != 0; + read_dates_private = (flags & 1073741824) != 0; + flags2 = stream.readInt32(exception); + sponsored_enabled = (flags2 & 128) != 0; + can_view_revenue = (flags2 & 512) != 0; + bot_can_manage_emoji_status = (flags2 & 1024) != 0; + id = stream.readInt64(exception); + if ((flags & 2) != 0) { + about = stream.readString(exception); + } + settings = PeerSettings.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 2097152) != 0) { + personal_photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 4) != 0) { + profile_photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 4194304) != 0) { + fallback_photo = Photo.TLdeserialize(stream, stream.readInt32(exception), exception); + } + notify_settings = PeerNotifySettings.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 8) != 0) { + bot_info = TL_bots.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); + } + if ((flags & 16384) != 0) { + ttl_period = stream.readInt32(exception); + } + if ((flags & 32768) != 0) { + theme_emoticon = stream.readString(exception); + } + if ((flags & 65536) != 0) { + private_forward_name = stream.readString(exception); + } + if ((flags & 131072) != 0) { + bot_group_admin_rights = TL_chatAdminRights.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 262144) != 0) { + bot_broadcast_admin_rights = TL_chatAdminRights.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 524288) != 0) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TL_premiumGiftOption object = TL_premiumGiftOption.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + premium_gifts.add(object); + } + } + if ((flags & 16777216) != 0) { + wallpaper = WallPaper.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 33554432) != 0) { + stories = TL_stories.PeerStories.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags2 & 1) != 0) { + business_work_hours = TL_businessWorkHours.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags2 & 2) != 0) { + business_location = TL_businessLocation.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags2 & 4) != 0) { + business_greeting_message = TL_businessGreetingMessage.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags2 & 8) != 0) { + business_away_message = TL_businessAwayMessage.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags2 & 16) != 0) { + business_intro = TL_businessIntro.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags2 & 32) != 0) { + birthday = TL_birthday.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags2 & 64) != 0) { + personal_channel_id = stream.readInt64(exception); + personal_channel_message = stream.readInt32(exception); + } + if ((flags2 & 256) != 0) { + stargifts_count = stream.readInt32(exception); + } + if ((flags2 & 2048) != 0) { + starref_program = TL_payments.starRefProgram.TLdeserialize(stream, stream.readInt32(exception), 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); + flags = has_scheduled ? (flags | 4096) : (flags &~ 4096); + flags = video_calls_available ? (flags | 8192) : (flags &~ 8192); + flags = voice_messages_forbidden ? (flags | 1048576) : (flags &~ 1048576); + flags = translations_disabled ? (flags | 8388608) : (flags &~ 8388608); + flags = stories_pinned_available ? (flags | 67108864) : (flags &~ 67108864); + flags = blocked_my_stories_from ? (flags | 134217728) : (flags &~ 134217728); + flags = wallpaper_overridden ? (flags | 268435456) : (flags &~ 268435456); + flags = contact_require_premium ? (flags | 536870912) : (flags &~ 536870912); + flags = read_dates_private ? (flags | 1073741824) : (flags &~ 1073741824); + stream.writeInt32(flags); + flags2 = sponsored_enabled ? (flags2 | 128) : (flags2 &~ 128); + flags2 = can_view_revenue ? (flags2 | 512) : (flags2 &~ 512); + flags2 = bot_can_manage_emoji_status ? (flags2 | 1024) : (flags2 &~ 1024); + stream.writeInt32(flags2); + stream.writeInt64(id); + if ((flags & 2) != 0) { + stream.writeString(about); + } + settings.serializeToStream(stream); + if ((flags & 2097152) != 0) { + personal_photo.serializeToStream(stream); + } + if ((flags & 4) != 0) { + profile_photo.serializeToStream(stream); + } + if ((flags & 4194304) != 0) { + fallback_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); + } + if ((flags & 16384) != 0) { + stream.writeInt32(ttl_period); + } + if ((flags & 32768) != 0) { + stream.writeString(theme_emoticon); + } + if ((flags & 65536) != 0) { + stream.writeString(private_forward_name); + } + if ((flags & 131072) != 0) { + bot_group_admin_rights.serializeToStream(stream); + } + if ((flags & 262144) != 0) { + bot_broadcast_admin_rights.serializeToStream(stream); + } + if ((flags & 524288) != 0) { + stream.writeInt32(0x1cb5c415); + int count = premium_gifts.size(); + stream.writeInt32(count); + for (int a = 0; a < count; a++) { + premium_gifts.get(a).serializeToStream(stream); + } + } + if ((flags & 16777216) != 0) { + wallpaper.serializeToStream(stream); + } + if ((flags & 33554432) != 0) { + stories.serializeToStream(stream); + } + if ((flags2 & 1) != 0) { + business_work_hours.serializeToStream(stream); + } + if ((flags2 & 2) != 0) { + business_location.serializeToStream(stream); + } + if ((flags2 & 4) != 0) { + business_greeting_message.serializeToStream(stream); + } + if ((flags2 & 8) != 0) { + business_away_message.serializeToStream(stream); + } + if ((flags2 & 16) != 0) { + business_intro.serializeToStream(stream); + } + if ((flags2 & 32) != 0) { + birthday.serializeToStream(stream); + } + if ((flags2 & 64) != 0) { + stream.writeInt64(personal_channel_id); + stream.writeInt32(personal_channel_message); + } + if ((flags2 & 256) != 0) { + stream.writeInt32(stargifts_count); + } + if ((flags2 & 2048) != 0) { + starref_program.serializeToStream(stream); + } + } + } + + public static class TL_userFull_layer194 extends TL_userFull { public static final int constructor = 0x1f58e369; public void readParams(AbstractSerializedData stream, boolean exception) { @@ -62309,9 +62617,11 @@ public class TLRPC { } public static class TL_contacts_resolveUsername extends TLObject { - public static final int constructor = 0xf93ccba3; + public static final int constructor = 0x725afbbc; + public int flags; public String username; + public String referer; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { return TL_contacts_resolvedPeer.TLdeserialize(stream, constructor, exception); @@ -62319,7 +62629,11 @@ public class TLRPC { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); + stream.writeInt32(flags); stream.writeString(username); + if ((flags & 1) != 0) { + stream.writeString(referer); + } } } @@ -78248,6 +78562,21 @@ public class TLRPC { } public static class TL_updateStarsBalance extends Update { + public static final int constructor = 0x4e80a379; + + public TL_stars.StarsAmount balance; + + public void readParams(AbstractSerializedData stream, boolean exception) { + balance = TL_stars.StarsAmount.TLdeserialize(stream, stream.readInt32(exception), exception); + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + balance.serializeToStream(stream); + } + } + + public static class TL_updateStarsBalance_layer194 extends TL_updateStarsBalance { public static final int constructor = 0xfb85198; public long balance; @@ -83821,13 +84150,13 @@ public class TLRPC { } public static class TL_starsRevenueStatus extends TLObject { - public static final int constructor = 0x79342946; + public static final int constructor = 0xfebe5491; public int flags; public boolean withdrawal_enabled; - public long current_balance; - public long available_balance; - public long overall_revenue; + public TL_stars.StarsAmount current_balance = new TL_stars.StarsAmount(); + public TL_stars.StarsAmount available_balance = new TL_stars.StarsAmount(); + public TL_stars.StarsAmount overall_revenue = new TL_stars.StarsAmount(); public int next_withdrawal_at; public static TL_starsRevenueStatus TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { @@ -83846,9 +84175,9 @@ public class TLRPC { public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); withdrawal_enabled = (flags & 1) != 0; - current_balance = stream.readInt64(exception); - available_balance = stream.readInt64(exception); - overall_revenue = stream.readInt64(exception); + current_balance = TL_stars.StarsAmount.TLdeserialize(stream, stream.readInt32(exception), exception); + available_balance = TL_stars.StarsAmount.TLdeserialize(stream, stream.readInt32(exception), exception); + overall_revenue = TL_stars.StarsAmount.TLdeserialize(stream, stream.readInt32(exception), exception); if ((flags & 2) != 0) { next_withdrawal_at = stream.readInt32(exception); } @@ -83859,9 +84188,9 @@ public class TLRPC { stream.writeInt32(constructor); flags = withdrawal_enabled ? flags | 1 : flags &~ 1; stream.writeInt32(flags); - stream.writeInt64(current_balance); - stream.writeInt64(available_balance); - stream.writeInt64(overall_revenue); + current_balance.serializeToStream(stream); + available_balance.serializeToStream(stream); + overall_revenue.serializeToStream(stream); if ((flags & 2) != 0) { stream.writeInt32(next_withdrawal_at); } @@ -84143,4 +84472,126 @@ public class TLRPC { } } + public static class messages_FoundStickers extends TLObject { + + public int flags; + public int next_offset; + + public static messages_FoundStickers TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + messages_FoundStickers result = null; + switch (constructor) { + case TL_messages_foundStickers.constructor: + result = new TL_messages_foundStickers(); + break; + case TL_messages_foundStickersNotModified.constructor: + result = new TL_messages_foundStickersNotModified(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in messages_FoundStickers", constructor)); + } + if (result != null) { + result.readParams(stream, exception); + } + return result; + } + } + + public static class TL_messages_foundStickers extends messages_FoundStickers { + public static final int constructor = 0x82c9e290; + + public long hash; + public ArrayList stickers = new ArrayList<>(); + + @Override + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + if ((flags & 1) != 0) { + next_offset = stream.readInt32(exception); + } + hash = stream.readInt64(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 i = 0; i < count; ++i) { + stickers.add(Document.TLdeserialize(stream, stream.readInt32(exception), exception)); + } + } + + @Override + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + if ((flags & 1) != 0) { + stream.writeInt32(next_offset); + } + stream.writeInt64(hash); + stream.writeInt32(0x1cb5c415); + stream.writeInt32(stickers.size()); + for (int i = 0; i < stickers.size(); ++i) { + stickers.get(i).serializeToStream(stream); + } + } + } + + public static class TL_messages_foundStickersNotModified extends messages_FoundStickers { + public static final int constructor = 0x6010c534; + + @Override + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + if ((flags & 1) != 0) { + next_offset = stream.readInt32(exception); + } + } + + @Override + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + if ((flags & 1) != 0) { + stream.writeInt32(next_offset); + } + } + } + + public static class TL_messages_searchStickers extends TLObject { + public static final int constructor = 0x29b1c66a; + + public int flags; + public boolean emojis; + public String q; + public String emoticon; + public ArrayList lang_code = new ArrayList<>(); + public int offset; + public int limit; + public long hash; + + @Override + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return messages_FoundStickers.TLdeserialize(stream, constructor, exception); + } + + @Override + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = emojis ? flags | 1 : flags &~ 1; + stream.writeInt32(flags); + stream.writeString(q); + stream.writeString(emoticon); + stream.writeInt32(0x1cb5c415); + stream.writeInt32(lang_code.size()); + for (int i = 0; i < lang_code.size(); ++i) + stream.writeString(lang_code.get(i)); + stream.writeInt32(offset); + stream.writeInt32(limit); + stream.writeInt64(hash); + } + } + } diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/tl/TL_bots.java b/TMessagesProj/src/main/java/org/telegram/tgnet/tl/TL_bots.java index 1f04ba2d6..3fb079c71 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/tl/TL_bots.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/tl/TL_bots.java @@ -1130,7 +1130,50 @@ public class TL_bots { stream.writeString(file_name); stream.writeString(url); } + } + public static class updateStarRefProgram extends TLObject { + public static final int constructor = 0x778b5ab3; + + public int flags; + public TLRPC.InputUser bot; + public int commission_permille; + public int duration_months; + + @Override + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return TL_payments.starRefProgram.TLdeserialize(stream, constructor, exception); + } + + @Override + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + bot.serializeToStream(stream); + stream.writeInt32(commission_permille); + if ((flags & 1) != 0) { + stream.writeInt32(duration_months); + } + } + } + + public static class getAdminedBots extends TLObject { + public static final int constructor = 0xb0711d83; + + @Override + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + TLRPC.Vector vector = new TLRPC.Vector(); + int size = stream.readInt32(exception); + for (int a = 0; a < size; a++) { + vector.objects.add(TLRPC.User.TLdeserialize(stream, stream.readInt32(exception), exception)); + } + return vector; + } + + @Override + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + } } } diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/tl/TL_payments.java b/TMessagesProj/src/main/java/org/telegram/tgnet/tl/TL_payments.java new file mode 100644 index 000000000..0f14ebbb8 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/tl/TL_payments.java @@ -0,0 +1,383 @@ +package org.telegram.tgnet.tl; + +import org.telegram.tgnet.AbstractSerializedData; +import org.telegram.tgnet.TLObject; +import org.telegram.tgnet.TLRPC; + +import java.util.ArrayList; + +public class TL_payments { + + public static class connectedBotStarRef extends TLObject { + public static final int constructor = 0x19a13f71; + + public int flags; + public boolean revoked; + public String url; + public int date; + public long bot_id; + public int commission_permille; + public int duration_months; + public long participants; + public long revenue; + + public static connectedBotStarRef TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (connectedBotStarRef.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_payments.connectedBotStarRef", constructor)); + } else { + return null; + } + } + connectedBotStarRef result = new connectedBotStarRef(); + result.readParams(stream, exception); + return result; + } + + @Override + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + revoked = (flags & 2) != 0; + url = stream.readString(exception); + date = stream.readInt32(exception); + bot_id = stream.readInt64(exception); + commission_permille = stream.readInt32(exception); + if ((flags & 1) != 0) { + duration_months = stream.readInt32(exception); + } + participants = stream.readInt64(exception); + revenue = stream.readInt64(exception); + } + + @Override + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = revoked ? flags | 2 : flags &~ 2; + stream.writeInt32(flags); + stream.writeString(url); + stream.writeInt32(date); + stream.writeInt64(bot_id); + stream.writeInt32(commission_permille); + if ((flags & 1) != 0) { + stream.writeInt32(duration_months); + } + stream.writeInt64(participants); + stream.writeInt64(revenue); + } + } + + public static class connectedStarRefBots extends TLObject { + public static final int constructor = 0x98d5ea1d; + + public int count; + public ArrayList connected_bots = new ArrayList<>(); + public ArrayList users = new ArrayList(); + + public static connectedStarRefBots TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (connectedStarRefBots.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_payments.connectedStarRefBots", constructor)); + } else { + return null; + } + } + connectedStarRefBots result = new connectedStarRefBots(); + result.readParams(stream, exception); + return result; + } + + @Override + public void readParams(AbstractSerializedData stream, boolean exception) { + count = stream.readInt32(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int i = 0; i < count; ++i) { + connected_bots.add(connectedBotStarRef.TLdeserialize(stream, stream.readInt32(exception), exception)); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int i = 0; i < count; ++i) { + users.add(TLRPC.User.TLdeserialize(stream, stream.readInt32(exception), exception)); + } + } + + @Override + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(count); + stream.writeInt32(0x1cb5c415); + int count = connected_bots.size(); + stream.writeInt32(count); + for (int i = 0; i < count; ++i) { + connected_bots.get(i).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = users.size(); + stream.writeInt32(count); + for (int i = 0; i < count; ++i) { + users.get(i).serializeToStream(stream); + } + } + } + + public static class suggestedStarRefBots extends TLObject { + public static final int constructor = 0xb4d5d859; + + public int flags; + public int count; + public ArrayList suggested_bots = new ArrayList<>(); + public ArrayList users = new ArrayList<>(); + public String next_offset; + + public static suggestedStarRefBots TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (suggestedStarRefBots.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_payments.suggestedStarRefBots", constructor)); + } else { + return null; + } + } + suggestedStarRefBots result = new suggestedStarRefBots(); + result.readParams(stream, exception); + return result; + } + + @Override + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + count = stream.readInt32(exception); + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int i = 0; i < count; ++i) { + suggested_bots.add(starRefProgram.TLdeserialize(stream, stream.readInt32(exception), exception)); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int i = 0; i < count; ++i) { + users.add(TLRPC.User.TLdeserialize(stream, stream.readInt32(exception), exception)); + } + if ((flags & 1) != 0) { + next_offset = stream.readString(exception); + } + } + + @Override + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(count); + stream.writeInt32(0x1cb5c415); + int count = suggested_bots.size(); + stream.writeInt32(count); + for (int i = 0; i < count; ++i) { + suggested_bots.get(i).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = users.size(); + stream.writeInt32(count); + for (int i = 0; i < count; ++i) { + users.get(i).serializeToStream(stream); + } + if ((flags & 1) != 0) { + stream.writeString(next_offset); + } + } + } + + public static class starRefProgram extends TLObject { + public static final int constructor = 0xdd0c66f2; + + public int flags; + public long bot_id; + public int commission_permille; + public int duration_months; + public int end_date; + public TL_stars.StarsAmount daily_revenue_per_user = new TL_stars.StarsAmount(0); + + public static starRefProgram TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (starRefProgram.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in TL_payments.starRefProgram", constructor)); + } else { + return null; + } + } + starRefProgram result = new starRefProgram(); + result.readParams(stream, exception); + return result; + } + + @Override + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + bot_id = stream.readInt64(exception); + commission_permille = stream.readInt32(exception); + if ((flags & 1) != 0) { + duration_months = stream.readInt32(exception); + } + if ((flags & 2) != 0) { + end_date = stream.readInt32(exception); + } + if ((flags & 4) != 0) { + daily_revenue_per_user = TL_stars.StarsAmount.TLdeserialize(stream, stream.readInt32(exception), exception); + } + } + + @Override + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + stream.writeInt64(bot_id); + stream.writeInt32(commission_permille); + if ((flags & 1) != 0) { + stream.writeInt32(duration_months); + } + if ((flags & 2) != 0) { + stream.writeInt32(end_date); + } + if ((flags & 4) != 0) { + daily_revenue_per_user.serializeToStream(stream); + } + } + } + + public static class connectStarRefBot extends TLObject { + public static final int constructor = 0x7ed5348a; + + public TLRPC.InputPeer peer; + public TLRPC.InputUser bot; + + @Override + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return connectedStarRefBots.TLdeserialize(stream, constructor, exception); + } + + @Override + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + bot.serializeToStream(stream); + } + } + + public static class getSuggestedStarRefBots extends TLObject { + public static final int constructor = 0xd6b48f7; + + public int flags; + public boolean order_by_revenue; + public boolean order_by_date; + public TLRPC.InputPeer peer; + public String offset; + public int limit; + + @Override + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return suggestedStarRefBots.TLdeserialize(stream, constructor, exception); + } + + @Override + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = order_by_revenue ? flags | 1 : flags &~ 1; + flags = order_by_date ? flags | 2 : flags &~ 2; + stream.writeInt32(flags); + peer.serializeToStream(stream); + stream.writeString(offset); + stream.writeInt32(limit); + } + } + + public static class getConnectedStarRefBots extends TLObject { + public static final int constructor = 0x5869a553; + + public int flags; + public TLRPC.InputPeer peer; + public int offset_date; + public String offset_link; + public int limit; + + @Override + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return connectedStarRefBots.TLdeserialize(stream, constructor, exception); + } + + @Override + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + peer.serializeToStream(stream); + if ((flags & 4) != 0) { + stream.writeInt32(offset_date); + stream.writeString(offset_link); + } + stream.writeInt32(limit); + } + } + + + public static class getConnectedStarRefBot extends TLObject { + public static final int constructor = 0xb7d998f0; + + public TLRPC.InputPeer peer; + public TLRPC.InputUser bot; + + @Override + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return connectedStarRefBots.TLdeserialize(stream, constructor, exception); + } + + @Override + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + peer.serializeToStream(stream); + bot.serializeToStream(stream); + } + } + + public static class editConnectedStarRefBot extends TLObject { + public static final int constructor = 0xe4fca4a3; + + public int flags; + public boolean revoked; + public TLRPC.InputPeer peer; + public String link; + + @Override + public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { + return connectedStarRefBots.TLdeserialize(stream, constructor, exception); + } + + @Override + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = revoked ? flags | 1 : flags &~ 1; + stream.writeInt32(flags); + peer.serializeToStream(stream); + stream.writeString(link); + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/tgnet/tl/TL_stars.java b/TMessagesProj/src/main/java/org/telegram/tgnet/tl/TL_stars.java index de89c858a..4c08dd4f9 100644 --- a/TMessagesProj/src/main/java/org/telegram/tgnet/tl/TL_stars.java +++ b/TMessagesProj/src/main/java/org/telegram/tgnet/tl/TL_stars.java @@ -782,7 +782,7 @@ public class TL_stars { public boolean subscription; public boolean floodskip; public String id; - public long stars; + public StarsAmount stars = new StarsAmount(0); public int date; public StarsTransactionPeer peer; public String title; @@ -797,6 +797,9 @@ public class TL_stars { public int giveaway_post_id; public StarGift stargift; public int floodskip_number; + public int starref_commission_permille; + public TLRPC.Peer starref_peer; + public StarsAmount starref_amount; public TLRPC.Peer sent_by; //custom public TLRPC.Peer received_by; //custom @@ -822,6 +825,9 @@ public class TL_stars { case TL_starsTransaction_layer191.constructor: result = new TL_starsTransaction_layer191(); break; + case TL_starsTransaction_layer194.constructor: + result = new TL_starsTransaction_layer194(); + break; case TL_starsTransaction.constructor: result = new TL_starsTransaction(); break; @@ -844,7 +850,7 @@ public class TL_stars { flags = stream.readInt32(exception); refund = (flags & 8) != 0; id = stream.readString(exception); - stars = stream.readInt64(exception); + stars = new StarsAmount(stream.readInt64(exception)); date = stream.readInt32(exception); peer = StarsTransactionPeer.TLdeserialize(stream, stream.readInt32(exception), exception); if ((flags & 1) != 0) { @@ -862,7 +868,7 @@ public class TL_stars { stream.writeInt32(constructor); flags = refund ? flags | 8 : flags &~ 8; stream.writeInt32(flags); - stream.writeInt64(stars); + stream.writeInt64(stars.amount); stream.writeInt32(date); peer.serializeToStream(stream); if ((flags & 1) != 0) { @@ -886,7 +892,7 @@ public class TL_stars { pending = (flags & 16) != 0; failed = (flags & 64) != 0; id = stream.readString(exception); - stars = stream.readInt64(exception); + stars = new StarsAmount(stream.readInt64(exception)); date = stream.readInt32(exception); peer = StarsTransactionPeer.TLdeserialize(stream, stream.readInt32(exception), exception); if ((flags & 1) != 0) { @@ -910,7 +916,7 @@ public class TL_stars { flags = pending ? flags | 16 : flags &~ 16; flags = failed ? flags | 64 : flags &~ 64; stream.writeInt32(flags); - stream.writeInt64(stars); + stream.writeInt64(stars.amount); stream.writeInt32(date); peer.serializeToStream(stream); if ((flags & 1) != 0) { @@ -929,7 +935,185 @@ public class TL_stars { } } + public static class StarsAmount extends TLObject { + public static final int constructor = 0xbbb6b4a3; + + public long amount; + public int nanos; + + public static StarsAmount TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + if (StarsAmount.constructor != constructor) { + if (exception) { + throw new RuntimeException(String.format("can't parse magic %x in StarsAmount", constructor)); + } else { + return null; + } + } + StarsAmount result = new StarsAmount(); + result.readParams(stream, exception); + return result; + } + + public StarsAmount() {} + public StarsAmount(long stars) { + this.amount = stars; + this.nanos = 0; + } + + public void readParams(AbstractSerializedData stream, boolean exception) { + amount = stream.readInt64(exception); + nanos = stream.readInt32(exception); + } + + @Override + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt64(amount); + stream.writeInt32(nanos); + } + + public boolean equals(TL_stars.StarsAmount amount) { + if (amount == null) return false; + return this.amount == amount.amount && this.nanos == amount.nanos; + } + } + public static class TL_starsTransaction extends StarsTransaction { + public static final int constructor = 0x64dfc926; + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + refund = (flags & 8) != 0; + pending = (flags & 16) != 0; + failed = (flags & 64) != 0; + gift = (flags & 1024) != 0; + reaction = (flags & 2048) != 0; + subscription = (flags & 4096) != 0; + floodskip = (flags & 32768) != 0; + id = stream.readString(exception); + stars = StarsAmount.TLdeserialize(stream, stream.readInt32(exception), exception); + date = stream.readInt32(exception); + peer = StarsTransactionPeer.TLdeserialize(stream, stream.readInt32(exception), exception); + if ((flags & 1) != 0) { + title = stream.readString(exception); + } + if ((flags & 2) != 0) { + description = stream.readString(exception); + } + if ((flags & 4) != 0) { + photo = TLRPC.WebDocument.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 32) != 0) { + transaction_date = stream.readInt32(exception); + transaction_url = stream.readString(exception); + } + if ((flags & 128) != 0) { + bot_payload = stream.readByteArray(exception); + } + if ((flags & 256) != 0) { + msg_id = stream.readInt32(exception); + } + if ((flags & 512) != 0) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TLRPC.MessageMedia object = TLRPC.MessageMedia.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + extended_media.add(object); + } + } + if ((flags & 4096) != 0) { + subscription_period = stream.readInt32(exception); + } + if ((flags & 8192) != 0) { + giveaway_post_id = stream.readInt32(exception); + } + if ((flags & 16384) != 0) { + stargift = StarGift.TLdeserialize(stream, stream.readInt32(exception), exception); + } + if ((flags & 32768) != 0) { + floodskip_number = stream.readInt32(exception); + } + if ((flags & 65536) != 0) { + starref_commission_permille = stream.readInt32(exception); + } + if ((flags & 131072) != 0) { + starref_peer = TLRPC.Peer.TLdeserialize(stream, stream.readInt32(exception), exception); + starref_amount = StarsAmount.TLdeserialize(stream, stream.readInt32(exception), exception); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + flags = refund ? flags | 8 : flags &~ 8; + flags = pending ? flags | 16 : flags &~ 16; + flags = failed ? flags | 64 : flags &~ 64; + flags = gift ? flags | 1024 : flags &~ 1024; + flags = reaction ? flags | 2048 : flags &~ 2048; + flags = subscription ? flags | 4096 : flags &~ 4096; + flags = floodskip ? flags | 32768 : flags &~ 32768; + stream.writeInt32(flags); + stars.serializeToStream(stream); + stream.writeInt32(date); + peer.serializeToStream(stream); + if ((flags & 1) != 0) { + stream.writeString(title); + } + if ((flags & 2) != 0) { + stream.writeString(description); + } + if ((flags & 4) != 0) { + photo.serializeToStream(stream); + } + if ((flags & 32) != 0) { + stream.writeInt32(transaction_date); + stream.writeString(transaction_url); + } + if ((flags & 128) != 0) { + stream.writeByteArray(bot_payload); + } + if ((flags & 256) != 0) { + stream.writeInt32(msg_id); + } + if ((flags & 512) != 0) { + stream.writeInt32(0x1cb5c415); + int count = extended_media.size(); + stream.writeInt32(count); + for (int i = 0; i < count; ++i) { + extended_media.get(i).serializeToStream(stream); + } + } + if ((flags & 4096) != 0) { + stream.writeInt32(subscription_period); + } + if ((flags & 8192) != 0) { + stream.writeInt32(giveaway_post_id); + } + if ((flags & 16384) != 0) { + stargift.serializeToStream(stream); + } + if ((flags & 32768) != 0) { + stream.writeInt32(floodskip_number); + } + if ((flags & 65536) != 0) { + stream.writeInt32(starref_commission_permille); + } + if ((flags & 131072) != 0) { + starref_peer.serializeToStream(stream); + starref_amount.serializeToStream(stream); + } + } + } + + public static class TL_starsTransaction_layer194 extends TL_starsTransaction { public static final int constructor = 0x35d4f276; public void readParams(AbstractSerializedData stream, boolean exception) { @@ -942,7 +1126,7 @@ public class TL_stars { subscription = (flags & 4096) != 0; floodskip = (flags & 32768) != 0; id = stream.readString(exception); - stars = stream.readInt64(exception); + stars = new StarsAmount(stream.readInt64(exception)); date = stream.readInt32(exception); peer = StarsTransactionPeer.TLdeserialize(stream, stream.readInt32(exception), exception); if ((flags & 1) != 0) { @@ -1005,7 +1189,7 @@ public class TL_stars { flags = subscription ? flags | 4096 : flags &~ 4096; flags = floodskip ? flags | 32768 : flags &~ 32768; stream.writeInt32(flags); - stream.writeInt64(stars); + stream.writeInt64(stars.amount); stream.writeInt32(date); peer.serializeToStream(stream); if ((flags & 1) != 0) { @@ -1062,7 +1246,7 @@ public class TL_stars { reaction = (flags & 2048) != 0; subscription = (flags & 4096) != 0; id = stream.readString(exception); - stars = stream.readInt64(exception); + stars = new StarsAmount(stream.readInt64(exception)); date = stream.readInt32(exception); peer = StarsTransactionPeer.TLdeserialize(stream, stream.readInt32(exception), exception); if ((flags & 1) != 0) { @@ -1121,7 +1305,7 @@ public class TL_stars { flags = reaction ? flags | 2048 : flags &~ 2048; flags = subscription ? flags | 4096 : flags &~ 4096; stream.writeInt32(flags); - stream.writeInt64(stars); + stream.writeInt64(stars.amount); stream.writeInt32(date); peer.serializeToStream(stream); if ((flags & 1) != 0) { @@ -1175,7 +1359,7 @@ public class TL_stars { reaction = (flags & 2048) != 0; subscription = (flags & 4096) != 0; id = stream.readString(exception); - stars = stream.readInt64(exception); + stars = new StarsAmount(stream.readInt64(exception)); date = stream.readInt32(exception); peer = StarsTransactionPeer.TLdeserialize(stream, stream.readInt32(exception), exception); if ((flags & 1) != 0) { @@ -1231,7 +1415,7 @@ public class TL_stars { flags = reaction ? flags | 2048 : flags &~ 2048; flags = subscription ? flags | 4096 : flags &~ 4096; stream.writeInt32(flags); - stream.writeInt64(stars); + stream.writeInt64(stars.amount); stream.writeInt32(date); peer.serializeToStream(stream); if ((flags & 1) != 0) { @@ -1282,7 +1466,7 @@ public class TL_stars { reaction = (flags & 2048) != 0; subscription = (flags & 4096) != 0; id = stream.readString(exception); - stars = stream.readInt64(exception); + stars = new StarsAmount(stream.readInt64(exception)); date = stream.readInt32(exception); peer = StarsTransactionPeer.TLdeserialize(stream, stream.readInt32(exception), exception); if ((flags & 1) != 0) { @@ -1335,7 +1519,7 @@ public class TL_stars { flags = reaction ? flags | 2048 : flags &~ 2048; flags = subscription ? flags | 4096 : flags &~ 4096; stream.writeInt32(flags); - stream.writeInt64(stars); + stream.writeInt64(stars.amount); stream.writeInt32(date); peer.serializeToStream(stream); if ((flags & 1) != 0) { @@ -1381,7 +1565,7 @@ public class TL_stars { failed = (flags & 64) != 0; gift = (flags & 1024) != 0; id = stream.readString(exception); - stars = stream.readInt64(exception); + stars = new StarsAmount(stream.readInt64(exception)); date = stream.readInt32(exception); peer = StarsTransactionPeer.TLdeserialize(stream, stream.readInt32(exception), exception); if ((flags & 1) != 0) { @@ -1429,7 +1613,7 @@ public class TL_stars { flags = failed ? flags | 64 : flags &~ 64; flags = gift ? flags | 1024 : flags &~ 1024; stream.writeInt32(flags); - stream.writeInt64(stars); + stream.writeInt64(stars.amount); stream.writeInt32(date); peer.serializeToStream(stream); if ((flags & 1) != 0) { @@ -1462,11 +1646,10 @@ public class TL_stars { } } - public static class TL_payments_starsStatus extends TLObject { - public static final int constructor = 0xbbfa316c; + public static class StarsStatus extends TLObject { public int flags; - public long balance; + public StarsAmount balance = new StarsAmount(0); public ArrayList subscriptions = new ArrayList<>(); public String subscriptions_next_offset; public long subscriptions_missing_balance; @@ -1475,22 +1658,32 @@ public class TL_stars { public ArrayList chats = new ArrayList<>(); public ArrayList users = new ArrayList<>(); - public static TL_payments_starsStatus TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { - if (TL_payments_starsStatus.constructor != constructor) { - if (exception) { - throw new RuntimeException(String.format("can't parse magic %x in TL_payments_starsStatus", constructor)); - } else { - return null; - } + public static StarsStatus TLdeserialize(AbstractSerializedData stream, int constructor, boolean exception) { + StarsStatus result = null; + switch (constructor) { + case TL_payments_starsStatus_layer194.constructor: + result = new TL_payments_starsStatus_layer194(); + break; + case TL_payments_starsStatus.constructor: + result = new TL_payments_starsStatus(); + break; + } + if (result == null && exception) { + throw new RuntimeException(String.format("can't parse magic %x in StarsStatus", constructor)); + } + if (result != null) { + result.readParams(stream, exception); } - TL_payments_starsStatus result = new TL_payments_starsStatus(); - result.readParams(stream, exception); return result; } + } + + public static class TL_payments_starsStatus extends StarsStatus { + public static final int constructor = 0x6c9ce8ed; public void readParams(AbstractSerializedData stream, boolean exception) { flags = stream.readInt32(exception); - balance = stream.readInt64(exception); + balance = StarsAmount.TLdeserialize(stream, stream.readInt32(exception), exception); if ((flags & 2) != 0) { int magic = stream.readInt32(exception); if (magic != 0x1cb5c415) { @@ -1569,7 +1762,131 @@ public class TL_stars { public void serializeToStream(AbstractSerializedData stream) { stream.writeInt32(constructor); stream.writeInt32(flags); - stream.writeInt64(balance); + balance.serializeToStream(stream); + if ((flags & 2) != 0) { + stream.writeInt32(0x1cb5c415); + int count = subscriptions.size(); + stream.writeInt32(count); + for (int i = 0; i < count; ++i) { + subscriptions.get(i).serializeToStream(stream); + } + } + if ((flags & 4) != 0) { + stream.writeString(subscriptions_next_offset); + } + if ((flags & 16) != 0) { + stream.writeInt64(subscriptions_missing_balance); + } + stream.writeInt32(0x1cb5c415); + int count = history.size(); + stream.writeInt32(count); + for (int i = 0; i < count; ++i) { + history.get(i).serializeToStream(stream); + } + if ((flags & 1) != 0) { + stream.writeString(next_offset); + } + stream.writeInt32(0x1cb5c415); + count = chats.size(); + stream.writeInt32(count); + for (int i = 0; i < count; ++i) { + chats.get(i).serializeToStream(stream); + } + stream.writeInt32(0x1cb5c415); + count = users.size(); + stream.writeInt32(count); + for (int i = 0; i < count; ++i) { + users.get(i).serializeToStream(stream); + } + } + } + + public static class TL_payments_starsStatus_layer194 extends TL_payments_starsStatus { + public static final int constructor = 0xbbfa316c; + + + public void readParams(AbstractSerializedData stream, boolean exception) { + flags = stream.readInt32(exception); + balance = new StarsAmount(stream.readInt64(exception)); + if ((flags & 2) != 0) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + StarsSubscription object = StarsSubscription.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + subscriptions.add(object); + } + } + if ((flags & 4) != 0) { + subscriptions_next_offset = stream.readString(exception); + } + if ((flags & 16) != 0) { + subscriptions_missing_balance = stream.readInt64(exception); + } + if ((flags & 8) != 0) { + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + StarsTransaction object = StarsTransaction.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + history.add(object); + } + } + if ((flags & 1) != 0) { + next_offset = stream.readString(exception); + } + int magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + int count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TLRPC.Chat object = TLRPC.Chat.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + chats.add(object); + } + magic = stream.readInt32(exception); + if (magic != 0x1cb5c415) { + if (exception) { + throw new RuntimeException(String.format("wrong Vector magic, got %x", magic)); + } + return; + } + count = stream.readInt32(exception); + for (int a = 0; a < count; a++) { + TLRPC.User object = TLRPC.User.TLdeserialize(stream, stream.readInt32(exception), exception); + if (object == null) { + return; + } + users.add(object); + } + } + + public void serializeToStream(AbstractSerializedData stream) { + stream.writeInt32(constructor); + stream.writeInt32(flags); + stream.writeInt64(balance.amount); if ((flags & 2) != 0) { stream.writeInt32(0x1cb5c415); int count = subscriptions.size(); @@ -1684,7 +2001,7 @@ public class TL_stars { public TLRPC.InputPeer peer; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return TL_payments_starsStatus.TLdeserialize(stream, constructor, exception); + return StarsStatus.TLdeserialize(stream, constructor, exception); } public void serializeToStream(AbstractSerializedData stream) { @@ -1703,7 +2020,7 @@ public class TL_stars { public String offset; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return TL_payments_starsStatus.TLdeserialize(stream, constructor, exception); + return StarsStatus.TLdeserialize(stream, constructor, exception); } public void serializeToStream(AbstractSerializedData stream) { @@ -1934,7 +2251,7 @@ public class TL_stars { public String offset; public TLObject deserializeResponse(AbstractSerializedData stream, int constructor, boolean exception) { - return TL_payments_starsStatus.TLdeserialize(stream, constructor, exception); + return StarsStatus.TLdeserialize(stream, constructor, exception); } public void serializeToStream(AbstractSerializedData stream) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuSubItem.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuSubItem.java index 7a74e30d9..11e13af6c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuSubItem.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/ActionBarMenuSubItem.java @@ -30,11 +30,11 @@ import org.telegram.ui.Components.RLottieImageView; public class ActionBarMenuSubItem extends FrameLayout { - private TextView textView; + public TextView textView; public TextView subtextView; public RLottieImageView imageView; - private boolean checkViewLeft; - private CheckBox2 checkView; + public boolean checkViewLeft; + public CheckBox2 checkView; private ImageView rightIcon; private int textColor; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AlertDialog.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AlertDialog.java index f9a2a9762..a3e32529c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AlertDialog.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/AlertDialog.java @@ -280,6 +280,7 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati super(context, R.style.TransparentDialog); this.resourcesProvider = resourcesProvider; + progressViewStyle = progressStyle; backgroundColor = getThemedColor(Theme.key_dialogBackground); final boolean isDark = AndroidUtilities.computePerceivedBrightness(backgroundColor) < 0.721f; blurredNativeBackground = supportsNativeBlur() && progressViewStyle == ALERT_TYPE_MESSAGE; @@ -293,8 +294,6 @@ public class AlertDialog extends Dialog implements Drawable.Callback, Notificati shadowDrawable.getPadding(backgroundPaddings); } withCancelDialog = progressViewStyle == ALERT_TYPE_SPINNER; - - progressViewStyle = progressStyle; } private long shownAt; 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 ec0f05e78..63f636647 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/BottomSheet.java @@ -66,6 +66,7 @@ import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; import org.telegram.messenger.Utilities; import org.telegram.messenger.camera.CameraView; +import org.telegram.tgnet.TLRPC; import org.telegram.ui.Components.AnimationProperties; import org.telegram.ui.Components.Bulletin; import org.telegram.ui.Components.CubicBezierInterpolator; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/FloatingToolbar.java b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/FloatingToolbar.java index 2a30792a5..2f9e23206 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/FloatingToolbar.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/FloatingToolbar.java @@ -16,6 +16,8 @@ package org.telegram.ui.ActionBar; +import static org.telegram.messenger.AndroidUtilities.allGlobalViews; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; @@ -70,6 +72,8 @@ import org.telegram.messenger.R; import org.telegram.messenger.UserConfig; import org.telegram.messenger.Utilities; import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.LaunchActivity; +import org.telegram.ui.PhotoViewer; import java.util.ArrayList; import java.util.Arrays; @@ -1406,11 +1410,70 @@ public final class FloatingToolbar { } private static PopupWindow createPopupWindow(ViewGroup content) { - ViewGroup popupContentHolder = new LinearLayout(content.getContext()); + ViewGroup popupContentHolder = new LinearLayout(content.getContext()) { + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return super.onInterceptTouchEvent(ev); + } + private boolean isParent(View child, View parent) { + if (child == parent) return true; + if (child.getParent() == null) return false; + if (child.getParent() instanceof View) { + return isParent((View) child.getParent(), parent); + } else if (child.getParent() == parent) { + return true; + } else if (child.getRootView() == parent) { + return true; + } + return false; + } + + private final int[] p = new int[2]; + private View downRootView = null; + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + boolean r = super.dispatchTouchEvent(ev); + if (!r) { + getLocationOnScreen(p); + ev.offsetLocation(p[0], p[1]); + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + final List views = allGlobalViews(); + if (views != null && views.size() > 1) { + for (int i = views.size() - 2; i >= 0; --i) { + final View view = views.get(i); + if (isParent(this, view)) continue; + view.getLocationOnScreen(p); + ev.offsetLocation(-p[0], -p[1]); + r = view.dispatchTouchEvent(ev); + if (r) { + downRootView = view; + return true; + } + ev.offsetLocation(p[0], p[1]); + } + } + } else if (downRootView != null) { + View view = downRootView; + view.getLocationOnScreen(p); + ev.offsetLocation(-p[0], -p[1]); + r = view.dispatchTouchEvent(ev); + } + } + if (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL) { + downRootView = null; + } + return r; + } + @Override + public boolean onTouchEvent(MotionEvent event) { + return super.onTouchEvent(event); + } + }; PopupWindow popupWindow = new PopupWindow(popupContentHolder); popupWindow.setClippingEnabled(false); popupWindow.setAnimationStyle(0); popupWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); + popupWindow.setSplitTouchEnabled(true); content.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); popupContentHolder.addView(content); return popupWindow; 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 bc51e8bc5..2ecb011e2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ActionBar/Theme.java @@ -5244,6 +5244,41 @@ public class Theme { return defaultDrawable; } + public static Drawable createOutlineCircleDrawable(int size, int color, int strokeWidth) { + return new Drawable() { + private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); { + paint.setStyle(Paint.Style.STROKE); + paint.setStrokeWidth(strokeWidth); + paint.setColor(color); + } + @Override + public void draw(@NonNull Canvas canvas) { + final Rect b = getBounds(); + canvas.drawCircle(b.centerX(), b.centerY(), size / 2.0f, paint); + } + @Override + public void setAlpha(int alpha) { + paint.setAlpha(alpha); + } + @Override + public void setColorFilter(@Nullable ColorFilter colorFilter) { + paint.setColorFilter(colorFilter); + } + @Override + public int getOpacity() { + return PixelFormat.TRANSPARENT; + } + @Override + public int getIntrinsicWidth() { + return size + strokeWidth; + } + @Override + public int getIntrinsicHeight() { + return size + strokeWidth; + } + }; + } + public static ShapeDrawable createCircleDrawable(int size, int colorTop, int colorBottom) { OvalShape ovalShape = new OvalShape(); ovalShape.resize(size, size); 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 371a6c487..b4798e01f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/DialogsAdapter.java @@ -570,6 +570,9 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter implements view = new ProfileSearchCell(mContext); } else { DialogCell dialogCell = new DialogCell(parentFragment, mContext, true, false, currentAccount, null); + if (showOpenBotButton()) { + dialogCell.allowBotOpenButton(true, this::onOpenBot); + } dialogCell.setArchivedPullAnimation(pullForegroundDrawable); dialogCell.setPreloader(preloader); dialogCell.setDialogCellDelegate(this); @@ -1540,4 +1543,12 @@ public class DialogsAdapter extends RecyclerListView.SelectionAdapter implements } return 0; } + + protected boolean showOpenBotButton() { + return false; + } + + protected void onOpenBot(TLRPC.User user) { + + } } 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 a819c37ba..99cebdd48 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Adapters/MentionsAdapter.java @@ -442,6 +442,7 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter implement stickers = null; stickersMap = null; notifyDataSetChanged(); + visibleByStickersSearch = false; if (lastReqId != 0) { ConnectionsManager.getInstance(currentAccount).cancelRequest(lastReqId, true); lastReqId = 0; @@ -619,6 +620,60 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter implement } } +// private String lastSearchForStickersQuery; +// private Runnable searchForStickersRunnable; +// private MediaDataController.SearchStickersKey loadingSearchKey; +// public void loadMoreStickers() { +// searchForStickers(lastSearchForStickersQuery, true); +// } +// private void searchForStickers(final String q, boolean allowNext) { +// if (TextUtils.isEmpty(q)) { +// if (loadingSearchKey != null) { +// MediaDataController.getInstance(currentAccount).cancelSearchStickers(loadingSearchKey); +// loadingSearchKey = null; +// } +// if (searchForStickersRunnable != null) { +// AndroidUtilities.cancelRunOnUIThread(searchForStickersRunnable); +// searchForStickersRunnable = null; +// } +// return; +// } +// if (TextUtils.equals(lastSearchForStickersQuery, q) && (!allowNext || loadingSearchKey == null && (stickers == null || stickers.isEmpty()))) { +// return; +// } +// lastSearchForStickersQuery = q; +// AndroidUtilities.runOnUIThread(searchForStickersRunnable = () -> { +// if (!TextUtils.equals(lastSearchForStickersQuery, q)) { +// return; +// } +// final String[] newLanguage = AndroidUtilities.getCurrentKeyboardLanguage(); +// final String lang_code = newLanguage == null || newLanguage.length == 0 ? "" : newLanguage[0]; +// if (loadingSearchKey != null) { +// MediaDataController.getInstance(currentAccount).cancelSearchStickers(loadingSearchKey); +// loadingSearchKey = null; +// } +// loadingSearchKey = MediaDataController.getInstance(currentAccount).searchStickers(false, lang_code, q, stickers -> { +// if (!TextUtils.equals(lastSearchForStickersQuery, q)) { +// return; +// } +// loadingSearchKey = null; +// int oldCount = stickers != null ? stickers.size() : 0; +// if (!stickers.isEmpty()) { +// addStickersToResult(stickers, null); +// } +// int newCount = stickers != null ? stickers.size() : 0; +// if (!visibleByStickersSearch && stickers != null && !stickers.isEmpty()) { +// checkStickerFilesExistAndDownload(); +// delegate.needChangePanelVisibility(getItemCountInternal() > 0); +// visibleByStickersSearch = true; +// } +// if (oldCount != newCount) { +// notifyDataSetChanged(); +// } +// }, allowNext); +// }, 600); +// } + private void searchForContextBot(final String username, final String query) { if (foundContextBot != null && foundContextBot.username != null && foundContextBot.username.equals(username) && searchingContextQuery != null && searchingContextQuery.equals(query)) { return; @@ -835,6 +890,7 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter implement searchResultSuggestions = null; searchResultCommandsHelp = null; searchResultCommandsUsers = null; + visibleByStickersSearch = false; delegate.needChangePanelVisibility(!searchResultBotContext.isEmpty() || searchResultBotContextSwitch != null || searchResultBotWebViewSwitch != null); if (added) { boolean hasTop = searchResultBotContextSwitch != null || searchResultBotWebViewSwitch != null; @@ -1075,8 +1131,13 @@ public class MentionsAdapter extends RecyclerListView.SelectionAdapter implement } else { username = ""; } +// searchForStickers(null, false); searchForContextBot(username, query); + } else if (allowStickers && parentFragment != null && parentFragment.getCurrentEncryptedChat() == null && (currentChat == null || ChatObject.canSendStickers(currentChat)) && text.trim().length() >= 2 && text.trim().indexOf(' ') < 0) { +// searchForStickers(text.trim(), false); + searchForContextBot(null, null); } else { +// searchForStickers(null, false); searchForContextBot(null, null); } if (foundContextBot != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/CachedStaticLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/CachedStaticLayout.java index 4f95076bb..9eba7de04 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/CachedStaticLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/CachedStaticLayout.java @@ -107,7 +107,6 @@ public class CachedStaticLayout { (e = (!lastLayoutBounds.equals(getLayoutBounds()))) || (f = (!emojiLoadedEquals(emojiLoaded = getEmojiLoaded(), lastEmojiLoaded))) ) { -// Log.i("lolkek", "draw \"" + getText() + "\" because " + (renderNode == null ? "first" : a ? "textcolor ["+Integer.toHexString(textColor) + " => "+Integer.toHexString(layout.getPaint().getColor())+"]" : (b ? "linkcolor" : (c ? "textsize" : (d ? "typeface" : (e ? "bounds" : (f ? "emojis" : "???"))))))); textColor = layout.getPaint().getColor(); linkColor = layout.getPaint().linkColor; textSize = layout.getPaint().getTextSize(); 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 df3020765..2c5ab2ee7 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ChatMessageCell.java @@ -14134,7 +14134,8 @@ public class ChatMessageCell extends BaseCell implements SeekBar.SeekBarDelegate canvas.save(); // canvas.translate(button.x * widthForButtons + addX + AndroidUtilities.dp(5), y + (AndroidUtilities.dp(44) - button.title.getLineBottom(button.title.getLineCount() - 1)) / 2); - button.title.ellipsize(Math.max(1, (int) (button.width * widthForButtons) - dp(10))).draw(canvas, button.x * widthForButtons + addX + (button.width * widthForButtons - button.title.getWidth()) / 2f, y + dp(44) / 2f); + button.title.ellipsize(Math.max(1, (int) (button.width * widthForButtons) - dp(15))); + button.title.draw(canvas, button.x * widthForButtons + addX + (button.width * widthForButtons - button.title.getWidth()) / 2f, y + dp(44) / 2f); canvas.restore(); if (button.button instanceof TLRPC.TL_keyboardButtonWebView) { Drawable drawable = getThemedDrawable(Theme.key_drawable_botWebView); 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 576c5b04e..217f716b5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/DialogCell.java @@ -87,6 +87,7 @@ import org.telegram.ui.Components.AnimatedEmojiSpan; import org.telegram.ui.Components.AnimatedFloat; import org.telegram.ui.Components.AvatarDrawable; import org.telegram.ui.Components.BubbleCounterPath; +import org.telegram.ui.Components.ButtonBounce; import org.telegram.ui.Components.CanvasButton; import org.telegram.ui.Components.CheckBox2; import org.telegram.ui.Components.ColoredImageSpan; @@ -104,6 +105,7 @@ import org.telegram.ui.Components.Reactions.ReactionsLayoutInBubble; import org.telegram.ui.Components.StaticLayoutEx; import org.telegram.ui.Components.StatusDrawable; import org.telegram.ui.Components.SwipeGestureSettingsView; +import org.telegram.ui.Components.Text; import org.telegram.ui.Components.TextStyleSpan; import org.telegram.ui.Components.TimerDrawable; import org.telegram.ui.Components.TypefaceSpan; @@ -205,7 +207,7 @@ public class DialogCell extends BaseCell implements StoriesListPlaceProvider.Ava private Path thumbPath; private SpoilerEffect thumbSpoiler = new SpoilerEffect(); - private boolean drawForwardIcon; + private boolean drawForwardIcon, drawGiftIcon; private boolean visibleOnScreen = true; private boolean updateLayout; private boolean wasDrawnOnline; @@ -382,6 +384,27 @@ public class DialogCell extends BaseCell implements StoriesListPlaceProvider.Ava private float currentRevealBounceProgress; private float archiveBackgroundProgress; + private boolean openBot; + private final ButtonBounce openButtonBounce = new ButtonBounce(this); + private final Paint openButtonBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final RectF openButtonRect = new RectF(); + private Text openButtonText; + public void setOpenBotButton(boolean show) { + if (openBot == show) return; + if (openButtonText == null) { + openButtonText = new Text(LocaleController.getString(R.string.BotOpen), 14, AndroidUtilities.bold()); + } + openBot = show; + openButtonBounce.setPressed(false); + } + private boolean allowBotOpenButton; + private Utilities.Callback onOpenButtonClick; + public DialogCell allowBotOpenButton(boolean allow, Utilities.Callback onOpenClick) { + allowBotOpenButton = allow; + onOpenButtonClick = onOpenClick; + return this; + } + protected boolean overrideSwipeAction = false; protected int overrideSwipeActionBackgroundColorKey; protected int overrideSwipeActionRevealBackgroundColorKey; @@ -1032,6 +1055,7 @@ public class DialogCell extends BaseCell implements StoriesListPlaceProvider.Ava drawVerified = false; drawPremium = false; drawForwardIcon = false; + drawGiftIcon = false; drawScam = 0; drawPinBackground = false; thumbsCount = 0; @@ -1045,6 +1069,7 @@ public class DialogCell extends BaseCell implements StoriesListPlaceProvider.Ava if (!isForumCell()) { buttonLayout = null; } + setOpenBotButton(false); int messageFormatType; if (Build.VERSION.SDK_INT >= 18) { @@ -1416,9 +1441,9 @@ public class DialogCell extends BaseCell implements StoriesListPlaceProvider.Ava ReactionsLayoutInBubble.VisibleReaction visibleReaction = ReactionsLayoutInBubble.VisibleReaction.fromTL(lastReaction.reaction); currentMessagePaint = Theme.dialogs_messagePrintingPaint[paintIndex]; if (visibleReaction.emojicon != null) { - messageString = LocaleController.formatString("ReactionInDialog", R.string.ReactionInDialog, visibleReaction.emojicon); + messageString = LocaleController.formatString(R.string.ReactionInDialog, visibleReaction.emojicon); } else { - String string = LocaleController.formatString("ReactionInDialog", R.string.ReactionInDialog, "**reaction**"); + String string = LocaleController.formatString(R.string.ReactionInDialog, "**reaction**"); int i = string.indexOf("**reaction**"); string = string.replace("**reaction**", "d"); @@ -1473,6 +1498,8 @@ public class DialogCell extends BaseCell implements StoriesListPlaceProvider.Ava if (ChatObject.isChannelAndNotMegaGroup(chat) && (message.messageOwner.action instanceof TLRPC.TL_messageActionChannelMigrateFrom)) { messageString = ""; showChecks = false; + } else if (message.messageTextShort != null) { + messageString = message.messageTextShort; } else { messageString = msgText; } @@ -1747,6 +1774,22 @@ public class DialogCell extends BaseCell implements StoriesListPlaceProvider.Ava } } + if (!drawForwardIcon && message != null && message.messageOwner != null && message.messageOwner.action instanceof TLRPC.TL_messageActionStarGift) { + drawGiftIcon = true; + SpannableStringBuilder builder = new SpannableStringBuilder(messageString); + builder.insert(0, "d "); + ColoredImageSpan coloredImageSpan = new ColoredImageSpan(ContextCompat.getDrawable(getContext(), R.drawable.mini_gift).mutate()); + coloredImageSpan.setScale(1.25f, 1.25f); + coloredImageSpan.spaceScaleX = 0.9f; + coloredImageSpan.setAlpha(0.9f); + builder.setSpan(coloredImageSpan, 0, 1, 0); + messageString = builder; + final TLRPC.TL_messageActionStarGift action = (TLRPC.TL_messageActionStarGift) message.messageOwner.action; + if (action.message != null && !TextUtils.isEmpty(action.message.text)) { + currentMessagePaint = Theme.dialogs_messagePaint[paintIndex]; + } + } + if (draftMessage != null) { timeString = LocaleController.stringForMessageListDate(draftMessage.date); } else if (lastMessageDate != 0) { @@ -2192,6 +2235,27 @@ public class DialogCell extends BaseCell implements StoriesListPlaceProvider.Ava messageNameLeft += w; } } + } else if (allowBotOpenButton && UserObject.isBot(user) && user.bot_has_main_app) { + setOpenBotButton(true); + int buttonWidth = (int) (dp(2 * 13) + openButtonText.getCurrentWidth()), p = dp(13); + messageWidth -= buttonWidth; + int y; + if (useForceThreeLines || SharedConfig.useThreeLinesLayout) { + y = dp(40); + } else { + y = isTopic ? dp(33) : dp(36); + } + if (!LocaleController.isRTL) { + openButtonRect.set(getMeasuredWidth() - buttonWidth - dp(13), y, getMeasuredWidth() - dp(13), y + dp(28)); + } else { + openButtonRect.set(dp(13), y, dp(13) + buttonWidth, y + dp(28)); + messageLeft += buttonWidth + p; + typingLeft += buttonWidth + p; + buttonLeft += buttonWidth + p; + messageNameLeft += buttonWidth + p; + } + drawCount = false; + drawMention = false; } else { if (getIsPinned()) { int w = Theme.dialogs_pinnedDrawable.getIntrinsicWidth() + dp(8); @@ -2553,7 +2617,7 @@ public class DialogCell extends BaseCell implements StoriesListPlaceProvider.Ava float x1 = layout.getPrimaryHorizontal(spanOffset); float x2 = layout.getPrimaryHorizontal(spanOffset + 1); int offset = (int) Math.ceil(Math.min(x1, x2)); - if (offset != 0 && !drawForwardIcon) { + if (offset != 0 && !drawForwardIcon && !drawGiftIcon) { offset += dp(3); } for (int i = 0; i < thumbsCount; ++i) { @@ -3278,59 +3342,59 @@ public class DialogCell extends BaseCell implements StoriesListPlaceProvider.Ava if (archiveHidden) { backgroundColor = Theme.getColor(Theme.key_chats_archivePinBackground, resourcesProvider); revealBackgroundColor = Theme.getColor(Theme.key_chats_archiveBackground, resourcesProvider); - swipeMessage = LocaleController.getString("UnhideFromTop", swipeMessageStringId = R.string.UnhideFromTop); + swipeMessage = LocaleController.getString(swipeMessageStringId = R.string.UnhideFromTop); translationDrawable = Theme.dialogs_unpinArchiveDrawable; } else { backgroundColor = Theme.getColor(Theme.key_chats_archiveBackground, resourcesProvider); revealBackgroundColor = Theme.getColor(Theme.key_chats_archivePinBackground, resourcesProvider); - swipeMessage = LocaleController.getString("HideOnTop", swipeMessageStringId = R.string.HideOnTop); + swipeMessage = LocaleController.getString(swipeMessageStringId = R.string.HideOnTop); translationDrawable = Theme.dialogs_pinArchiveDrawable; } } else { if (promoDialog) { backgroundColor = Theme.getColor(Theme.key_chats_archiveBackground, resourcesProvider); revealBackgroundColor = Theme.getColor(Theme.key_chats_archivePinBackground, resourcesProvider); - swipeMessage = LocaleController.getString("PsaHide", swipeMessageStringId = R.string.PsaHide); + swipeMessage = LocaleController.getString(swipeMessageStringId = R.string.PsaHide); translationDrawable = Theme.dialogs_hidePsaDrawable; } else if (folderId == 0) { backgroundColor = Theme.getColor(Theme.key_chats_archiveBackground, resourcesProvider); revealBackgroundColor = Theme.getColor(Theme.key_chats_archivePinBackground, resourcesProvider); if (SharedConfig.getChatSwipeAction(currentAccount) == SwipeGestureSettingsView.SWIPE_GESTURE_MUTE) { if (dialogMuted) { - swipeMessage = LocaleController.getString("SwipeUnmute", swipeMessageStringId = R.string.SwipeUnmute); + swipeMessage = LocaleController.getString(swipeMessageStringId = R.string.SwipeUnmute); translationDrawable = Theme.dialogs_swipeUnmuteDrawable; } else { - swipeMessage = LocaleController.getString("SwipeMute", swipeMessageStringId = R.string.SwipeMute); + swipeMessage = LocaleController.getString(swipeMessageStringId = R.string.SwipeMute); translationDrawable = Theme.dialogs_swipeMuteDrawable; } } else if (SharedConfig.getChatSwipeAction(currentAccount) == SwipeGestureSettingsView.SWIPE_GESTURE_DELETE) { - swipeMessage = LocaleController.getString("SwipeDeleteChat", swipeMessageStringId = R.string.SwipeDeleteChat); + swipeMessage = LocaleController.getString(swipeMessageStringId = R.string.SwipeDeleteChat); backgroundColor = Theme.getColor(Theme.key_dialogSwipeRemove, resourcesProvider); translationDrawable = Theme.dialogs_swipeDeleteDrawable; } else if (SharedConfig.getChatSwipeAction(currentAccount) == SwipeGestureSettingsView.SWIPE_GESTURE_READ) { if (unreadCount > 0 || markUnread) { - swipeMessage = LocaleController.getString("SwipeMarkAsRead", swipeMessageStringId = R.string.SwipeMarkAsRead); + swipeMessage = LocaleController.getString(swipeMessageStringId = R.string.SwipeMarkAsRead); translationDrawable = Theme.dialogs_swipeReadDrawable; } else { - swipeMessage = LocaleController.getString("SwipeMarkAsUnread", swipeMessageStringId = R.string.SwipeMarkAsUnread); + swipeMessage = LocaleController.getString(swipeMessageStringId = R.string.SwipeMarkAsUnread); translationDrawable = Theme.dialogs_swipeUnreadDrawable; } } else if (SharedConfig.getChatSwipeAction(currentAccount) == SwipeGestureSettingsView.SWIPE_GESTURE_PIN) { if (getIsPinned()) { - swipeMessage = LocaleController.getString("SwipeUnpin", swipeMessageStringId = R.string.SwipeUnpin); + swipeMessage = LocaleController.getString(swipeMessageStringId = R.string.SwipeUnpin); translationDrawable = Theme.dialogs_swipeUnpinDrawable; } else { - swipeMessage = LocaleController.getString("SwipePin", swipeMessageStringId = R.string.SwipePin); + swipeMessage = LocaleController.getString(swipeMessageStringId = R.string.SwipePin); translationDrawable = Theme.dialogs_swipePinDrawable; } } else { - swipeMessage = LocaleController.getString("Archive", swipeMessageStringId = R.string.Archive); + swipeMessage = LocaleController.getString(swipeMessageStringId = R.string.Archive); translationDrawable = Theme.dialogs_archiveDrawable; } } else { backgroundColor = Theme.getColor(Theme.key_chats_archivePinBackground, resourcesProvider); revealBackgroundColor = Theme.getColor(Theme.key_chats_archiveBackground, resourcesProvider); - swipeMessage = LocaleController.getString("Unarchive", swipeMessageStringId = R.string.Unarchive); + swipeMessage = LocaleController.getString(swipeMessageStringId = R.string.Unarchive); translationDrawable = Theme.dialogs_unarchiveDrawable; } } @@ -3945,6 +4009,16 @@ public class DialogCell extends BaseCell implements StoriesListPlaceProvider.Ava Theme.dialogs_reactionsMentionDrawable.draw(canvas); canvas.restore(); } + } else if (openBot) { + canvas.save(); + final float scale = openButtonBounce.getScale(.05f); + canvas.scale(scale, scale, openButtonRect.centerX(), openButtonRect.centerY()); + openButtonBackgroundPaint.setColor(Theme.getColor(Theme.key_featuredStickers_addButton, resourcesProvider)); + canvas.drawRoundRect(openButtonRect, openButtonRect.height() / 2.0f, openButtonRect.height() / 2.0f, openButtonBackgroundPaint); + if (openButtonText != null) { + openButtonText.draw(canvas, openButtonRect.left + dp(13), openButtonRect.centerY(), Theme.getColor(Theme.key_featuredStickers_buttonText, resourcesProvider), 1.0f); + } + canvas.restore(); } else if (getIsPinned()) { Theme.dialogs_pinnedDrawable.setAlpha((int) ((1.0f - reorderIconProgress) * 255)); setDrawableBounds(Theme.dialogs_pinnedDrawable, pinLeft, pinTop); @@ -5337,6 +5411,22 @@ public class DialogCell extends BaseCell implements StoriesListPlaceProvider.Ava return true; } if (delegate == null || delegate.canClickButtonInside()) { + if (openBot) { + final boolean hit = openButtonRect.contains(event.getX(), event.getY()); + if (event.getAction() == MotionEvent.ACTION_DOWN || event.getAction() == MotionEvent.ACTION_MOVE) { + openButtonBounce.setPressed(hit); + } else if (openButtonBounce.isPressed() && event.getAction() == MotionEvent.ACTION_UP) { + if (onOpenButtonClick != null) { + onOpenButtonClick.run(user); + } + openButtonBounce.setPressed(false); + return true; + } else if (openButtonBounce.isPressed() && event.getAction() == MotionEvent.ACTION_CANCEL) { + openButtonBounce.setPressed(false); + return true; + } + if (hit) return true; + } if (lastTopicMessageUnread && canvasButton != null && buttonLayout != null && (dialogsType == DialogsActivity.DIALOGS_TYPE_DEFAULT || dialogsType == DialogsActivity.DIALOGS_TYPE_FOLDER1 || dialogsType == DialogsActivity.DIALOGS_TYPE_FOLDER2) && canvasButton.checkTouchEvent(event)) { return true; } 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 3a7af2d30..755b80ee9 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/ProfileSearchCell.java @@ -101,7 +101,7 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No private boolean drawCount; private int lastUnreadCount; - private int countTop = AndroidUtilities.dp(19); + private int countTop = dp(19); private int countLeft; private int countWidth; private StaticLayout countLayout; @@ -134,7 +134,7 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No this.resourcesProvider = resourcesProvider; avatarImage = new ImageReceiver(this); - avatarImage.setRoundRadius(AndroidUtilities.dp(23)); + avatarImage.setRoundRadius(dp(23)); avatarDrawable = new AvatarDrawable(); checkBox = new CheckBox2(context, 21, resourcesProvider); @@ -143,7 +143,7 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No checkBox.setDrawBackgroundAsArc(3); addView(checkBox); - statusDrawable = new AnimatedEmojiDrawable.SwapAnimatedEmojiDrawable(this, AndroidUtilities.dp(20)); + statusDrawable = new AnimatedEmojiDrawable.SwapAnimatedEmojiDrawable(this, dp(20)); statusDrawable.setCallback(this); } @@ -319,9 +319,9 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (checkBox != null) { - checkBox.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(24), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(24), MeasureSpec.EXACTLY)); + checkBox.measure(MeasureSpec.makeMeasureSpec(dp(24), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(dp(24), MeasureSpec.EXACTLY)); } - setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), AndroidUtilities.dp(60) + (useSeparator ? 1 : 0)); + setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), dp(60) + (useSeparator ? 1 : 0)); } @Override @@ -330,8 +330,8 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No return; } if (checkBox != null) { - int x = LocaleController.isRTL ? (right - left) - AndroidUtilities.dp(42) : AndroidUtilities.dp(42); - int y = AndroidUtilities.dp(36); + int x = LocaleController.isRTL ? (right - left) - dp(42) : dp(42); + int y = dp(36); checkBox.layout(x, y, x + checkBox.getMeasuredWidth(), y + checkBox.getMeasuredHeight()); } if (changed) { @@ -364,40 +364,40 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No drawNameLock = true; dialog_id = DialogObject.makeEncryptedDialogId(encryptedChat.id); if (!LocaleController.isRTL) { - nameLockLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); - nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline + 4) + Theme.dialogs_lockDrawable.getIntrinsicWidth(); + nameLockLeft = dp(AndroidUtilities.leftBaseline); + nameLeft = dp(AndroidUtilities.leftBaseline + 4) + Theme.dialogs_lockDrawable.getIntrinsicWidth(); } else { - nameLockLeft = getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline + 2) - Theme.dialogs_lockDrawable.getIntrinsicWidth(); - nameLeft = AndroidUtilities.dp(11); + nameLockLeft = getMeasuredWidth() - dp(AndroidUtilities.leftBaseline + 2) - Theme.dialogs_lockDrawable.getIntrinsicWidth(); + nameLeft = dp(11); } - nameLockTop = AndroidUtilities.dp(22.0f); + nameLockTop = dp(22.0f); updateStatus(false, null, null, false); } else if (chat != null) { dialog_id = -chat.id; drawCheck = chat.verified; if (!LocaleController.isRTL) { - nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + nameLeft = dp(AndroidUtilities.leftBaseline); } else { - nameLeft = AndroidUtilities.dp(11); + nameLeft = dp(11); } updateStatus(drawCheck, null, chat, false); } else if (user != null) { dialog_id = user.id; if (!LocaleController.isRTL) { - nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + nameLeft = dp(AndroidUtilities.leftBaseline); } else { - nameLeft = AndroidUtilities.dp(11); + nameLeft = dp(11); } - nameLockTop = AndroidUtilities.dp(21); + nameLockTop = dp(21); drawCheck = user.verified; drawPremium = !savedMessages && MessagesController.getInstance(currentAccount).isPremiumUser(user); updateStatus(drawCheck, user, null, false); } else if (contact != null) { dialog_id = 0; if (!LocaleController.isRTL) { - nameLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + nameLeft = dp(AndroidUtilities.leftBaseline); } else { - nameLeft = AndroidUtilities.dp(11); + nameLeft = dp(11); } if (actionButton == null) { actionButton = new CanvasButton(this); @@ -412,9 +412,9 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No } } if (!LocaleController.isRTL) { - statusLeft = AndroidUtilities.dp(AndroidUtilities.leftBaseline); + statusLeft = dp(AndroidUtilities.leftBaseline); } else { - statusLeft = AndroidUtilities.dp(11); + statusLeft = dp(11); } if (currentName != null) { @@ -440,7 +440,7 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No namePaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); namePaint.setTypeface(AndroidUtilities.bold()); } - namePaint.setTextSize(AndroidUtilities.dp(16)); + namePaint.setTextSize(dp(16)); if (encryptedChat != null) { namePaint.setColor(Theme.getColor(Theme.key_chats_secretName, resourcesProvider)); } else { @@ -455,25 +455,25 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No int statusWidth; if (!LocaleController.isRTL) { - statusWidth = nameWidth = getMeasuredWidth() - nameLeft - AndroidUtilities.dp(14); + statusWidth = nameWidth = getMeasuredWidth() - nameLeft - dp(14); } else { - statusWidth = nameWidth = getMeasuredWidth() - nameLeft - AndroidUtilities.dp(AndroidUtilities.leftBaseline); + statusWidth = nameWidth = getMeasuredWidth() - nameLeft - dp(AndroidUtilities.leftBaseline); } if (drawNameLock) { - nameWidth -= AndroidUtilities.dp(6) + Theme.dialogs_lockDrawable.getIntrinsicWidth(); + nameWidth -= dp(6) + Theme.dialogs_lockDrawable.getIntrinsicWidth(); } if (contact != null) { int w = (int) (Theme.dialogs_countTextPaint.measureText(LocaleController.getString(R.string.Invite)) + 1); actionLayout = new StaticLayout(LocaleController.getString(R.string.Invite), Theme.dialogs_countTextPaint, w, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); if (!LocaleController.isRTL) { - actionLeft = getMeasuredWidth() - w - AndroidUtilities.dp(19) - AndroidUtilities.dp(16); + actionLeft = getMeasuredWidth() - w - dp(19) - dp(16); } else { - actionLeft = AndroidUtilities.dp(19) + AndroidUtilities.dp(16); + actionLeft = dp(19) + dp(16); nameLeft += w; statusLeft += w; } - nameWidth -= AndroidUtilities.dp(32) + w; + nameWidth -= dp(32) + w; } nameWidth -= getPaddingLeft() + getPaddingRight(); @@ -485,15 +485,15 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No if (unreadCount != 0) { lastUnreadCount = unreadCount; String countString = String.format(Locale.US, "%d", unreadCount); - countWidth = Math.max(AndroidUtilities.dp(12), (int) Math.ceil(Theme.dialogs_countTextPaint.measureText(countString))); + countWidth = Math.max(dp(12), (int) Math.ceil(Theme.dialogs_countTextPaint.measureText(countString))); countLayout = new StaticLayout(countString, Theme.dialogs_countTextPaint, countWidth, Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); - int w = countWidth + AndroidUtilities.dp(18); + int w = countWidth + dp(18); nameWidth -= w; statusWidth -= w; if (!LocaleController.isRTL) { - countLeft = getMeasuredWidth() - countWidth - AndroidUtilities.dp(19); + countLeft = getMeasuredWidth() - countWidth - dp(19); } else { - countLeft = AndroidUtilities.dp(19); + countLeft = dp(19); nameLeft += w; statusLeft += w; } @@ -509,9 +509,9 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No if (nameWidth < 0) { nameWidth = 0; } - CharSequence nameStringFinal = TextUtils.ellipsize(nameString, currentNamePaint, nameWidth - AndroidUtilities.dp(12), TextUtils.TruncateAt.END); + CharSequence nameStringFinal = TextUtils.ellipsize(nameString, currentNamePaint, nameWidth - dp(12), TextUtils.TruncateAt.END); if (nameStringFinal != null) { - nameStringFinal = Emoji.replaceEmoji(nameStringFinal, currentNamePaint.getFontMetricsInt(), AndroidUtilities.dp(20), false); + nameStringFinal = Emoji.replaceEmoji(nameStringFinal, currentNamePaint.getFontMetricsInt(), dp(20), false); } nameLayout = new StaticLayout(nameStringFinal, currentNamePaint, nameWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); @@ -548,7 +548,7 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No } if (savedMessages || UserObject.isReplyUser(user)) { statusString = null; - nameTop = AndroidUtilities.dp(20); + nameTop = dp(20); } } else { if (ChatObject.isChannel(chat) && !chat.megagroup) { @@ -574,13 +574,13 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No } } } - nameTop = AndroidUtilities.dp(19); + nameTop = dp(19); } if (customPaints) { if (statusPaint == null) { statusPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); } - statusPaint.setTextSize(AndroidUtilities.dp(15)); + statusPaint.setTextSize(dp(15)); if (currentStatusPaint == Theme.dialogs_offlinePaint) { statusPaint.setColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText3, resourcesProvider)); } else if (currentStatusPaint == Theme.dialogs_onlinePaint) { @@ -590,22 +590,22 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No } if (!TextUtils.isEmpty(statusString)) { - CharSequence statusStringFinal = TextUtils.ellipsize(statusString, currentStatusPaint, statusWidth - AndroidUtilities.dp(12), TextUtils.TruncateAt.END); + CharSequence statusStringFinal = TextUtils.ellipsize(statusString, currentStatusPaint, statusWidth - dp(12), TextUtils.TruncateAt.END); statusLayout = new StaticLayout(statusStringFinal, currentStatusPaint, statusWidth, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false); - nameTop = AndroidUtilities.dp(9); - nameLockTop -= AndroidUtilities.dp(10); + nameTop = dp(9); + nameLockTop -= dp(10); } else { - nameTop = AndroidUtilities.dp(20); + nameTop = dp(20); statusLayout = null; } int avatarLeft; if (LocaleController.isRTL) { - avatarLeft = getMeasuredWidth() - AndroidUtilities.dp(57) - getPaddingRight(); + avatarLeft = getMeasuredWidth() - dp(57) - getPaddingRight(); } else { - avatarLeft = AndroidUtilities.dp(11) + getPaddingLeft(); + avatarLeft = dp(rectangularAvatar ? 15 : 11) + getPaddingLeft(); } - avatarStoryParams.originalAvatarRect.set(avatarLeft, AndroidUtilities.dp(7), avatarLeft + AndroidUtilities.dp(46), AndroidUtilities.dp(7) + AndroidUtilities.dp(46)); + avatarStoryParams.originalAvatarRect.set(avatarLeft, dp(7), avatarLeft + dp(rectangularAvatar ? 42 : 46), dp(7) + dp(46)); double widthpx; float left; @@ -674,6 +674,11 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No } } + private boolean rectangularAvatar; + public void setRectangularAvatar(boolean value) { + rectangularAvatar = value; + } + public void update(int mask) { TLRPC.FileLocation photo = null; if (user != null) { @@ -712,7 +717,7 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No avatarImage.setImage(null, null, avatarDrawable, null, null, 0); } - avatarImage.setRoundRadius(chat != null && chat.forum ? AndroidUtilities.dp(16) : AndroidUtilities.dp(23)); + avatarImage.setRoundRadius(rectangularAvatar ? dp(10) : chat != null && chat.forum ? dp(16) : dp(23)); if (mask != 0) { boolean continueUpdate = false; if ((mask & MessagesController.UPDATE_MASK_AVATAR) != 0 && user != null || (mask & MessagesController.UPDATE_MASK_CHAT_AVATAR) != 0 && chat != null) { @@ -794,9 +799,9 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No dividerPaint = Theme.dividerPaint; } if (LocaleController.isRTL) { - canvas.drawLine(0, getMeasuredHeight() - 1, getMeasuredWidth() - AndroidUtilities.dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, dividerPaint); + canvas.drawLine(0, getMeasuredHeight() - 1, getMeasuredWidth() - dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, dividerPaint); } else { - canvas.drawLine(AndroidUtilities.dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, getMeasuredWidth(), getMeasuredHeight() - 1, dividerPaint); + canvas.drawLine(dp(AndroidUtilities.leftBaseline), getMeasuredHeight() - 1, getMeasuredWidth(), getMeasuredHeight() - 1, dividerPaint); } } @@ -814,13 +819,13 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No int x; if (LocaleController.isRTL) { if (nameLayout.getLineLeft(0) == 0) { - x = nameLeft - AndroidUtilities.dp(3) - statusDrawable.getIntrinsicWidth(); + x = nameLeft - dp(3) - statusDrawable.getIntrinsicWidth(); } else { float w = nameLayout.getLineWidth(0); - x = (int) (nameLeft + nameWidth - Math.ceil(w) - AndroidUtilities.dp(3) - statusDrawable.getIntrinsicWidth()); + x = (int) (nameLeft + nameWidth - Math.ceil(w) - dp(3) - statusDrawable.getIntrinsicWidth()); } } else { - x = (int) (nameLeft + nameLayout.getLineRight(0) + AndroidUtilities.dp(6)); + x = (int) (nameLeft + nameLayout.getLineRight(0) + dp(6)); } setDrawableBounds(statusDrawable, x, nameTop + (nameLayout.getHeight() - statusDrawable.getIntrinsicHeight()) / 2f); statusDrawable.draw(canvas); @@ -828,31 +833,31 @@ public class ProfileSearchCell extends BaseCell implements NotificationCenter.No if (statusLayout != null) { canvas.save(); - canvas.translate(statusLeft + sublabelOffsetX, AndroidUtilities.dp(33) + sublabelOffsetY); + canvas.translate(statusLeft + sublabelOffsetX, dp(33) + sublabelOffsetY); statusLayout.draw(canvas); canvas.restore(); } if (countLayout != null) { - int x = countLeft - AndroidUtilities.dp(5.5f); - rect.set(x, countTop, x + countWidth + AndroidUtilities.dp(11), countTop + AndroidUtilities.dp(23)); + int x = countLeft - dp(5.5f); + rect.set(x, countTop, x + countWidth + dp(11), countTop + dp(23)); canvas.drawRoundRect(rect, 11.5f * AndroidUtilities.density, 11.5f * AndroidUtilities.density, MessagesController.getInstance(currentAccount).isDialogMuted(dialog_id, 0) ? Theme.dialogs_countGrayPaint : Theme.dialogs_countPaint); canvas.save(); - canvas.translate(countLeft, countTop + AndroidUtilities.dp(4)); + canvas.translate(countLeft, countTop + dp(4)); countLayout.draw(canvas); canvas.restore(); } if (actionLayout != null) { actionButton.setColor(Theme.getColor(Theme.key_chats_unreadCounter), Theme.getColor(Theme.key_chats_unreadCounterText)); - AndroidUtilities.rectTmp.set(actionLeft, countTop, actionLeft + actionLayout.getWidth(), countTop + AndroidUtilities.dp(23)); - AndroidUtilities.rectTmp.inset(-AndroidUtilities.dp(16), -AndroidUtilities.dp(4)); + AndroidUtilities.rectTmp.set(actionLeft, countTop, actionLeft + actionLayout.getWidth(), countTop + dp(23)); + AndroidUtilities.rectTmp.inset(-dp(16), -dp(4)); actionButton.setRect(AndroidUtilities.rectTmp); actionButton.setRounded(true); actionButton.draw(canvas); canvas.save(); - canvas.translate(actionLeft, countTop + AndroidUtilities.dp(4)); + canvas.translate(actionLeft, countTop + dp(4)); actionLayout.draw(canvas); canvas.restore(); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SlideIntChooseView.java b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SlideIntChooseView.java index 4c4a7e8cb..d0d289b45 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Cells/SlideIntChooseView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Cells/SlideIntChooseView.java @@ -94,7 +94,10 @@ public class SlideIntChooseView extends FrameLayout { if (options == null || whenChanged == null) { return; } - final int newValue = (int) Math.round(options.min + stepsCount * progress); + int newValue = (int) Math.round(options.min + stepsCount * progress); + if (minValueAllowed != Integer.MIN_VALUE) { + newValue = Math.max(newValue, minValueAllowed); + } if (value != newValue) { value = newValue; AndroidUtilities.vibrateCursor(seekBarView); @@ -114,6 +117,7 @@ public class SlideIntChooseView extends FrameLayout { } private int value; + private int minValueAllowed = Integer.MIN_VALUE; private Utilities.Callback whenChanged; private Options options; @@ -132,28 +136,23 @@ public class SlideIntChooseView extends FrameLayout { updateTexts(value, false); } + public void setMinValueAllowed(int value) { + minValueAllowed = value; + if (this.value < minValueAllowed) { + this.value = minValueAllowed; + } + seekBarView.setMinProgress(Utilities.clamp01((float) (value - options.min) / stepsCount)); + updateTexts(this.value, false); + invalidate(); + } + public void updateTexts(int value, boolean animated) { minText.cancelAnimation(); maxText.cancelAnimation(); - if (!TextUtils.isEmpty(options.resId)) { - valueText.cancelAnimation(); - valueText.setText(LocaleController.formatPluralString(options.resId, value), animated); - minText.setText("" + options.min, animated); - maxText.setText("" + options.max, animated); - } else { - int valueResId; - if (value <= options.min) { - valueResId = options.valueMinStringResId; - } else if (value < options.max) { - valueResId = options.valueStringResId; - } else { - valueResId = options.valueMaxStringResId; - } - valueText.cancelAnimation(); - valueText.setText(processText(valueResId, value), animated); - minText.setText(processText(options.minStringResId, options.min), animated); - maxText.setText(processText(options.maxStringResId, options.max), animated); - } + valueText.cancelAnimation(); + valueText.setText(options.toString.run(0, value), animated); + minText.setText(options.toString.run(-1, options.min), animated); + maxText.setText(options.toString.run(+1, options.max), animated); maxText.setTextColor(Theme.getColor(value >= options.max ? Theme.key_windowBackgroundWhiteValueText : Theme.key_windowBackgroundWhiteGrayText, resourcesProvider), animated); setMaxTextEmojiSaturation(value >= options.max ? 1f : 0f, animated); } @@ -203,14 +202,6 @@ public class SlideIntChooseView extends FrameLayout { } } - private CharSequence processText(int resId, int value) { - String string = getString(resId); - string = string.replace("%d", "" + value); - CharSequence cs = AndroidUtilities.replaceTags(string); - cs = ChannelMonetizationLayout.replaceTON(cs, valueText.getPaint()); - return cs; - } - @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure( @@ -232,27 +223,18 @@ public class SlideIntChooseView extends FrameLayout { public int min; public int max; - public String resId; - - public int minStringResId; - public int valueMinStringResId, valueStringResId, valueMaxStringResId; - public int maxStringResId; + public Utilities.Callback2Return toString; public static Options make( int style, - int min, int minStringResId, - int valueMinStringResId, int valueStringResId, int valueMaxStringResId, - int max, int maxStringResId + int min, int max, + Utilities.CallbackReturn toString ) { Options o = new Options(); o.style = style; o.min = min; - o.minStringResId = minStringResId; - o.valueMinStringResId = valueMinStringResId; - o.valueStringResId = valueStringResId; - o.valueMaxStringResId = valueMaxStringResId; o.max = max; - o.maxStringResId = maxStringResId; + o.toString = (type, val) -> toString.run(val); return o; } @@ -263,8 +245,8 @@ public class SlideIntChooseView extends FrameLayout { Options o = new Options(); o.style = style; o.min = min; - o.resId = resId; o.max = max; + o.toString = (type, val) -> type == 0 ? LocaleController.formatPluralString(resId, val) : "" + val; return o; } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChannelMonetizationLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/ChannelMonetizationLayout.java index 4f3e8dbbe..31c7aad03 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChannelMonetizationLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChannelMonetizationLayout.java @@ -6,6 +6,7 @@ import static org.telegram.messenger.AndroidUtilities.makeBlurBitmap; import static org.telegram.messenger.LocaleController.formatPluralString; import static org.telegram.messenger.LocaleController.formatString; import static org.telegram.messenger.LocaleController.getString; +import static org.telegram.ui.ChatEditActivity.applyNewSpan; import android.app.Activity; import android.content.Context; @@ -91,6 +92,8 @@ import org.telegram.ui.Stars.BotStarsActivity; import org.telegram.ui.Stars.BotStarsController; import org.telegram.ui.Stars.StarsIntroActivity; import org.telegram.ui.Stories.recorder.ButtonWithCounterView; +import org.telegram.ui.bots.AffiliateProgramFragment; +import org.telegram.ui.bots.ChannelAffiliateProgramsFragment; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; @@ -125,7 +128,7 @@ public class ChannelMonetizationLayout extends SizeNotifierFrameLayout implement private int starsBalanceBlockedUntil; private final LinearLayout starsBalanceLayout; private final RelativeSizeSpan starsBalanceTitleSizeSpan; - private long starsBalance; + private TL_stars.StarsAmount starsBalance = new TL_stars.StarsAmount(0); private final AnimatedTextView starsBalanceTitle; private final AnimatedTextView starsBalanceSubtitle; private final ButtonWithCounterView starsBalanceButton; @@ -327,16 +330,15 @@ public class ChannelMonetizationLayout extends SizeNotifierFrameLayout implement @Override public void afterTextChanged(Editable s) { if (starsBalanceEditTextIgnore) return; - long balance = starsBalance; starsBalanceEditTextValue = TextUtils.isEmpty(s) ? 0 : Long.parseLong(s.toString()); - if (starsBalanceEditTextValue > balance) { - starsBalanceEditTextValue = balance; + if (starsBalanceEditTextValue > starsBalance.amount) { + starsBalanceEditTextValue = starsBalance.amount; starsBalanceEditTextIgnore = true; starsBalanceEditText.setText(Long.toString(starsBalanceEditTextValue)); starsBalanceEditText.setSelection(starsBalanceEditText.getText().length()); starsBalanceEditTextIgnore = false; } - starsBalanceEditTextAll = starsBalanceEditTextValue == balance; + starsBalanceEditTextAll = starsBalanceEditTextValue == starsBalance.amount; AndroidUtilities.cancelRunOnUIThread(setStarsBalanceButtonText); setStarsBalanceButtonText.run(); starsBalanceEditTextAll = false; @@ -380,10 +382,9 @@ public class ChannelMonetizationLayout extends SizeNotifierFrameLayout implement Drawable starDrawable = getContext().getResources().getDrawable(R.drawable.star_small_inner).mutate(); BulletinFactory.of(fragment).createSimpleBulletin(starDrawable, AndroidUtilities.replaceSingleTag(LocaleController.formatPluralString("BotStarsWithdrawMinLimit", (int) MessagesController.getInstance(currentAccount).starsRevenueWithdrawalMin), () -> { Bulletin.hideVisible(); - long balance = starsBalance; - if (balance < MessagesController.getInstance(currentAccount).starsRevenueWithdrawalMin) { + if (starsBalance.amount < MessagesController.getInstance(currentAccount).starsRevenueWithdrawalMin) { starsBalanceEditTextAll = true; - starsBalanceEditTextValue = balance; + starsBalanceEditTextValue = starsBalance.amount; } else { starsBalanceEditTextAll = false; starsBalanceEditTextValue = MessagesController.getInstance(currentAccount).starsRevenueWithdrawalMin; @@ -661,29 +662,29 @@ public class ChannelMonetizationLayout extends SizeNotifierFrameLayout implement balanceSubtitle.setText("≈" + BillingController.getInstance().formatCurrency(amount, "USD")); } - private void setStarsBalance(long crypto_amount, int blockedUntil) { + private void setStarsBalance(TL_stars.StarsAmount amount, int blockedUntil) { if (balanceTitle == null || balanceSubtitle == null) return; - long amount = (long) (stars_rate * crypto_amount * 100.0); - SpannableStringBuilder ssb = new SpannableStringBuilder(StarsIntroActivity.replaceStarsWithPlain("XTR " + LocaleController.formatNumber(crypto_amount, ' '), 1f)); +// long amount = (long) (stars_rate * crypto_amount * 100.0); + SpannableStringBuilder ssb = new SpannableStringBuilder(StarsIntroActivity.replaceStarsWithPlain(TextUtils.concat("XTR ", StarsIntroActivity.formatStarsAmount(amount, 0.8f, ' ')), 1f)); int index = TextUtils.indexOf(ssb, "."); if (index >= 0) { ssb.setSpan(balanceTitleSizeSpan, index, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } - starsBalance = crypto_amount; + starsBalance = amount; starsBalanceTitle.setText(ssb); - starsBalanceSubtitle.setText("≈" + BillingController.getInstance().formatCurrency(amount, "USD")); - starsBalanceEditTextContainer.setVisibility(crypto_amount > 0 ? VISIBLE : GONE); + starsBalanceSubtitle.setText("≈" + BillingController.getInstance().formatCurrency((long) (stars_rate * amount.amount * 100.0), "USD")); + starsBalanceEditTextContainer.setVisibility(amount.amount > 0 ? VISIBLE : GONE); if (starsBalanceEditTextAll) { starsBalanceEditTextIgnore = true; - starsBalanceEditText.setText(Long.toString(starsBalanceEditTextValue = crypto_amount)); + starsBalanceEditText.setText(Long.toString(starsBalanceEditTextValue = amount.amount)); starsBalanceEditText.setSelection(starsBalanceEditText.getText().length()); starsBalanceEditTextIgnore = false; starsBalanceButton.setEnabled(starsBalanceEditTextValue > 0); } if (starsAdsButton != null) { - starsAdsButton.setEnabled(amount > 0); + starsAdsButton.setEnabled(amount.amount > 0); } starsBalanceBlockedUntil = blockedUntil; @@ -829,23 +830,23 @@ public class ChannelMonetizationLayout extends SizeNotifierFrameLayout implement } availableValue.contains2 = true; availableValue.crypto_amount2 = balances.available_balance; - availableValue.amount2 = (long) (availableValue.crypto_amount2 * stars_rate * 100.0); + availableValue.amount2 = (long) (availableValue.crypto_amount2.amount * stars_rate * 100.0); setStarsBalance(availableValue.crypto_amount2, balances.next_withdrawal_at); availableValue.currency = "USD"; lastWithdrawalValue.contains2 = true; lastWithdrawalValue.crypto_amount2 = balances.current_balance; - lastWithdrawalValue.amount2 = (long) (lastWithdrawalValue.crypto_amount2 * stars_rate * 100.0); + lastWithdrawalValue.amount2 = (long) (lastWithdrawalValue.crypto_amount2.amount * stars_rate * 100.0); lastWithdrawalValue.currency = "USD"; lifetimeValue.contains2 = true; lifetimeValue.crypto_amount2 = balances.overall_revenue; - lifetimeValue.amount2 = (long) (lifetimeValue.crypto_amount2 * stars_rate * 100.0); + lifetimeValue.amount2 = (long) (lifetimeValue.crypto_amount2.amount * stars_rate * 100.0); lifetimeValue.currency = "USD"; proceedsAvailable = true; if (starsBalanceButtonsLayout != null) { starsBalanceButtonsLayout.setVisibility(balances.withdrawal_enabled ? View.VISIBLE : View.GONE); } if (starsBalanceButton != null) { - starsBalanceButton.setVisibility(balances.available_balance > 0 || BuildVars.DEBUG_PRIVATE_VERSION ? View.VISIBLE : View.GONE); + starsBalanceButton.setVisibility(balances.available_balance.amount > 0 || BuildVars.DEBUG_PRIVATE_VERSION ? View.VISIBLE : View.GONE); } if (listView != null && listView.adapter != null) { @@ -899,6 +900,7 @@ public class ChannelMonetizationLayout extends SizeNotifierFrameLayout implement private final static int CHECK_SWITCHOFF = 1; private final static int BUTTON_LOAD_MORE_TRANSACTIONS = 2; private final static int STARS_BALANCE = 3; + private final static int BUTTON_AFFILIATE =4; private void fillItems(ArrayList items, UniversalAdapter adapter) { int stats_dc = -1; @@ -946,6 +948,10 @@ public class ChannelMonetizationLayout extends SizeNotifierFrameLayout implement items.add(UItem.asShadow(-6, starsBalanceInfo)); } } + if (MessagesController.getInstance(currentAccount).starrefConnectAllowed) { + items.add(AffiliateProgramFragment.ColorfulTextCell.Factory.as(BUTTON_AFFILIATE, Theme.getColor(Theme.key_color_green, resourcesProvider), R.drawable.filled_earn_stars, applyNewSpan(getString(R.string.ChannelAffiliateProgramRowTitle)), getString(R.string.ChannelAffiliateProgramRowText))); + items.add(UItem.asShadow(-7, null)); + } if (transactionsLayout.hasTransactions()) { items.add(UItem.asFullscreenCustom(transactionsLayout, 0)); } else { @@ -970,6 +976,8 @@ public class ChannelMonetizationLayout extends SizeNotifierFrameLayout implement AndroidUtilities.cancelRunOnUIThread(sendCpmUpdateRunnable); AndroidUtilities.runOnUIThread(sendCpmUpdateRunnable, 1000); listView.adapter.update(true); + } else if (item.id == BUTTON_AFFILIATE) { + fragment.presentFragment(new ChannelAffiliateProgramsFragment(dialogId)); } } @@ -1095,7 +1103,11 @@ public class ChannelMonetizationLayout extends SizeNotifierFrameLayout implement for (int i = 0; i < 2; ++i) { final String crypto_currency = i == 0 ? value.crypto_currency : value.crypto_currency2; - final long crypto_amount = i == 0 ? value.crypto_amount : value.crypto_amount2; +// final long crypto_amount = i == 0 ? value.crypto_amount : value.crypto_amount2; +// CharSequence cryptoAmount; +// if (i == 0) { +// cryptoAmount +// } final long amount = i == 0 ? value.amount : value.amount2; if (i == 0 && !value.contains1) { @@ -1107,17 +1119,23 @@ public class ChannelMonetizationLayout extends SizeNotifierFrameLayout implement continue; } - CharSequence s = crypto_currency + " "; + SpannableStringBuilder s = new SpannableStringBuilder(crypto_currency + " "); + CharSequence finalS; if ("TON".equalsIgnoreCase(crypto_currency)) { - s += formatter.format(crypto_amount / 1_000_000_000.0); - s = replaceTON(s, cryptoAmountView[i].getPaint(), .87f, true); + s.append(formatter.format(value.crypto_amount / 1_000_000_000.0)); + finalS = replaceTON(s, cryptoAmountView[i].getPaint(), .87f, true); } else if ("XTR".equalsIgnoreCase(crypto_currency)) { - s += LocaleController.formatNumber(crypto_amount, ' '); - s = StarsIntroActivity.replaceStarsWithPlain(s, .8f); + if (i == 0) { + s.append(LocaleController.formatNumber(value.crypto_amount, ' ')); + } else { + s.append(StarsIntroActivity.formatStarsAmount(value.crypto_amount2, .8f, ' ')); + } + finalS = StarsIntroActivity.replaceStarsWithPlain(s, .8f); } else { - s += Long.toString(crypto_amount); + s.append(Long.toString(value.crypto_amount)); + finalS = s; } - SpannableStringBuilder cryptoAmount = new SpannableStringBuilder(s); + SpannableStringBuilder cryptoAmount = new SpannableStringBuilder(finalS); if ("TON".equalsIgnoreCase(crypto_currency)) { int index = TextUtils.indexOf(cryptoAmount, "."); if (index >= 0) { @@ -1147,7 +1165,7 @@ public class ChannelMonetizationLayout extends SizeNotifierFrameLayout implement public boolean contains2; public String crypto_currency2; - public long crypto_amount2; + public TL_stars.StarsAmount crypto_amount2 = new TL_stars.StarsAmount(0); public long amount2; public static ProceedOverview as(String cryptoCurrency, CharSequence text) { @@ -1808,8 +1826,8 @@ public class ChannelMonetizationLayout extends SizeNotifierFrameLayout implement req.peer = MessagesController.getInstance(currentAccount).getInputPeer(dialogId); req.offset = starsLastOffset; ConnectionsManager.getInstance(currentAccount).sendRequest(req, (res, err) -> AndroidUtilities.runOnUIThread(() -> { - if (res instanceof TL_stars.TL_payments_starsStatus) { - TL_stars.TL_payments_starsStatus r = (TL_stars.TL_payments_starsStatus) res; + if (res instanceof TL_stars.StarsStatus) { + TL_stars.StarsStatus r = (TL_stars.StarsStatus) res; MessagesController.getInstance(currentAccount).putUsers(r.users, false); MessagesController.getInstance(currentAccount).putChats(r.chats, false); starsTransactions.addAll(r.history); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java index 9565beb91..c5b9e92b8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatActivity.java @@ -12796,7 +12796,7 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } chatAttachAlert.enableDefaultMode(); chatAttachAlert.init(); - chatAttachAlert.getCommentTextView().setText(chatActivityEnterView.getFieldText()); + chatAttachAlert.getCommentView().setText(chatActivityEnterView.getFieldText()); chatAttachAlert.parentThemeDelegate = themeDelegate; showDialog(chatAttachAlert); } @@ -23462,7 +23462,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionGameScore) { messageObject.generateGameMessageText(null); } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSent) { - messageObject.generatePaymentSentMessageText(null); + messageObject.generatePaymentSentMessageText(null, false); + }else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSentMe) { + messageObject.generatePaymentSentMessageText(null, true); } } @@ -24498,7 +24500,9 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionGameScore) { messageObject.generateGameMessageText(null); } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSent) { - messageObject.generatePaymentSentMessageText(null); + messageObject.generatePaymentSentMessageText(null, false); + } else if (messageObject.messageOwner.action instanceof TLRPC.TL_messageActionPaymentSentMe) { + messageObject.generatePaymentSentMessageText(null, true); } } if (old.isWebpage() && messageObject.isWebpage()) { @@ -37153,23 +37157,46 @@ public class ChatActivity extends BaseFragment implements NotificationCenter.Not if (getMessagesController().premiumFeaturesBlocked() || span == null || span.standard) { return false; } - long documentId = span.getDocumentId(); - TLRPC.Document document = span.document == null ? AnimatedEmojiDrawable.findDocument(currentAccount, documentId) : span.document; - if (document == null) { - return false; - } - Bulletin bulletin = BulletinFactory.of(ChatActivity.this).createContainsEmojiBulletin(document, BulletinFactory.CONTAINS_EMOJI_IN_MESSAGE, set -> { - ArrayList inputSets = new ArrayList<>(1); - inputSets.add(set); - EmojiPacksAlert alert = new EmojiPacksAlert(ChatActivity.this, getParentActivity(), themeDelegate, inputSets); - alert.setCalcMandatoryInsets(isKeyboardVisible()); - showDialog(alert); - }); - if (bulletin != null) { - bulletin.show(); - return true; - } - return false; + final long documentId = span.getDocumentId(); + final TLRPC.Document document = span.document == null ? AnimatedEmojiDrawable.findDocument(currentAccount, documentId) : span.document; + if (document == null) return false; + final TLRPC.InputStickerSet inputStickerSet = MessageObject.getInputStickerSet(document); + if (inputStickerSet == null) return false; + final TLRPC.TL_messages_stickerSet cachedSet = MediaDataController.getInstance(UserConfig.selectedAccount).getStickerSet(inputStickerSet, true); +// if (cachedSet == null || cachedSet.set == null) { +// final boolean[] cancelled = new boolean[1]; +// final AlertDialog progressDialog = new AlertDialog(getContext(), AlertDialog.ALERT_TYPE_SPINNER); +// progressDialog.showDelayed(200); +// progressDialog.setCanCancel(true); +// progressDialog.setOnCancelListener(d -> cancelled[0] = true); +// MediaDataController.getInstance(UserConfig.selectedAccount).getStickerSet(inputStickerSet, null, false, set -> { +// if (cancelled[0]) return; +// ArrayList inputSets = new ArrayList<>(1); +// inputSets.add(inputStickerSet); +// EmojiPacksAlert alert = new EmojiPacksAlert(ChatActivity.this, getParentActivity(), themeDelegate, inputSets); +// alert.setCalcMandatoryInsets(isKeyboardVisible()); +// showDialog(alert); +// }); +// } else { + final ArrayList inputSets = new ArrayList<>(1); + inputSets.add(inputStickerSet); + final EmojiPacksAlert alert = new EmojiPacksAlert(ChatActivity.this, getParentActivity(), themeDelegate, inputSets); + alert.setPreviewEmoji(document); + alert.setCalcMandatoryInsets(isKeyboardVisible()); + showDialog(alert); +// } +// Bulletin bulletin = BulletinFactory.of(ChatActivity.this).createContainsEmojiBulletin(document, BulletinFactory.CONTAINS_EMOJI_IN_MESSAGE, set -> { +// ArrayList inputSets = new ArrayList<>(1); +// inputSets.add(set); +// EmojiPacksAlert alert = new EmojiPacksAlert(ChatActivity.this, getParentActivity(), themeDelegate, inputSets); +// alert.setCalcMandatoryInsets(isKeyboardVisible()); +// showDialog(alert); +// }); +// if (bulletin != null) { +// bulletin.show(); +// return true; +// } + return true; } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatEditActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatEditActivity.java index e8d5d1324..36ce11b1a 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatEditActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatEditActivity.java @@ -11,6 +11,8 @@ package org.telegram.ui; import static org.telegram.messenger.AndroidUtilities.dp; import static org.telegram.messenger.LocaleController.getString; import static org.telegram.ui.ChannelMonetizationLayout.replaceTON; +import static org.telegram.ui.Stars.StarsIntroActivity.formatStarsAmount; +import static org.telegram.ui.Stars.StarsIntroActivity.formatStarsAmountShort; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -18,6 +20,7 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.app.Dialog; +import android.app.Notification; import android.content.Context; import android.content.Intent; import android.graphics.Canvas; @@ -97,10 +100,13 @@ import org.telegram.ui.Components.RadialProgressView; import org.telegram.ui.Components.Reactions.ChatCustomReactionsEditActivity; import org.telegram.ui.Components.Reactions.ReactionsUtils; import org.telegram.ui.Components.SizeNotifierFrameLayout; +import org.telegram.ui.Components.Text; import org.telegram.ui.Components.UndoView; import org.telegram.ui.Stars.BotStarsActivity; import org.telegram.ui.Stars.BotStarsController; import org.telegram.ui.Stars.StarsIntroActivity; +import org.telegram.ui.bots.AffiliateProgramFragment; +import org.telegram.ui.bots.ChannelAffiliateProgramsFragment; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; @@ -155,6 +161,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image private TextCell adminCell; private TextCell blockCell; private TextCell logCell; + private TextCell channelAffiliateProgramsCell; private TextCell statsAndBoosts; private TextCell setAvatarCell; private ShadowSectionCell infoSectionCell; @@ -166,6 +173,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image private TextCell publicLinkCell; private TextCell tonBalanceCell; private TextCell starsBalanceCell; + private TextCell botAffiliateProgramCell; private TextCell editIntroCell; private TextCell editCommandsCell; private TextCell changeBotSettingsCell; @@ -350,6 +358,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image canForum = userId == 0 && (forum || Math.max(info == null ? 0 : info.participants_count, currentChat.participants_count) >= getMessagesController().forumUpgradeParticipantsMin) && (info == null || info.linked_chat_id == 0); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.chatInfoDidLoad); NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.chatAvailableReactionsUpdated); + NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.channelConnectedBotsUpdate); } else { avatarDrawable.setInfo(5, currentUser.first_name, null); isChannel = false; @@ -395,6 +404,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image if (currentChat != null) { NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.chatInfoDidLoad); NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.chatAvailableReactionsUpdated); + NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.channelConnectedBotsUpdate); } else { NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.userInfoDidLoad); if (currentUser.bot) { @@ -1117,6 +1127,14 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image }); } + channelAffiliateProgramsCell = new TextCell(context); + channelAffiliateProgramsCell.setTextAndIcon(ChatEditActivity.applyNewSpan(LocaleController.getString(R.string.ChannelAffiliatePrograms)), R.drawable.menu_feature_premium, false); + channelAffiliateProgramsCell.setBackground(Theme.getSelectorDrawable(false)); + channelAffiliateProgramsCell.setOnClickListener(v -> { + presentFragment(new ChannelAffiliateProgramsFragment(-chatId)); + }); + channelAffiliateProgramsCell.setVisibility(View.GONE); + if (ChatObject.isChannel(currentChat) || currentChat.gigagroup) { logCell = new TextCell(context); logCell.setTextAndIcon(LocaleController.getString(R.string.EventLog), R.drawable.msg_log, false); @@ -1158,6 +1176,16 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image if (logCell != null) { infoContainer.addView(logCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); } + if (channelAffiliateProgramsCell != null) { + infoContainer.addView(channelAffiliateProgramsCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } + + if (channelAffiliateProgramsCell != null && getMessagesController().starrefConnectAllowed) { + channelAffiliateProgramsCell.setVisibility(View.VISIBLE); + } + if (logCell != null) { + logCell.setNeedDivider(channelAffiliateProgramsCell != null && channelAffiliateProgramsCell.getVisibility() == View.VISIBLE); + } } if (currentUser != null) { @@ -1173,6 +1201,21 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image updatePublicLinksCount(); + botAffiliateProgramCell = new TextCell(context); + botAffiliateProgramCell.setBackground(Theme.getSelectorDrawable(false)); + botAffiliateProgramCell.setTextAndValueAndIcon(applyNewSpan(getString(R.string.AffiliateProgramBot)), "", R.drawable.msg_shareout, true); + infoContainer.addView(botAffiliateProgramCell, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + botAffiliateProgramCell.setOnClickListener(v -> { + presentFragment(new AffiliateProgramFragment(userId)); + }); + botAffiliateProgramCell.setDrawLoading(userInfo == null, 45, false); + if (userInfo != null) { + botAffiliateProgramCell.setValue(userInfo.starref_program == null ? getString(R.string.AffiliateProgramBotOff) : String.format(Locale.US, "%.1f%%", userInfo.starref_program.commission_permille / 10.0f), false); + } + if (!getMessagesController().starrefProgramAllowed) { + botAffiliateProgramCell.setVisibility(View.GONE); + } + editIntroCell = new TextCell(context); editIntroCell.setBackground(Theme.getSelectorDrawable(false)); editIntroCell.setTextAndIcon(getString(R.string.BotEditIntro), R.drawable.msg_log, true); @@ -1284,7 +1327,8 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image loadingStr.setSpan(new LoadingSpan(starsBalanceCell.valueTextView, dp(30)), 0, loadingStr.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); starsBalanceCell.setTextAndValueAndIcon(getString(R.string.BotBalanceStars), loadingStr, R.drawable.menu_premium_main, false); } else { - starsBalanceCell.setTextAndValueAndIcon(getString(R.string.BotBalanceStars), c.getBotStarsBalance(userId)<=0?"":StarsIntroActivity.replaceStarsWithPlain("XTR" + LocaleController.formatNumber(c.getBotStarsBalance(userId), ' '), .85f), R.drawable.menu_premium_main, false); + + starsBalanceCell.setTextAndValueAndIcon(getString(R.string.BotBalanceStars), c.getBotStarsBalance(userId).amount <= 0?"":StarsIntroActivity.replaceStarsWithPlain(TextUtils.concat("XTR", formatStarsAmountShort(c.getBotStarsBalance(userId), .85f, ' ')), .85f), R.drawable.menu_premium_main, false); } starsBalanceCell.setVisibility(c.botHasStars(userId) ? View.VISIBLE : View.GONE); @@ -1352,6 +1396,16 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image return fragmentView; } + public static CharSequence applyNewSpan(String str) { + SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(str); + spannableStringBuilder.append(" d"); + FilterCreateActivity.NewSpan span = new FilterCreateActivity.NewSpan(false, 10); + span.setTypeface(AndroidUtilities.getTypeface("fonts/num.otf")); + span.setColor(Theme.getColor(Theme.key_premiumGradient1)); + spannableStringBuilder.setSpan(span, spannableStringBuilder.length() - 1, spannableStringBuilder.length(), 0); + return spannableStringBuilder; + } + private void updatePublicLinksCount() { if (publicLinkCell == null) { return; @@ -1475,7 +1529,7 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image if (starsBalanceCell != null) { BotStarsController c = BotStarsController.getInstance(currentAccount); starsBalanceCell.setVisibility(c.botHasStars(userId) ? View.VISIBLE : View.GONE); - starsBalanceCell.setValue(StarsIntroActivity.replaceStarsWithPlain("XTR" + LocaleController.formatNumber(c.getBotStarsBalance(userId), ' '), .85f), true); + starsBalanceCell.setValue(StarsIntroActivity.replaceStarsWithPlain(TextUtils.concat("XTR", formatStarsAmount(c.getBotStarsBalance(userId), .8f, ' ')), .85f), true); if (publicLinkCell != null) { publicLinkCell.setNeedDivider(c.botHasStars(userId) || c.botHasTON(userId)); } @@ -1504,6 +1558,16 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image } } } + } else if (id == NotificationCenter.userInfoDidLoad) { + Long uid = (Long) args[0]; + if (uid == userId) { + setInfo(getMessagesController().getUserFull(userId)); + } + } else if (id == NotificationCenter.channelConnectedBotsUpdate) { + Long did = (Long) args[0]; + if (did == -chatId) { + + } } } @@ -1851,6 +1915,12 @@ public class ChatEditActivity extends BaseFragment implements ImageUpdater.Image if (currentUser == null) { currentUser = userId == 0 ? null : getMessagesController().getUser(userId); } + if (botAffiliateProgramCell != null) { + botAffiliateProgramCell.setDrawLoading(userInfo == null, 45, true); + if (userInfo != null) { + botAffiliateProgramCell.setValue(userInfo.starref_program == null ? getString(R.string.AffiliateProgramBotOff) : String.format(Locale.US, "%.1f%%", userInfo.starref_program.commission_permille / 10.0f), false); + } + } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java index e3c3c8aab..be11b71dd 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ChatUsersActivity.java @@ -3401,13 +3401,13 @@ public class ChatUsersActivity extends BaseFragment implements NotificationCente actionCell.setColors(Theme.key_windowBackgroundWhiteBlueIcon, Theme.key_windowBackgroundWhiteBlueButton); boolean showDivider = addNew2Row != -1 || (!(loadingUsers && !firstLoaded) && membersHeaderRow == -1 && !participants.isEmpty()); if (isChannel) { - actionCell.setText(getString("AddSubscriber", R.string.AddSubscriber), null, R.drawable.msg_contact_add, showDivider); + actionCell.setText(getString(R.string.AddSubscriber), null, R.drawable.msg_contact_add, showDivider); } else { - actionCell.setText(getString("AddMember", R.string.AddMember), null, R.drawable.msg_contact_add, showDivider); + actionCell.setText(getString(R.string.AddMember), null, R.drawable.msg_contact_add, showDivider); } } } else if (position == recentActionsRow) { - actionCell.setText(getString("EventLog", R.string.EventLog), null, R.drawable.msg_log, antiSpamRow > recentActionsRow); + actionCell.setText(getString(R.string.EventLog), null, R.drawable.msg_log, antiSpamRow > recentActionsRow); } else if (position == addNew2Row) { actionCell.setColors(Theme.key_windowBackgroundWhiteBlueIcon, Theme.key_windowBackgroundWhiteBlueButton); boolean showDivider = !(loadingUsers && !firstLoaded) && membersHeaderRow == -1 && !participants.isEmpty(); 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 1c7744c29..9a5af4101 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AlertsCreator.java @@ -1805,10 +1805,10 @@ public class AlertsCreator { lastMessageIsJoined = true; } - if (user != null && user.bot) { + if (user != null && user.bot && user.id != UserObject.VERIFY) { cell[0] = new CheckBoxCell(context, 1, resourcesProvider); cell[0].setBackgroundDrawable(Theme.getSelectorDrawable(false)); - cell[0].setText(LocaleController.getString(R.string.BlockBot), "", false, false); + cell[0].setText(getString(R.string.BlockBot), "", false, false); cell[0].setPadding(LocaleController.isRTL ? dp(16) : dp(8), 0, LocaleController.isRTL ? dp(8) : dp(16), 0); cell[0].setChecked(deleteForAll[0] = true, false); frameLayout.addView(cell[0], LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 0, 0)); @@ -1822,14 +1822,14 @@ public class AlertsCreator { cell[0].setBackgroundDrawable(Theme.getSelectorDrawable(false)); if (deleteChatForAll) { if (ChatObject.isChannel(chat) && !chat.megagroup) { - cell[0].setText(LocaleController.getString(R.string.DeleteChannelForAll), "", false, false); + cell[0].setText(getString(R.string.DeleteChannelForAll), "", false, false); } else { - cell[0].setText(LocaleController.getString(R.string.DeleteGroupForAll), "", false, false); + cell[0].setText(getString(R.string.DeleteGroupForAll), "", false, false); } } else if (clear) { - cell[0].setText(LocaleController.formatString("ClearHistoryOptionAlso", R.string.ClearHistoryOptionAlso, UserObject.getFirstName(user)), "", false, false); + cell[0].setText(LocaleController.formatString(R.string.ClearHistoryOptionAlso, UserObject.getFirstName(user)), "", false, false); } else { - cell[0].setText(LocaleController.formatString("DeleteMessagesOptionAlso", R.string.DeleteMessagesOptionAlso, UserObject.getFirstName(user)), "", false, false); + cell[0].setText(LocaleController.formatString(R.string.DeleteMessagesOptionAlso, UserObject.getFirstName(user)), "", false, false); } cell[0].setPadding(LocaleController.isRTL ? dp(16) : dp(8), 0, LocaleController.isRTL ? dp(8) : dp(16), 0); frameLayout.addView(cell[0], LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 0, 0)); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedFileDrawable.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedFileDrawable.java index d41457a00..f62e11a78 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedFileDrawable.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/AnimatedFileDrawable.java @@ -667,8 +667,21 @@ public class AnimatedFileDrawable extends BitmapDrawable implements Animatable, } if (force && decodeSingleFrame) { singleFrameDecoded = false; +// if ((!PRERENDER_FRAME || nextRenderingBitmap2 != null) && nextRenderingBitmap != null) { +// renderingBitmap = nextRenderingBitmap; +// renderingBitmapTime = nextRenderingBitmapTime; +// for (int i = 0; i < backgroundShader.length; i++) { +// renderingShader[i] = nextRenderingShader[i]; +// nextRenderingShader[i] = nextRenderingShader2[i]; +// nextRenderingShader2[i] = null; +// } +// nextRenderingBitmap = nextRenderingBitmap2; +// nextRenderingBitmapTime = nextRenderingBitmapTime2; +// nextRenderingBitmap2 = null; +// nextRenderingBitmapTime2 = 0; +// } if (loadFrameTask == null) { - scheduleNextGetFrame(); + scheduleNextGetFrame(false, true); } else { forceDecodeAfterNextFrame = true; } @@ -676,28 +689,10 @@ public class AnimatedFileDrawable extends BitmapDrawable implements Animatable, } } -// public void seekToSync(long ms) { -// if (nativePtr != 0) { -// if (renderingBitmap == null) { -// if (!unusedBitmaps.isEmpty()) { -// renderingBitmap = unusedBitmaps.remove(0); -// } else { -// renderingBitmap = Bitmap.createBitmap((int) (metaData[0] * scaleFactor), (int) (metaData[1] * scaleFactor), Bitmap.Config.ARGB_8888); -// } -// } -// if (decodeQueue == null) { -// decodeQueue = new DispatchQueue("decodeQueue" + this); -// } -// decodeQueue.postRunnable(() -> { -// prepareToSeek(nativePtr); -// seekToMs(nativePtr, ms, false); -// getVideoFrame(nativePtr, renderingBitmap, metaData, renderingBitmap.getRowBytes(), false, startTime, endTime, true); -// AndroidUtilities.runOnUIThread(() -> { -// invalidateInternal(); -// }); -// }); -// } -// } + public void seekToSync(long ms) { + if (nativePtr == 0) return; + seekToMs(nativePtr, ms, metaData, true); + } public void recycle() { if (!secondParentViews.isEmpty()) { @@ -819,23 +814,32 @@ public class AnimatedFileDrawable extends BitmapDrawable implements Animatable, } private void scheduleNextGetFrame() { - if (loadFrameTask != null || ((!PRERENDER_FRAME || nextRenderingBitmap2 != null) && nextRenderingBitmap != null) || !canLoadFrames() || destroyWhenDone || !isRunning && (!decodeSingleFrame || decodeSingleFrame && singleFrameDecoded) || parents.size() == 0 && !ignoreNoParent || generatingCache) { + scheduleNextGetFrame(true, false); + } + private void scheduleNextGetFrame(boolean wait, boolean cancel) { + if (loadFrameTask != null && !cancel || ((!PRERENDER_FRAME || nextRenderingBitmap2 != null) && nextRenderingBitmap != null) || !canLoadFrames() || destroyWhenDone || !isRunning && (!decodeSingleFrame || decodeSingleFrame && singleFrameDecoded) || parents.size() == 0 && !ignoreNoParent || generatingCache) { return; } long ms = 0; - if (lastFrameDecodeTime != 0) { + if (wait && lastFrameDecodeTime != 0) { ms = Math.min(invalidateAfter, Math.max(0, invalidateAfter - (System.currentTimeMillis() - lastFrameDecodeTime))); } if (useSharedQueue) { if (limitFps) { DispatchQueuePoolBackground.execute(loadFrameTask = loadFrameRunnable); } else { + if (cancel && loadFrameTask != null) { + executor.remove(loadFrameTask); + } executor.schedule(loadFrameTask = loadFrameRunnable, ms, TimeUnit.MILLISECONDS); } } else { if (decodeQueue == null) { decodeQueue = new DispatchQueue("decodeQueue" + this); } + if (cancel && loadFrameTask != null) { + decodeQueue.cancelRunnable(loadFrameTask); + } decodeQueue.postRunnable(loadFrameTask = loadFrameRunnable, ms); } } @@ -1151,6 +1155,13 @@ public class AnimatedFileDrawable extends BitmapDrawable implements Animatable, return backgroundBitmap; } + public void skipNextFrame(boolean loop) { + if (nativePtr == 0) { + return; + } + getVideoFrame(nativePtr, null, metaData, 0, false, startTime, endTime, loop); + } + public void setLimitFps(boolean limitFps) { this.limitFps = limitFps; if (limitFps) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BlurringShader.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BlurringShader.java index 83cc66a0e..38957a940 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BlurringShader.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BlurringShader.java @@ -1,5 +1,7 @@ package org.telegram.ui.Components; +import static org.telegram.messenger.AndroidUtilities.dp; + import android.animation.ValueAnimator; import android.graphics.Bitmap; import android.graphics.BitmapShader; @@ -9,16 +11,19 @@ import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Matrix; import android.graphics.Paint; +import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.RenderEffect; import android.graphics.RenderNode; import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.opengl.GLES11Ext; import android.opengl.GLES20; +import android.os.Build; import android.text.TextUtils; import android.view.View; @@ -29,6 +34,7 @@ import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ImageReceiver; import org.telegram.messenger.R; import org.telegram.messenger.Utilities; +import org.telegram.ui.ActionBar.Theme; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -427,6 +433,37 @@ public class BlurringShader { private final Object textureLock = new Object(); + private int renderNodeBackgroundColor; + private View renderNodeView; + private Object renderNode; + private Object blurRenderNode; + public void setRenderNode(View view, Object renderNode) { + setRenderNode(view, renderNode, 0xFF000000); + } + public void setRenderNode(View view, Object renderNode, int bgColor) { + this.renderNodeView = view; + this.renderNode = renderNode; + this.renderNodeBackgroundColor = bgColor; + if (renderNode != null && Build.VERSION.SDK_INT >= 31) { + RenderNode parent = (RenderNode) renderNode; + RenderNode node = new RenderNode("blurRenderNode"); + node.setRenderEffect(RenderEffect.createBlurEffect(dp(35), dp(35), Shader.TileMode.CLAMP)); + node.setPosition(0, 0, parent.getWidth(), parent.getHeight()); + Canvas renderNodeCanvas = node.beginRecording(); + renderNodeCanvas.drawColor(bgColor); +// renderNodeCanvas.translate(dp(32), dp(32)); + renderNodeCanvas.drawRenderNode(parent); + node.endRecording(); + this.blurRenderNode = node; + } else { + this.blurRenderNode = null; + } + } + + public boolean hasRenderNode() { + return this.blurRenderNode != null; + } + public BlurManager(View parentView) { this.view = parentView; if (view.isAttachedToWindow()) { @@ -714,6 +751,7 @@ public class BlurringShader { public RenderNode renderNode; public final ColorMatrix colorMatrix; + public boolean xfer; private boolean animateBitmapChange; private boolean oldPaintSet; @@ -722,6 +760,7 @@ public class BlurringShader { public Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); private final int type; + private Integer bgColor; public StoryBlurDrawer(@Nullable BlurManager manager, @NonNull View view, int type) { this(manager, view, type, false); @@ -740,12 +779,14 @@ public class BlurringShader { paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); oldPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); AndroidUtilities.adjustSaturationColorMatrix(colorMatrix, +.3f); + xfer = true; } else if (type == BLUR_TYPE_CAPTION_XFER) { paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); oldPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); AndroidUtilities.adjustBrightnessColorMatrix(colorMatrix, +.4f); AndroidUtilities.adjustSaturationColorMatrix(colorMatrix, +.3f); // AndroidUtilities.multiplyBrightnessColorMatrix(colorMatrix, 1.4f); + xfer = true; } else if (type == BLUR_TYPE_CAPTION) { AndroidUtilities.adjustSaturationColorMatrix(colorMatrix, +.35f); AndroidUtilities.adjustBrightnessColorMatrix(colorMatrix, +.7f); @@ -753,6 +794,7 @@ public class BlurringShader { } else if (type == BLUR_TYPE_AUDIO_BACKGROUND) { AndroidUtilities.adjustSaturationColorMatrix(colorMatrix, +.5f); } else if (type == BLUR_TYPE_AUDIO_WAVEFORM_BACKGROUND) { + bgColor = 0xFF626262; AndroidUtilities.adjustSaturationColorMatrix(colorMatrix, +.6f); AndroidUtilities.adjustBrightnessColorMatrix(colorMatrix, +.3f); AndroidUtilities.multiplyBrightnessColorMatrix(colorMatrix, 1.2f); @@ -770,6 +812,7 @@ public class BlurringShader { oldPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); AndroidUtilities.adjustBrightnessColorMatrix(colorMatrix, +.4f); AndroidUtilities.adjustSaturationColorMatrix(colorMatrix, +.45f); + xfer = true; // AndroidUtilities.multiplyBrightnessColorMatrix(colorMatrix, 1.4f); } else if (type == BLUR_TYPE_ACTION_BACKGROUND) { colorMatrix.setSaturation(1.6f); @@ -800,6 +843,68 @@ public class BlurringShader { }); } + private int getBackgroundColor() { + if (bgColor != null) return bgColor; + return manager.renderNodeBackgroundColor; + } + + private final Path clipPath = new Path(); + private int clipPathWidth, clipPathHeight; + + public void drawRect(Canvas canvas) { + drawRect(canvas, 0, 0, 1.0f); + } + public void drawRect(Canvas canvas, float tx, float ty, float alpha) { + drawRect(canvas, tx, ty, alpha, true); + } + public void drawRect(Canvas canvas, float tx, float ty, float alpha, boolean clip) { + if (manager.hasRenderNode() && Build.VERSION.SDK_INT >= 31) { + if (!canvas.isHardwareAccelerated()) { + canvas.drawColor(getBackgroundColor()); + return; + } + final RenderNode node = (RenderNode) manager.blurRenderNode; + if (!node.hasDisplayList()) { + final RenderNode parentNode = (RenderNode) manager.renderNode; + node.setPosition(0, 0, parentNode.getWidth(), parentNode.getHeight()); + Canvas renderNodeCanvas = node.beginRecording(); + renderNodeCanvas.drawColor(getBackgroundColor()); +// renderNodeCanvas.translate(dp(32), dp(32)); + renderNodeCanvas.drawRenderNode(parentNode); + node.endRecording(); + } + if (!node.hasDisplayList()) { + canvas.drawColor(getBackgroundColor()); + } else { + canvas.drawColor(getBackgroundColor()); + if (setupMatrix(node.getWidth(), node.getHeight(), true)) { + if (node.hasDisplayList()) { + matrix.postTranslate(-tx, -ty); + paint.setAlpha((int) (0xFF * alpha)); + canvas.saveLayer(null, paint); + canvas.concat(matrix); + if (clip) { + if (clipPathWidth != node.getWidth() || clipPathHeight != node.getHeight()) { + clipPath.rewind(); + AndroidUtilities.rectTmp.set(0, 0, clipPathWidth = node.getWidth(), clipPathHeight = node.getHeight()); + clipPath.addRoundRect(AndroidUtilities.rectTmp, dp(12), dp(12), Path.Direction.CW); + } + canvas.clipPath(clipPath); + } +// canvas.translate(-dp(32), -dp(32)); + canvas.drawRenderNode(node); + canvas.restore(); + } + } + } + } else { + Paint paint = getPaint(alpha, tx, ty); + if (paint != null) { + canvas.drawPaint(paint); + } + } + } + private boolean customOffset; private float customOffsetX, customOffsetY; public StoryBlurDrawer setOffset(float ox, float oy) { @@ -889,7 +994,7 @@ public class BlurringShader { paint.setShader(bitmapShader); } - if (!setupMatrix(bitmap.getWidth(), bitmap.getHeight())) { + if (!setupMatrix(bitmap.getWidth(), bitmap.getHeight(), false)) { return null; } matrix.postTranslate(-tx, -ty); @@ -938,11 +1043,13 @@ public class BlurringShader { paint.setShader(bitmapShader = null); } - private boolean setupMatrix(int bitmapWidth, int bitmapHeight) { + private final int[] loc1 = new int[2], loc2 = new int[2]; + private boolean setupMatrix(int bitmapWidth, int bitmapHeight, boolean renderNode) { matrix.reset(); + final View parentView = manager != null ? (renderNode ? manager.renderNodeView : manager.view) : null; if (customOffset) { matrix.postTranslate(-customOffsetX, -customOffsetY); - } else { + } else if (manager != null) { View view = this.view; do { matrix.preScale(1f / view.getScaleX(), 1f / view.getScaleY(), view.getPivotX(), view.getPivotY()); @@ -953,25 +1060,33 @@ public class BlurringShader { } else { break; } - } while (view != null && manager != null && !manager.parents.contains(view)); + } while (view != null && !manager.parents.contains(view)); - if (manager != null && manager.view != view) { + if (parentView != view) { int index = manager.parents.indexOf(view) + 1; + if (index == 0) { + View child = manager.parents.get(index); + if (child != null) { + view.getLocationOnScreen(loc1); + child.getLocationOnScreen(loc2); + matrix.preTranslate(loc2[0] - loc1[0], loc2[1] - loc1[1]); + } + } while (index >= 0 && index < manager.parents.size()) { View child = manager.parents.get(index); if (child == null) { continue; } - matrix.postTranslate(child.getX(), child.getY()); - matrix.postScale(1f / child.getScaleX(), 1f / child.getScaleY(), child.getPivotX(), child.getPivotY()); - matrix.postRotate(child.getRotation(), child.getPivotX(), child.getPivotY()); + matrix.preScale(child.getScaleX(), child.getScaleY(), child.getPivotX(), child.getPivotY()); + matrix.preRotate(child.getRotation(), child.getPivotX(), child.getPivotY()); + matrix.preTranslate(child.getX(), child.getY()); index++; } } } - if (manager != null && manager.view != null) { - matrix.preScale((float) manager.view.getWidth() / bitmapWidth, (float) manager.view.getHeight() / bitmapHeight); + if (parentView != null) { + matrix.preScale((float) parentView.getWidth() / bitmapWidth, (float) parentView.getHeight() / bitmapHeight); } return true; } @@ -982,6 +1097,7 @@ public class BlurringShader { float alpha = 1f; private final Paint dimPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Rect rect = new Rect(); + private final Path clipPath = new Path(); @Nullable private Paint getPaint() { @@ -1012,12 +1128,29 @@ public class BlurringShader { public void draw(@NonNull Canvas canvas) { Paint paint = getPaint(); Rect bounds = getBounds(); - if (paint != null) { + if (paint != null || manager != null && manager.hasRenderNode()) { if (base != null) { canvas.saveLayerAlpha(bounds.left, bounds.top, bounds.right, bounds.bottom, 0xFF, Canvas.ALL_SAVE_FLAG); base.setBounds(bounds); base.draw(canvas); - canvas.drawRect(bounds, paint); + if (manager != null && manager.hasRenderNode()) { + canvas.save(); + getPadding(rect); + AndroidUtilities.rectTmp.set( + bounds.left + rect.left, + bounds.top + rect.top, + bounds.right - rect.right, + bounds.bottom - rect.bottom + ); + clipPath.rewind(); + clipPath.addRoundRect(AndroidUtilities.rectTmp, r, r, Path.Direction.CW); + canvas.clipPath(clipPath); +// canvas.translate(Math.max(0, -customOffsetX - offsetX / 2.0f), Math.max(0, -customOffsetY - offsetY / 2.0f)); + drawRect(canvas, 0, 0, 1, false); + canvas.restore(); + } else { + canvas.drawRect(bounds, paint); + } canvas.restore(); getPadding(rect); AndroidUtilities.rectTmp.set( @@ -1031,9 +1164,27 @@ public class BlurringShader { } else { if (r > 0) { AndroidUtilities.rectTmp.set(bounds); - canvas.drawRoundRect(AndroidUtilities.rectTmp, r, r, paint); + if (manager != null && manager.hasRenderNode()) { + canvas.save(); + clipPath.rewind(); + clipPath.addRoundRect(AndroidUtilities.rectTmp, r, r, Path.Direction.CW); + canvas.clipPath(clipPath); +// canvas.translate(-customOffsetX - offsetX, -customOffsetY - offsetY); + drawRect(canvas, 0, 0, 1, false); + canvas.restore(); + } else { + canvas.drawRoundRect(AndroidUtilities.rectTmp, r, r, paint); + } } else { - canvas.drawRect(bounds, paint); + if (manager != null && manager.hasRenderNode()) { + canvas.save(); + canvas.clipRect(bounds); +// canvas.translate(-customOffsetX - offsetX, -customOffsetY - offsetY); + drawRect(canvas, 0, 0, 1, false); + canvas.restore(); + } else { + canvas.drawRect(bounds, paint); + } } dimPaint.setColor(0x66000000); if (r > 0) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Bulletin.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Bulletin.java index 1176139e2..35bb4ee66 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Bulletin.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Bulletin.java @@ -133,6 +133,15 @@ public class Bulletin { return null; } + public Bulletin setImageScale(float scale) { + if (layout instanceof Bulletin.TwoLineLottieLayout) { + View imageView = ((TwoLineLottieLayout) layout).imageView; + imageView.setScaleX(scale); + imageView.setScaleY(scale); + } + return this; + } + public static void hide(@NonNull FrameLayout containerLayout) { hide(containerLayout, true); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/BulletinFactory.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/BulletinFactory.java index 7b74cf561..63b61dcd1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/BulletinFactory.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/BulletinFactory.java @@ -33,6 +33,8 @@ import androidx.annotation.NonNull; import androidx.core.content.FileProvider; import androidx.core.graphics.ColorUtils; +import com.google.common.collect.Lists; + import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.BuildVars; @@ -62,6 +64,7 @@ import org.telegram.ui.Stories.recorder.HintView2; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; public final class BulletinFactory { @@ -471,6 +474,10 @@ public final class BulletinFactory { return createUsersBulletin(users, text, null, null); } + public Bulletin createUsersBulletin(TLObject user, CharSequence text, CharSequence subtitle) { + return createUsersBulletin(Arrays.asList(user), text, subtitle, null); + } + public Bulletin createUsersBulletin(List users, CharSequence text, CharSequence subtitle) { return createUsersBulletin(users, text, subtitle, null); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/CaptionPhotoViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/CaptionPhotoViewer.java index 7daa8c5d8..6a627e43d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/CaptionPhotoViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/CaptionPhotoViewer.java @@ -1,22 +1,25 @@ package org.telegram.ui.Components; import static org.telegram.messenger.AndroidUtilities.dp; +import static org.telegram.messenger.AndroidUtilities.dpf2; +import static org.telegram.messenger.AndroidUtilities.lerp; +import static org.telegram.messenger.LocaleController.getString; import static org.telegram.ui.ActionBar.Theme.RIPPLE_MASK_CIRCLE_20DP; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.Path; +import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.RectF; +import android.graphics.drawable.Drawable; import android.view.Gravity; +import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; -import com.google.android.exoplayer2.util.Util; - import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MessagesController; @@ -46,36 +49,60 @@ public class CaptionPhotoViewer extends CaptionContainerView { private final HintView2 hint; private final Runnable applyCaption; + private final RectF moveButtonBounds = new RectF(); + private Drawable moveButtonIcon; + private final AnimatedTextView.AnimatedTextDrawable moveButtonText = new AnimatedTextView.AnimatedTextDrawable(); + private final ButtonBounce moveButtonBounce = new ButtonBounce(this); + @Override protected int getEditTextStyle() { return EditTextEmoji.STYLE_PHOTOVIEWER; } - public CaptionPhotoViewer(Context context, FrameLayout rootView, SizeNotifierFrameLayout sizeNotifierFrameLayout, FrameLayout containerView, Theme.ResourcesProvider resourcesProvider, BlurringShader.BlurManager blurManager, Runnable applyCaption) { + public CaptionPhotoViewer( + Context context, + FrameLayout rootView, + SizeNotifierFrameLayout sizeNotifierFrameLayout, + FrameLayout containerView, + Theme.ResourcesProvider resourcesProvider, + BlurringShader.BlurManager blurManager, + Runnable applyCaption + ) { super(context, rootView, sizeNotifierFrameLayout, containerView, resourcesProvider, blurManager); this.applyCaption = applyCaption; + moveButtonText.setTextSize(dp(14)); + moveButtonText.setOverrideFullWidth(AndroidUtilities.displaySize.x); + moveButtonText.setTextColor(0xFFFFFFFF); + if (isAtTop()) { + moveButtonText.setText(getString(R.string.MoveCaptionDown)); + moveButtonIcon = context.getResources().getDrawable(R.drawable.menu_link_below); + } else { + moveButtonText.setText(getString(R.string.MoveCaptionUp)); + moveButtonIcon = context.getResources().getDrawable(R.drawable.menu_link_above); + } + addPhotoButton = new ImageView(context); addPhotoButton.setImageResource(R.drawable.filled_add_photo); addPhotoButton.setScaleType(ImageView.ScaleType.CENTER); addPhotoButton.setColorFilter(new PorterDuffColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN)); addPhotoButton.setBackground(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR, RIPPLE_MASK_CIRCLE_20DP, dp(18))); setAddPhotoVisible(false, false); - addView(addPhotoButton, LayoutHelper.createFrame(44, 44, Gravity.LEFT | Gravity.BOTTOM, 14, 0, 0, 10)); + addView(addPhotoButton, LayoutHelper.createFrame(44, 44, Gravity.LEFT | (isAtTop() ? Gravity.TOP : Gravity.BOTTOM), 14, isAtTop() ? 10 : 0, 0, isAtTop() ? 0 : 10)); timerButton = new ImageView(context); timerButton.setImageDrawable(timerDrawable = new PeriodDrawable()); timerButton.setBackground(Theme.createSelectorDrawable(Theme.ACTION_BAR_WHITE_SELECTOR_COLOR, RIPPLE_MASK_CIRCLE_20DP, dp(18))); timerButton.setScaleType(ImageView.ScaleType.CENTER); setTimerVisible(false, false); - addView(timerButton, LayoutHelper.createFrame(44, 44, Gravity.RIGHT | Gravity.BOTTOM, 0, 0, 11, 10)); + addView(timerButton, LayoutHelper.createFrame(44, 44, Gravity.RIGHT | (isAtTop() ? Gravity.TOP : Gravity.BOTTOM), 0, isAtTop() ? 10 : 0, 11, isAtTop() ? 0 : 10)); - hint = new HintView2(context, HintView2.DIRECTION_BOTTOM); + hint = new HintView2(context, isAtTop() ? HintView2.DIRECTION_TOP : HintView2.DIRECTION_BOTTOM); hint.setRounding(12); - hint.setPadding(dp(12), 0, dp(12), dp(8)); + hint.setPadding(dp(12), dp(isAtTop() ? 8 : 0), dp(12), dp(isAtTop() ? 0 : 8)); hint.setJoint(1, -21); hint.setMultilineText(true); - addView(hint, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 80, Gravity.RIGHT | Gravity.BOTTOM)); + addView(hint, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 80, Gravity.RIGHT | (isAtTop() ? Gravity.TOP : Gravity.BOTTOM))); timerButton.setOnClickListener(e -> { if (timerPopup != null && timerPopup.isShown()) { @@ -87,14 +114,14 @@ public class CaptionPhotoViewer extends CaptionContainerView { timerPopup = ItemOptions.makeOptions(rootView, new DarkThemeResourceProvider(), timerButton); timerPopup.setDimAlpha(0); - timerPopup.addText(LocaleController.getString(R.string.TimerPeriodHint), 13, dp(200)); + timerPopup.addText(getString(R.string.TimerPeriodHint), 13, dp(200)); timerPopup.addGap(); for (int value : values) { String text; if (value == 0) { - text = LocaleController.getString(R.string.TimerPeriodDoNotDelete); + text = getString(R.string.TimerPeriodDoNotDelete); } else if (value == SHOW_ONCE) { - text = LocaleController.getString(R.string.TimerPeriodOnce); + text = getString(R.string.TimerPeriodOnce); } else { text = LocaleController.formatPluralString("Seconds", value); } @@ -107,6 +134,98 @@ public class CaptionPhotoViewer extends CaptionContainerView { }); } +// private final AnimatedFloat aboveAnimated = new AnimatedFloat(this, 0, 350, CubicBezierInterpolator.EASE_OUT_QUINT); +// +// @Override +// protected float forceRound() { +// return aboveAnimated.set(isAtTop()); +// } + + private final AnimatedFloat moveButtonAnimated = new AnimatedFloat(this, 0, 350, CubicBezierInterpolator.EASE_OUT_QUINT); + private final AnimatedFloat moveButtonExpandedAnimated = new AnimatedFloat(this, 0, 350, CubicBezierInterpolator.EASE_OUT_QUINT); + private boolean moveButtonVisible; + private boolean moveButtonExpanded; + + public void expandMoveButton() { + AndroidUtilities.cancelRunOnUIThread(collapseMoveButton); + moveButtonExpanded = MessagesController.getInstance(currentAccount).shouldShowMoveCaptionHint(); + if (moveButtonExpanded) { + MessagesController.getInstance(currentAccount).incrementMoveCaptionHint(); + invalidate(); + AndroidUtilities.runOnUIThread(collapseMoveButton, 5000); + } + } + + private final Runnable collapseMoveButton = () -> { + if (moveButtonExpanded) { + moveButtonExpanded = false; + invalidate(); + } + }; + + protected void openedKeyboard() { + expandMoveButton(); + } + + @Override + public void updateKeyboard(int keyboardHeight) { + final boolean wasOpen = super.toKeyboardShow; + super.updateKeyboard(keyboardHeight); + if (!wasOpen && keyboardNotifier.keyboardVisible()) { + openedKeyboard(); + } + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + + final float moveButtonAlpha = moveButtonAnimated.set(moveButtonVisible, !showMoveButton()); + final float moveButtonExpanded = moveButtonExpandedAnimated.set(this.moveButtonExpanded); + if (moveButtonAlpha > 0.0f) { + float s = moveButtonBounce.getScale(.03f); + if (isAtTop()) { + moveButtonBounds.set(dp(10), bounds.bottom + dp(10), dp(10 + 34) + (moveButtonText.getCurrentWidth() + dp(11)) * moveButtonExpanded, bounds.bottom + dp(10 + 32)); + } else { + moveButtonBounds.set(dp(10), bounds.top - dp(32 + 10), dp(10 + 34) + (moveButtonText.getCurrentWidth() + dp(11)) * moveButtonExpanded, bounds.top - dp(10)); + } + if (moveButtonAlpha < 1) { + canvas.saveLayerAlpha(moveButtonBounds, (int) (0xFF * moveButtonAlpha), Canvas.ALL_SAVE_FLAG); + } else { + canvas.save(); + } + canvas.scale(s, s, moveButtonBounds.centerX(), moveButtonBounds.centerY()); + canvas.clipRect(moveButtonBounds); + float r = dpf2(8.33f); + if (customBlur()) { + drawBlur(backgroundBlur, canvas, moveButtonBounds, r, false, 0, 0, true, 1.0f); + backgroundPaint.setAlpha((int) (lerp(0, 0x40, moveButtonAlpha))); + canvas.drawRoundRect(moveButtonBounds, r, r, backgroundPaint); + } else { + Paint[] blurPaints = backgroundBlur.getPaints(moveButtonAlpha, 0, 0); + if (blurPaints == null || blurPaints[1] == null) { + backgroundPaint.setAlpha(lerp(0, 0x80, moveButtonAlpha)); + canvas.drawRoundRect(moveButtonBounds, r, r, backgroundPaint); + } else { + if (blurPaints[0] != null) { + canvas.drawRoundRect(moveButtonBounds, r, r, blurPaints[0]); + } + if (blurPaints[1] != null) { + canvas.drawRoundRect(moveButtonBounds, r, r, blurPaints[1]); + } + backgroundPaint.setAlpha(lerp(0, 0x33, moveButtonAlpha)); + canvas.drawRoundRect(moveButtonBounds, r, r, backgroundPaint); + } + } + moveButtonIcon.setBounds((int) (moveButtonBounds.left + dp(9)), (int) (moveButtonBounds.centerY() - dp(9)), (int) (moveButtonBounds.left + dp(9 + 18)), (int) (moveButtonBounds.centerY() + dp(9))); + moveButtonIcon.draw(canvas); + moveButtonText.setBounds(moveButtonBounds.left + dp(34), moveButtonBounds.top, moveButtonBounds.right, moveButtonBounds.bottom); + moveButtonText.setAlpha((int) (0xFF * moveButtonExpanded)); + moveButtonText.draw(canvas); + canvas.restore(); + } + } + public void setOnAddPhotoClick(View.OnClickListener listener) { addPhotoButton.setOnClickListener(listener); } @@ -193,14 +312,14 @@ public class CaptionPhotoViewer extends CaptionContainerView { } CharSequence text; if (value == 0) { - text = LocaleController.getString(isVideo ? R.string.TimerPeriodVideoKeep : R.string.TimerPeriodPhotoKeep); + text = getString(isVideo ? R.string.TimerPeriodVideoKeep : R.string.TimerPeriodPhotoKeep); hint.setMaxWidthPx(getMeasuredWidth()); hint.setMultilineText(false); hint.setInnerPadding(13, 4, 10, 4); hint.setIconMargin(0); hint.setIconTranslate(0, -dp(1)); } else if (value == SHOW_ONCE) { - text = LocaleController.getString(isVideo ? R.string.TimerPeriodVideoSetOnce : R.string.TimerPeriodPhotoSetOnce); + text = getString(isVideo ? R.string.TimerPeriodVideoSetOnce : R.string.TimerPeriodPhotoSetOnce); hint.setMaxWidthPx(getMeasuredWidth()); hint.setMultilineText(false); hint.setInnerPadding(13, 4, 10, 4); @@ -216,18 +335,22 @@ public class CaptionPhotoViewer extends CaptionContainerView { } else { return; } - hint.setTranslationY(-Math.min(dp(34), getEditTextHeight()) - dp(14)); + hint.setTranslationY((-Math.min(dp(34), getEditTextHeight()) - dp(14)) * (isAtTop() ? -1.0f : 1.0f)); hint.setText(text); final int iconResId = value > 0 ? R.raw.fire_on : R.raw.fire_off; RLottieDrawable icon = new RLottieDrawable(iconResId, "" + iconResId, dp(34), dp(34)); icon.start(); hint.setIcon(icon); hint.show(); + + moveButtonExpanded = false; + AndroidUtilities.cancelRunOnUIThread(collapseMoveButton); + invalidate(); } @Override protected void onEditHeightChange(int height) { - hint.setTranslationY(-Math.min(dp(34), height) - dp(10)); + hint.setTranslationY((-Math.min(dp(34), height) - dp(10)) * (isAtTop() ? -1.0f : 1.0f)); } @Override @@ -297,4 +420,47 @@ public class CaptionPhotoViewer extends CaptionContainerView { protected void setupMentionContainer() { } + + protected boolean showMoveButton() { + return false; + } + + public void setShowMoveButtonVisible(boolean visible, boolean animated) { + if (moveButtonVisible == visible && animated) return; + moveButtonVisible = visible; + if (!animated) { + moveButtonAnimated.set(visible, true); + } + invalidate(); + } + + protected void onMoveButtonClick() { + + } + + @Override + public int getEditTextHeight() { + return super.getEditTextHeight(); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + moveButtonBounce.setPressed(moveButtonBounds.contains(event.getX(), event.getY())); + } else if (event.getAction() == MotionEvent.ACTION_MOVE) { + if (moveButtonBounce.isPressed() && !moveButtonBounds.contains(event.getX(), event.getY())) { + moveButtonBounce.setPressed(false); + } + } else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) { + if (moveButtonBounce.isPressed()) { + if (event.getAction() == MotionEvent.ACTION_UP) { + onMoveButtonClick(); + moveButtonText.setText(getString(isAtTop() ? R.string.MoveCaptionDown : R.string.MoveCaptionUp), true); + } + moveButtonBounce.setPressed(false); + return true; + } + } + return moveButtonBounce.isPressed() || super.dispatchTouchEvent(event); + } } 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 453b34dd2..554f92cc1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlert.java @@ -8,6 +8,7 @@ package org.telegram.ui.Components; +import static org.telegram.messenger.AndroidUtilities.dp; import static org.telegram.messenger.LocaleController.formatPluralString; import static org.telegram.messenger.LocaleController.getString; @@ -28,6 +29,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; +import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; @@ -115,6 +117,7 @@ import org.telegram.ui.Business.ChatAttachAlertQuickRepliesLayout; import org.telegram.ui.Business.QuickRepliesController; import org.telegram.ui.ChatActivity; import org.telegram.ui.DialogsActivity; +import org.telegram.ui.GradientClip; import org.telegram.ui.LaunchActivity; import org.telegram.ui.MessageSendPreview; import org.telegram.ui.PassportActivity; @@ -145,6 +148,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N public ChatActivity.ThemeDelegate parentThemeDelegate; private final NumberTextView captionLimitView; + private final NumberTextView topCaptionLimitView; public boolean forUser; public boolean isPhotoPicker; public boolean isStickerMode; @@ -184,7 +188,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } public float getClipLayoutBottom() { - float alphaOffset = (frameLayout2.getMeasuredHeight() - AndroidUtilities.dp(84)) * (1f - frameLayout2.getAlpha()); + float alphaOffset = (frameLayout2.getMeasuredHeight() - dp(84)) * (1f - frameLayout2.getAlpha()); return frameLayout2.getMeasuredHeight() - alphaOffset; } @@ -380,7 +384,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N float value = (float) animation.getAnimatedValue(); buttonsRecyclerView.setAlpha(1f - value); botMainButtonTextView.setAlpha(value); - botMainButtonOffsetY = value * AndroidUtilities.dp(36); + botMainButtonOffsetY = value * dp(36); shadow.setTranslationY(botMainButtonOffsetY); buttonsRecyclerView.setTranslationY(botMainButtonOffsetY); }); @@ -391,7 +395,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N botMainButtonTextView.setAlpha(0f); botMainButtonTextView.setVisibility(View.VISIBLE); - int offsetY = AndroidUtilities.dp(36); + int offsetY = dp(36); for (int i = 0; i < botAttachLayouts.size(); i++) { botAttachLayouts.valueAt(i).setMeasureOffsetY(offsetY); } @@ -409,7 +413,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N buttonsRecyclerView.setVisibility(View.GONE); } - int offsetY = isVisible ? AndroidUtilities.dp(36) : 0; + int offsetY = isVisible ? dp(36) : 0; for (int i = 0; i < botAttachLayouts.size(); i++) { botAttachLayouts.valueAt(i).setMeasureOffsetY(offsetY); } @@ -587,7 +591,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N if (nextAttachLayout == pollLayout || currentAttachLayout == pollLayout) { updateSelectedPosition(nextAttachLayout == pollLayout ? 1 : 0); } - nextAttachLayout.setTranslationY(AndroidUtilities.dp(78) * value); + nextAttachLayout.setTranslationY(dp(78) * value); currentAttachLayout.onHideShowProgress(1.0f - Math.min(1.0f, value / 0.7f)); currentAttachLayout.onContainerTranslationUpdated(currentPanTranslationY); } @@ -744,7 +748,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } public int getButtonsHideOffset() { - return AndroidUtilities.dp(needsActionBar() != 0 ? 12 : 17); + return dp(needsActionBar() != 0 ? 12 : 17); } public int getListTopPadding() { @@ -811,8 +815,11 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N private AttachAlertLayout currentAttachLayout; private AttachAlertLayout nextAttachLayout; + private FrameLayout captionContainer; private FrameLayout frameLayout2; public EditTextEmoji commentTextView; + public FrameLayout moveCaptionButton; + public ImageView moveCaptionButtonIcon; private int[] commentTextViewLocation = new int[2]; private FrameLayout writeButtonContainer; private ChatActivityEnterView.SendButton writeButton; @@ -823,6 +830,10 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); private AnimatorSet commentsAnimator; + public FrameLayout topCommentContainer; + public EditTextEmoji topCommentTextView; + public ImageView topCommentMoveButton; + protected int avatarPicker; protected boolean avatarSearch; protected boolean typeButtonsAvailable; @@ -894,7 +905,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N private float chatActivityEnterViewAnimateFromTop; private ValueAnimator topBackgroundAnimator; - private int attachItemSize = AndroidUtilities.dp(85); + private int attachItemSize = dp(85); private DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator(); @@ -946,7 +957,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N textView.setEllipsize(TextUtils.TruncateAt.END); textView.setTextColor(getThemedColor(Theme.key_dialogTextGray2)); textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); - textView.setLineSpacing(-AndroidUtilities.dp(2), 1.0f); + textView.setLineSpacing(-dp(2), 1.0f); textView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.LEFT | Gravity.TOP, 0, 62, 0, 0)); } @@ -1004,7 +1015,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(MeasureSpec.makeMeasureSpec(attachItemSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(84), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(attachItemSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(dp(84), MeasureSpec.EXACTLY)); } public void setTextAndIcon(int id, CharSequence text, RLottieDrawable drawable, int background, int textColor) { @@ -1028,20 +1039,20 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N @Override protected void onDraw(Canvas canvas) { float scale = imageView.getScaleX() + 0.06f * checkedState; - float radius = AndroidUtilities.dp(23) * scale; + float radius = dp(23) * scale; float cx = imageView.getLeft() + imageView.getMeasuredWidth() / 2f; float cy = imageView.getTop() + imageView.getMeasuredWidth() / 2f; attachButtonPaint.setColor(getThemedColor(backgroundKey)); attachButtonPaint.setStyle(Paint.Style.STROKE); - attachButtonPaint.setStrokeWidth(AndroidUtilities.dp(3) * scale); + attachButtonPaint.setStrokeWidth(dp(3) * scale); attachButtonPaint.setAlpha(Math.round(255f * checkedState)); canvas.drawCircle(cx, cy, radius - 0.5f * attachButtonPaint.getStrokeWidth(), attachButtonPaint); attachButtonPaint.setAlpha(255); attachButtonPaint.setStyle(Paint.Style.FILL); - canvas.drawCircle(cx, cy, radius - AndroidUtilities.dp(5) * checkedState, attachButtonPaint); + canvas.drawCircle(cx, cy, radius - dp(5) * checkedState, attachButtonPaint); } @Override @@ -1092,12 +1103,12 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N AttachBotButton.this.invalidate(); } }; - imageView.setRoundRadius(AndroidUtilities.dp(25)); + imageView.setRoundRadius(dp(25)); addView(imageView, LayoutHelper.createFrame(46, 46, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 9, 0, 0)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { selector = new View(context); - selector.setBackground(Theme.createSelectorDrawable(getThemedColor(Theme.key_dialogButtonSelector), 1, AndroidUtilities.dp(23))); + selector.setBackground(Theme.createSelectorDrawable(getThemedColor(Theme.key_dialogButtonSelector), 1, dp(23))); addView(selector, LayoutHelper.createFrame(46, 46, Gravity.TOP | Gravity.CENTER_HORIZONTAL, 0, 9, 0, 0)); } @@ -1122,7 +1133,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(MeasureSpec.makeMeasureSpec(attachItemSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(100), MeasureSpec.EXACTLY)); + super.onMeasure(MeasureSpec.makeMeasureSpec(attachItemSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(dp(100), MeasureSpec.EXACTLY)); } public void setCheckedState(float state) { @@ -1135,9 +1146,9 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N private void updateMargins() { MarginLayoutParams params = (MarginLayoutParams) nameTextView.getLayoutParams(); - params.topMargin = AndroidUtilities.dp(attachMenuBot != null ? 62 : 60); + params.topMargin = dp(attachMenuBot != null ? 62 : 60); params = (MarginLayoutParams) imageView.getLayoutParams(); - params.topMargin = AndroidUtilities.dp(attachMenuBot != null ? 11 : 9); + params.topMargin = dp(attachMenuBot != null ? 11 : 9); } @Override @@ -1145,20 +1156,20 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N if (attachMenuBot != null) { float imageScale = imageView.getScaleX(); float scale = imageScale + 0.06f * checkedState; - float radius = AndroidUtilities.dp(23) * scale; + float radius = dp(23) * scale; float cx = imageView.getLeft() + imageView.getMeasuredWidth() / 2f; float cy = imageView.getTop() + imageView.getMeasuredWidth() / 2f; attachButtonPaint.setColor(iconBackgroundColor); attachButtonPaint.setStyle(Paint.Style.STROKE); - attachButtonPaint.setStrokeWidth(AndroidUtilities.dp(3) * scale); + attachButtonPaint.setStrokeWidth(dp(3) * scale); attachButtonPaint.setAlpha(Math.round(255f * checkedState)); canvas.drawCircle(cx, cy, radius - 0.5f * attachButtonPaint.getStrokeWidth(), attachButtonPaint); attachButtonPaint.setAlpha(255); attachButtonPaint.setStyle(Paint.Style.FILL); - canvas.drawCircle(cx, cy, radius - AndroidUtilities.dp(5) * checkedState, attachButtonPaint); + canvas.drawCircle(cx, cy, radius - dp(5) * checkedState, attachButtonPaint); } } @@ -1268,7 +1279,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N imageView.setImage(ImageLocation.getForDocument(iconDoc), String.valueOf(bot.bot_id), animated ? "tgs" : "svg", DocumentObject.getSvgThumb(iconDoc, Theme.key_windowBackgroundGray, 1f), bot); } - imageView.setSize(AndroidUtilities.dp(28), AndroidUtilities.dp(28)); + imageView.setSize(dp(28), dp(28)); imageView.setColorFilter(new PorterDuffColorFilter(getThemedColor(Theme.key_chat_attachIcon), PorterDuff.Mode.SRC_IN)); attachMenuBot = bot; selector.setVisibility(GONE); @@ -1312,7 +1323,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N private Bulletin.Delegate bulletinDelegate = new Bulletin.Delegate() { @Override public int getBottomOffset(int tag) { - return getHeight() - frameLayout2.getTop() + AndroidUtilities.dp(52); + return getHeight() - frameLayout2.getTop() + dp(52); } }; private int lastNotifyWidth; @@ -1357,7 +1368,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N if (currentAttachLayout instanceof ChatAttachAlertBotWebViewLayout) { if (!botButtonWasVisible) { - int offsetY = keyboardVisible ? AndroidUtilities.dp(84) : 0; + int offsetY = keyboardVisible ? dp(84) : 0; for (int i = 0; i < botAttachLayouts.size(); i++) { botAttachLayouts.valueAt(i).setMeasureOffsetY(offsetY); } @@ -1377,7 +1388,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N searchItem.setTranslationY(currentPanTranslationY); } doneItem.setTranslationY(currentPanTranslationY); - actionBarShadow.setTranslationY(currentPanTranslationY); + actionBarShadow.setTranslationY(currentPanTranslationY + topCommentContainer.getMeasuredHeight() * topCommentContainer.getAlpha()); updateSelectedPosition(0); setCurrentPanTranslationY(currentPanTranslationY); @@ -1396,7 +1407,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N if (isDismissed() || !openTransitionFinished) { return false; } - return currentAttachLayout != pollLayout && !commentTextView.isPopupVisible() || currentAttachLayout == pollLayout && !pollLayout.isPopupVisible(); + return currentAttachLayout != pollLayout && !getCommentView().isPopupVisible() || currentAttachLayout == pollLayout && !pollLayout.isPopupVisible(); } }; @@ -1437,11 +1448,11 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N int availableWidth = MeasureSpec.getSize(widthMeasureSpec) - backgroundPaddingLeft * 2; if (AndroidUtilities.isTablet()) { - selectedMenuItem.setAdditionalYOffset(-AndroidUtilities.dp(3)); + selectedMenuItem.setAdditionalYOffset(-dp(3)); } else if (AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y) { selectedMenuItem.setAdditionalYOffset(0); } else { - selectedMenuItem.setAdditionalYOffset(-AndroidUtilities.dp(3)); + selectedMenuItem.setAdditionalYOffset(-dp(3)); } LayoutParams layoutParams = (LayoutParams) actionBarShadow.getLayoutParams(); @@ -1473,23 +1484,30 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N commentTextView.hideEmojiView(); ignoreLayout = false; } + if (!topCommentTextView.isWaitingForKeyboardOpen() && keyboardSize <= AndroidUtilities.dp(20) && !topCommentTextView.isPopupShowing() && !topCommentTextView.isAnimatePopupClosing()) { + ignoreLayout = true; + topCommentTextView.hideEmojiView(); + ignoreLayout = false; + } - if (pollLayout != null && keyboardSize <= AndroidUtilities.dp(20) && !pollLayout.isWaitingForKeyboardOpen() && !pollLayout.isPopupShowing() && !pollLayout.isAnimatePopupClosing() && !pollLayout.isEmojiSearchOpened) { + if (pollLayout != null && keyboardSize <= dp(20) && !pollLayout.isWaitingForKeyboardOpen() && !pollLayout.isPopupShowing() && !pollLayout.isAnimatePopupClosing() && !pollLayout.isEmojiSearchOpened) { ignoreLayout = true; pollLayout.hideEmojiView(); ignoreLayout = false; } - if (keyboardSize <= AndroidUtilities.dp(20)) { + if (keyboardSize <= dp(20)) { int paddingBottom; if (keyboardVisible) { paddingBottom = 0; if (currentAttachLayout == pollLayout && pollLayout.emojiView != null && pollLayout.isEmojiSearchOpened) { - paddingBottom += AndroidUtilities.dp(120); + paddingBottom += dp(120); } } else { if (currentAttachLayout == pollLayout && pollLayout.emojiView != null) { paddingBottom = pollLayout.getEmojiPadding(); + } else if (captionAbove) { + paddingBottom = topCommentTextView.getEmojiPadding(); } else { paddingBottom = commentTextView.getEmojiPadding(); } @@ -1512,12 +1530,12 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N if (child == null || child.getVisibility() == GONE) { continue; } - if (commentTextView != null && commentTextView.isPopupView(child) || pollLayout != null && child == pollLayout.emojiView) { + if (commentTextView != null && commentTextView.isPopupView(child) || topCommentTextView != null && topCommentTextView.isPopupView(child) || pollLayout != null && child == pollLayout.emojiView) { if (inBubbleMode) { child.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(heightSize + getPaddingTop(), MeasureSpec.EXACTLY)); } else if (AndroidUtilities.isInMultiwindow || AndroidUtilities.isTablet()) { if (AndroidUtilities.isTablet()) { - child.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(Math.min(AndroidUtilities.dp(AndroidUtilities.isTablet() ? 200 : 320), heightSize - AndroidUtilities.statusBarHeight + getPaddingTop()), MeasureSpec.EXACTLY)); + child.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(Math.min(dp(AndroidUtilities.isTablet() ? 200 : 320), heightSize - AndroidUtilities.statusBarHeight + getPaddingTop()), MeasureSpec.EXACTLY)); } else { child.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(heightSize - AndroidUtilities.statusBarHeight + getPaddingTop(), MeasureSpec.EXACTLY)); } @@ -1549,11 +1567,11 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N int paddingBottom = getPaddingBottom(); if (!keyboardVisible) { if (pollLayout != null && currentAttachLayout == pollLayout && pollLayout.emojiView != null) { - if (keyboardSize <= AndroidUtilities.dp(20) && !AndroidUtilities.isInMultiwindow && !AndroidUtilities.isTablet()) { + if (keyboardSize <= dp(20) && !AndroidUtilities.isInMultiwindow && !AndroidUtilities.isTablet()) { paddingBottom += pollLayout.getEmojiPadding(); } } else { - paddingBottom += keyboardSize <= AndroidUtilities.dp(20) && !AndroidUtilities.isInMultiwindow && !AndroidUtilities.isTablet() ? commentTextView.getEmojiPadding() : 0; + paddingBottom += keyboardSize <= dp(20) && !AndroidUtilities.isInMultiwindow && !AndroidUtilities.isTablet() ? getCommentView().getEmojiPadding() : 0; } } setBottomClip(paddingBottom); @@ -1605,12 +1623,16 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N childTop = lp.topMargin; } - if (commentTextView != null && commentTextView.isPopupView(child) || pollLayout != null && child == pollLayout.emojiView) { + if (commentTextView != null && commentTextView.isPopupView(child) || topCommentTextView != null && topCommentTextView.isPopupView(child) || pollLayout != null && child == pollLayout.emojiView) { if (AndroidUtilities.isTablet()) { childTop = getMeasuredHeight() - child.getMeasuredHeight(); } else { childTop = getMeasuredHeight() + keyboardSize - child.getMeasuredHeight(); } + } else if (child == mentionContainer) { + if (captionAbove) { + childTop = AndroidUtilities.statusBarHeight + ActionBar.getCurrentActionBarHeight(); + } } child.layout(childLeft, childTop, childLeft + width, childTop + height); } @@ -1618,6 +1640,9 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N notifyHeightChanged(); updateLayout(currentAttachLayout, false, 0); updateLayout(nextAttachLayout, false, 0); + if (captionAbove) { + updateCommentTextViewPosition(); + } } @Override @@ -1633,21 +1658,21 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N AttachAlertLayout layout = (AttachAlertLayout) child; int actionBarType = layout.needsActionBar(); - int offset = AndroidUtilities.dp(13) + (int) ((headerView != null ? headerView.getAlpha() : 0f) * AndroidUtilities.dp(26)); + int offset = dp(13) + (int) ((headerView != null ? headerView.getAlpha() : 0f) * dp(26)) + (int) (topCommentContainer != null ? topCommentContainer.getAlpha() * topCommentContainer.getMeasuredHeight() : 0); int top = getScrollOffsetY(0) - backgroundPaddingTop - offset; if (currentSheetAnimationType == 1 || viewChangeAnimator != null) { top += child.getTranslationY(); } - int y = top + AndroidUtilities.dp(20); + int y = top + dp(20); int h = (actionBarType != 0 ? ActionBar.getCurrentActionBarHeight() : backgroundPaddingTop); if (actionBarType != 2 && top + backgroundPaddingTop < h) { float toMove = offset; if (layout == locationLayout) { - toMove += AndroidUtilities.dp(11); + toMove += dp(11); } else if (layout == pollLayout) { - toMove -= AndroidUtilities.dp(3); + toMove -= dp(3); } else { - toMove += AndroidUtilities.dp(4); + toMove += dp(4); } float availableToMove = h - toMove + AndroidUtilities.statusBarHeight; y -= (int) (availableToMove * actionBar.getAlpha()); @@ -1668,14 +1693,14 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N AttachAlertLayout layout = (AttachAlertLayout) child; int actionBarType = layout.needsActionBar(); - int offset = AndroidUtilities.dp(13) + (int) ((headerView != null ? headerView.getAlpha() : 0f) * AndroidUtilities.dp(26)); + int offset = dp(13) + (int) ((headerView != null ? headerView.getAlpha() : 0f) * dp(26)) + (int) (topCommentContainer != null ? topCommentContainer.getAlpha() * topCommentContainer.getMeasuredHeight() : 0); int top = getScrollOffsetY(0) - backgroundPaddingTop - offset; if (currentSheetAnimationType == 1 || viewChangeAnimator != null) { top += child.getTranslationY(); } - int y = top + AndroidUtilities.dp(20); + int y = top + dp(20); - int height = getMeasuredHeight() + AndroidUtilities.dp(45) + backgroundPaddingTop; + int height = getMeasuredHeight() + dp(45) + backgroundPaddingTop; float rad = 1.0f; int h = (actionBarType != 0 ? ActionBar.getCurrentActionBarHeight() : backgroundPaddingTop); @@ -1686,11 +1711,11 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } else { float toMove = offset; if (layout == locationLayout) { - toMove += AndroidUtilities.dp(11); + toMove += dp(11); } else if (layout == pollLayout) { - toMove -= AndroidUtilities.dp(3); + toMove -= dp(3); } else { - toMove += AndroidUtilities.dp(4); + toMove += dp(4); } float moveProgress = actionBar.getAlpha(); float availableToMove = h - toMove + AndroidUtilities.statusBarHeight; @@ -1710,25 +1735,25 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N int backgroundColor = currentAttachLayout.hasCustomBackground() ? currentAttachLayout.getCustomBackground() : getThemedColor(forceDarkTheme ? Theme.key_voipgroup_listViewBackground : Theme.key_dialogBackground); shadowDrawable.setAlpha(viewAlpha); - shadowDrawable.setBounds(0, top, getMeasuredWidth(), height); + shadowDrawable.setBounds(0, top, getMeasuredWidth(), getMeasuredHeight() + dp(45) + backgroundPaddingTop); shadowDrawable.draw(canvas); if (actionBarType == 2) { Theme.dialogs_onlineCirclePaint.setColor(backgroundColor); Theme.dialogs_onlineCirclePaint.setAlpha(viewAlpha); - rect.set(backgroundPaddingLeft, backgroundPaddingTop + top, getMeasuredWidth() - backgroundPaddingLeft, backgroundPaddingTop + top + AndroidUtilities.dp(24)); + rect.set(backgroundPaddingLeft, backgroundPaddingTop + top, getMeasuredWidth() - backgroundPaddingLeft, backgroundPaddingTop + top + dp(24)); canvas.save(); canvas.clipRect(rect.left, rect.top, rect.right, rect.top + rect.height() / 2); - canvas.drawRoundRect(rect, AndroidUtilities.dp(12) * rad, AndroidUtilities.dp(12) * rad, Theme.dialogs_onlineCirclePaint); + canvas.drawRoundRect(rect, dp(12) * rad, dp(12) * rad, Theme.dialogs_onlineCirclePaint); canvas.restore(); } if ((rad != 1.0f && actionBarType != 2) || currentAttachLayout.hasCustomActionBarBackground()) { Theme.dialogs_onlineCirclePaint.setColor(currentAttachLayout.hasCustomActionBarBackground() ? currentAttachLayout.getCustomActionBarBackground() : backgroundColor); Theme.dialogs_onlineCirclePaint.setAlpha(viewAlpha); - rect.set(backgroundPaddingLeft, backgroundPaddingTop + top, getMeasuredWidth() - backgroundPaddingLeft, backgroundPaddingTop + top + AndroidUtilities.dp(24)); + rect.set(backgroundPaddingLeft, backgroundPaddingTop + top, getMeasuredWidth() - backgroundPaddingLeft, backgroundPaddingTop + top + dp(24)); canvas.save(); canvas.clipRect(rect.left, rect.top, rect.right, rect.top + rect.height() / 2); - canvas.drawRoundRect(rect, AndroidUtilities.dp(12) * rad, AndroidUtilities.dp(12) * rad, Theme.dialogs_onlineCirclePaint); + canvas.drawRoundRect(rect, dp(12) * rad, dp(12) * rad, Theme.dialogs_onlineCirclePaint); canvas.restore(); } @@ -1739,15 +1764,15 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N if (Build.VERSION.SDK_INT >= 21 && !inBubbleMode) { top2 += AndroidUtilities.statusBarHeight; } - rect.set(backgroundPaddingLeft, (backgroundPaddingTop + top + AndroidUtilities.dp(12)) * (rad), getMeasuredWidth() - backgroundPaddingLeft, top2 + AndroidUtilities.dp(12)); + rect.set(backgroundPaddingLeft, (backgroundPaddingTop + top + dp(12)) * (rad), getMeasuredWidth() - backgroundPaddingLeft, top2 + dp(12)); canvas.save(); canvas.drawRect(rect, Theme.dialogs_onlineCirclePaint); canvas.restore(); } if ((headerView == null || headerView.getAlpha() != 1.0f) && rad != 0) { - int w = AndroidUtilities.dp(36); - rect.set((getMeasuredWidth() - w) / 2, y, (getMeasuredWidth() + w) / 2, y + AndroidUtilities.dp(4)); + int w = dp(36); + rect.set((getMeasuredWidth() - w) / 2, y, (getMeasuredWidth() + w) / 2, y + dp(4)); int color; float alphaProgress; if (actionBarType == 2) { @@ -1765,7 +1790,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N int alpha = Color.alpha(color); Theme.dialogs_onlineCirclePaint.setColor(color); Theme.dialogs_onlineCirclePaint.setAlpha((int) (alpha * alphaProgress * rad * child.getAlpha())); - canvas.drawRoundRect(rect, AndroidUtilities.dp(2), AndroidUtilities.dp(2), Theme.dialogs_onlineCirclePaint); + canvas.drawRoundRect(rect, dp(2), dp(2), Theme.dialogs_onlineCirclePaint); } canvas.restore(); } @@ -1780,14 +1805,14 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N AttachAlertLayout layout = (AttachAlertLayout) child; int actionBarType = layout.needsActionBar(); - int offset = AndroidUtilities.dp(13) + (headerView != null ? AndroidUtilities.dp(headerView.getAlpha() * 26) : 0); + int offset = dp(13) + (headerView != null ? dp(headerView.getAlpha() * 26) : 0) + (int) (topCommentContainer != null ? topCommentContainer.getAlpha() * topCommentContainer.getMeasuredHeight() : 0); int top = getScrollOffsetY(layout == currentAttachLayout ? 0 : 1) - backgroundPaddingTop - offset; if (currentSheetAnimationType == 1 || viewChangeAnimator != null) { top += child.getTranslationY(); } - int y = top + AndroidUtilities.dp(20); + int y = top + dp(20); - int height = getMeasuredHeight() + AndroidUtilities.dp(45) + backgroundPaddingTop; + int height = getMeasuredHeight() + dp(45) + backgroundPaddingTop; float rad = 1.0f; int h = (actionBarType != 0 ? ActionBar.getCurrentActionBarHeight() : backgroundPaddingTop); @@ -1798,11 +1823,11 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } else if (top + backgroundPaddingTop < h) { float toMove = offset; if (layout == locationLayout) { - toMove += AndroidUtilities.dp(11); + toMove += dp(11); } else if (layout == pollLayout) { - toMove -= AndroidUtilities.dp(3); + toMove -= dp(3); } else { - toMove += AndroidUtilities.dp(4); + toMove += dp(4); } float moveProgress = Math.min(1.0f, (h - top - backgroundPaddingTop) / toMove); float availableToMove = h - toMove; @@ -1829,10 +1854,10 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N if (actionBarType == 2) { Theme.dialogs_onlineCirclePaint.setColor(backgroundColor); Theme.dialogs_onlineCirclePaint.setAlpha(viewAlpha); - rect.set(backgroundPaddingLeft, backgroundPaddingTop + top, getMeasuredWidth() - backgroundPaddingLeft, backgroundPaddingTop + top + AndroidUtilities.dp(24)); + rect.set(backgroundPaddingLeft, backgroundPaddingTop + top, getMeasuredWidth() - backgroundPaddingLeft, backgroundPaddingTop + top + dp(24)); canvas.save(); canvas.clipRect(rect.left, rect.top, rect.right, rect.top + rect.height() / 2); - canvas.drawRoundRect(rect, AndroidUtilities.dp(12) * rad, AndroidUtilities.dp(12) * rad, Theme.dialogs_onlineCirclePaint); + canvas.drawRoundRect(rect, dp(12) * rad, dp(12) * rad, Theme.dialogs_onlineCirclePaint); canvas.restore(); } } @@ -1851,16 +1876,16 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N if (rad != 1.0f && actionBarType != 2) { Theme.dialogs_onlineCirclePaint.setColor(backgroundColor); Theme.dialogs_onlineCirclePaint.setAlpha(viewAlpha); - rect.set(backgroundPaddingLeft, backgroundPaddingTop + top, getMeasuredWidth() - backgroundPaddingLeft, backgroundPaddingTop + top + AndroidUtilities.dp(24)); + rect.set(backgroundPaddingLeft, backgroundPaddingTop + top, getMeasuredWidth() - backgroundPaddingLeft, backgroundPaddingTop + top + dp(24)); canvas.save(); canvas.clipRect(rect.left, rect.top, rect.right, rect.top + rect.height() / 2); - canvas.drawRoundRect(rect, AndroidUtilities.dp(12) * rad, AndroidUtilities.dp(12) * rad, Theme.dialogs_onlineCirclePaint); + canvas.drawRoundRect(rect, dp(12) * rad, dp(12) * rad, Theme.dialogs_onlineCirclePaint); canvas.restore(); } if ((headerView == null || headerView.getAlpha() != 1.0f) && rad != 0) { - int w = AndroidUtilities.dp(36); - rect.set((getMeasuredWidth() - w) / 2, y, (getMeasuredWidth() + w) / 2, y + AndroidUtilities.dp(4)); + int w = dp(36); + rect.set((getMeasuredWidth() - w) / 2, y, (getMeasuredWidth() + w) / 2, y + dp(4)); int color; float alphaProgress; if (actionBarType == 2) { @@ -1873,7 +1898,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N int alpha = Color.alpha(color); Theme.dialogs_onlineCirclePaint.setColor(color); Theme.dialogs_onlineCirclePaint.setAlpha((int) (alpha * alphaProgress * rad * child.getAlpha())); - canvas.drawRoundRect(rect, AndroidUtilities.dp(2), AndroidUtilities.dp(2), Theme.dialogs_onlineCirclePaint); + canvas.drawRoundRect(rect, dp(2), dp(2), Theme.dialogs_onlineCirclePaint); } } canvas.restore(); @@ -1903,7 +1928,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } private int getCurrentTop() { - int y = scrollOffsetY[0] - backgroundPaddingTop * 2 - (AndroidUtilities.dp(13) + (headerView != null ? AndroidUtilities.dp(headerView.getAlpha() * 26) : 0)) + AndroidUtilities.dp(20); + int y = scrollOffsetY[0] - backgroundPaddingTop * 2 - (dp(13) + (headerView != null ? dp(headerView.getAlpha() * 26) : 0)) - (int) (topCommentContainer != null ? topCommentContainer.getAlpha() * topCommentContainer.getMeasuredHeight() : 0) + dp(20); if (Build.VERSION.SDK_INT >= 21 && !inBubbleMode) { y += AndroidUtilities.statusBarHeight; } @@ -1953,6 +1978,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N adjustPanLayoutHelper.setResizableView(this); adjustPanLayoutHelper.onAttach(); commentTextView.setAdjustPanLayoutHelper(adjustPanLayoutHelper); + topCommentTextView.setAdjustPanLayoutHelper(adjustPanLayoutHelper); // Bulletin.addDelegate(this, bulletinDelegate); } @@ -1989,10 +2015,10 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N if (currentAttachLayout == null || currentAttachLayout.shouldHideBottomButtons()) { buttonsRecyclerView.setAlpha(1.0f - alpha); shadow.setAlpha(1.0f - alpha); - buttonsRecyclerView.setTranslationY(AndroidUtilities.dp(44) * alpha); + buttonsRecyclerView.setTranslationY(dp(44) * alpha); } - frameLayout2.setTranslationY(AndroidUtilities.dp(48) * alpha); - shadow.setTranslationY(AndroidUtilities.dp(84) * alpha + botMainButtonOffsetY); + frameLayout2.setTranslationY(dp(48) * alpha); + shadow.setTranslationY(dp(84) * alpha + botMainButtonOffsetY); } else if (currentAttachLayout == null) { float value = alpha == 0.0f ? 1.0f : 0.0f; if (buttonsRecyclerView.getAlpha() != value) { @@ -2034,8 +2060,8 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N selectedMenuItem.setScaleY(0.6f); selectedMenuItem.setSubMenuOpenSide(2); selectedMenuItem.setDelegate(id -> actionBar.getActionBarMenuOnItemClick().onItemClick(id)); - selectedMenuItem.setAdditionalYOffset(AndroidUtilities.dp(72)); - selectedMenuItem.setTranslationX(AndroidUtilities.dp(6)); + selectedMenuItem.setAdditionalYOffset(dp(72)); + selectedMenuItem.setTranslationX(dp(6)); selectedMenuItem.setBackgroundDrawable(Theme.createSelectorDrawable(getThemedColor(Theme.key_dialogButtonSelector), 6)); selectedMenuItem.setOnClickListener(v -> selectedMenuItem.toggleSubMenu()); @@ -2044,7 +2070,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N doneItem.setText(getString(R.string.Create).toUpperCase()); doneItem.setVisibility(View.INVISIBLE); doneItem.setAlpha(0.0f); - doneItem.setTranslationX(-AndroidUtilities.dp(12)); + doneItem.setTranslationX(-dp(12)); doneItem.setBackgroundDrawable(Theme.createSelectorDrawable(getThemedColor(Theme.key_dialogButtonSelector), 3)); doneItem.setOnClickListener(v -> currentAttachLayout.onMenuItemClick(40)); @@ -2055,7 +2081,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N searchItem.setContentDescription(getString(R.string.Search)); searchItem.setVisibility(View.INVISIBLE); searchItem.setAlpha(0.0f); - searchItem.setTranslationX(-AndroidUtilities.dp(42)); + searchItem.setTranslationX(-dp(42)); searchItem.setBackgroundDrawable(Theme.createSelectorDrawable(getThemedColor(Theme.key_dialogButtonSelector), 6)); searchItem.setOnClickListener(v -> { if (avatarPicker != 0) { @@ -2183,8 +2209,8 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N PhotoViewer.getInstance().enableStickerMode(null, true, customStickerHandler); } }); - optionsItem.setMenuYOffset(AndroidUtilities.dp(-12)); - optionsItem.setAdditionalXOffset(AndroidUtilities.dp(12)); + optionsItem.setMenuYOffset(dp(-12)); + optionsItem.setAdditionalXOffset(dp(12)); optionsItem.setOnClickListener(v -> { optionsItem.toggleSubMenu(); }); @@ -2272,6 +2298,35 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N containerView.addView(photoLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); containerView.addView(headerView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.LEFT, 23, 0, 12, 0)); + topCommentContainer = new FrameLayout(context) { + private final Paint backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint backgroundPaint2 = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Path path = new Path(); + private final GradientClip clip = new GradientClip(); + @Override + protected void dispatchDraw(@NonNull Canvas canvas) { + final float r = dp(20); + AndroidUtilities.rectTmp.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()); + path.rewind(); + path.addRoundRect(AndroidUtilities.rectTmp, r, r, Path.Direction.CW); + canvas.save(); + canvas.clipRect(0, 0, getWidth(), getHeight() * getAlpha()); + backgroundPaint.setColor(getThemedColor(Theme.key_dialogBackground)); + canvas.drawRect(0, 0, getWidth(), getHeight(), backgroundPaint); + canvas.clipPath(path); + backgroundPaint2.setColor(getThemedColor(Theme.key_graySection)); + canvas.drawPaint(backgroundPaint2); + canvas.saveLayerAlpha(AndroidUtilities.rectTmp, 0xFF, Canvas.ALL_SAVE_FLAG); + super.dispatchDraw(canvas); + AndroidUtilities.rectTmp.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getPaddingTop() + dp(6)); + clip.draw(canvas, AndroidUtilities.rectTmp, GradientClip.TOP, 1.0f); + AndroidUtilities.rectTmp.set(getPaddingLeft(), getHeight() - getPaddingBottom() - dp(6), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom()); + clip.draw(canvas, AndroidUtilities.rectTmp, GradientClip.BOTTOM, 1.0f); + canvas.restore(); + canvas.restore(); + } + }; + containerView.addView(topCommentContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.TOP | Gravity.FILL_HORIZONTAL)); containerView.addView(actionBar, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); containerView.addView(selectedMenuItem, LayoutHelper.createFrame(48, 48, Gravity.TOP | Gravity.RIGHT)); if (searchItem != null) { @@ -2406,7 +2461,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } int left = view.getLeft(); int right = view.getRight(); - int extra = AndroidUtilities.dp(10); + int extra = dp(10); if (left - extra < 0) { buttonsRecyclerView.smoothScrollBy(left - extra, 0); } else if (right + extra > buttonsRecyclerView.getMeasuredWidth()) { @@ -2436,7 +2491,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } } - if (view.getX() + view.getWidth() >= buttonsRecyclerView.getMeasuredWidth() - AndroidUtilities.dp(32)) { + if (view.getX() + view.getWidth() >= buttonsRecyclerView.getMeasuredWidth() - dp(32)) { buttonsRecyclerView.smoothScrollBy((int) (view.getWidth() * 1.5f), 0); } }); @@ -2458,7 +2513,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N botMainButtonTextView.setSingleLine(); botMainButtonTextView.setGravity(Gravity.CENTER); botMainButtonTextView.setTypeface(AndroidUtilities.bold()); - int padding = AndroidUtilities.dp(16); + int padding = dp(16); botMainButtonTextView.setPadding(padding, 0, padding, 0); botMainButtonTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); botMainButtonTextView.setOnClickListener(v -> { @@ -2472,13 +2527,39 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N containerView.addView(botMainButtonTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 48, Gravity.BOTTOM | Gravity.LEFT)); botProgressView = new RadialProgressView(context); - botProgressView.setSize(AndroidUtilities.dp(18)); + botProgressView.setSize(dp(18)); botProgressView.setAlpha(0f); botProgressView.setScaleX(0.1f); botProgressView.setScaleY(0.1f); botProgressView.setVisibility(View.GONE); containerView.addView(botProgressView, LayoutHelper.createFrame(28, 28, Gravity.BOTTOM | Gravity.RIGHT, 0, 0, 10, 10)); + moveCaptionButton = new FrameLayout(context); + ScaleStateListAnimator.apply(moveCaptionButton, .20f, 1.5f); + Drawable shadowDrawable = getContext().getResources().getDrawable(R.drawable.popup_fixed_alert3).mutate(); + Rect shadowDrawablePadding = new Rect(); + shadowDrawable.getPadding(shadowDrawablePadding); + shadowDrawable.setColorFilter(new PorterDuffColorFilter(getThemedColor(Theme.key_windowBackgroundWhite), PorterDuff.Mode.SRC_IN)); + moveCaptionButton.setBackground(shadowDrawable); + moveCaptionButton.setAlpha(0.0f); + moveCaptionButtonIcon = new ImageView(context); + moveCaptionButtonIcon.setScaleType(ImageView.ScaleType.CENTER); + moveCaptionButtonIcon.setColorFilter(new PorterDuffColorFilter(getThemedColor(Theme.key_windowBackgroundWhiteGrayText2), PorterDuff.Mode.SRC_IN)); + moveCaptionButtonIcon.setImageResource(R.drawable.menu_link_above); + moveCaptionButton.addView(moveCaptionButtonIcon, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.FILL)); + FrameLayout.LayoutParams moveCaptionButtonLayoutParams = LayoutHelper.createFrame(38, 32, Gravity.BOTTOM | Gravity.LEFT); + moveCaptionButtonLayoutParams.width += shadowDrawablePadding.left + shadowDrawablePadding.right; + moveCaptionButtonLayoutParams.height += shadowDrawablePadding.top + shadowDrawablePadding.bottom; + moveCaptionButtonLayoutParams.leftMargin = dp(10) - shadowDrawablePadding.left; + moveCaptionButtonLayoutParams.bottomMargin = dp(10) - shadowDrawablePadding.bottom; + containerView.addView(moveCaptionButton, moveCaptionButtonLayoutParams); + moveCaptionButton.setOnClickListener(v -> { + if (moveCaptionButton.getAlpha() < 1.0f) return; + if (!captionAbove) { + toggleCaptionAbove(); + } + }); + frameLayout2 = new FrameLayout(context) { private final Paint p = new Paint(); @@ -2492,6 +2573,9 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N @Override protected void onDraw(Canvas canvas) { + if (captionContainer.getAlpha() <= 0) { + return; + } if (chatActivityEnterViewAnimateFromTop != 0 && chatActivityEnterViewAnimateFromTop != frameLayout2.getTop() + chatActivityEnterViewAnimateFromTop) { if (topBackgroundAnimator != null) { topBackgroundAnimator.cancel(); @@ -2509,15 +2593,15 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N chatActivityEnterViewAnimateFromTop = 0; } - float alphaOffset = (frameLayout2.getMeasuredHeight() - AndroidUtilities.dp(84)) * (1f - getAlpha()); - shadow.setTranslationY(-(frameLayout2.getMeasuredHeight() - AndroidUtilities.dp(84)) + captionEditTextTopOffset + currentPanTranslationY + bottomPannelTranslation + alphaOffset + botMainButtonOffsetY); + float alphaOffset = (frameLayout2.getMeasuredHeight() - dp(84)) * (1f - getAlpha()); + shadow.setTranslationY(-(frameLayout2.getMeasuredHeight() - dp(84)) + captionEditTextTopOffset + currentPanTranslationY + bottomPannelTranslation + alphaOffset + botMainButtonOffsetY + captionContainer.getTranslationY()); int newColor = currentAttachLayout.hasCustomBackground() ? currentAttachLayout.getCustomBackground() : getThemedColor(forceDarkTheme ? Theme.key_voipgroup_listViewBackground : Theme.key_dialogBackground); if (color != newColor) { color = newColor; p.setColor(color); } - canvas.drawRect(0, captionEditTextTopOffset, getMeasuredWidth(), getMeasuredHeight(), p); + canvas.drawRect(0, captionEditTextTopOffset + captionContainer.getTranslationY(), getMeasuredWidth(), getMeasuredHeight(), p); } @Override @@ -2529,6 +2613,9 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } }; + captionContainer = new FrameLayout(context); + frameLayout2.addView(captionContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.FILL)); + frameLayout2.setWillNotDraw(false); frameLayout2.setVisibility(View.INVISIBLE); frameLayout2.setAlpha(0.0f); @@ -2541,7 +2628,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N captionLimitView.setTextColor(getThemedColor(Theme.key_windowBackgroundWhiteGrayText)); captionLimitView.setTypeface(AndroidUtilities.bold()); captionLimitView.setCenterAlign(true); - frameLayout2.addView(captionLimitView, LayoutHelper.createFrame(56, 20, Gravity.BOTTOM | Gravity.RIGHT, 3, 0, 14, 78)); + captionContainer.addView(captionLimitView, LayoutHelper.createFrame(56, 20, Gravity.BOTTOM | Gravity.RIGHT, 3, 0, 14, 78)); currentLimit = MessagesController.getInstance(UserConfig.selectedAccount).getCaptionMaxLengthLimit(); @@ -2611,10 +2698,9 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N @Override protected void bottomPanelTranslationY(float translation) { bottomPannelTranslation = translation; - // buttonsRecyclerView.setTranslationY(translation); frameLayout2.setTranslationY(translation); + moveCaptionButton.setTranslationY(bottomPannelTranslation - commentTextView.getHeight() + captionContainer.getTranslationY()); writeButtonContainer.setTranslationY(translation); -// selectedCountView.setTranslationY(translation); frameLayout2.invalidate(); updateLayout(currentAttachLayout, true, 0); } @@ -2659,7 +2745,9 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N createMentionsContainer(); } if (mentionContainer.getAdapter() != null) { + mentionContainer.setReversed(false); mentionContainer.getAdapter().searchUsernameOrHashtag(charSequence, commentTextView.getEditText().getSelectionStart(), null, false, false); + updateCommentTextViewPosition(); } } @@ -2676,7 +2764,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N for (int i = 0; i < spans.length; i++) { editable.removeSpan(spans[i]); } - Emoji.replaceEmoji(editable, commentTextView.getEditText().getPaint().getFontMetricsInt(), AndroidUtilities.dp(20), false); + Emoji.replaceEmoji(editable, commentTextView.getEditText().getPaint().getFontMetricsInt(), dp(20), false); processChange = false; } int beforeLimit; @@ -2701,6 +2789,8 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } else { captionLimitView.setTextColor(getThemedColor(Theme.key_windowBackgroundWhiteGrayText)); } + topCaptionLimitView.setNumber(beforeLimit, false); + topCaptionLimitView.setAlpha(1.0f); } else { captionLimitView.animate().alpha(0).scaleX(0.5f).scaleY(0.5f).setDuration(100).setListener(new AnimatorListenerAdapter() { @Override @@ -2708,6 +2798,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N captionLimitView.setVisibility(View.GONE); } }); + topCaptionLimitView.setAlpha(0.0f); } if (sendButtonEnabled != sendButtonEnabledLocal) { @@ -2721,10 +2812,161 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N // } } }); - frameLayout2.addView(commentTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 84, 0)); + captionContainer.addView(commentTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 84, 0)); + captionContainer.setClipChildren(false); frameLayout2.setClipChildren(false); commentTextView.setClipChildren(false); + topCommentContainer.setPadding(dp(10), dp(2), dp(10), dp(10)); + topCommentContainer.setWillNotDraw(false); + topCommentTextView = new EditTextEmoji(context, sizeNotifierFrameLayout, null, EditTextEmoji.STYLE_DIALOG, true, resourcesProvider) { + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (!enterCommentEventSent) { + if (ev.getX() > topCommentTextView.getEditText().getLeft() && ev.getX() < topCommentTextView.getEditText().getRight() + && ev.getY() > topCommentTextView.getEditText().getTop() && ev.getY() < topCommentTextView.getEditText().getBottom()) { + makeFocusable(topCommentTextView.getEditText(), true); + } else { + makeFocusable(topCommentTextView.getEditText(), false); + } + } + return super.onInterceptTouchEvent(ev); + } + + @Override + protected void onLineCountChanged(int oldLineCount, int newLineCount) { + super.onLineCountChanged(oldLineCount, newLineCount); + updatedTopCaptionHeight(); + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + updatedTopCaptionHeight(); + } + + @Override + protected void extendActionMode(ActionMode actionMode, Menu menu) { + if (baseFragment instanceof ChatActivity) { + ChatActivity.fillActionModeMenu(menu, ((ChatActivity) baseFragment).getCurrentEncryptedChat(), true); + } + super.extendActionMode(actionMode, menu); + } + }; + topCommentTextView.getEditText().addTextChangedListener(new TextWatcher() { + + private boolean processChange; + private boolean wasEmpty; + + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int start, int before, int count) { + if ((count - before) >= 1) { + processChange = true; + } + if (mentionContainer == null) { + createMentionsContainer(); + } + if (mentionContainer.getAdapter() != null) { + mentionContainer.setReversed(true); + mentionContainer.getAdapter().searchUsernameOrHashtag(charSequence, topCommentTextView.getEditText().getSelectionStart(), null, false, false); + updateCommentTextViewPosition(); + } + } + + @Override + public void afterTextChanged(Editable editable) { + if (wasEmpty != TextUtils.isEmpty(editable)) { + if (currentAttachLayout != null) { + currentAttachLayout.onSelectedItemsCountChanged(currentAttachLayout.getSelectedItemsCount()); + } + wasEmpty = !wasEmpty; + } + if (processChange) { + ImageSpan[] spans = editable.getSpans(0, editable.length(), ImageSpan.class); + for (int i = 0; i < spans.length; i++) { + editable.removeSpan(spans[i]); + } + Emoji.replaceEmoji(editable, topCommentTextView.getEditText().getPaint().getFontMetricsInt(), dp(20), false); + processChange = false; + } + int beforeLimit; + codepointCount = Character.codePointCount(editable, 0, editable.length()); + boolean sendButtonEnabledLocal = true; + if (currentLimit > 0 && (beforeLimit = currentLimit - codepointCount) <= 100) { + if (beforeLimit < -9999) { + beforeLimit = -9999; + } + topCaptionLimitView.setNumber(beforeLimit, topCaptionLimitView.getVisibility() == View.VISIBLE); + if (topCaptionLimitView.getVisibility() != View.VISIBLE) { + topCaptionLimitView.setVisibility(View.VISIBLE); + topCaptionLimitView.setAlpha(0); + topCaptionLimitView.setScaleX(0.5f); + topCaptionLimitView.setScaleY(0.5f); + } + topCaptionLimitView.animate().setListener(null).cancel(); + topCaptionLimitView.animate().alpha(1f).scaleX(1f).scaleY(1f).setDuration(100).start(); + if (beforeLimit < 0) { + sendButtonEnabledLocal = false; + topCaptionLimitView.setTextColor(getThemedColor(Theme.key_text_RedRegular)); + } else { + topCaptionLimitView.setTextColor(getThemedColor(Theme.key_windowBackgroundWhiteGrayText)); + } + captionLimitView.setNumber(beforeLimit, false); + captionLimitView.setAlpha(1.0f); + } else { + topCaptionLimitView.animate().alpha(0).scaleX(0.5f).scaleY(0.5f).setDuration(100).setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + topCaptionLimitView.setVisibility(View.GONE); + } + }); + captionLimitView.setAlpha(0.0f); + } + + if (sendButtonEnabled != sendButtonEnabledLocal) { + sendButtonEnabled = sendButtonEnabledLocal; + writeButton.invalidate(); + } + + if (!captionLimitBulletinShown && !MessagesController.getInstance(currentAccount).premiumFeaturesBlocked() && !UserConfig.getInstance(currentAccount).isPremium() && codepointCount > MessagesController.getInstance(currentAccount).captionLengthLimitDefault && codepointCount < MessagesController.getInstance(currentAccount).captionLengthLimitPremium) { + captionLimitBulletinShown = true; + showCaptionLimitBulletin(parentFragment); + } + } + }); + topCommentTextView.getEditText().setPadding(0, dp(9), 0, dp(9)); + topCommentTextView.getEditText().setLayoutParams(LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.CENTER_VERTICAL, 48, 0, 36, 0)); + topCommentTextView.getEditText().setTextSize(TypedValue.COMPLEX_UNIT_DIP, 17); + topCommentTextView.getEmojiButton().setLayoutParams(LayoutHelper.createFrame(40, 40, Gravity.BOTTOM | Gravity.LEFT, 0, 0, 0, 0)); + topCommentTextView.setHint(getString("AddCaption", R.string.AddCaption)); + topCommentContainer.addView(topCommentTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.FILL)); + topCommentContainer.setAlpha(0.0f); + topCommentContainer.setVisibility(View.GONE); + + topCaptionLimitView = new NumberTextView(context); + topCaptionLimitView.setVisibility(View.GONE); + topCaptionLimitView.setTextSize(12); + topCaptionLimitView.setTextColor(getThemedColor(Theme.key_windowBackgroundWhiteGrayText)); + topCaptionLimitView.setTypeface(AndroidUtilities.bold()); + topCaptionLimitView.setCenterAlign(true); + topCommentTextView.addView(topCaptionLimitView, LayoutHelper.createFrame(46, 20, Gravity.BOTTOM | Gravity.RIGHT, 3, 0, 0, 40)); + + topCommentMoveButton = new ImageView(context); + topCommentMoveButton.setScaleType(ImageView.ScaleType.CENTER); + topCommentMoveButton.setImageResource(R.drawable.menu_link_below); + topCommentMoveButton.setColorFilter(new PorterDuffColorFilter(getThemedColor(Theme.key_chat_messagePanelIcons), PorterDuff.Mode.SRC_IN)); + topCommentTextView.addView(topCommentMoveButton, LayoutHelper.createFrame(40, 40, Gravity.BOTTOM | Gravity.RIGHT, 0, 0, 2, 0)); + topCommentMoveButton.setOnClickListener(v -> { + if (captionAbove) { + toggleCaptionAbove(); + } + }); + writeButtonContainer = new FrameLayout(context) { @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { @@ -2781,10 +3023,10 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N writeButton.setOnClickListener(v -> { if (currentLimit - codepointCount < 0) { AndroidUtilities.shakeView(captionLimitView); + AndroidUtilities.shakeView(topCaptionLimitView); try { - captionLimitView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); - } catch (Exception ignored) { - } + writeButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); + } catch (Exception ignored) {} if (!MessagesController.getInstance(currentAccount).premiumFeaturesBlocked() && MessagesController.getInstance(currentAccount).captionLengthLimitPremium > codepointCount) { showCaptionLimitBulletin(parentFragment); @@ -2847,8 +3089,9 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } if (currentLimit - codepointCount < 0) { AndroidUtilities.shakeView(captionLimitView); + AndroidUtilities.shakeView(topCaptionLimitView); try { - captionLimitView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); + writeButton.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP, HapticFeedbackConstants.FLAG_IGNORE_GLOBAL_SETTING); } catch (Exception ignored) {} if (!MessagesController.getInstance(currentAccount).premiumFeaturesBlocked() && MessagesController.getInstance(currentAccount).captionLengthLimitPremium > codepointCount) { showCaptionLimitBulletin(parentFragment); @@ -2874,7 +3117,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N dismiss(); } } - captionAbove = false; + setCaptionAbove(false, false); }); boolean hasMessageToEffect = false; @@ -2960,7 +3203,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N msg.media.spoiler = photoEntry.hasSpoiler; msg.message = photoEntry.caption == null ? "" : photoEntry.caption.toString(); if (TextUtils.isEmpty(msg.message) && i == 0 && a == 0) { - CharSequence[] message = new CharSequence[]{ commentTextView.getText() }; + CharSequence[] message = new CharSequence[]{ getCommentView().getText() }; MessageObject.addLinks(true, message[0]); msg.entities = MediaDataController.getInstance(currentAccount).getEntities(message, true); msg.message = message[0].toString(); @@ -2994,14 +3237,14 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } } else { if (currentAttachLayout == contactsLayout) { - if (!TextUtils.isEmpty(commentTextView.getText())) { + if (!TextUtils.isEmpty(getCommentView().getText())) { hasMessageToEffect = true; TLRPC.TL_message msg = new TLRPC.TL_message(); msg.id = id++; msg.out = true; msg.from_id = MessagesController.getInstance(currentAccount).getPeer(UserConfig.getInstance(currentAccount).getClientUserId()); msg.peer_id = MessagesController.getInstance(currentAccount).getPeer(dialogId); - CharSequence[] message = new CharSequence[]{ commentTextView.getText() }; + CharSequence[] message = new CharSequence[]{ getCommentView().getText() }; MessageObject.addLinks(true, message[0]); msg.entities = MediaDataController.getInstance(currentAccount).getEntities(message, true); msg.message = message[0].toString(); @@ -3054,7 +3297,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N msg.media.document.file_name = filename; msg.media.document.size = new File(path).length(); if (TextUtils.isEmpty(msg.message) && i == 0) { - CharSequence[] message = new CharSequence[]{ commentTextView.getText() }; + CharSequence[] message = new CharSequence[]{ getCommentView().getText() }; msg.entities = MediaDataController.getInstance(currentAccount).getEntities(message, true); msg.message = message[0].toString(); } @@ -3074,7 +3317,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N messageObjects.addAll(audioLayout.getSelected()); if (!messageObjects.isEmpty()) { MessageObject firstMessageObject = messageObjects.get(0); - CharSequence[] message = new CharSequence[]{ commentTextView.getText() }; + CharSequence[] message = new CharSequence[]{ getCommentView().getText() }; MessageObject.addLinks(true, message[0]); firstMessageObject.messageOwner.entities = MediaDataController.getInstance(currentAccount).getEntities(message, true); firstMessageObject.messageOwner.message = message[0].toString(); @@ -3114,7 +3357,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N final MessageObject msg = messageWithCaption; button.setState(!(msg.messageOwner.invert_media = captionAbove), false); button.setOnClickListener(v -> { - captionAbove = !captionAbove; + setCaptionAbove(!captionAbove); msg.messageOwner.invert_media = captionAbove; button.setState(!captionAbove, true); messageSendPreview.changeMessage(msg); @@ -3205,7 +3448,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N return true; }); - textPaint.setTextSize(AndroidUtilities.dp(12)); + textPaint.setTextSize(dp(12)); textPaint.setTypeface(AndroidUtilities.bold()); selectedCountView = new View(context) { @@ -3213,20 +3456,20 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N protected void onDraw(Canvas canvas) { String text = String.format("%d", Math.max(1, currentAttachLayout.getSelectedItemsCount())); int textSize = (int) Math.ceil(textPaint.measureText(text)); - int size = Math.max(AndroidUtilities.dp(16) + textSize, AndroidUtilities.dp(24)); + int size = Math.max(dp(16) + textSize, dp(24)); int cx = getMeasuredWidth() / 2; int color = getThemedColor(Theme.key_dialogRoundCheckBoxCheck); textPaint.setColor(ColorUtils.setAlphaComponent(color, (int) (Color.alpha(color) * (0.58 + 0.42 * sendButtonEnabledProgress)))); paint.setColor(getThemedColor(Theme.key_dialogBackground)); rect.set(cx - size / 2, 0, cx + size / 2, getMeasuredHeight()); - canvas.drawRoundRect(rect, AndroidUtilities.dp(12), AndroidUtilities.dp(12), paint); + canvas.drawRoundRect(rect, dp(12), dp(12), paint); paint.setColor(getThemedColor(Theme.key_chat_attachCheckBoxBackground)); - rect.set(cx - size / 2 + AndroidUtilities.dp(2), AndroidUtilities.dp(2), cx + size / 2 - AndroidUtilities.dp(2), getMeasuredHeight() - AndroidUtilities.dp(2)); - canvas.drawRoundRect(rect, AndroidUtilities.dp(10), AndroidUtilities.dp(10), paint); + rect.set(cx - size / 2 + dp(2), dp(2), cx + size / 2 - dp(2), getMeasuredHeight() - dp(2)); + canvas.drawRoundRect(rect, dp(10), dp(10), paint); - canvas.drawText(text, cx - textSize / 2, AndroidUtilities.dp(16.2f), textPaint); + canvas.drawText(text, cx - textSize / 2, dp(16.2f), textPaint); } }; selectedCountView.setAlpha(0.0f); @@ -3260,8 +3503,8 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } MediaController.PhotoEntry photoEntry = (MediaController.PhotoEntry) selectedPhotos.get(selectedPhotosOrder.get(i * 10 + a)); CharSequence caption = photoEntry.caption == null ? "" : photoEntry.caption.toString(); - if (commentTextView != null && TextUtils.isEmpty(caption) && a == 0) { - caption = commentTextView.getText().toString(); + if (getCommentView() != null && TextUtils.isEmpty(caption) && a == 0) { + caption = getCommentView().getText().toString(); } if (!TextUtils.isEmpty(caption)) { if (hasCaption) return false; @@ -3273,7 +3516,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N return hasCaption; } - private boolean isCaptionAbove() { + public boolean isCaptionAbove() { return captionAbove && (currentAttachLayout == photoLayout || currentAttachLayout == photoPreviewLayout); } @@ -3306,12 +3549,24 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N public void updateCommentTextViewPosition() { commentTextView.getLocationOnScreen(commentTextViewLocation); if (mentionContainer != null) { - float y = -commentTextView.getHeight(); - if (mentionContainer.getY() != y) { + float y; + final boolean above = (currentAttachLayout == photoLayout || currentAttachLayout == photoPreviewLayout) && captionAbove; + if (above) { + y = topCommentContainer.getY() - mentionContainer.getTop() + topCommentContainer.getMeasuredHeight() * topCommentContainer.getAlpha(); + } else { + y = -commentTextView.getHeight(); + } + if (Math.abs(mentionContainer.getTranslationY() - y) > 0.5f) { mentionContainer.setTranslationY(y); mentionContainer.invalidate(); + if (photoLayout != null) { + photoLayout.checkCameraViewPosition(); + } } } + if (moveCaptionButton != null) { + moveCaptionButton.setTranslationY(bottomPannelTranslation - commentTextView.getHeight() + captionContainer.getTranslationY()); + } } public int getCommentTextViewTop() { @@ -3439,10 +3694,10 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } protected void applyCaption() { - if (commentTextView.length() <= 0) { + if (getCommentView().length() <= 0) { return; } - currentAttachLayout.applyCaption(commentTextView.getText()); + currentAttachLayout.applyCaption(getCommentView().getText()); } private void sendPressed(boolean notify, int scheduleDate, long effectId, boolean invertMedia) { @@ -3457,7 +3712,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N MessagesController.getNotificationsSettings(currentAccount).edit().putBoolean("silent_" + chatActivity.getDialogId(), !notify).commit(); } } - if (checkCaption(commentTextView.getText())) { + if (checkCaption(getCommentView().getText())) { return; } applyCaption(); @@ -3529,7 +3784,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N attachButton.updateCheckedState(true); } } - int t = currentAttachLayout.getFirstOffset() - AndroidUtilities.dp(11) - scrollOffsetY[0]; + int t = currentAttachLayout.getFirstOffset() - dp(11) - scrollOffsetY[0]; nextAttachLayout = layout; if (Build.VERSION.SDK_INT >= 20) { container.setLayerType(View.LAYER_TYPE_HARDWARE, null); @@ -3568,15 +3823,17 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N currentAttachLayout = nextAttachLayout; nextAttachLayout = null; scrollOffsetY[0] = scrollOffsetY[1]; + + setCaptionAbove(captionAbove, false); }; if (!(currentAttachLayout instanceof ChatAttachAlertPhotoLayoutPreview || nextAttachLayout instanceof ChatAttachAlertPhotoLayoutPreview)) { if (animated) { AnimatorSet animator = new AnimatorSet(); nextAttachLayout.setAlpha(0.0f); - nextAttachLayout.setTranslationY(AndroidUtilities.dp(78)); + nextAttachLayout.setTranslationY(dp(78)); animator.playTogether( - ObjectAnimator.ofFloat(currentAttachLayout, View.TRANSLATION_Y, AndroidUtilities.dp(78) + t), + ObjectAnimator.ofFloat(currentAttachLayout, View.TRANSLATION_Y, dp(78) + t), ObjectAnimator.ofFloat(currentAttachLayout, ATTACH_ALERT_LAYOUT_TRANSLATION, 0.0f, 1.0f), ObjectAnimator.ofFloat(actionBar, View.ALPHA, actionBar.getAlpha(), 0f) ); @@ -3587,7 +3844,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N @Override public void onAnimationEnd(Animator animation) { currentAttachLayout.setAlpha(0.0f); - currentAttachLayout.setTranslationY(AndroidUtilities.dp(78) + t); + currentAttachLayout.setTranslationY(dp(78) + t); ATTACH_ALERT_LAYOUT_TRANSLATION.set(currentAttachLayout, 1.0f); actionBar.setAlpha(0f); SpringAnimation springAnimation = new SpringAnimation(nextAttachLayout, DynamicAnimation.TRANSLATION_Y, 0); @@ -3661,8 +3918,8 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N mediaPreviewAlpha = Utilities.clamp(mediaPreviewAlpha, 1, 0); mediaPreviewView.setAlpha(mediaPreviewAlpha); selectedView.setAlpha(1f - mediaPreviewAlpha); - selectedView.setTranslationX(mediaPreviewAlpha * -AndroidUtilities.dp(16)); - mediaPreviewView.setTranslationX((1f - mediaPreviewAlpha) * AndroidUtilities.dp(16)); + selectedView.setTranslationX(mediaPreviewAlpha * -dp(16)); + mediaPreviewView.setTranslationX((1f - mediaPreviewAlpha) * dp(16)); }); springAnimation.addEndListener((animation, canceled, value, velocity) -> { currentAttachLayout.onHideShowProgress(1f); @@ -3878,6 +4135,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N AndroidUtilities.hideKeyboard(commentTextView.getEditText()); } commentTextView.hidePopup(true); + topCommentTextView.hidePopup(true); if (show) { if (!isSoundPicker) { frameLayout2.setVisibility(View.VISIBLE); @@ -3889,10 +4147,23 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } else if (typeButtonsAvailable) { buttonsRecyclerView.setVisibility(View.VISIBLE); } + final boolean allowAbove = (currentAttachLayout == photoLayout || currentAttachLayout == photoPreviewLayout); + final boolean above = allowAbove && captionAbove; if (animated) { commentsAnimator = new AnimatorSet(); + if (above) { + topCommentContainer.setVisibility(View.VISIBLE); + } ArrayList animators = new ArrayList<>(); animators.add(ObjectAnimator.ofFloat(frameLayout2, View.ALPHA, show ? 1.0f : 0.0f)); + animators.add(ObjectAnimator.ofFloat(captionContainer, View.ALPHA, show && !above ? 1.0f : 0.0f)); + if (show && !above) { + captionContainer.setVisibility(View.VISIBLE); + animators.add(ObjectAnimator.ofFloat(captionContainer, View.TRANSLATION_Y, 0)); + } + moveCaptionButton.setVisibility(View.VISIBLE); + animators.add(ObjectAnimator.ofFloat(moveCaptionButton, View.ALPHA, show && allowAbove && !above ? 1.0f : 0.0f)); + animators.add(ObjectAnimator.ofFloat(topCommentContainer, View.ALPHA, show && above ? 1.0f : 0.0f)); animators.add(ObjectAnimator.ofFloat(writeButtonContainer, View.SCALE_X, show ? 1.0f : 0.2f)); animators.add(ObjectAnimator.ofFloat(writeButtonContainer, View.SCALE_Y, show ? 1.0f : 0.2f)); animators.add(ObjectAnimator.ofFloat(writeButtonContainer, View.ALPHA, show ? 1.0f : 0.0f)); @@ -3900,17 +4171,24 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N animators.add(ObjectAnimator.ofFloat(writeButton, View.SCALE_Y, show ? 1.0f : 0.2f)); animators.add(ObjectAnimator.ofFloat(writeButton, View.ALPHA, show ? 1.0f : 0.0f)); if (actionBar.getTag() != null) { - animators.add(ObjectAnimator.ofFloat(frameLayout2, View.TRANSLATION_Y, show ? 0.0f : AndroidUtilities.dp(48))); - animators.add(ObjectAnimator.ofFloat(shadow, View.TRANSLATION_Y, show ? AndroidUtilities.dp(36) : AndroidUtilities.dp(48 + 36))); + animators.add(ObjectAnimator.ofFloat(frameLayout2, View.TRANSLATION_Y, show ? 0.0f : dp(48))); + animators.add(ObjectAnimator.ofFloat(shadow, View.TRANSLATION_Y, show ? dp(36) : dp(48 + 36))); animators.add(ObjectAnimator.ofFloat(shadow, View.ALPHA, show ? 1.0f : 0.0f)); } else if (typeButtonsAvailable) { - animators.add(ObjectAnimator.ofFloat(buttonsRecyclerView, View.TRANSLATION_Y, show ? AndroidUtilities.dp(36) : 0)); - animators.add(ObjectAnimator.ofFloat(shadow, View.TRANSLATION_Y, show ? AndroidUtilities.dp(36) : 0)); + animators.add(ObjectAnimator.ofFloat(buttonsRecyclerView, View.TRANSLATION_Y, show ? dp(84) : 0)); + animators.add(ObjectAnimator.ofFloat(shadow, View.TRANSLATION_Y, show ? dp(36) : 0)); } else if (!isSoundPicker) { - shadow.setTranslationY(AndroidUtilities.dp(36) + botMainButtonOffsetY); + shadow.setTranslationY(dp(36) + botMainButtonOffsetY); animators.add(ObjectAnimator.ofFloat(shadow, View.ALPHA, show ? 1.0f : 0.0f)); } + if (above) { + ValueAnimator va = ValueAnimator.ofFloat(0, 1); + va.addUpdateListener(a -> { + updatedTopCaptionHeight(); + }); + animators.add(va); + } commentsAnimator.playTogether(animators); commentsAnimator.setInterpolator(new DecelerateInterpolator()); commentsAnimator.setDuration(180); @@ -3931,6 +4209,11 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N buttonsRecyclerView.setVisibility(View.INVISIBLE); } } + moveCaptionButton.setTranslationY(bottomPannelTranslation - commentTextView.getHeight() + captionContainer.getTranslationY()); + if (above) { + updatedTopCaptionHeight(); + topCommentContainer.setVisibility(show ? View.VISIBLE : View.GONE); + } commentsAnimator = null; } } @@ -3945,23 +4228,33 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N commentsAnimator.start(); } else { frameLayout2.setAlpha(show ? 1.0f : 0.0f); + captionContainer.setAlpha(show && above ? 1.0f : 0.0f); + if (show && !above) { + captionContainer.setVisibility(View.VISIBLE); + captionContainer.setTranslationY(0); + } + moveCaptionButton.setAlpha(show && allowAbove && !above ? 1.0f : 0.0f); + moveCaptionButton.setVisibility(show && allowAbove && !above ? View.VISIBLE : View.GONE); + moveCaptionButton.setTranslationY(bottomPannelTranslation - commentTextView.getHeight() + captionContainer.getTranslationY()); writeButtonContainer.setScaleX(show ? 1.0f : 0.2f); writeButtonContainer.setScaleY(show ? 1.0f : 0.2f); writeButtonContainer.setAlpha(show ? 1.0f : 0.0f); + topCommentContainer.setVisibility(show && above ? View.VISIBLE : View.GONE); + topCommentContainer.setAlpha(show && above ? 1.0f : 0.0f); writeButton.setScaleX(show ? 1.0f : 0.2f); writeButton.setScaleY(show ? 1.0f : 0.2f); writeButton.setAlpha(show ? 1.0f : 0.0f); if (actionBar.getTag() != null) { - frameLayout2.setTranslationY(show ? 0.0f : AndroidUtilities.dp(48)); - shadow.setTranslationY((show ? AndroidUtilities.dp(36) : AndroidUtilities.dp(48 + 36)) + botMainButtonOffsetY); + frameLayout2.setTranslationY(show ? 0.0f : dp(48)); + shadow.setTranslationY((show ? dp(36) : dp(48 + 36)) + botMainButtonOffsetY); shadow.setAlpha(show ? 1.0f : 0.0f); } else if (typeButtonsAvailable) { if (currentAttachLayout == null || currentAttachLayout.shouldHideBottomButtons()) { - buttonsRecyclerView.setTranslationY(show ? AndroidUtilities.dp(36) : 0); + buttonsRecyclerView.setTranslationY(show ? dp(84) : 0); } - shadow.setTranslationY((show ? AndroidUtilities.dp(36) : 0) + botMainButtonOffsetY); + shadow.setTranslationY((show ? dp(36) : 0) + botMainButtonOffsetY); } else { - shadow.setTranslationY(AndroidUtilities.dp(36) + botMainButtonOffsetY); + shadow.setTranslationY(dp(36) + botMainButtonOffsetY); shadow.setAlpha(show ? 1.0f : 0.0f); } if (!show) { @@ -3971,6 +4264,10 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N shadow.setVisibility(View.INVISIBLE); } } + actionBarShadow.setTranslationY(currentPanTranslationY + topCommentContainer.getMeasuredHeight() * topCommentContainer.getAlpha()); + if (above) { + updatedTopCaptionHeight(); + } } writeButton.setCount(show ? Math.max(1, currentAttachLayout.getSelectedItemsCount()) : 0, animated); return true; @@ -4246,7 +4543,8 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N buttonsRecyclerView.setGlowColor(getThemedColor(Theme.key_dialogScrollGlow)); buttonsRecyclerView.setBackgroundColor(getThemedColor(forceDarkTheme ? Theme.key_voipgroup_listViewBackground : Theme.key_dialogBackground)); - frameLayout2.setBackgroundColor(getThemedColor(forceDarkTheme ? Theme.key_voipgroup_listViewBackground : Theme.key_dialogBackground)); + captionContainer.setBackgroundColor(getThemedColor(forceDarkTheme ? Theme.key_voipgroup_listViewBackground : Theme.key_dialogBackground)); + topCommentContainer.setBackgroundColor(getThemedColor(Theme.key_dialogBackground)); actionBar.setBackgroundColor(forceDarkTheme ? getThemedColor(Theme.key_voipgroup_actionBar) : getThemedColor(Theme.key_dialogBackground)); actionBar.setItemsColor(forceDarkTheme ? getThemedColor(Theme.key_voipgroup_actionBarItems) : getThemedColor(Theme.key_dialogTextBlack), false); @@ -4343,11 +4641,11 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N int t = scrollOffset - backgroundPaddingTop; float toMove; if (layout == pollLayout) { - t -= AndroidUtilities.dp(13); - toMove = AndroidUtilities.dp(11); + t -= dp(13); + toMove = dp(11); } else { - t -= AndroidUtilities.dp(39); - toMove = AndroidUtilities.dp(43); + t -= dp(39); + toMove = dp(43); } if (t + backgroundPaddingTop < ActionBar.getCurrentActionBarHeight()) { moveProgress = Math.min(1.0f, (ActionBar.getCurrentActionBarHeight() - t - backgroundPaddingTop) / toMove); @@ -4366,11 +4664,11 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N finalMove = 12; } - float offset = actionBar.getAlpha() != 0 ? 0.0f : AndroidUtilities.dp(26 * (1.0f - headerView.getAlpha())); + float offset = actionBar.getAlpha() != 0 ? 0.0f : dp(26 * (1.0f - headerView.getAlpha())); if (menuShowed && avatarPicker == 0 && !storyMediaPicker) { - selectedMenuItem.setTranslationY(scrollOffset - AndroidUtilities.dp(37 + finalMove * moveProgress) + offset + currentPanTranslationY); + selectedMenuItem.setTranslationY(Math.max(ActionBar.getCurrentActionBarHeight() - dp(4) - dp(37 + finalMove), scrollOffset - dp(37 + finalMove * moveProgress) + offset - topCommentContainer.getMeasuredHeight() * topCommentContainer.getAlpha()) + currentPanTranslationY); } else { - selectedMenuItem.setTranslationY(ActionBar.getCurrentActionBarHeight() - AndroidUtilities.dp(4) - AndroidUtilities.dp(37 + finalMove) + currentPanTranslationY); + selectedMenuItem.setTranslationY(ActionBar.getCurrentActionBarHeight() - dp(4) - dp(37 + finalMove) + currentPanTranslationY); } float swapOffset = 0; if (isPhotoPicker && openTransitionFinished) { @@ -4381,9 +4679,14 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } } if (searchItem != null) { - searchItem.setTranslationY(ActionBar.getCurrentActionBarHeight() - AndroidUtilities.dp(4) - AndroidUtilities.dp(37 + finalMove) + currentPanTranslationY); + searchItem.setTranslationY(ActionBar.getCurrentActionBarHeight() - dp(4) - dp(37 + finalMove) + currentPanTranslationY); + } + baseSelectedTextViewTranslationY = scrollOffset - dp(25 + finalMove * moveProgress) + offset + currentPanTranslationY + swapOffset - topCommentContainer.getMeasuredHeight() * topCommentContainer.getAlpha(); + headerView.setTranslationY(Math.max(currentPanTranslationY, baseSelectedTextViewTranslationY)); + topCommentContainer.setTranslationY(Math.max(ActionBar.getCurrentActionBarHeight() + currentPanTranslationY, baseSelectedTextViewTranslationY + dp(26) * headerView.getAlpha() + dp(8))); + if (captionAbove) { + updateCommentTextViewPosition(); } - headerView.setTranslationY(baseSelectedTextViewTranslationY = scrollOffset - AndroidUtilities.dp(25 + finalMove * moveProgress) + offset + currentPanTranslationY + swapOffset); if (pollLayout != null && layout == pollLayout) { if (AndroidUtilities.isTablet()) { finalMove = 63; @@ -4392,7 +4695,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } else { finalMove = 59; } - doneItem.setTranslationY(Math.max(0, pollLayout.getTranslationY() + scrollOffset - AndroidUtilities.dp(7 + finalMove * moveProgress)) + currentPanTranslationY); + doneItem.setTranslationY(Math.max(0, pollLayout.getTranslationY() + scrollOffset - dp(7 + finalMove * moveProgress)) + currentPanTranslationY); } } @@ -4515,7 +4818,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N updateActionBarVisibility(show, true); } FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) layout.getLayoutParams(); - newOffset += (layoutParams == null ? 0 : layoutParams.topMargin) - AndroidUtilities.dp(11); + newOffset += (layoutParams == null ? 0 : layoutParams.topMargin) - dp(11); int idx = currentAttachLayout == layout ? 0 : 1; boolean previewAnimationIsRunning = (currentAttachLayout instanceof ChatAttachAlertPhotoLayoutPreview || nextAttachLayout instanceof ChatAttachAlertPhotoLayoutPreview) && (viewChangeAnimator instanceof SpringAnimation && ((SpringAnimation) viewChangeAnimator).isRunning()); if (scrollOffsetY[idx] != newOffset || previewAnimationIsRunning) { @@ -4678,6 +4981,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } photoLayout.onInit(videosEnabled, photosEnabled, documentsEnabled); commentTextView.hidePopup(true); + topCommentTextView.hidePopup(true); enterCommentEventSent = false; setFocusable(false); ChatAttachAlert.AttachAlertLayout layoutToSet; @@ -4752,6 +5056,8 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N layoutToSet.onShown(); actionBar.setVisibility(layoutToSet.needsActionBar() != 0 ? View.VISIBLE : View.INVISIBLE); actionBarShadow.setVisibility(actionBar.getVisibility()); + + setCaptionAbove(captionAbove, false); } if (currentAttachLayout != photoLayout) { photoLayout.setCheckCameraWhenShown(true); @@ -4759,7 +5065,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N updateCountButton(0); buttonsAdapter.notifyDataSetChanged(); - commentTextView.setText(""); + getCommentView().setText(""); buttonsLayoutManager.scrollToPositionWithOffset(0, 1000000); } @@ -4777,6 +5083,9 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N if (commentTextView != null) { commentTextView.onDestroy(); } + if (topCommentTextView != null) { + topCommentTextView.onDestroy(); + } } @Override @@ -4864,7 +5173,7 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N isStickerMode = true; this.customStickerHandler = customStickerHandler; if (optionsItem != null) { - selectedTextView.setTranslationY(-AndroidUtilities.dp(8)); + selectedTextView.setTranslationY(-dp(8)); optionsItem.setVisibility(View.VISIBLE); optionsItem.setClickable(true); optionsItem.setAlpha(1f); @@ -5195,13 +5504,17 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N if (currentAttachLayout.onBackPressed()) { return; } - if (commentTextView != null && commentTextView.isPopupShowing()) { - commentTextView.hidePopup(true); + if (getCommentView() != null && getCommentView().isPopupShowing()) { + getCommentView().hidePopup(true); return; } super.onBackPressed(); } + public EditTextEmoji getCommentView() { + return captionAbove && (currentAttachLayout == photoLayout || currentAttachLayout == photoPreviewLayout) ? topCommentTextView : commentTextView; + } + @Override public void dismissWithButtonClick(int item) { super.dismissWithButtonClick(item); @@ -5238,6 +5551,9 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N if (commentTextView != null) { AndroidUtilities.hideKeyboard(commentTextView.getEditText()); } + if (topCommentTextView != null) { + AndroidUtilities.hideKeyboard(topCommentTextView.getEditText()); + } botAttachLayouts.clear(); BaseFragment baseFragment = this.baseFragment; if (baseFragment == null) { @@ -5249,27 +5565,27 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } confirmationAlertShown = true; AlertDialog dialog = - new AlertDialog.Builder(baseFragment.getParentActivity(), resourcesProvider) - .setTitle(getString("DiscardSelectionAlertTitle", R.string.DiscardSelectionAlertTitle)) - .setMessage(getString("DiscardSelectionAlertMessage", R.string.DiscardSelectionAlertMessage)) - .setPositiveButton(getString("PassportDiscard", R.string.PassportDiscard), (dialogInterface, i) -> { - allowPassConfirmationAlert = true; - dismiss(); - }) - .setNegativeButton(getString("Cancel", R.string.Cancel), null) - .setOnCancelListener(di -> { - if (appearSpringAnimation != null) { - appearSpringAnimation.cancel(); - } - appearSpringAnimation = new SpringAnimation(super.containerView, DynamicAnimation.TRANSLATION_Y, 0); - appearSpringAnimation.getSpring().setDampingRatio(1.5f); - appearSpringAnimation.getSpring().setStiffness(1500.0f); - appearSpringAnimation.start(); - }) - .setOnPreDismissListener(di -> { - confirmationAlertShown = false; - }) - .create(); + new AlertDialog.Builder(baseFragment.getParentActivity(), resourcesProvider) + .setTitle(getString(R.string.DiscardSelectionAlertTitle)) + .setMessage(getString(R.string.DiscardSelectionAlertMessage)) + .setPositiveButton(getString(R.string.Discard), (dialogInterface, i) -> { + allowPassConfirmationAlert = true; + dismiss(); + }) + .setNegativeButton(getString(R.string.Cancel), null) + .setOnCancelListener(di -> { + if (appearSpringAnimation != null) { + appearSpringAnimation.cancel(); + } + appearSpringAnimation = new SpringAnimation(super.containerView, DynamicAnimation.TRANSLATION_Y, 0); + appearSpringAnimation.getSpring().setDampingRatio(1.5f); + appearSpringAnimation.getSpring().setStiffness(1500.0f); + appearSpringAnimation.start(); + }) + .setOnPreDismissListener(di -> { + confirmationAlertShown = false; + }) + .create(); dialog.show(); TextView button = (TextView) dialog.getButton(DialogInterface.BUTTON_POSITIVE); if (button != null) { @@ -5329,17 +5645,17 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } private void replaceWithText(int start, int len, CharSequence text, boolean parseEmoji) { - if (commentTextView == null) { + if (getCommentView() == null) { return; } try { - SpannableStringBuilder builder = new SpannableStringBuilder(commentTextView.getText()); + SpannableStringBuilder builder = new SpannableStringBuilder(getCommentView().getText()); builder.replace(start, start + len, text); if (parseEmoji) { - Emoji.replaceEmoji(builder, commentTextView.getEditText().getPaint().getFontMetricsInt(), AndroidUtilities.dp(20), false); + Emoji.replaceEmoji(builder, getCommentView().getEditText().getPaint().getFontMetricsInt(), dp(20), false); } - commentTextView.setText(builder); - commentTextView.setSelection(start + text.length()); + getCommentView().setText(builder); + getCommentView().setSelection(start + text.length()); } catch (Exception e) { FileLog.e(e); } @@ -5362,26 +5678,22 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } } }; - setupMentionContainer(); mentionContainer.withDelegate(new MentionsContainerView.Delegate() { - @Override public void replaceText(int start, int len, CharSequence replacingString, boolean allowShort) { replaceWithText(start, len, replacingString, allowShort); } - @Override public Paint.FontMetricsInt getFontMetrics() { return commentTextView.getEditText().getPaint().getFontMetricsInt(); } }); containerView.addView(mentionContainer, containerView.indexOfChild(frameLayout2), LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.BOTTOM)); - mentionContainer.setTranslationY(-commentTextView.getHeight()); - - setupMentionContainer(); + setupMentionContainer(mentionContainer); + updateCommentTextViewPosition(); } - protected void setupMentionContainer() { + protected void setupMentionContainer(MentionsContainerView mentionContainer) { mentionContainer.getAdapter().setAllowStickers(false); mentionContainer.getAdapter().setAllowBots(false); mentionContainer.getAdapter().setAllowChats(false); @@ -5397,4 +5709,102 @@ public class ChatAttachAlert extends BottomSheet implements NotificationCenter.N } mentionContainer.getAdapter().setNeedBotContext(false); } + + public void setCaptionAbove(boolean above) { + setCaptionAbove(above, true); + } + public void setCaptionAbove(boolean above, boolean animated) { + final EditTextEmoji fromView = getCommentView(); + captionAbove = above; + final EditTextEmoji toView = getCommentView(); + + final boolean allowCaption = (frameLayout2.getTag() != null); + final boolean allowCaptionAbove = currentAttachLayout == photoLayout || currentAttachLayout == photoPreviewLayout; + final boolean showAbove = captionAbove && allowCaptionAbove; + if (animated) { + topCommentContainer.setVisibility(allowCaption ? View.VISIBLE : View.GONE); + topCommentContainer.animate() + .alpha(showAbove && allowCaption ? 1.0f : 0.0f) + .setDuration(320) + .setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT) + .setUpdateListener(a -> { + updatedTopCaptionHeight(); + }) + .withEndAction(() -> { + if (!(showAbove && allowCaption)) { + topCommentContainer.setVisibility(View.GONE); + } + updatedTopCaptionHeight(); + }) + .start(); + + captionContainer.setVisibility(View.VISIBLE); + if (moveCaptionButton != null) { + moveCaptionButton.setVisibility(View.VISIBLE); + } + captionContainer.animate() + .translationY(!showAbove && allowCaption ? 0 : captionContainer.getMeasuredHeight()) + .alpha(!showAbove && allowCaption ? 1.0f : 0.0f) + .setDuration(320) + .setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT) + .setUpdateListener(a -> { + if (moveCaptionButton != null) { + moveCaptionButton.setTranslationY(bottomPannelTranslation - commentTextView.getHeight() + captionContainer.getTranslationY()); + moveCaptionButton.setAlpha(captionContainer.getAlpha()); + } + frameLayout2.invalidate(); + }) + .withEndAction(() -> { + if (showAbove || !allowCaption) { + captionContainer.setVisibility(View.GONE); + if (moveCaptionButton != null) { + moveCaptionButton.setVisibility(View.GONE); + } + } + }) + .start(); + } else { + topCommentContainer.setVisibility(showAbove && allowCaption ? View.VISIBLE : View.GONE); + topCommentContainer.setAlpha(showAbove && allowCaption ? 1.0f : 0.0f); + updatedTopCaptionHeight(); + + captionContainer.setAlpha(!showAbove && allowCaption ? 1.0f : 0.0f); + captionContainer.setTranslationY(!showAbove && allowCaption ? 0 : captionContainer.getMeasuredHeight()); + captionContainer.setVisibility(!showAbove && allowCaption ? View.VISIBLE : View.GONE); + moveCaptionButton.setAlpha(!showAbove && allowCaption ? 1.0f : 0.0f); + moveCaptionButton.setVisibility(!showAbove && allowCaption ? View.VISIBLE : View.GONE); + } + + if (fromView != toView) { + fromView.hidePopup(true); + toView.setText(AnimatedEmojiSpan.cloneSpans(fromView.getText())); + toView.getEditText().setAllowTextEntitiesIntersection(fromView.getEditText().getAllowTextEntitiesIntersection()); + if (fromView.getEditText().isFocused()) { + toView.getEditText().requestFocus(); + toView.getEditText().setSelection( + fromView.getEditText().getSelectionStart(), + fromView.getEditText().getSelectionEnd() + ); + } + } + } + + private void updatedTopCaptionHeight() { + actionBarShadow.setTranslationY(currentPanTranslationY + topCommentContainer.getMeasuredHeight() * topCommentContainer.getAlpha()); + updateSelectedPosition(0); + sizeNotifierFrameLayout.invalidate(); + topCommentContainer.invalidate(); + if (photoLayout != null) { + photoLayout.checkCameraViewPosition(); + if (photoLayout.gridView != null && photoLayout.gridView.getFastScroll() != null) { + photoLayout.gridView.getFastScroll().topOffset = captionAbove ? (int) (topCommentContainer.getMeasuredHeight() * topCommentContainer.getAlpha()) : 0; + photoLayout.gridView.getFastScroll().invalidate(); + } + } + updateCommentTextViewPosition(); + } + + private void toggleCaptionAbove() { + setCaptionAbove(!captionAbove); + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertAudioLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertAudioLayout.java index 077ac500e..dba54760e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertAudioLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertAudioLayout.java @@ -467,7 +467,7 @@ public class ChatAttachAlertAudioLayout extends ChatAttachAlert.AttachAlertLayou sendPressed = true; ArrayList audios = new ArrayList<>(); audios.add(audioEntry.messageObject); - delegate.didSelectAudio(audios, parentAlert.commentTextView.getText(), false, 0, 0, false); + delegate.didSelectAudio(audios, parentAlert.getCommentView().getText(), false, 0, 0, false); add = true; } else if (selectedAudios.indexOfKey(audioEntry.id) >= 0) { selectedAudios.remove(audioEntry.id); @@ -502,7 +502,7 @@ public class ChatAttachAlertAudioLayout extends ChatAttachAlert.AttachAlertLayou for (int a = 0; a < selectedAudiosOrder.size(); a++) { audios.add(selectedAudiosOrder.get(a).messageObject); } - delegate.didSelectAudio(audios, parentAlert.commentTextView.getText(), notify, scheduleDate, effectId, invertMedia); + delegate.didSelectAudio(audios, parentAlert.getCommentView().getText(), notify, scheduleDate, effectId, invertMedia); } public ArrayList getSelected() { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertContactsLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertContactsLayout.java index 00378d5ec..7ee847575 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertContactsLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertContactsLayout.java @@ -704,7 +704,7 @@ public class ChatAttachAlertContactsLayout extends ChatAttachAlert.AttachAlertLa users.add(prepareContact(object)); } - delegate.didSelectContacts(users, parentAlert.commentTextView.getText().toString(), notify, scheduleDate, effectId, invertMedia); + delegate.didSelectContacts(users, parentAlert.getCommentView().getText().toString(), notify, scheduleDate, effectId, invertMedia); } public ArrayList getSelected() { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertDocumentLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertDocumentLayout.java index 274d4c6da..1ff7c2d1c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertDocumentLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertDocumentLayout.java @@ -764,7 +764,7 @@ public class ChatAttachAlertDocumentLayout extends ChatAttachAlert.AttachAlertLa fmessages.add(selectedMessages.get(hashId)); } ArrayList files = new ArrayList<>(selectedFilesOrder); - delegate.didSelectFiles(files, parentAlert.commentTextView.getText().toString(), fmessages, notify, scheduleDate, effectId, invertMedia); + delegate.didSelectFiles(files, parentAlert.getCommentView().getText().toString(), fmessages, notify, scheduleDate, effectId, invertMedia); parentAlert.dismiss(true); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertPhotoLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertPhotoLayout.java index 20989c929..af0f64b83 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertPhotoLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertPhotoLayout.java @@ -10,6 +10,7 @@ package org.telegram.ui.Components; import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; +import static org.telegram.messenger.AndroidUtilities.dp; import static org.telegram.messenger.LocaleController.formatPluralString; import static org.telegram.messenger.LocaleController.getString; @@ -41,6 +42,7 @@ import android.provider.Settings; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.TextUtils; +import android.util.Log; import android.util.Pair; import android.util.TypedValue; import android.view.Gravity; @@ -203,7 +205,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou private boolean cameraPhotoRecyclerViewIgnoreLayout; - private int itemSize = AndroidUtilities.dp(80); + private int itemSize = dp(80); private int lastItemSize = itemSize; private int itemsPerRow = 3; @@ -482,7 +484,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou MessageObject.addEntitiesToText(firstPhotoCaption, entities, false, false, false, false); } } - parentAlert.commentTextView.setText(AnimatedEmojiSpan.cloneSpans(firstPhotoCaption, AnimatedEmojiDrawable.CACHE_TYPE_ALERT_PREVIEW)); + parentAlert.getCommentView().setText(AnimatedEmojiSpan.cloneSpans(firstPhotoCaption, AnimatedEmojiDrawable.CACHE_TYPE_ALERT_PREVIEW)); } } @@ -501,7 +503,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou if (selectedPhotos.isEmpty() && photoEntry != null) { addToSelectedPhotos(photoEntry, -1); } - if (parentAlert.checkCaption(parentAlert.commentTextView.getText())) { + if (parentAlert.checkCaption(parentAlert.getCommentView().getText())) { return; } parentAlert.applyCaption(); @@ -527,7 +529,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou } } } - parentAlert.delegate.didPressedButton(7, true, notify, scheduleDate, 0, false, forceDocument); + parentAlert.delegate.didPressedButton(7, true, notify, scheduleDate, 0, parentAlert.isCaptionAbove(), forceDocument); selectedPhotos.clear(); cameraPhotos.clear(); selectedPhotosOrder.clear(); @@ -545,6 +547,21 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou return ((ChatActivity) parentAlert.baseFragment).getDialogId(); return super.getDialogId(); } + + @Override + public boolean canMoveCaptionAbove() { + return parentAlert != null && parentAlert.baseFragment instanceof ChatActivity; + } + @Override + public boolean isCaptionAbove() { + return parentAlert != null && parentAlert.captionAbove; + } + @Override + public void moveCaptionAbove(boolean above) { + if (parentAlert == null || parentAlert.captionAbove == above) return; + parentAlert.setCaptionAbove(above); + captionItem.setState(!parentAlert.captionAbove, true); + } }; protected void updateCheckedPhotoIndices() { @@ -686,8 +703,8 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou dropDown.setTypeface(AndroidUtilities.bold()); dropDownDrawable = context.getResources().getDrawable(R.drawable.ic_arrow_drop_down).mutate(); dropDownDrawable.setColorFilter(new PorterDuffColorFilter(getThemedColor(Theme.key_dialogTextBlack), PorterDuff.Mode.MULTIPLY)); - dropDown.setCompoundDrawablePadding(AndroidUtilities.dp(4)); - dropDown.setPadding(0, 0, AndroidUtilities.dp(10), 0); + dropDown.setCompoundDrawablePadding(dp(4)); + dropDown.setPadding(0, 0, dp(10), 0); dropDownContainer.addView(dropDown, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 16, 0, 0, 0)); checkCamera(false); @@ -715,7 +732,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou gridView = new RecyclerListView(context, resourcesProvider) { @Override public boolean onTouchEvent(MotionEvent e) { - if (e.getAction() == MotionEvent.ACTION_DOWN && e.getY() < parentAlert.scrollOffsetY[0] - AndroidUtilities.dp(80)) { + if (e.getAction() == MotionEvent.ACTION_DOWN && e.getY() < parentAlert.scrollOffsetY[0] - dp(80)) { return false; } return super.onTouchEvent(e); @@ -723,7 +740,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou @Override public boolean onInterceptTouchEvent(MotionEvent e) { - if (e.getAction() == MotionEvent.ACTION_DOWN && e.getY() < parentAlert.scrollOffsetY[0] - AndroidUtilities.dp(80)) { + if (e.getAction() == MotionEvent.ACTION_DOWN && e.getY() < parentAlert.scrollOffsetY[0] - dp(80)) { return false; } return super.onInterceptTouchEvent(e); @@ -772,13 +789,13 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { if (newState == RecyclerView.SCROLL_STATE_IDLE) { - int offset = AndroidUtilities.dp(13) + (parentAlert.selectedMenuItem != null ? AndroidUtilities.dp(parentAlert.selectedMenuItem.getAlpha() * 26) : 0); + int offset = dp(13) + (parentAlert.selectedMenuItem != null ? dp(parentAlert.selectedMenuItem.getAlpha() * 26) : 0); int backgroundPaddingTop = parentAlert.getBackgroundPaddingTop(); int top = parentAlert.scrollOffsetY[0] - backgroundPaddingTop - offset; - if (top + backgroundPaddingTop < ActionBar.getCurrentActionBarHeight()) { + if (top + backgroundPaddingTop < ActionBar.getCurrentActionBarHeight() + parentAlert.topCommentContainer.getMeasuredHeight() * parentAlert.topCommentContainer.getAlpha()) { RecyclerListView.Holder holder = (RecyclerListView.Holder) gridView.findViewHolderForAdapterPosition(0); - if (holder != null && holder.itemView.getTop() > AndroidUtilities.dp(7)) { - gridView.smoothScrollBy(0, holder.itemView.getTop() - AndroidUtilities.dp(7)); + if (holder != null && holder.itemView.getTop() > dp(7)) { + gridView.smoothScrollBy(0, holder.itemView.getTop() - dp(7)); } } } @@ -796,7 +813,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou @Override public int calculateDyToMakeVisible(View view, int snapPreference) { int dy = super.calculateDyToMakeVisible(view, snapPreference); - dy -= (gridView.getPaddingTop() - AndroidUtilities.dp(7)); + dy -= (gridView.getPaddingTop() - dp(7)); return dy; } @@ -815,7 +832,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou if (position == adapter.itemsCount - 1) { return layoutManager.getSpanCount(); } - return itemSize + (position % itemsPerRow != itemsPerRow - 1 ? AndroidUtilities.dp(5) : 0); + return itemSize + (position % itemsPerRow != itemsPerRow - 1 ? dp(5) : 0); } }); gridView.setLayoutManager(layoutManager); @@ -876,7 +893,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou addToSelectedPhotos(photoEntry, -1); } parentAlert.applyCaption(); - parentAlert.delegate.didPressedButton(7, true, true, 0, 0, false, false); + parentAlert.delegate.didPressedButton(7, true, true, 0, 0, parentAlert.isCaptionAbove(), false); selectedPhotos.clear(); cameraPhotos.clear(); selectedPhotosOrder.clear(); @@ -916,11 +933,11 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou Object o = selectedPhotos.get(selectedPhotosOrder.get(0)); if (o instanceof MediaController.PhotoEntry) { MediaController.PhotoEntry photoEntry1 = (MediaController.PhotoEntry) o; - photoEntry1.caption = parentAlert.getCommentTextView().getText(); + photoEntry1.caption = parentAlert.getCommentView().getText(); } if (o instanceof MediaController.SearchImage) { MediaController.SearchImage photoEntry1 = (MediaController.SearchImage) o; - photoEntry1.caption = parentAlert.getCommentTextView().getText(); + photoEntry1.caption = parentAlert.getCommentView().getText(); } } if (parentAlert.getAvatarFor() != null) { @@ -960,7 +977,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou PhotoViewer.getInstance().enableStickerMode(null, false, parentAlert.customStickerHandler); } if (captionForAllMedia()) { - PhotoViewer.getInstance().setCaption(parentAlert.getCommentTextView().getText()); + PhotoViewer.getInstance().setCaption(parentAlert.getCommentView().getText()); } }, hasSpoiler ? 250 : 0); } else { @@ -968,7 +985,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou openCamera(true); } else { if (parentAlert.delegate != null) { - parentAlert.delegate.didPressedButton(0, false, true, 0, 0, false, false); + parentAlert.delegate.didPressedButton(0, false, true, 0, 0, parentAlert.isCaptionAbove(), false); } } } @@ -979,7 +996,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou } if (position == 0 && selectedAlbumEntry == galleryAlbumEntry) { if (parentAlert.delegate != null) { - parentAlert.delegate.didPressedButton(0, false, true, 0, 0, false, false); + parentAlert.delegate.didPressedButton(0, false, true, 0, 0, parentAlert.isCaptionAbove(), false); } return true; } else if (view instanceof PhotoAttachPhotoCell) { @@ -1060,7 +1077,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou } } super.onDraw(canvas); - canvas.drawCircle(AndroidUtilities.dp(14), getMeasuredHeight() / 2, AndroidUtilities.dp(4), recordPaint); + canvas.drawCircle(dp(14), getMeasuredHeight() / 2, dp(4), recordPaint); invalidate(); } }; @@ -1071,7 +1088,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou recordTime.setTypeface(AndroidUtilities.bold()); recordTime.setAlpha(0.0f); recordTime.setTextColor(0xffffffff); - recordTime.setPadding(AndroidUtilities.dp(24), AndroidUtilities.dp(5), AndroidUtilities.dp(10), AndroidUtilities.dp(5)); + recordTime.setPadding(dp(24), dp(5), dp(10), dp(5)); container.addView(recordTime, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 0, 16, 0, 0)); cameraPanel = new FrameLayout(context) { @@ -1084,22 +1101,22 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou int cx3; int cy3; - if (getMeasuredWidth() == AndroidUtilities.dp(126)) { + if (getMeasuredWidth() == dp(126)) { cx = getMeasuredWidth() / 2; cy = getMeasuredHeight() / 2; cx3 = cx2 = getMeasuredWidth() / 2; - cy2 = cy + cy / 2 + AndroidUtilities.dp(17); - cy3 = cy / 2 - AndroidUtilities.dp(17); + cy2 = cy + cy / 2 + dp(17); + cy3 = cy / 2 - dp(17); } else { cx = getMeasuredWidth() / 2; - cy = getMeasuredHeight() / 2 - AndroidUtilities.dp(13); - cx2 = cx + cx / 2 + AndroidUtilities.dp(17); - cx3 = cx / 2 - AndroidUtilities.dp(17); - cy3 = cy2 = getMeasuredHeight() / 2 - AndroidUtilities.dp(13); + cy = getMeasuredHeight() / 2 - dp(13); + cx2 = cx + cx / 2 + dp(17); + cx3 = cx / 2 - dp(17); + cy3 = cy2 = getMeasuredHeight() / 2 - dp(13); } - int y = getMeasuredHeight() - tooltipTextView.getMeasuredHeight() - AndroidUtilities.dp(12); - if (getMeasuredWidth() == AndroidUtilities.dp(126)) { + int y = getMeasuredHeight() - tooltipTextView.getMeasuredHeight() - dp(12); + if (getMeasuredWidth() == dp(126)) { tooltipTextView.layout(cx - tooltipTextView.getMeasuredWidth() / 2, getMeasuredHeight(), cx + tooltipTextView.getMeasuredWidth() / 2, getMeasuredHeight() + tooltipTextView.getMeasuredHeight()); } else { tooltipTextView.layout(cx - tooltipTextView.getMeasuredWidth() / 2, y, cx + tooltipTextView.getMeasuredWidth() / 2, y + tooltipTextView.getMeasuredHeight()); @@ -1124,8 +1141,8 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou counterTextView.setPivotY(0); counterTextView.setTypeface(AndroidUtilities.bold()); counterTextView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.photos_arrow, 0); - counterTextView.setCompoundDrawablePadding(AndroidUtilities.dp(4)); - counterTextView.setPadding(AndroidUtilities.dp(16), 0, AndroidUtilities.dp(16), 0); + counterTextView.setCompoundDrawablePadding(dp(4)); + counterTextView.setPadding(dp(16), 0, dp(16), 0); container.addView(counterTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 38, Gravity.LEFT | Gravity.TOP, 0, 0, 0, 100 + 16)); counterTextView.setOnClickListener(v -> { if (cameraView == null) { @@ -1180,9 +1197,9 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou } } for (int a = 0; a < 2; a++) { - flashModeButton[a].animate().alpha(0f).translationX(AndroidUtilities.dp(30)).setDuration(150).setInterpolator(CubicBezierInterpolator.DEFAULT).start(); + flashModeButton[a].animate().alpha(0f).translationX(dp(30)).setDuration(150).setInterpolator(CubicBezierInterpolator.DEFAULT).start(); } - switchCameraButton.animate().alpha(0f).translationX(-AndroidUtilities.dp(30)).setDuration(150).setInterpolator(CubicBezierInterpolator.DEFAULT).start(); + switchCameraButton.animate().alpha(0f).translationX(-dp(30)).setDuration(150).setInterpolator(CubicBezierInterpolator.DEFAULT).start(); tooltipTextView.animate().alpha(0f).setDuration(150).setInterpolator(CubicBezierInterpolator.DEFAULT).start(); outputFile = AndroidUtilities.generateVideoPath(parentAlert.baseFragment instanceof ChatActivity && ((ChatActivity) parentAlert.baseFragment).isSecretChat()); AndroidUtilities.updateViewVisibilityAnimated(recordTime, true); @@ -1287,7 +1304,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou } if (val2 < 0) { showZoomControls(true, true); - zoomControlView.setZoom(-val2 / AndroidUtilities.dp(200), true); + zoomControlView.setZoom(-val2 / dp(200), true); zoomingWas = true; return false; } @@ -1346,8 +1363,8 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou setCameraFlashModeIcon(nextImage, next); AnimatorSet animatorSet = new AnimatorSet(); animatorSet.playTogether( - ObjectAnimator.ofFloat(currentImage, View.TRANSLATION_Y, 0, AndroidUtilities.dp(48)), - ObjectAnimator.ofFloat(nextImage, View.TRANSLATION_Y, -AndroidUtilities.dp(48), 0), + ObjectAnimator.ofFloat(currentImage, View.TRANSLATION_Y, 0, dp(48)), + ObjectAnimator.ofFloat(nextImage, View.TRANSLATION_Y, -dp(48), 0), ObjectAnimator.ofFloat(currentImage, View.ALPHA, 1.0f, 0.0f), ObjectAnimator.ofFloat(nextImage, View.ALPHA, 0.0f, 1.0f)); animatorSet.setDuration(220); @@ -1369,8 +1386,8 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou tooltipTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); tooltipTextView.setTextColor(0xffffffff); tooltipTextView.setText(LocaleController.getString(R.string.TapForVideo)); - tooltipTextView.setShadowLayer(AndroidUtilities.dp(3.33333f), 0, AndroidUtilities.dp(0.666f), 0x4c000000); - tooltipTextView.setPadding(AndroidUtilities.dp(6), 0, AndroidUtilities.dp(6), 0); + tooltipTextView.setShadowLayer(dp(3.33333f), 0, dp(0.666f), 0x4c000000); + tooltipTextView.setPadding(dp(6), 0, dp(6), 0); cameraPanel.addView(tooltipTextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, 0, 0, 0, 16)); cameraPhotoRecyclerView = new RecyclerListView(context, resourcesProvider) { @@ -1386,7 +1403,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou cameraPhotoRecyclerView.setAdapter(cameraAttachAdapter = new PhotoAttachAdapter(context, false)); cameraAttachAdapter.createCache(); cameraPhotoRecyclerView.setClipToPadding(false); - cameraPhotoRecyclerView.setPadding(AndroidUtilities.dp(8), 0, AndroidUtilities.dp(8), 0); + cameraPhotoRecyclerView.setPadding(dp(8), 0, dp(8), 0); cameraPhotoRecyclerView.setItemAnimator(null); cameraPhotoRecyclerView.setLayoutAnimation(null); cameraPhotoRecyclerView.setOverScrollMode(RecyclerListView.OVER_SCROLL_NEVER); @@ -1575,7 +1592,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou } selectedPhotos.put(-1, photoEntry); selectedPhotosOrder.add(-1); - parentAlert.delegate.didPressedButton(7, true, false, 0, 0, false, false); + parentAlert.delegate.didPressedButton(7, true, false, 0, 0, parentAlert.isCaptionAbove(), false); if (!avatarConstructorFragment.finishOnDone) { if (parentAlert.baseFragment != null) { parentAlert.baseFragment.removeSelfFromStack(); @@ -1807,7 +1824,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou } } else { if (cameraView != null) { - float diff = (newDistance - pinchStartDistance) / AndroidUtilities.dp(100); + float diff = (newDistance - pinchStartDistance) / dp(100); pinchStartDistance = newDistance; cameraZoom += diff; if (cameraZoom < 0.0f) { @@ -2032,7 +2049,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou public void needAddMorePhotos() { cancelTakingPhotos = false; if (mediaFromExternalCamera) { - parentAlert.delegate.didPressedButton(0, true, true, 0, 0, false, false); + parentAlert.delegate.didPressedButton(0, true, true, 0, 0, parentAlert.isCaptionAbove(), false); return; } if (!cameraOpened) { @@ -2065,7 +2082,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou } parentAlert.applyCaption(); closeCamera(false); - parentAlert.delegate.didPressedButton(forceDocument ? 4 : 8, true, notify, scheduleDate, 0, false, forceDocument); + parentAlert.delegate.didPressedButton(forceDocument ? 4 : 8, true, notify, scheduleDate, 0, parentAlert.isCaptionAbove(), forceDocument); cameraPhotos.clear(); selectedPhotosOrder.clear(); selectedPhotos.clear(); @@ -2290,8 +2307,8 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou counterTextView.setVisibility(View.VISIBLE); cameraPhotoRecyclerView.setVisibility(View.VISIBLE); } - if (parentAlert.commentTextView.isKeyboardVisible() && isFocusable()) { - parentAlert.commentTextView.closeKeyboard(); + if (parentAlert.getCommentView().isKeyboardVisible() && isFocusable()) { + parentAlert.getCommentView().closeKeyboard(); } zoomControlView.setVisibility(View.VISIBLE); zoomControlView.setAlpha(0.0f); @@ -2404,7 +2421,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou Bulletin.Delegate bulletinDelegate = new Bulletin.Delegate() { @Override public int getBottomOffset(int tag) { - return AndroidUtilities.dp(126) + parentAlert.getBottomInset(); + return dp(126) + parentAlert.getBottomInset(); } }; @Override @@ -2415,7 +2432,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou if (Build.VERSION.SDK_INT >= 21) { super.dispatchDraw(canvas); } else { - int maxY = (int) Math.min(parentAlert.getCommentTextViewTop() + currentPanTranslationY + parentAlert.getContainerView().getTranslationY() - cameraView.getTranslationY() - (parentAlert.mentionContainer != null ? parentAlert.mentionContainer.clipBottom() + AndroidUtilities.dp(8) : 0), getMeasuredHeight()); + int maxY = (int) Math.min(parentAlert.getCommentTextViewTop() + currentPanTranslationY + parentAlert.getContainerView().getTranslationY() - cameraView.getTranslationY() - (parentAlert.mentionContainer != null ? parentAlert.mentionContainer.clipBottom() + dp(8) : 0), getMeasuredHeight()); if (cameraAnimationInProgress) { AndroidUtilities.rectTmp.set(animationClipLeft + cameraViewOffsetX * (1f - cameraOpenProgress), animationClipTop + cameraViewOffsetY * (1f - cameraOpenProgress), animationClipRight, Math.min(maxY, animationClipBottom)); } else if (!cameraAnimationInProgress && !cameraOpened) { @@ -2452,7 +2469,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou cameraView.setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { - int maxY = (int) Math.min(parentAlert.getCommentTextViewTop() - (parentAlert.mentionContainer != null ? parentAlert.mentionContainer.clipBottom() + AndroidUtilities.dp(8) : 0) + currentPanTranslationY + parentAlert.getContainerView().getTranslationY() - cameraView.getTranslationY(), view.getMeasuredHeight()); + int maxY = (int) Math.min(parentAlert.getCommentTextViewTop() - (parentAlert.mentionContainer != null ? parentAlert.mentionContainer.clipBottom() + dp(8) : 0) + currentPanTranslationY + parentAlert.getContainerView().getTranslationY() - cameraView.getTranslationY(), view.getMeasuredHeight()); if (cameraOpened) { maxY = view.getMeasuredHeight(); } else if (cameraAnimationInProgress) { @@ -2462,7 +2479,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou AndroidUtilities.rectTmp.set(animationClipLeft + cameraViewOffsetX * (1f - cameraOpenProgress), animationClipTop + cameraViewOffsetY * (1f - cameraOpenProgress), animationClipRight, animationClipBottom); outline.setRect((int) AndroidUtilities.rectTmp.left,(int) AndroidUtilities.rectTmp.top, (int) AndroidUtilities.rectTmp.right, Math.min(maxY, (int) AndroidUtilities.rectTmp.bottom)); } else if (!cameraAnimationInProgress && !cameraOpened) { - int rad = AndroidUtilities.dp(8 * parentAlert.cornerRadius); + int rad = dp(8 * parentAlert.cornerRadius); outline.setRoundRect((int) cameraViewOffsetX, (int) cameraViewOffsetY, view.getMeasuredWidth() + rad, Math.min(maxY, view.getMeasuredHeight()) + rad, rad); } else { outline.setRect(0, 0, view.getMeasuredWidth(), Math.min(maxY, view.getMeasuredHeight())); @@ -2978,7 +2995,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && recordTime != null) { MarginLayoutParams params = (MarginLayoutParams) recordTime.getLayoutParams(); - params.topMargin = (getRootWindowInsets() == null ? AndroidUtilities.dp(16) : getRootWindowInsets().getSystemWindowInsetTop() + AndroidUtilities.dp(2)); + params.topMargin = (getRootWindowInsets() == null ? dp(16) : getRootWindowInsets().getSystemWindowInsetTop() + dp(2)); } if (!deviceHasGoodCamera) { @@ -3001,7 +3018,10 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou left -= getRootWindowInsets().getSystemWindowInsetLeft(); } - float maxY = (Build.VERSION.SDK_INT >= 21 && !parentAlert.inBubbleMode ? AndroidUtilities.statusBarHeight : 0) + ActionBar.getCurrentActionBarHeight(); + float maxY = (Build.VERSION.SDK_INT >= 21 && !parentAlert.inBubbleMode ? AndroidUtilities.statusBarHeight : 0) + ActionBar.getCurrentActionBarHeight() + parentAlert.topCommentContainer.getMeasuredHeight() * parentAlert.topCommentContainer.getAlpha(); + if (parentAlert.mentionContainer != null && parentAlert.mentionContainer.isReversed()) { + maxY = Math.max(maxY, parentAlert.mentionContainer.getY() + parentAlert.mentionContainer.clipTop() - parentAlert.currentPanTranslationY); + } float newCameraViewOffsetY; if (topLocal < maxY) { newCameraViewOffsetY = maxY - topLocal; @@ -3026,11 +3046,11 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou int containerHeight = parentAlert.getSheetContainer().getMeasuredHeight(); maxY = (int) (containerHeight - parentAlert.buttonsRecyclerView.getMeasuredHeight() + parentAlert.buttonsRecyclerView.getTranslationY()); if (parentAlert.mentionContainer != null) { - maxY -= parentAlert.mentionContainer.clipBottom() - AndroidUtilities.dp(6); + maxY -= parentAlert.mentionContainer.clipBottom() - dp(6); } if (topLocal + child.getMeasuredHeight() > maxY) { - cameraViewOffsetBottomY = Math.min(-AndroidUtilities.dp(5), topLocal - maxY) + child.getMeasuredHeight(); + cameraViewOffsetBottomY = Math.min(-dp(5), topLocal - maxY) + child.getMeasuredHeight(); } else { cameraViewOffsetBottomY = 0; } @@ -3058,7 +3078,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou } } - cameraViewLocation[0] = AndroidUtilities.dp(-400); + cameraViewLocation[0] = dp(-400); cameraViewLocation[1] = 0; applyCameraViewPosition(); @@ -3196,7 +3216,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou @Override public void onMenuItemClick(int id) { if (id == caption) { - parentAlert.captionAbove = !parentAlert.captionAbove; + parentAlert.setCaptionAbove(!parentAlert.captionAbove); captionItem.setState(!parentAlert.captionAbove, true); return; } @@ -3213,21 +3233,21 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou if (parentAlert.editingMessageObject == null && parentAlert.baseFragment instanceof ChatActivity && ((ChatActivity) parentAlert.baseFragment).isInScheduleMode()) { AlertsCreator.createScheduleDatePickerDialog(getContext(), ((ChatActivity) parentAlert.baseFragment).getDialogId(), (notify, scheduleDate) -> { parentAlert.applyCaption(); - parentAlert.delegate.didPressedButton(7, false, notify, scheduleDate, 0, false, false); + parentAlert.delegate.didPressedButton(7, false, notify, scheduleDate, 0, parentAlert.isCaptionAbove(), false); }, resourcesProvider); } else { parentAlert.applyCaption(); - parentAlert.delegate.didPressedButton(7, false, true, 0, 0, false, false); + parentAlert.delegate.didPressedButton(7, false, true, 0, 0, parentAlert.isCaptionAbove(), false); } } else if (id == compress) { if (parentAlert.editingMessageObject == null && parentAlert.baseFragment instanceof ChatActivity && ((ChatActivity) parentAlert.baseFragment).isInScheduleMode()) { AlertsCreator.createScheduleDatePickerDialog(getContext(), ((ChatActivity) parentAlert.baseFragment).getDialogId(), (notify, scheduleDate) -> { parentAlert.applyCaption(); - parentAlert.delegate.didPressedButton(4, true, notify, scheduleDate, 0, false, false); + parentAlert.delegate.didPressedButton(4, true, notify, scheduleDate, 0, parentAlert.isCaptionAbove(), false); }, resourcesProvider); } else { parentAlert.applyCaption(); - parentAlert.delegate.didPressedButton(4, true, true, 0, 0, false, false); + parentAlert.delegate.didPressedButton(4, true, true, 0, 0, parentAlert.isCaptionAbove(), false); } } else if (id == spoiler) { if (parentAlert.getPhotoPreviewLayout() != null) { @@ -3335,7 +3355,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou } adapter.notifyDataSetChanged(); cameraAttachAdapter.notifyDataSetChanged(); - layoutManager.scrollToPositionWithOffset(0, -gridView.getPaddingTop() + AndroidUtilities.dp(7)); + layoutManager.scrollToPositionWithOffset(0, -gridView.getPaddingTop() + dp(7)); } } @@ -3531,18 +3551,18 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou View child = gridView.getChildAt(0); RecyclerListView.Holder holder = (RecyclerListView.Holder) gridView.findContainingViewHolder(child); int top = child.getTop(); - int newOffset = AndroidUtilities.dp(7); - if (top >= AndroidUtilities.dp(7) && holder != null && holder.getAdapterPosition() == 0) { + int newOffset = dp(7); + if (top >= dp(7) && holder != null && holder.getAdapterPosition() == 0) { newOffset = top; } - progressView.setTranslationY(newOffset + (getMeasuredHeight() - newOffset - AndroidUtilities.dp(50) - progressView.getMeasuredHeight()) / 2); + progressView.setTranslationY(newOffset + (getMeasuredHeight() - newOffset - dp(50) - progressView.getMeasuredHeight()) / 2); gridView.setTopGlowOffset(newOffset); return currentItemTop = newOffset; } @Override public int getFirstOffset() { - return getListTopPadding() + AndroidUtilities.dp(56); + return getListTopPadding() + dp(56); } @Override @@ -3712,7 +3732,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou Runnable setScrollY = () -> { int currentItemTop = previousLayout.getCurrentItemTop(), paddingTop = previousLayout.getListTopPadding(); - gridView.scrollBy(0, (currentItemTop > AndroidUtilities.dp(8) ? paddingTop - currentItemTop : paddingTop)); + gridView.scrollBy(0, (currentItemTop > dp(8) ? paddingTop - currentItemTop : paddingTop)); }; gridView.post(setScrollY); } @@ -3886,17 +3906,17 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou LayoutParams layoutParams = (LayoutParams) getLayoutParams(); layoutParams.topMargin = ActionBar.getCurrentActionBarHeight(); - itemSize = (availableWidth - AndroidUtilities.dp(6 * 2) - AndroidUtilities.dp(5 * 2)) / itemsPerRow; + itemSize = (availableWidth - dp(6 * 2) - dp(5 * 2)) / itemsPerRow; if (lastItemSize != itemSize) { lastItemSize = itemSize; AndroidUtilities.runOnUIThread(() -> adapter.notifyDataSetChanged()); } - layoutManager.setSpanCount(Math.max(1, itemSize * itemsPerRow + AndroidUtilities.dp(5) * (itemsPerRow - 1))); + layoutManager.setSpanCount(Math.max(1, itemSize * itemsPerRow + dp(5) * (itemsPerRow - 1))); int rows = (int) Math.ceil((adapter.getItemCount() - 1) / (float) itemsPerRow); - int contentSize = rows * itemSize + (rows - 1) * AndroidUtilities.dp(5); - int newSize = Math.max(0, availableHeight - contentSize - ActionBar.getCurrentActionBarHeight() - AndroidUtilities.dp(48 + 12)); + int contentSize = rows * itemSize + (rows - 1) * dp(5); + int newSize = Math.max(0, availableHeight - contentSize - ActionBar.getCurrentActionBarHeight() - dp(48 + 12)); if (gridExtraSpace != newSize) { gridExtraSpace = newSize; adapter.notifyDataSetChanged(); @@ -3907,12 +3927,12 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou } else { paddingTop = (availableHeight / 5 * 2); } - paddingTop -= AndroidUtilities.dp(52); + paddingTop -= dp(52); if (paddingTop < 0) { paddingTop = 0; } if (gridView.getPaddingTop() != paddingTop) { - gridView.setPadding(AndroidUtilities.dp(6), paddingTop, AndroidUtilities.dp(6), AndroidUtilities.dp(48)); + gridView.setPadding(dp(6), paddingTop, dp(6), dp(48)); } dropDown.setTextSize(!AndroidUtilities.isTablet() && AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y ? 18 : 20); ignoreLayout = false; @@ -4011,31 +4031,31 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou } } else if (view == cameraPanel) { if (isPortrait) { - cameraPanel.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(126), View.MeasureSpec.EXACTLY)); + cameraPanel.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(dp(126), View.MeasureSpec.EXACTLY)); } else { - cameraPanel.measure(View.MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(126), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)); + cameraPanel.measure(View.MeasureSpec.makeMeasureSpec(dp(126), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)); } return true; } else if (view == zoomControlView) { if (isPortrait) { - zoomControlView.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(50), View.MeasureSpec.EXACTLY)); + zoomControlView.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(dp(50), View.MeasureSpec.EXACTLY)); } else { - zoomControlView.measure(View.MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(50), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)); + zoomControlView.measure(View.MeasureSpec.makeMeasureSpec(dp(50), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)); } return true; } else if (view == cameraPhotoRecyclerView) { cameraPhotoRecyclerViewIgnoreLayout = true; if (isPortrait) { - cameraPhotoRecyclerView.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(80), View.MeasureSpec.EXACTLY)); + cameraPhotoRecyclerView.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(dp(80), View.MeasureSpec.EXACTLY)); if (cameraPhotoLayoutManager.getOrientation() != LinearLayoutManager.HORIZONTAL) { - cameraPhotoRecyclerView.setPadding(AndroidUtilities.dp(8), 0, AndroidUtilities.dp(8), 0); + cameraPhotoRecyclerView.setPadding(dp(8), 0, dp(8), 0); cameraPhotoLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); cameraAttachAdapter.notifyDataSetChanged(); } } else { - cameraPhotoRecyclerView.measure(View.MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(80), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)); + cameraPhotoRecyclerView.measure(View.MeasureSpec.makeMeasureSpec(dp(80), View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY)); if (cameraPhotoLayoutManager.getOrientation() != LinearLayoutManager.VERTICAL) { - cameraPhotoRecyclerView.setPadding(0, AndroidUtilities.dp(8), 0, AndroidUtilities.dp(8)); + cameraPhotoRecyclerView.setPadding(0, dp(8), 0, dp(8)); cameraPhotoLayoutManager.setOrientation(LinearLayoutManager.VERTICAL); cameraAttachAdapter.notifyDataSetChanged(); } @@ -4054,30 +4074,30 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou if (view == cameraPanel) { if (isPortrait) { if (cameraPhotoRecyclerView.getVisibility() == View.VISIBLE) { - cameraPanel.layout(0, bottom - AndroidUtilities.dp(126 + 96), width, bottom - AndroidUtilities.dp(96)); + cameraPanel.layout(0, bottom - dp(126 + 96), width, bottom - dp(96)); } else { - cameraPanel.layout(0, bottom - AndroidUtilities.dp(126), width, bottom); + cameraPanel.layout(0, bottom - dp(126), width, bottom); } } else { if (cameraPhotoRecyclerView.getVisibility() == View.VISIBLE) { - cameraPanel.layout(right - AndroidUtilities.dp(126 + 96), 0, right - AndroidUtilities.dp(96), height); + cameraPanel.layout(right - dp(126 + 96), 0, right - dp(96), height); } else { - cameraPanel.layout(right - AndroidUtilities.dp(126), 0, right, height); + cameraPanel.layout(right - dp(126), 0, right, height); } } return true; } else if (view == zoomControlView) { if (isPortrait) { if (cameraPhotoRecyclerView.getVisibility() == View.VISIBLE) { - zoomControlView.layout(0, bottom - AndroidUtilities.dp(126 + 96 + 38 + 50), width, bottom - AndroidUtilities.dp(126 + 96 + 38)); + zoomControlView.layout(0, bottom - dp(126 + 96 + 38 + 50), width, bottom - dp(126 + 96 + 38)); } else { - zoomControlView.layout(0, bottom - AndroidUtilities.dp(126 + 50), width, bottom - AndroidUtilities.dp(126)); + zoomControlView.layout(0, bottom - dp(126 + 50), width, bottom - dp(126)); } } else { if (cameraPhotoRecyclerView.getVisibility() == View.VISIBLE) { - zoomControlView.layout(right - AndroidUtilities.dp(126 + 96 + 38 + 50), 0, right - AndroidUtilities.dp(126 + 96 + 38), height); + zoomControlView.layout(right - dp(126 + 96 + 38 + 50), 0, right - dp(126 + 96 + 38), height); } else { - zoomControlView.layout(right - AndroidUtilities.dp(126 + 50), 0, right - AndroidUtilities.dp(126), height); + zoomControlView.layout(right - dp(126 + 50), 0, right - dp(126), height); } } return true; @@ -4086,27 +4106,27 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou int cy; if (isPortrait) { cx = (width - counterTextView.getMeasuredWidth()) / 2; - cy = bottom - AndroidUtilities.dp(113 + 16 + 38); + cy = bottom - dp(113 + 16 + 38); counterTextView.setRotation(0); if (cameraPhotoRecyclerView.getVisibility() == View.VISIBLE) { - cy -= AndroidUtilities.dp(96); + cy -= dp(96); } } else { - cx = right - AndroidUtilities.dp(113 + 16 + 38); + cx = right - dp(113 + 16 + 38); cy = height / 2 + counterTextView.getMeasuredWidth() / 2; counterTextView.setRotation(-90); if (cameraPhotoRecyclerView.getVisibility() == View.VISIBLE) { - cx -= AndroidUtilities.dp(96); + cx -= dp(96); } } counterTextView.layout(cx, cy, cx + counterTextView.getMeasuredWidth(), cy + counterTextView.getMeasuredHeight()); return true; } else if (view == cameraPhotoRecyclerView) { if (isPortrait) { - int cy = height - AndroidUtilities.dp(88); + int cy = height - dp(88); view.layout(0, cy, view.getMeasuredWidth(), cy + view.getMeasuredHeight()); } else { - int cx = left + width - AndroidUtilities.dp(88); + int cx = left + width - dp(88); view.layout(cx, 0, cx + view.getMeasuredWidth(), view.getMeasuredHeight()); } return true; @@ -4197,10 +4217,10 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou position++; } if (position == 0) { - int rad = AndroidUtilities.dp(8 * parentAlert.cornerRadius); + int rad = dp(8 * parentAlert.cornerRadius); outline.setRoundRect(0, 0, view.getMeasuredWidth() + rad, view.getMeasuredHeight() + rad, rad); } else if (position == itemsPerRow - 1) { - int rad = AndroidUtilities.dp(8 * parentAlert.cornerRadius); + int rad = dp(8 * parentAlert.cornerRadius); outline.setRoundRect(-rad, 0, view.getMeasuredWidth(), view.getMeasuredHeight() + rad, rad); } else { outline.setRect(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); @@ -4353,7 +4373,7 @@ public class ChatAttachAlertPhotoLayout extends ChatAttachAlert.AttachAlertLayou cameraCell.setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { - int rad = AndroidUtilities.dp(8 * parentAlert.cornerRadius); + int rad = dp(8 * parentAlert.cornerRadius); outline.setRoundRect(0, 0, view.getMeasuredWidth() + rad, view.getMeasuredHeight() + rad, rad); } }); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertPhotoLayoutPreview.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertPhotoLayoutPreview.java index 4c32260ca..64c5152c0 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertPhotoLayoutPreview.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ChatAttachAlertPhotoLayoutPreview.java @@ -1842,7 +1842,7 @@ public class ChatAttachAlertPhotoLayoutPreview extends ChatAttachAlert.AttachAle ArrayList objectArrayList = new ArrayList<>(arrayList); PhotoViewer.getInstance().openPhotoForSelect(objectArrayList, position, type, false, photoViewerProvider, chatActivity); if (photoLayout.captionForAllMedia()) { - PhotoViewer.getInstance().setCaption(parentAlert.getCommentTextView().getText()); + PhotoViewer.getInstance().setCaption(parentAlert.getCommentView().getText()); } } tapMediaCell = null; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/DialogsBotsAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/DialogsBotsAdapter.java index 4d717bfb0..64aa9d923 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/DialogsBotsAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/DialogsBotsAdapter.java @@ -166,7 +166,7 @@ public class DialogsBotsAdapter extends UniversalAdapter { final TLRPC.User user = popular.bots.get(i); if (uids.contains(user.id)) continue; uids.add(user.id); - items.add(UItem.asProfileCell(user).accent()); + items.add(UItem.asProfileCell(user).accent().red()); hasAdded = true; } if (popular.loading) { 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 982c2e0bd..5e61abf60 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextCaption.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextCaption.java @@ -135,6 +135,10 @@ public class EditTextCaption extends EditTextBoldCursor { allowTextEntitiesIntersection = value; } + public boolean getAllowTextEntitiesIntersection() { + return allowTextEntitiesIntersection; + } + public void makeSelectedBold() { TextStyleSpan.TextStyleRun run = new TextStyleSpan.TextStyleRun(); run.flags |= TextStyleSpan.FLAG_STYLE_BOLD; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextEmoji.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextEmoji.java index 909339c19..3147a9fbb 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextEmoji.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EditTextEmoji.java @@ -149,7 +149,7 @@ public class EditTextEmoji extends FrameLayout implements NotificationCenter.Not NotificationCenter.getGlobalInstance().addObserver(this, NotificationCenter.emojiLoaded); parentFragment = fragment; sizeNotifierLayout = parent; - sizeNotifierLayout.setDelegate(this); + sizeNotifierLayout.addDelegate(this); editText = new EditTextCaption(context, resourcesProvider) { @Override @@ -387,8 +387,11 @@ public class EditTextEmoji extends FrameLayout implements NotificationCenter.Not } public void setSizeNotifierLayout(SizeNotifierFrameLayout layout) { + if (sizeNotifierLayout != null) { + sizeNotifierLayout.removeDelegate(this); + } sizeNotifierLayout = layout; - sizeNotifierLayout.setDelegate(this); + sizeNotifierLayout.addDelegate(this); } @Override @@ -477,7 +480,7 @@ public class EditTextEmoji extends FrameLayout implements NotificationCenter.Not emojiView.onDestroy(); } if (sizeNotifierLayout != null) { - sizeNotifierLayout.setDelegate(null); + sizeNotifierLayout.removeDelegate(this); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiPacksAlert.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiPacksAlert.java index ab7b057e5..0cf571558 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiPacksAlert.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiPacksAlert.java @@ -46,7 +46,10 @@ import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.DocumentObject; +import org.telegram.messenger.FileLoader; import org.telegram.messenger.FileLog; +import org.telegram.messenger.ImageLocation; import org.telegram.messenger.ImageReceiver; import org.telegram.messenger.LocaleController; import org.telegram.messenger.MediaDataController; @@ -55,6 +58,7 @@ import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; import org.telegram.messenger.SharedConfig; +import org.telegram.messenger.SvgHelper; import org.telegram.messenger.UserConfig; import org.telegram.messenger.UserObject; import org.telegram.messenger.Utilities; @@ -350,6 +354,14 @@ public class EmojiPacksAlert extends BottomSheet implements NotificationCenter.N } } }); + listView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + if (contentView != null && listView.scrollingByUser) { + contentView.hidePreviewEmoji(); + } + } + }); final Theme.ResourcesProvider finalResourceProvider = resourceProvider; RecyclerListView.OnItemClickListener stickersOnItemClickListener; listView.setOnItemClickListener(stickersOnItemClickListener = (view, position) -> { @@ -559,6 +571,10 @@ public class EmojiPacksAlert extends BottomSheet implements NotificationCenter.N shown = show; } + public void setPreviewEmoji(TLRPC.Document document) { + contentView.setPreviewEmoji(document); + } + private class ContentView extends FrameLayout { public ContentView(Context context) { super(context); @@ -576,6 +592,39 @@ public class EmojiPacksAlert extends BottomSheet implements NotificationCenter.N private final AnimatedFloat statusBarT = new AnimatedFloat(this, 0, 350, CubicBezierInterpolator.EASE_OUT_QUINT); + private ImageReceiver previewImageReceiver; + private boolean previewImageVisible; + private final AnimatedFloat previewImageVisibleT = new AnimatedFloat(this, 0, 320, CubicBezierInterpolator.EASE_OUT_QUINT); + + public void setPreviewEmoji(TLRPC.Document document) { + previewImageReceiver = new ImageReceiver(this); + if (attached) previewImageReceiver.onAttachedToWindow(); + previewImageVisible = true; + previewImageVisibleT.set(1.0f, true); + + final TLRPC.PhotoSize thumb = FileLoader.getClosestPhotoSizeWithSize(document.thumbs, 90); + final SvgHelper.SvgDrawable svgThumb = DocumentObject.getSvgThumb(document.thumbs, Theme.key_windowBackgroundWhiteGrayIcon, 0.2f, true); + previewImageReceiver.setImage( + ImageLocation.getForDocument(document), "140_140", + ImageLocation.getForDocument(thumb, document), "140_140", + svgThumb, + 0, null, null, 0 + ); + previewImageReceiver.setLayerNum(7); + previewImageReceiver.setAllowStartLottieAnimation(true); + previewImageReceiver.setAllowStartAnimation(true); + previewImageReceiver.setAutoRepeat(1); + previewImageReceiver.setAllowDecodeSingleFrame(true); + previewImageReceiver.setParentView(this); + } + + public void hidePreviewEmoji() { + if (previewImageVisible) { + previewImageVisible = false; + invalidate(); + } + } + @Override protected void dispatchDraw(Canvas canvas) { if (!attached) { @@ -586,13 +635,25 @@ public class EmojiPacksAlert extends BottomSheet implements NotificationCenter.N path.reset(); float y = lastY = getListTop(); float pad = 0; -// if (fromY != null) { -// float wasY = y; -// y = AndroidUtilities.lerp(fromY, y + containerView.getY(), loadT) - containerView.getY(); -// pad = y - wasY; -// } final float statusBarT = this.statusBarT.set(y <= containerView.getPaddingTop()); y = AndroidUtilities.lerp(y, 0, statusBarT); + + if (previewImageReceiver != null) { + final float sz = dp(140), p = dp(20); + if (y < sz + p) previewImageVisible = false; + previewImageReceiver.setAlpha(previewImageVisibleT.set(previewImageVisible)); + if (previewImageReceiver.getAlpha() > 0.0f) { + final float scale = .6f + .4f * previewImageReceiver.getAlpha(); + final float size = sz * scale; + final float cx = getWidth() / 2.0f, cy = y - p - sz / 2.0f; + previewImageReceiver.setImageCoords(cx - size / 2.0f, cy - size / 2.0f, size, size); + previewImageReceiver.draw(canvas); + } else { + previewImageReceiver.onDetachedFromWindow(); + previewImageReceiver = null; + } + } + float r = dp((1f - statusBarT) * 14); AndroidUtilities.rectTmp.set(getPaddingLeft(), y, getWidth() - getPaddingRight(), getBottom() + r); path.addRoundRect(AndroidUtilities.rectTmp, r, r, Path.Direction.CW); @@ -861,6 +922,9 @@ public class EmojiPacksAlert extends BottomSheet implements NotificationCenter.N protected void onAttachedToWindow() { super.onAttachedToWindow(); attached = true; + if (previewImageReceiver != null) { + previewImageReceiver.onAttachedToWindow(); + } } @Override @@ -875,6 +939,9 @@ public class EmojiPacksAlert extends BottomSheet implements NotificationCenter.N } lineDrawables.clear(); AnimatedEmojiSpan.release(this, animatedEmojiDrawables); + if (previewImageReceiver != null) { + previewImageReceiver.onDetachedFromWindow(); + } } } @@ -1187,6 +1254,9 @@ public class EmojiPacksAlert extends BottomSheet implements NotificationCenter.N @Override public void dismiss() { + if (contentView != null) { + contentView.hidePreviewEmoji(); + } super.dismiss(); if (customEmojiPacks != null) { customEmojiPacks.recycle(); 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 0ff260e62..a66b9d895 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/EmojiView.java @@ -7740,6 +7740,26 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific next.run(); }, null, SharedConfig.suggestAnimatedEmoji || UserConfig.getInstance(currentAccount).isPremium(), false, true, 25); }, + next -> { + if (ConnectionsManager.getInstance(currentAccount).getConnectionState() != ConnectionsManager.ConnectionStateConnected) { + next.run(); + return; + } + final String lang_code = newLanguage == null || newLanguage.length == 0 ? "" : newLanguage[0]; + MediaDataController.getInstance(currentAccount).searchStickers(true, lang_code, query, emojis -> { + if (!query.equals(lastSearchEmojiString)) { + return; + } + AnimatedEmojiDrawable.getDocumentFetcher(currentAccount).putDocuments(emojis); + for (TLRPC.Document emoji : emojis) { + MediaDataController.KeywordResult keywordResult = new MediaDataController.KeywordResult(); + keywordResult.emoji = "animated_" + emoji.id; + keywordResult.keyword = null; + searchResult.add(keywordResult); + } + next.run(); + }); + }, next -> { if (SharedConfig.suggestAnimatedEmoji || UserConfig.getInstance(currentAccount).isPremium()) { final String q = translitSafe((query + "").toLowerCase()); @@ -8714,20 +8734,34 @@ public class EmojiView extends FrameLayout implements NotificationCenter.Notific } private void searchStickerSets(Runnable finished) { - final TLRPC.TL_messages_searchStickerSets req = new TLRPC.TL_messages_searchStickerSets(); - req.q = query; - reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { + final String[] newLanguage = AndroidUtilities.getCurrentKeyboardLanguage(); + final String lang_code = newLanguage == null || newLanguage.length == 0 ? "" : newLanguage[0]; + MediaDataController.getInstance(currentAccount).searchStickers(false, lang_code, query, stickers -> { if (emojiSearchId != lastId) { return; } - - if (response instanceof TLRPC.TL_messages_foundStickerSets) { - reqId = 0; - TLRPC.TL_messages_foundStickerSets res = (TLRPC.TL_messages_foundStickerSets) response; - serverPacks.addAll(res.sets); + emojiStickersArray.addAll(stickers); + for (TLRPC.Document sticker : stickers) { + emojiStickersMap.put(sticker.id, sticker); } + emojiStickers.put(emojiStickersArray, searchQuery); + emojiArrays.add(emojiStickersArray); finished.run(); - })); + }); +// final TLRPC.TL_messages_searchStickerSets req = new TLRPC.TL_messages_searchStickerSets(); +// req.q = query; +// reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (response, error) -> AndroidUtilities.runOnUIThread(() -> { +// if (emojiSearchId != lastId) { +// return; +// } +// +// if (response instanceof TLRPC.TL_messages_foundStickerSets) { +// reqId = 0; +// TLRPC.TL_messages_foundStickerSets res = (TLRPC.TL_messages_foundStickerSets) response; +// serverPacks.addAll(res.sets); +// } +// finished.run(); +// })); } private void searchStickers(Runnable finished) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/ItemOptions.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/ItemOptions.java index 3c9c805a8..eff977608 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/ItemOptions.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/ItemOptions.java @@ -5,12 +5,18 @@ import static org.telegram.messenger.LocaleController.getString; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.ColorFilter; import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelFormat; import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -23,6 +29,7 @@ import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.WindowManager; import android.widget.FrameLayout; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.PopupWindow; import android.widget.ScrollView; @@ -34,10 +41,13 @@ import androidx.core.graphics.ColorUtils; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.BotWebViewVibrationEffect; +import org.telegram.messenger.ChatObject; import org.telegram.messenger.Emoji; +import org.telegram.messenger.ImageReceiver; import org.telegram.messenger.LocaleController; import org.telegram.messenger.NotificationCenter; import org.telegram.messenger.R; +import org.telegram.messenger.UserConfig; import org.telegram.messenger.UserObject; import org.telegram.messenger.Utilities; import org.telegram.tgnet.TLObject; @@ -82,6 +92,19 @@ public class ItemOptions { private int gravity = Gravity.RIGHT; private boolean ignoreX; + private int scrimViewPadding; + private int scrimViewRoundRadius; + + public ItemOptions setRoundRadius(int r) { + return setRoundRadius(r, 0); + } + + public ItemOptions setRoundRadius(int r, int p) { + scrimViewRoundRadius = r; + scrimViewPadding = p; + return this; + } + private ActionBarPopupWindow actionBarPopupWindow; private final float[] point = new float[2]; @@ -91,6 +114,13 @@ public class ItemOptions { private int dimAlpha; private boolean drawScrim = true; + private boolean blur; + + public ItemOptions setBlur(boolean b) { + this.blur = b; + return this; + } + private View dimView; private ViewTreeObserver.OnPreDrawListener preDrawListener; @@ -258,6 +288,99 @@ public class ItemOptions { return this; } + public ItemOptions addChecked(boolean checked, CharSequence text, Runnable onClickListener) { + if (context == null) { + return this; + } + + final int textColorKey = Theme.key_actionBarDefaultSubmenuItem; + final int iconColorKey = Theme.key_actionBarDefaultSubmenuItemIcon; + + ActionBarMenuSubItem subItem = new ActionBarMenuSubItem(context, true, false, false, resourcesProvider); + subItem.setPadding(dp(18), 0, dp(18), 0); + subItem.setText(text); + subItem.setChecked(checked); + + subItem.setColors(textColor != null ? textColor : Theme.getColor(textColorKey, resourcesProvider), iconColor != null ? iconColor : Theme.getColor(iconColorKey, resourcesProvider)); + subItem.setSelectorColor(selectorColor != null ? selectorColor : Theme.multAlpha(Theme.getColor(textColorKey, resourcesProvider), .12f)); + + subItem.setOnClickListener(view1 -> { + if (onClickListener != null) { + onClickListener.run(); + } + dismiss(); + }); + if (minWidthDp > 0) { + subItem.setMinimumWidth(dp(minWidthDp)); + addView(subItem, LayoutHelper.createLinear(minWidthDp, LayoutHelper.WRAP_CONTENT)); + } else { + addView(subItem, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } + + return this; + } + + public ItemOptions addChat(TLObject obj, boolean checked, Runnable onClickListener) { + if (context == null) { + return this; + } + + final int textColorKey = Theme.key_actionBarDefaultSubmenuItem; + final int iconColorKey = Theme.key_actionBarDefaultSubmenuItemIcon; + + ActionBarMenuSubItem subItem = new ActionBarMenuSubItem(context, false, false, resourcesProvider); + subItem.setPadding(dp(18), 0, dp(18), 0); + if (obj instanceof TLRPC.Chat) { + TLRPC.Chat chat = (TLRPC.Chat) obj; + subItem.setText(chat == null ? "" : chat.title); + subItem.setSubtext(ChatObject.isChannelAndNotMegaGroup(chat) ? getString(R.string.DiscussChannel) : getString(R.string.AccDescrGroup).toLowerCase()); + } else if (obj instanceof TLRPC.User) { + TLRPC.User user = (TLRPC.User) obj; + subItem.setText(UserObject.getUserName(user)); + if (user.id == UserConfig.getInstance(UserConfig.selectedAccount).getClientUserId()) { + subItem.setSubtext(getString(R.string.VoipGroupPersonalAccount)); + } else if (UserObject.isBot(user)) { + subItem.setSubtext(getString(R.string.Bot)); + } + } + + subItem.setClipToPadding(false); + subItem.textView.setPadding(subItem.checkViewLeft ? (subItem.checkView != null ? dp(43) : 0) : dp(43), 0, subItem.checkViewLeft ? dp(43) : (subItem.checkView != null ? dp(43) : 0), 0); + BackupImageView imageView = new BackupImageView(context); + AvatarDrawable avatarDrawable = new AvatarDrawable(); + avatarDrawable.setInfo(obj); + imageView.setRoundRadius(dp(34)); + imageView.setForUserOrChat(obj, avatarDrawable); + imageView.setScaleX(checked ? 0.84f : 1.0f); + imageView.setScaleY(checked ? 0.84f : 1.0f); + subItem.addView(imageView, LayoutHelper.createFrame(34, 34, Gravity.CENTER_VERTICAL | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), -5, 0, -5, 0)); + + if (checked) { + final float strokeWidth = 2; + View checkView = new View(context); + checkView.setBackground(Theme.createOutlineCircleDrawable(dp(34), Theme.getColor(Theme.key_featuredStickers_addButton, resourcesProvider), dp(strokeWidth))); + subItem.addView(checkView, LayoutHelper.createFrame(34 + strokeWidth, 34 + strokeWidth, Gravity.CENTER_VERTICAL | (LocaleController.isRTL ? Gravity.RIGHT : Gravity.LEFT), -5 - strokeWidth / 2.0f, 0, -5, 0)); + } + + subItem.setColors(textColor != null ? textColor : Theme.getColor(textColorKey, resourcesProvider), iconColor != null ? iconColor : Theme.getColor(iconColorKey, resourcesProvider)); + subItem.setSelectorColor(selectorColor != null ? selectorColor : Theme.multAlpha(Theme.getColor(textColorKey, resourcesProvider), .12f)); + + subItem.setOnClickListener(view1 -> { + if (onClickListener != null) { + onClickListener.run(); + } + dismiss(); + }); + if (minWidthDp > 0) { + subItem.setMinimumWidth(dp(minWidthDp)); + addView(subItem, LayoutHelper.createLinear(minWidthDp, LayoutHelper.WRAP_CONTENT)); + } else { + addView(subItem, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT)); + } + + return this; + } + public ItemOptions add(CharSequence text, CharSequence subtext, Runnable onClickListener) { if (context == null) { return this; @@ -502,12 +625,19 @@ public class ItemOptions { } private int minWidthDp; + private int fixedWidthDp; public ItemOptions setMinWidth(int minWidthDp) { this.minWidthDp = minWidthDp; return this; } + public ItemOptions setFixedWidth(int fixedWidthDp) { + this.fixedWidthDp = fixedWidthDp; + return this; + } + + public ItemOptions setDimAlpha(int dimAlpha) { this.dimAlpha = dimAlpha; return this; @@ -524,6 +654,12 @@ public class ItemOptions { return this; } + private boolean allowCenter; + public ItemOptions allowCenter(boolean allow) { + allowCenter = allow; + return this; + } + private boolean forceBottom; public ItemOptions forceBottom(boolean force) { forceBottom = force; @@ -631,7 +767,18 @@ public class ItemOptions { setupSelectors(); - if (minWidthDp > 0) { + if (fixedWidthDp > 0) { + for (int j = 0; j < layout.getChildCount() - 1; ++j) { + View child = j == layout.getChildCount() - 1 ? lastLayout : layout.getChildAt(j); + if (child instanceof ActionBarPopupWindow.ActionBarPopupWindowLayout) { + ActionBarPopupWindow.ActionBarPopupWindowLayout popupLayout = (ActionBarPopupWindow.ActionBarPopupWindowLayout) child; + for (int i = 0; i < popupLayout.getItemsCount(); ++i) { + ViewGroup.LayoutParams lp = popupLayout.getItemAt(i).getLayoutParams(); + lp.width = dp(fixedWidthDp); + } + } + } + } else if (minWidthDp > 0) { for (int j = 0; j < layout.getChildCount() - 1; ++j) { View child = j == layout.getChildCount() - 1 ? lastLayout : layout.getChildAt(j); if (child instanceof ActionBarPopupWindow.ActionBarPopupWindowLayout) { @@ -654,9 +801,18 @@ public class ItemOptions { if (scrimView != null) { getPointOnScreen(scrimView, container, point); y = point[1]; + x = point[0]; } + RectF scrimViewBounds = new RectF(); + if (scrimView instanceof ScrimView) { + ((ScrimView) scrimView).getBounds(scrimViewBounds); + } else { + scrimViewBounds.set(0, 0, scrimView.getMeasuredWidth(), scrimView.getMeasuredHeight()); + } + x += scrimViewBounds.left; + y += scrimViewBounds.top; if (ignoreX) { - point[0] = 0; + x = point[0] = 0; } if (dimAlpha > 0) { @@ -668,7 +824,11 @@ public class ItemOptions { container.getViewTreeObserver().addOnPreDrawListener(preDrawListener); container.addView(dimView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); dimView.setAlpha(0); - dimView.animate().alpha(1f).setDuration(150); + dimView.animate().alpha(1f).setUpdateListener(anm -> { + if (dimView != null && (scrimViewRoundRadius > 0 || scrimViewPadding > 0)) { + dimView.invalidate(); + } + }).setDuration(150); } layout.measure(View.MeasureSpec.makeMeasureSpec(container.getMeasuredWidth(), View.MeasureSpec.AT_MOST), View.MeasureSpec.makeMeasureSpec(container.getMeasuredHeight(), View.MeasureSpec.AT_MOST)); @@ -710,23 +870,28 @@ public class ItemOptions { int X; if (scrimView != null) { if (gravity == Gravity.RIGHT) { - X = (int) (point[0] + scrimView.getMeasuredWidth() - layout.getMeasuredWidth() + container.getX()); + X = (int) (container.getX() + x + scrimViewBounds.width() - layout.getMeasuredWidth()); + } else if (gravity == Gravity.CENTER_HORIZONTAL) { + X = (int) (container.getX() + x + scrimViewBounds.width() / 2.0f - layout.getMeasuredWidth() / 2.0f); } else { - X = (int) (container.getX() + point[0]); + X = (int) (container.getX() + x); } } else { X = (container.getWidth() - layout.getMeasuredWidth()) / 2; // at the center } int Y; if (forceBottom) { - Y = (int) (Math.min(y + scrimView.getMeasuredHeight(), AndroidUtilities.displaySize.y) - layout.getMeasuredHeight() + container.getY()); + Y = (int) (Math.min(y + scrimViewBounds.height(), AndroidUtilities.displaySize.y) - layout.getMeasuredHeight() + container.getY()); } else if (scrimView != null) { - if (forceTop || y + layout.getMeasuredHeight() + dp(16) > AndroidUtilities.displaySize.y - AndroidUtilities.navigationBarHeight) { + if (forceTop || y + scrimViewBounds.height() + layout.getMeasuredHeight() + dp(16) > AndroidUtilities.displaySize.y - AndroidUtilities.navigationBarHeight) { // put above scrimView - y -= scrimView.getMeasuredHeight(); + y -= scrimViewBounds.height(); y -= layout.getMeasuredHeight(); + if (allowCenter && Math.max(0, y + scrimViewBounds.height()) + layout.getMeasuredHeight() > point[1] + scrimViewBounds.top && scrimViewBounds.height() == scrimView.getHeight()) { + y = (container.getHeight() - layout.getMeasuredHeight()) / 2f - scrimViewBounds.height() - container.getY(); + } } - Y = (int) (y + scrimView.getMeasuredHeight() + container.getY()); // under scrimView + Y = (int) (y + scrimViewBounds.height() + container.getY()); // under scrimView } else { Y = (container.getHeight() - layout.getMeasuredHeight()) / 2; // at the center } @@ -906,9 +1071,15 @@ public class ItemOptions { private final Bitmap cachedBitmap; private final Paint cachedBitmapPaint; + private Bitmap blurBitmap; + private Paint blurPaint; + private final float clipTop; private final int dim; + private final Path clipPath = new Path(); + private final RectF bounds = new RectF(); + public DimView(Context context) { super(context); @@ -929,12 +1100,28 @@ public class ItemOptions { cachedBitmapPaint = null; cachedBitmap = null; } + + if (blur) { + blurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + AndroidUtilities.makeGlobalBlurBitmap(b -> { + blurBitmap = b; + }, 12.0f); + } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); - canvas.drawColor(dim); + + if (blurBitmap != null) { + canvas.save(); + final float scale = Math.max((float) getWidth() / blurBitmap.getWidth(), (float) getHeight() / blurBitmap.getHeight()); + canvas.scale(scale, scale); + canvas.drawBitmap(blurBitmap, 0, 0, blurPaint); + canvas.restore(); + } else { + canvas.drawColor(dim); + } if (!drawScrim) { } else if (cachedBitmap != null && scrimView.getParent() instanceof View) { @@ -962,6 +1149,12 @@ public class ItemOptions { } scrimViewBackground.draw(canvas); } + if (scrimViewPadding > 0 || scrimViewRoundRadius > 0) { + clipPath.rewind(); + AndroidUtilities.rectTmp.set(-viewAdditionalOffsets.left + scrimViewPadding * getAlpha(), -viewAdditionalOffsets.top + scrimViewPadding * getAlpha(), -viewAdditionalOffsets.left + cachedBitmap.getWidth() - scrimViewPadding * getAlpha(), -viewAdditionalOffsets.top + cachedBitmap.getHeight() - scrimViewPadding * getAlpha()); + clipPath.addRoundRect(AndroidUtilities.rectTmp, scrimViewRoundRadius * getAlpha(), scrimViewRoundRadius * getAlpha(), Path.Direction.CW); + canvas.clipPath(clipPath); + } canvas.drawBitmap(cachedBitmap, -viewAdditionalOffsets.left, -viewAdditionalOffsets.top, cachedBitmapPaint); canvas.restore(); } else if (scrimView != null && scrimView.getParent() instanceof View) { @@ -989,9 +1182,38 @@ public class ItemOptions { } scrimViewBackground.draw(canvas); } - scrimView.draw(canvas); + if (scrimViewPadding > 0 || scrimViewRoundRadius > 0) { + clipPath.rewind(); + if (scrimView instanceof ScrimView) { + ((ScrimView) scrimView).getBounds(bounds); + } else { + bounds.set(0, 0, getWidth(), getHeight()); + } + AndroidUtilities.rectTmp.set(-viewAdditionalOffsets.left + bounds.left + scrimViewPadding * getAlpha(), -viewAdditionalOffsets.top + bounds.top + scrimViewPadding * getAlpha(), -viewAdditionalOffsets.left + bounds.right - scrimViewPadding * getAlpha(), -viewAdditionalOffsets.top + bounds.bottom - scrimViewPadding * getAlpha()); + clipPath.addRoundRect(AndroidUtilities.rectTmp, scrimViewRoundRadius * getAlpha(), scrimViewRoundRadius * getAlpha(), Path.Direction.CW); + canvas.clipPath(clipPath); + } + if (scrimView instanceof ScrimView) { + ((ScrimView) scrimView).drawScrim(canvas, getAlpha()); + } else { + scrimView.draw(canvas); + } canvas.restore(); } } } + + public static interface ScrimView { + default void drawScrim(Canvas canvas, float progress) { + if (this instanceof View) { + ((View) this).draw(canvas); + } + } + default void getBounds(RectF bounds) { + if (this instanceof View) { + View view = (View) this; + bounds.set(0, 0, view.getWidth(), view.getHeight()); + } + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/LayoutHelper.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/LayoutHelper.java index df9d34608..8f0eb035c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/LayoutHelper.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/LayoutHelper.java @@ -91,6 +91,12 @@ public class LayoutHelper { return new FrameLayout.LayoutParams(getSize(width), getSize(height), gravity); } + public static FrameLayout.LayoutParams createFrame(float width, float height, int gravity, float leftMargin, float topMargin, float rightMargin, float bottomMargin) { + FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(getSize(width), getSize(height), gravity); + layoutParams.setMargins(AndroidUtilities.dp(leftMargin), AndroidUtilities.dp(topMargin), AndroidUtilities.dp(rightMargin), AndroidUtilities.dp(bottomMargin)); + return layoutParams; + } + public static FrameLayout.LayoutParams createFrameRelatively(float width, float height, int gravity, float startMargin, float topMargin, float endMargin, float bottomMargin) { final FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(getSize(width), getSize(height), getAbsoluteGravity(gravity)); layoutParams.leftMargin = AndroidUtilities.dp(LocaleController.isRTL ? endMargin : startMargin); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/MentionsContainerView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/MentionsContainerView.java index 0656fac1e..db0d2d9f5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/MentionsContainerView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/MentionsContainerView.java @@ -288,9 +288,11 @@ public class MentionsContainerView extends BlurredFrameLayout implements Notific } public void setReversed(boolean reversed) { - scrollToFirst = true; - linearLayoutManager.setReverseLayout(reversed); - adapter.setIsReversed(reversed); + if (reversed != isReversed()) { + scrollToFirst = true; + linearLayoutManager.setReverseLayout(reversed); + adapter.setIsReversed(reversed); + } } public boolean isReversed() { @@ -328,6 +330,7 @@ public class MentionsContainerView extends BlurredFrameLayout implements Notific boolean reversed = isReversed(); boolean topPadding = (adapter.isStickers() || adapter.isBotContext()) && adapter.isMediaLayout() && adapter.getBotContextSwitch() == null && adapter.getBotWebViewSwitch() == null; containerPadding = AndroidUtilities.dp(2 + (topPadding ? 2 : 0)); + canvas.save(); float r = AndroidUtilities.dp(6); float wasContainerTop = containerTop; @@ -338,6 +341,7 @@ public class MentionsContainerView extends BlurredFrameLayout implements Notific rect.set(0, (int) (containerTop = 0), getMeasuredWidth(), (int) (containerBottom = top)); r = Math.min(r, Math.abs(getMeasuredHeight() - containerBottom)); if (r > 0) { + canvas.clipRect(0, 0, getWidth(), getHeight()); rect.top -= (int) r; } } else { @@ -351,6 +355,7 @@ public class MentionsContainerView extends BlurredFrameLayout implements Notific rect.set(0, (int) (containerTop = top), getMeasuredWidth(), (int) (containerBottom = getMeasuredHeight())); r = Math.min(r, Math.abs(containerTop)); if (r > 0) { + canvas.clipRect(0, 0, getWidth(), getHeight()); rect.bottom += (int) r; } } @@ -383,7 +388,6 @@ public class MentionsContainerView extends BlurredFrameLayout implements Notific } else { drawRoundRect(canvas, rect, r); } - canvas.save(); canvas.clipRect(rect); super.dispatchDraw(canvas); canvas.restore(); @@ -719,6 +723,7 @@ public class MentionsContainerView extends BlurredFrameLayout implements Notific } int visibleItemCount = lastVisibleItem == RecyclerView.NO_POSITION ? 0 : lastVisibleItem; if (visibleItemCount > 0 && lastVisibleItem > adapter.getLastItemCount() - 5) { +// adapter.loadMoreStickers(); adapter.searchForContextBotForNextOffset(); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/GLIcon/GLIconTextureView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/GLIcon/GLIconTextureView.java index 58772b2f7..0b5eaec19 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/GLIcon/GLIconTextureView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/GLIcon/GLIconTextureView.java @@ -80,7 +80,7 @@ public class GLIconTextureView extends TextureView implements TextureView.Surfac super(context); this.type = type; - animationsCount = type == Icon3D.TYPE_COIN ? 1 : 5; + animationsCount = type == Icon3D.TYPE_COIN || type == Icon3D.TYPE_DEAL ? 1 : 5; setOpaque(false); setRenderer(new GLIconRenderer(context, style, type)); initialize(context); @@ -638,7 +638,7 @@ public class GLIconTextureView extends TextureView implements TextureView.Surfac private void pullAnimation() { int i = Math.abs(Utilities.random.nextInt() % 4); animatorSet = new AnimatorSet(); - if (i == 0 && type != Icon3D.TYPE_COIN) { + if (i == 0 && type != Icon3D.TYPE_COIN && type != Icon3D.TYPE_DEAL) { int a = 48; ValueAnimator v1 = ValueAnimator.ofFloat(mRenderer.angleY, a); @@ -655,7 +655,7 @@ public class GLIconTextureView extends TextureView implements TextureView.Surfac animatorSet.playTogether(v1, v2); } else { int dg = 485; - if (type == Icon3D.TYPE_COIN) { + if (type == Icon3D.TYPE_COIN || type == Icon3D.TYPE_DEAL) { dg = 360; } int a = dg; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/GLIcon/Icon3D.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/GLIcon/Icon3D.java index 38621484b..434b93957 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/GLIcon/Icon3D.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/GLIcon/Icon3D.java @@ -84,6 +84,7 @@ public class Icon3D { public static final int TYPE_STAR = 0; public static final int TYPE_COIN = 1; public static final int TYPE_GOLDEN_STAR = 2; + public static final int TYPE_DEAL = 3; private static final String[] starModel = new String[] { "models/star.binobj" @@ -94,12 +95,20 @@ public class Icon3D { "models/coin_logo.binobj", "models/coin_stars.binobj" }; + private static final String[] dealModel = new String[] { + "models/coin_outer.binobj", + "models/coin_inner.binobj", + "models/deal_logo.binobj", + "models/coin_stars.binobj" + }; public Icon3D(Context context, int type) { this.type = type; String[] modelPaths; if (type == TYPE_COIN) { modelPaths = coinModel; + } else if (type == TYPE_DEAL) { + modelPaths = dealModel; } else if (type == TYPE_STAR || type == TYPE_GOLDEN_STAR) { modelPaths = starModel; } else { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/boosts/cells/selector/SelectorUserCell.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/boosts/cells/selector/SelectorUserCell.java index 33ac8d5d0..7a9f45118 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/boosts/cells/selector/SelectorUserCell.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Premium/boosts/cells/selector/SelectorUserCell.java @@ -217,7 +217,7 @@ public class SelectorUserCell extends BaseCell { } } - private String buildCountDownTime(long diff) { + public static String buildCountDownTime(long diff) { long oneHourMs = 3600 * 1000; long oneMinuteMs = 60 * 1000; long oneSecondMs = 1000; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBarView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBarView.java index f258d3299..1032b3f06 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBarView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SeekBarView.java @@ -55,6 +55,7 @@ public class SeekBarView extends FrameLayout { private AnimatedFloat animatedThumbX = new AnimatedFloat(this, 0, 80, CubicBezierInterpolator.EASE_OUT); private int thumbDX; private float progressToSet = -100; + private float minProgress = -1; private boolean pressed, pressedDelayed; public SeekBarViewDelegate delegate; private boolean reportChanges; @@ -186,6 +187,14 @@ public class SeekBarView extends FrameLayout { reportChanges = value; } + public void setMinProgress(float progress) { + minProgress = progress; + if (getProgress() < minProgress) { + setProgress(minProgress, false); + } + invalidate(); + } + public void setDelegate(SeekBarViewDelegate seekBarViewDelegate) { delegate = seekBarViewDelegate; } @@ -205,8 +214,8 @@ public class SeekBarView extends FrameLayout { int additionWidth = (getMeasuredHeight() - thumbSize) / 2; if (!(thumbX - additionWidth <= ev.getX() && ev.getX() <= thumbX + thumbSize + additionWidth)) { thumbX = (int) ev.getX() - thumbSize / 2; - if (thumbX < 0) { - thumbX = 0; + if (thumbX < minThumbX()) { + thumbX = minThumbX(); } else if (thumbX > getMeasuredWidth() - selectorWidth) { thumbX = getMeasuredWidth() - selectorWidth; } @@ -250,8 +259,8 @@ public class SeekBarView extends FrameLayout { if (ev.getY() >= 0 && ev.getY() <= getMeasuredHeight()) { if (!(thumbX - additionWidth <= ev.getX() && ev.getX() <= thumbX + thumbSize + additionWidth)) { thumbX = (int) ev.getX() - thumbSize / 2; - if (thumbX < 0) { - thumbX = 0; + if (thumbX < minThumbX()) { + thumbX = minThumbX(); } else if (thumbX > getMeasuredWidth() - selectorWidth) { thumbX = getMeasuredWidth() - selectorWidth; } @@ -270,8 +279,8 @@ public class SeekBarView extends FrameLayout { } else { if (pressed) { thumbX = (int) (ev.getX() - thumbDX); - if (thumbX < 0) { - thumbX = 0; + if (thumbX < minThumbX()) { + thumbX = minThumbX(); } else if (thumbX > getMeasuredWidth() - selectorWidth) { thumbX = getMeasuredWidth() - selectorWidth; } @@ -298,6 +307,10 @@ public class SeekBarView extends FrameLayout { return false; } + private int minThumbX() { + return Math.max((int) (minProgress * (getMeasuredWidth() - selectorWidth)), 0); + } + public void setLineWidth(int dp) { lineWidthDp = dp; } @@ -351,8 +364,8 @@ public class SeekBarView extends FrameLayout { transitionProgress = 0f; } thumbX = newThumbX; - if (thumbX < 0) { - thumbX = 0; + if (thumbX < minThumbX()) { + thumbX = minThumbX(); } else if (thumbX > getMeasuredWidth() - selectorWidth) { thumbX = getMeasuredWidth() - selectorWidth; } @@ -412,8 +425,18 @@ public class SeekBarView extends FrameLayout { canvas.drawRect(thumbX + selectorWidth / 2, getMeasuredHeight() / 2 - AndroidUtilities.dp(1), getMeasuredWidth() / 2, getMeasuredHeight() / 2 + AndroidUtilities.dp(1), outerPaint1); } } else { - rect.set(left, top, selectorWidth / 2 + thumbX, bottom); - drawProgressBar(canvas, rect, outerPaint1); + if (minProgress >= 0) { + rect.set(left + minProgress * (right - left), top, left + thumbX, bottom); + drawProgressBar(canvas, rect, outerPaint1); + int wasAlpha = outerPaint1.getAlpha(); + rect.set(left, top, left + minProgress * (right - left), bottom); + outerPaint1.setAlpha((int) (0.50f * wasAlpha)); + drawProgressBar(canvas, rect, outerPaint1); + outerPaint1.setAlpha(wasAlpha); + } else { + rect.set(left, top, left + thumbX, bottom); + drawProgressBar(canvas, rect, outerPaint1); + } } if (hoverDrawable != null) { 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 71d4cc15b..70a0d9c02 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayout.java @@ -72,6 +72,7 @@ public class SizeNotifierFrameLayout extends FrameLayout { protected int keyboardHeight; private int bottomClip; protected SizeNotifierFrameLayoutDelegate delegate; + protected final ArrayList delegates = new ArrayList<>(); private boolean occupyStatusBar = true; private WallpaperParallaxEffect parallaxEffect; private float translationX; @@ -409,6 +410,13 @@ public class SizeNotifierFrameLayout extends FrameLayout { public void setDelegate(SizeNotifierFrameLayoutDelegate delegate) { this.delegate = delegate; } + public void addDelegate(SizeNotifierFrameLayoutDelegate delegate) { + this.delegates.add(delegate); + } + public void removeDelegate(SizeNotifierFrameLayoutDelegate delegate) { + this.delegates.remove(delegate); + } + public void setOccupyStatusBar(boolean value) { occupyStatusBar = value; @@ -452,13 +460,16 @@ public class SizeNotifierFrameLayout extends FrameLayout { if (parallaxEffect != null) { parallaxScale = parallaxEffect.getScale(getMeasuredWidth(), getMeasuredHeight()); } - if (delegate != null) { + if (delegate != null || !delegates.isEmpty()) { keyboardHeight = measureKeyboardHeight(); final boolean isWidthGreater = AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y; post(() -> { if (delegate != null) { delegate.onSizeChanged(keyboardHeight, isWidthGreater); } + for (int i = 0; i < delegates.size(); ++i) { + delegates.get(i).onSizeChanged(keyboardHeight, isWidthGreater); + } }); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayoutPhoto.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayoutPhoto.java index f250b524a..3a0cd51c2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayoutPhoto.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SizeNotifierFrameLayoutPhoto.java @@ -69,13 +69,16 @@ public class SizeNotifierFrameLayoutPhoto extends SizeNotifierFrameLayout { @Override public void notifyHeightChanged() { - if (super.delegate != null) { + if (super.delegate != null || !super.delegates.isEmpty()) { keyboardHeight = measureKeyboardHeight(); final boolean isWidthGreater = AndroidUtilities.displaySize.x > AndroidUtilities.displaySize.y; post(() -> { if (delegate != null) { delegate.onSizeChanged(keyboardHeight, isWidthGreater); } + for (int i = 0; i < super.delegates.size(); ++i) { + super.delegates.get(i).onSizeChanged(keyboardHeight, isWidthGreater); + } }); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SlideChooseView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SlideChooseView.java index f4d97f1fd..9ab2fdaca 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SlideChooseView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SlideChooseView.java @@ -47,6 +47,7 @@ public class SlideChooseView extends View { private Drawable[] leftDrawables; private int selectedIndex; + private int minIndex = Integer.MIN_VALUE; private float selectedIndexTouch; private AnimatedFloat selectedIndexAnimatedHolder = new AnimatedFloat(this, 120, CubicBezierInterpolator.DEFAULT); private AnimatedFloat movingAnimatedHolder = new AnimatedFloat(this, 150, CubicBezierInterpolator.DEFAULT); @@ -118,6 +119,19 @@ public class SlideChooseView extends View { requestLayout(); } + public void setMinAllowedIndex(int index) { + if (index != -1 && optionsStr != null) { + index = Math.min(index, optionsStr.length - 1); + } + if (minIndex != index) { + minIndex = index; + if (selectedIndex < index) { + selectedIndex = index; + } + invalidate(); + } + } + public void setDashedFrom(int from) { dashedFrom = from; } @@ -131,6 +145,9 @@ public class SlideChooseView extends View { if (isClose) { indexTouch = Math.round(indexTouch); } + if (minIndex != Integer.MIN_VALUE) { + indexTouch = Math.max(indexTouch, minIndex); + } if (event.getAction() == MotionEvent.ACTION_DOWN) { xTouchDown = x; yTouchDown = y; @@ -210,7 +227,7 @@ public class SlideChooseView extends View { int cx = sideSide + (lineSize + gapSize * 2 + circleSize) * a + circleSize / 2; float t = Math.max(0, 1f - Math.abs(a - selectedIndexAnimated)); float ut = MathUtils.clamp(selectedIndexAnimated - a + 1f, 0, 1); - int color = ColorUtils.blendARGB(getThemedColor(Theme.key_switchTrack), getThemedColor(Theme.key_switchTrackChecked), ut); + int color = ColorUtils.blendARGB(getThemedColor(Theme.key_switchTrack), Theme.multAlpha(getThemedColor(Theme.key_switchTrackChecked), minIndex != Integer.MIN_VALUE && a <= minIndex ? .50f : 1.0f), ut); paint.setColor(color); linePaint.setColor(color); canvas.drawCircle(cx, cy, AndroidUtilities.lerp(circleSize / 2, AndroidUtilities.dp(6), t), paint); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/SuggestEmojiView.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/SuggestEmojiView.java index b0d10aa0a..cbabae91d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/SuggestEmojiView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/SuggestEmojiView.java @@ -40,6 +40,7 @@ import org.telegram.messenger.R; import org.telegram.messenger.SharedConfig; import org.telegram.messenger.UserConfig; import org.telegram.messenger.UserObject; +import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.Theme; @@ -48,6 +49,7 @@ import org.telegram.ui.ContentPreviewViewer; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; public class SuggestEmojiView extends FrameLayout implements NotificationCenter.NotificationCenterDelegate { @@ -488,6 +490,7 @@ public class SuggestEmojiView extends FrameLayout implements NotificationCenter. return lastLang; } + private MediaDataController.SearchStickersKey loadingKey; private void searchKeywords(String query) { if (query == null) { return; @@ -501,6 +504,10 @@ public class SuggestEmojiView extends FrameLayout implements NotificationCenter. return; } final int id = ++lastQueryId; + if (loadingKey != null) { + MediaDataController.getInstance(currentAccount).cancelSearchStickers(loadingKey); + loadingKey = null; + } String[] lang = detectKeyboardLangThrottleFirstWithDelay(); if (lastLang == null || !Arrays.equals(lang, lastLang)) { @@ -513,11 +520,22 @@ public class SuggestEmojiView extends FrameLayout implements NotificationCenter. searchRunnable = null; } searchRunnable = () -> { - MediaDataController.getInstance(currentAccount).getEmojiSuggestions(lang, query, true, (param, alias) -> { - if (id == lastQueryId) { + final HashSet addedToResult = new HashSet<>(); + final ArrayList result = new ArrayList<>(); +// Runnable localSearch = () -> { + MediaDataController.getInstance(currentAccount).getEmojiSuggestions(lang, query, true, (param, alias) -> { + if (id != lastQueryId) return; lastQueryType = 1; lastQuery = query; - if (param != null && !param.isEmpty()) { + if (param != null) { + for (MediaDataController.KeywordResult r : param) { + if (!addedToResult.contains(r.emoji)) { + addedToResult.add(r.emoji); + result.add(r); + } + } + } + if (!result.isEmpty()) { clear = false; forceClose = false; createListView(); @@ -539,8 +557,29 @@ public class SuggestEmojiView extends FrameLayout implements NotificationCenter. clear = true; forceClose(); } - } - }, SharedConfig.suggestAnimatedEmoji && UserConfig.getInstance(currentAccount).isPremium()); + }, SharedConfig.suggestAnimatedEmoji && UserConfig.getInstance(currentAccount).isPremium()); +// }; +// Runnable serverSearch = () -> { +// if (ConnectionsManager.getInstance(currentAccount).getConnectionState() != ConnectionsManager.ConnectionStateConnected) { +// localSearch.run(); +// return; +// } +// loadingKey = MediaDataController.getInstance(currentAccount).searchStickers(true, query, lang == null ? "" : lang[0], emojis -> { +// if (id != lastQueryId) return; +// AnimatedEmojiDrawable.getDocumentFetcher(currentAccount).putDocuments(emojis); +// for (TLRPC.Document doc : emojis) { +// final String emoji = "animated_" + doc.id; +// if (!addedToResult.contains(emoji)) { +// MediaDataController.KeywordResult keywordResult = new MediaDataController.KeywordResult(); +// keywordResult.emoji = emoji; +// addedToResult.add(emoji); +// result.add(keywordResult); +// } +// } +// localSearch.run(); +// }); +// }; +// serverSearch.run(); }; if (keywordResults == null || keywordResults.isEmpty()) { AndroidUtilities.runOnUIThread(searchRunnable, 600); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/Text.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/Text.java index d95fa6d2b..df7806619 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/Text.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/Text.java @@ -26,7 +26,7 @@ public class Text { private final TextPaint paint; private StaticLayout layout; private float width, left; - private float maxWidth = 999999; + private float maxWidth = 9999; public Text(CharSequence text, TextPaint paint) { this.paint = paint; @@ -52,10 +52,10 @@ public class Text { public void setText(CharSequence text) { layout = new StaticLayout(AndroidUtilities.replaceNewLines(text), paint, (int) Math.max(maxWidth, 1), Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false); width = 0; - left = 0; + left = layout.getWidth(); for (int i = 0; i < layout.getLineCount(); ++i) { width = Math.max(width, layout.getLineWidth(i)); - left = Math.max(left, layout.getLineLeft(i)); + left = Math.min(left, layout.getLineLeft(i)); } } @@ -118,7 +118,7 @@ public class Text { if (!doNotSave) { canvas.save(); } - canvas.translate(x - left, cy - layout.getHeight() / 2f); + canvas.translate(x, cy - layout.getHeight() / 2f); draw(canvas); if (!doNotSave) { canvas.restore(); @@ -133,7 +133,7 @@ public class Text { if (!doNotSave) { canvas.save(); } - canvas.translate(x - left, cy - layout.getHeight() / 2f); + canvas.translate(x, cy - layout.getHeight() / 2f); draw(canvas); if (!doNotSave) { canvas.restore(); @@ -163,11 +163,14 @@ public class Text { if (!doNotSave && ellipsizeWidth >= 0 && width > ellipsizeWidth) { canvas.saveLayerAlpha(0, -vertPad, ellipsizeWidth - 1, layout.getHeight() + vertPad, 0xFF, Canvas.ALL_SAVE_FLAG); } + canvas.save(); + canvas.translate(-left, 0); if (hackClipBounds) { canvas.drawText(layout.getText().toString(), 0, -paint.getFontMetricsInt().ascent, paint); } else { layout.draw(canvas); } + canvas.restore(); if (!doNotSave && ellipsizeWidth >= 0 && width > ellipsizeWidth) { if (ellipsizeGradient == null) { ellipsizeGradient = new LinearGradient(0, 0, dp(8), 0, new int[] { 0x00ffffff, 0xffffffff }, new float[] {0, 1}, Shader.TileMode.CLAMP); @@ -178,9 +181,9 @@ public class Text { } canvas.save(); ellipsizeMatrix.reset(); - ellipsizeMatrix.postTranslate(ellipsizeWidth - left - dp(8), 0); + ellipsizeMatrix.postTranslate(ellipsizeWidth - dp(8), 0); ellipsizeGradient.setLocalMatrix(ellipsizeMatrix); - canvas.drawRect(ellipsizeWidth - left - dp(8), 0, ellipsizeWidth - left, layout.getHeight(), ellipsizePaint); + canvas.drawRect(ellipsizeWidth - dp(8), 0, ellipsizeWidth, layout.getHeight(), ellipsizePaint); canvas.restore(); canvas.restore(); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/UItem.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/UItem.java index 8258c2a86..5fac1bce7 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/UItem.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/UItem.java @@ -294,23 +294,29 @@ public class UItem extends AdapterWithDiffUtils.Item { item.texts = choices; item.intValue = chosen; item.intCallback = whenChose; + item.longValue = -1; return item; } public static UItem asIntSlideView( int style, - int minStringResId, int min, - int valueMinStringResId, int valueStringResId, int valueMaxStringResId, int value, - int maxStringResId, int max, + int min, int value, int max, + Utilities.CallbackReturn toString, Utilities.Callback whenChose ) { UItem item = new UItem(UniversalAdapter.VIEW_TYPE_INTSLIDE, false); item.intValue = value; item.intCallback = whenChose; - item.object = SlideIntChooseView.Options.make(style, min, minStringResId, valueMinStringResId, valueStringResId, valueMaxStringResId, max, maxStringResId); + item.object = SlideIntChooseView.Options.make(style, min, max, toString); + item.longValue = -1; return item; } + public UItem setMinSliderValue(int value) { + this.longValue = value; + return this; + } + public static UItem asQuickReply(QuickRepliesController.QuickReply quickReply) { UItem item = new UItem(UniversalAdapter.VIEW_TYPE_QUICK_REPLY, false); item.object = quickReply; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/UniversalAdapter.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/UniversalAdapter.java index 6793a0328..7dd413808 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/UniversalAdapter.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/UniversalAdapter.java @@ -754,6 +754,7 @@ public class UniversalAdapter extends AdapterWithDiffUtils { case VIEW_TYPE_SLIDE: SlideChooseView slideView = (SlideChooseView) holder.itemView; slideView.setOptions(item.intValue, item.texts); + slideView.setMinAllowedIndex((int) item.longValue); slideView.setCallback(index -> { if (item.intCallback != null) { item.intCallback.run(index); @@ -763,6 +764,7 @@ public class UniversalAdapter extends AdapterWithDiffUtils { case VIEW_TYPE_INTSLIDE: SlideIntChooseView slideIntChooseView = (SlideIntChooseView) holder.itemView; slideIntChooseView.set(item.intValue, (SlideIntChooseView.Options) item.object, item.intCallback); + slideIntChooseView.setMinValueAllowed((int) item.longValue); break; case VIEW_TYPE_QUICK_REPLY: QuickRepliesActivity.QuickReplyView replyView = (QuickRepliesActivity.QuickReplyView) holder.itemView; @@ -843,7 +845,7 @@ public class UniversalAdapter extends AdapterWithDiffUtils { ProfileSearchCell profileCell = (ProfileSearchCell) holder.itemView; Object object = item.object; CharSequence s = ""; - if (item.accent && object instanceof TLRPC.User && ((TLRPC.User) object).bot_active_users != 0) { // show bot dau + if (item.accent && object instanceof TLRPC.User && ((TLRPC.User) object).bot_active_users != 0) { // show bot mau TLRPC.User user = (TLRPC.User) object; if (user.bot_active_users != 0) { s = LocaleController.formatPluralStringSpaced("BotUsers", user.bot_active_users); @@ -883,6 +885,7 @@ public class UniversalAdapter extends AdapterWithDiffUtils { // add status text title = UserObject.getUserName(user); } + profileCell.setRectangularAvatar(item.red); profileCell.setData(object, null, title, s, false, false); profileCell.useSeparator = divider; break; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoPlayer.java b/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoPlayer.java index 6627c6f4a..bf5205b75 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoPlayer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Components/VideoPlayer.java @@ -20,6 +20,7 @@ import android.media.MediaCodecInfo; import android.media.MediaCodecList; import android.media.MediaFormat; import android.net.Uri; +import android.opengl.EGLContext; import android.os.Build; import android.os.Handler; import android.os.Looper; @@ -214,6 +215,16 @@ public class VideoPlayer implements Player.Listener, VideoListener, AnalyticsLis } } + private Looper looper; + public void setLooper(Looper looper) { + this.looper = looper; + } + + private EGLContext eglParentContext; + public void setEGLContext(EGLContext ctx) { + eglParentContext = ctx; + } + private void ensurePlayerCreated() { DefaultLoadControl loadControl; if (isStory) { @@ -247,9 +258,16 @@ public class VideoPlayer implements Player.Listener, VideoListener, AnalyticsLis factory = new DefaultRenderersFactory(ApplicationLoader.applicationContext); } factory.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER); - player = new ExoPlayer.Builder(ApplicationLoader.applicationContext).setRenderersFactory(factory) + ExoPlayer.Builder builder = new ExoPlayer.Builder(ApplicationLoader.applicationContext).setRenderersFactory(factory) .setTrackSelector(trackSelector) - .setLoadControl(loadControl).build(); + .setLoadControl(loadControl); + if (looper != null) { + builder.setLooper(looper); + } + if (eglParentContext != null) { + builder.eglContext = eglParentContext; + } + player = builder.build(); player.addAnalyticsListener(this); player.addListener(this); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java index 36c734dc4..c52968aff 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/DialogsActivity.java @@ -4422,6 +4422,15 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. protected void onArchiveSettingsClick() { presentFragment(new ArchiveSettingsActivity()); } + + @Override + protected boolean showOpenBotButton() { + return initialDialogsType == DIALOGS_TYPE_DEFAULT; + } + @Override + protected void onOpenBot(TLRPC.User bot) { + MessagesController.getInstance(currentAccount).openApp(bot, 0); + } }; viewPage.dialogsAdapter.setRecyclerListView(viewPage.listView); viewPage.dialogsAdapter.setForceShowEmptyCell(afterSignup); @@ -5490,7 +5499,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. c.loadInsufficientSubscriptions(); return false; } else { - long starsNeeded = -c.balance; + long starsNeeded = -c.balance.amount; for (int i = 0; i < c.insufficientSubscriptions.size(); ++i) { final TL_stars.StarsSubscription sub = c.insufficientSubscriptions.get(i); final long did = DialogObject.getPeerDialogId(sub.peer); @@ -5820,7 +5829,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. updateDialogsHint(); }).show(); }); - dialogsHintCell.setText(StarsIntroActivity.replaceStarsWithPlain(formatPluralStringComma("StarsSubscriptionExpiredHintTitle2", (int) (starsNeeded - c.balance <= 0 ? starsNeeded : starsNeeded - c.balance), starsNeededName), .72f), LocaleController.getString(R.string.StarsSubscriptionExpiredHintText)); + dialogsHintCell.setText(StarsIntroActivity.replaceStarsWithPlain(formatPluralStringComma("StarsSubscriptionExpiredHintTitle2", (int) (starsNeeded - c.balance.amount <= 0 ? starsNeeded : starsNeeded - c.balance.amount), starsNeededName), .72f), LocaleController.getString(R.string.StarsSubscriptionExpiredHintText)); dialogsHintCell.setOnCloseListener(v -> { MessagesController.getInstance(currentAccount).removeSuggestion(0, "STARS_SUBSCRIPTION_LOW_BALANCE"); ChangeBounds transition = new ChangeBounds(); @@ -8618,19 +8627,17 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. previewMenu[0].addView(muteItem); } - if (dialogId != UserObject.VERIFY) { - ActionBarMenuSubItem deleteItem = new ActionBarMenuSubItem(getParentActivity(), false, true); - deleteItem.setIconColor(getThemedColor(Theme.key_text_RedRegular)); - deleteItem.setTextColor(getThemedColor(Theme.key_text_RedBold)); - deleteItem.setSelectorColor(Theme.multAlpha(getThemedColor(Theme.key_text_RedBold), .12f)); - deleteItem.setTextAndIcon(LocaleController.getString(R.string.Delete), R.drawable.msg_delete); - deleteItem.setMinimumWidth(160); - deleteItem.setOnClickListener(e -> { - performSelectedDialogsAction(dialogIdArray, delete, false, false); - finishPreviewFragment(); - }); - previewMenu[0].addView(deleteItem); - } + ActionBarMenuSubItem deleteItem = new ActionBarMenuSubItem(getParentActivity(), false, true); + deleteItem.setIconColor(getThemedColor(Theme.key_text_RedRegular)); + deleteItem.setTextColor(getThemedColor(Theme.key_text_RedBold)); + deleteItem.setSelectorColor(Theme.multAlpha(getThemedColor(Theme.key_text_RedBold), .12f)); + deleteItem.setTextAndIcon(LocaleController.getString(R.string.Delete), R.drawable.msg_delete); + deleteItem.setMinimumWidth(160); + deleteItem.setOnClickListener(e -> { + performSelectedDialogsAction(dialogIdArray, delete, false, false); + finishPreviewFragment(); + }); + previewMenu[0].addView(deleteItem); if (getMessagesController().checkCanOpenChat(args, DialogsActivity.this)) { if (searchString != null) { @@ -9612,9 +9619,7 @@ public class DialogsActivity extends BaseFragment implements NotificationCenter. canPinCount++; } canClearHistoryCount++; - if (dialog.id != UserObject.VERIFY) { - canDeleteCount++; - } + canDeleteCount++; } } if (deleteItem != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/FilterCreateActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/FilterCreateActivity.java index e4224f31e..78e56398e 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/FilterCreateActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/FilterCreateActivity.java @@ -14,6 +14,7 @@ import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; +import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; import android.text.Editable; @@ -2067,6 +2068,10 @@ public class FilterCreateActivity extends BaseFragment { } } + public void setTypeface(Typeface typeface) { + textPaint.setTypeface(typeface); + } + public NewSpan(float textSize) { this.outline = false; textPaint.setTypeface(AndroidUtilities.bold()); @@ -2078,9 +2083,18 @@ public class FilterCreateActivity extends BaseFragment { this.color = color; } + private CharSequence text = "NEW"; + public void setText(CharSequence text) { + this.text = text; + if (layout != null) { + layout = null; + makeLayout(); + } + } + public StaticLayout makeLayout() { if (layout == null) { - layout = new StaticLayout("NEW"/*LocaleController.getString(R.string.New)*/, textPaint, AndroidUtilities.displaySize.x, Layout.Alignment.ALIGN_NORMAL, 1, 0, false); + layout = new StaticLayout(text, textPaint, AndroidUtilities.displaySize.x, Layout.Alignment.ALIGN_NORMAL, 1, 0, false); width = layout.getLineWidth(0); height = layout.getHeight(); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/GradientClip.java b/TMessagesProj/src/main/java/org/telegram/ui/GradientClip.java index fdf814931..94b5184df 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/GradientClip.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/GradientClip.java @@ -1,6 +1,5 @@ package org.telegram.ui; - import android.graphics.Canvas; import android.graphics.LinearGradient; import android.graphics.Matrix; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/GradientHeaderActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/GradientHeaderActivity.java index ac103766e..576a63a77 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/GradientHeaderActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/GradientHeaderActivity.java @@ -92,6 +92,11 @@ public abstract class GradientHeaderActivity extends BaseFragment { public int statusBarHeight; private int firstViewHeight; private final Paint headerBgPaint = new Paint(); + private int minusHeaderHeight; + + public void setMinusHeaderHeight(int h) { + minusHeaderHeight = h; + } public boolean whiteBackground; @@ -118,7 +123,7 @@ public abstract class GradientHeaderActivity extends BaseFragment { } else { int h = AndroidUtilities.dp(140) + statusBarHeight; if (backgroundView.getMeasuredHeight() + AndroidUtilities.dp(24) > h) { - h = backgroundView.getMeasuredHeight() + AndroidUtilities.dp(24); + h = Math.max(h, backgroundView.getMeasuredHeight() + AndroidUtilities.dp(24) - minusHeaderHeight); } firstViewHeight = h; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java index c427ed8b0..d5034057c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/LaunchActivity.java @@ -2109,6 +2109,7 @@ public class LaunchActivity extends BasePermissionsActivity implements INavigati Uri data = intent.getData(); if (data != null) { String username = null; + String referrer = null; String login = null; String group = null; String sticker = null; @@ -2383,6 +2384,24 @@ public class LaunchActivity extends BasePermissionsActivity implements INavigati } botUser = data.getQueryParameter("start"); botChat = data.getQueryParameter("startgroup"); + if (!TextUtils.isEmpty(username)) { + referrer = data.getQueryParameter("ref"); + if (TextUtils.isEmpty(referrer) && !TextUtils.isEmpty(botUser)) { + for (String prefix : MessagesController.getInstance(intentAccount[0]).starrefStartParamPrefixes) { + if (botUser.startsWith(prefix)) { + referrer = botUser.substring(prefix.length()); + break; + } + } + } else if (TextUtils.isEmpty(referrer) && !TextUtils.isEmpty(startApp)) { + for (String prefix : MessagesController.getInstance(intentAccount[0]).starrefStartParamPrefixes) { + if (startApp.startsWith(prefix)) { + referrer = startApp.substring(prefix.length()); + break; + } + } + } + } botChannel = data.getQueryParameter("startchannel"); botChatAdminParams = data.getQueryParameter("admin"); game = data.getQueryParameter("game"); @@ -2492,6 +2511,24 @@ public class LaunchActivity extends BasePermissionsActivity implements INavigati commentId = null; } } + if (!TextUtils.isEmpty(username)) { + referrer = data.getQueryParameter("ref"); + if (TextUtils.isEmpty(referrer) && !TextUtils.isEmpty(botUser)) { + for (String prefix : MessagesController.getInstance(intentAccount[0]).starrefStartParamPrefixes) { + if (botUser.startsWith(prefix)) { + referrer = botUser.substring(prefix.length()); + break; + } + } + } else if (TextUtils.isEmpty(referrer) && !TextUtils.isEmpty(startApp)) { + for (String prefix : MessagesController.getInstance(intentAccount[0]).starrefStartParamPrefixes) { + if (startApp.startsWith(prefix)) { + referrer = startApp.substring(prefix.length()); + break; + } + } + } + } } else if (url.startsWith("tg:invoice") || url.startsWith("tg://invoice")) { url = url.replace("tg:invoice", "tg://invoice"); data = Uri.parse(url); @@ -2860,7 +2897,7 @@ public class LaunchActivity extends BasePermissionsActivity implements INavigati if (message != null && message.startsWith("@")) { message = " " + message; } - runLinkRequest(intentAccount[0], username, group, sticker, emoji, botUser, botChat, botChannel, botChatAdminParams, message, contactToken, folderSlug, text, hasUrl, messageId, channelId, threadId, commentId, game, auth, lang, unsupportedUrl, code, login, wallPaper, inputInvoiceSlug, theme, voicechat, videochat, livestream, 0, videoTimestamp, setAsAttachBot, attachMenuBotToOpen, attachMenuBotChoose, botAppMaybe, startApp, progress, forceNotInternalForApps, storyId, isBoost, chatLinkSlug, botCompact, botFullscreen, openedTelegram, openProfile, forceRequest); + runLinkRequest(intentAccount[0], username, group, sticker, emoji, botUser, botChat, botChannel, botChatAdminParams, message, contactToken, folderSlug, text, hasUrl, messageId, channelId, threadId, commentId, game, auth, lang, unsupportedUrl, code, login, wallPaper, inputInvoiceSlug, theme, voicechat, videochat, livestream, 0, videoTimestamp, setAsAttachBot, attachMenuBotToOpen, attachMenuBotChoose, botAppMaybe, startApp, progress, forceNotInternalForApps, storyId, isBoost, chatLinkSlug, botCompact, botFullscreen, openedTelegram, openProfile, forceRequest, referrer); } else { try (Cursor cursor = getContentResolver().query(intent.getData(), null, null, null, null)) { if (cursor != null) { @@ -3881,9 +3918,9 @@ public class LaunchActivity extends BasePermissionsActivity implements INavigati final boolean forceNotInternalForApps, final int storyId, final boolean isBoost, - final String chatLinkSlug, boolean botCompact, boolean botFullscreen, boolean openedTelegram, boolean openProfile, boolean forceRequest) { + final String chatLinkSlug, boolean botCompact, boolean botFullscreen, boolean openedTelegram, boolean openProfile, boolean forceRequest, String referrer) { if (state == 0 && ChatActivity.SCROLL_DEBUG_DELAY && progress != null) { - Runnable runnable = () -> runLinkRequest(intentAccount, username, group, sticker, emoji, botUser, botChat, botChannel, botChatAdminParams, message, contactToken, folderSlug, text, hasUrl, messageId, channelId, threadId, commentId, game, auth, lang, unsupportedUrl, code, loginToken, wallPaper, inputInvoiceSlug, theme, voicechat, videochat, livestream, 1, videoTimestamp, setAsAttachBot, attachMenuBotToOpen, attachMenuBotChoose, botAppMaybe, botAppStartParam, progress, forceNotInternalForApps, storyId, isBoost, chatLinkSlug, botCompact, botFullscreen, openedTelegram, openProfile, forceRequest); + Runnable runnable = () -> runLinkRequest(intentAccount, username, group, sticker, emoji, botUser, botChat, botChannel, botChatAdminParams, message, contactToken, folderSlug, text, hasUrl, messageId, channelId, threadId, commentId, game, auth, lang, unsupportedUrl, code, loginToken, wallPaper, inputInvoiceSlug, theme, voicechat, videochat, livestream, 1, videoTimestamp, setAsAttachBot, attachMenuBotToOpen, attachMenuBotChoose, botAppMaybe, botAppStartParam, progress, forceNotInternalForApps, storyId, isBoost, chatLinkSlug, botCompact, botFullscreen, openedTelegram, openProfile, forceRequest, referrer); progress.init(); progress.onCancel(() -> AndroidUtilities.cancelRunOnUIThread(runnable)); AndroidUtilities.runOnUIThread(runnable, 7500); @@ -3893,7 +3930,7 @@ public class LaunchActivity extends BasePermissionsActivity implements INavigati if (account != intentAccount) { switchToAccount(account, true); } - runLinkRequest(account, username, group, sticker, emoji, botUser, botChat, botChannel, botChatAdminParams, message, contactToken, folderSlug, text, hasUrl, messageId, channelId, threadId, commentId, game, auth, lang, unsupportedUrl, code, loginToken, wallPaper, inputInvoiceSlug, theme, voicechat, videochat, livestream, 1, videoTimestamp, setAsAttachBot, attachMenuBotToOpen, attachMenuBotChoose, botAppMaybe, botAppStartParam, progress, forceNotInternalForApps, storyId, isBoost, chatLinkSlug, botCompact, botFullscreen, openedTelegram, openProfile, forceRequest); + runLinkRequest(account, username, group, sticker, emoji, botUser, botChat, botChannel, botChatAdminParams, message, contactToken, folderSlug, text, hasUrl, messageId, channelId, threadId, commentId, game, auth, lang, unsupportedUrl, code, loginToken, wallPaper, inputInvoiceSlug, theme, voicechat, videochat, livestream, 1, videoTimestamp, setAsAttachBot, attachMenuBotToOpen, attachMenuBotChoose, botAppMaybe, botAppStartParam, progress, forceNotInternalForApps, storyId, isBoost, chatLinkSlug, botCompact, botFullscreen, openedTelegram, openProfile, forceRequest, referrer); }).show(); return; } else if (code != null) { @@ -4047,7 +4084,20 @@ public class LaunchActivity extends BasePermissionsActivity implements INavigati if (progress != null) { progress.init(); } - MessagesController.getInstance(intentAccount).getUserNameResolver().resolve(username, (peerId) -> { + MessagesController.getInstance(intentAccount).getUserNameResolver().resolve(username, referrer, (peerId) -> { + if (peerId != null && peerId == Long.MAX_VALUE) { + try { + dismissLoading.run(); + } catch (Exception e) { + FileLog.e(e); + } + new AlertDialog.Builder(this, null) + .setTitle(LocaleController.getString(R.string.AffiliateLinkExpiredTitle)) + .setMessage(LocaleController.getString(R.string.AffiliateLinkExpiredText)) + .setNegativeButton(LocaleController.getString(R.string.OK), null) + .show(); + return; + } if (!LaunchActivity.this.isFinishing()) { boolean hideProgressDialog = true; if (storyId != 0 && peerId != null) { @@ -4088,7 +4138,7 @@ public class LaunchActivity extends BasePermissionsActivity implements INavigati getAttachMenuBot.bot = MessagesController.getInstance(intentAccount).getInputUser(peerId); ConnectionsManager.getInstance(intentAccount).sendRequest(getAttachMenuBot, (response1, error1) -> AndroidUtilities.runOnUIThread(() -> { if (error1 != null) { - AndroidUtilities.runOnUIThread(() -> runLinkRequest(intentAccount, username, group, sticker, emoji, botUser, botChat, botChannel, botChatAdminParams, message, contactToken, folderSlug, text, hasUrl, messageId, channelId, threadId, commentId, game, auth, lang, unsupportedUrl, code, loginToken, wallPaper, inputInvoiceSlug, theme, voicechat, videochat, livestream, state, videoTimestamp, setAsAttachBot, attachMenuBotToOpen, attachMenuBotChoose, null, null, progress, forceNotInternalForApps, storyId, isBoost, chatLinkSlug, botCompact, botFullscreen, openedTelegram, openProfile, forceRequest)); + AndroidUtilities.runOnUIThread(() -> runLinkRequest(intentAccount, username, group, sticker, emoji, botUser, botChat, botChannel, botChatAdminParams, message, contactToken, folderSlug, text, hasUrl, messageId, channelId, threadId, commentId, game, auth, lang, unsupportedUrl, code, loginToken, wallPaper, inputInvoiceSlug, theme, voicechat, videochat, livestream, state, videoTimestamp, setAsAttachBot, attachMenuBotToOpen, attachMenuBotChoose, null, null, progress, forceNotInternalForApps, storyId, isBoost, chatLinkSlug, botCompact, botFullscreen, openedTelegram, openProfile, forceRequest, referrer)); } else if (response1 instanceof TLRPC.TL_attachMenuBotsBot) { TLRPC.TL_attachMenuBotsBot bot = (TLRPC.TL_attachMenuBotsBot) response1; TLRPC.TL_attachMenuBot attachBot = bot.bot; @@ -4109,7 +4159,7 @@ public class LaunchActivity extends BasePermissionsActivity implements INavigati } }), ConnectionsManager.RequestFlagInvokeAfter | ConnectionsManager.RequestFlagFailOnServerErrors); - processWebAppBot(intentAccount, username, group, sticker, emoji, botUser, botChat, botChannel, botChatAdminParams, message, contactToken, folderSlug, text, hasUrl, messageId, channelId, threadId, commentId, game, auth, lang, unsupportedUrl, code, loginToken, wallPaper, inputInvoiceSlug, theme, voicechat, videochat, livestream, state, videoTimestamp, setAsAttachBot, attachMenuBotToOpen, attachMenuBotChoose, botAppMaybe, botAppStartParam, progress, forceNotInternalForApps, storyId, isBoost, chatLinkSlug, user, dismissLoading, botAttachable, true, botCompact, botFullscreen, openedTelegram, openProfile, forceRequest); + processWebAppBot(intentAccount, username, group, sticker, emoji, botUser, botChat, botChannel, botChatAdminParams, message, contactToken, folderSlug, text, hasUrl, messageId, channelId, threadId, commentId, game, auth, lang, unsupportedUrl, code, loginToken, wallPaper, inputInvoiceSlug, theme, voicechat, videochat, livestream, state, videoTimestamp, setAsAttachBot, attachMenuBotToOpen, attachMenuBotChoose, botAppMaybe, botAppStartParam, progress, forceNotInternalForApps, storyId, isBoost, chatLinkSlug, user, dismissLoading, botAttachable, true, botCompact, botFullscreen, openedTelegram, openProfile, forceRequest, referrer); }, null, progress != null ? progress::end : null); } else if (attachBot.request_write_access || forceNotInternalForApps) { AtomicBoolean allowWrite = new AtomicBoolean(true); @@ -4129,15 +4179,15 @@ public class LaunchActivity extends BasePermissionsActivity implements INavigati } }), ConnectionsManager.RequestFlagInvokeAfter | ConnectionsManager.RequestFlagFailOnServerErrors); - processWebAppBot(intentAccount, username, group, sticker, emoji, botUser, botChat, botChannel, botChatAdminParams, message, contactToken, folderSlug, text, hasUrl, messageId, channelId, threadId, commentId, game, auth, lang, unsupportedUrl, code, loginToken, wallPaper, inputInvoiceSlug, theme, voicechat, videochat, livestream, state, videoTimestamp, setAsAttachBot, attachMenuBotToOpen, attachMenuBotChoose, botAppMaybe, botAppStartParam, progress, forceNotInternalForApps, storyId, isBoost, chatLinkSlug, user, dismissLoading, false, false, botCompact, botFullscreen, openedTelegram, openProfile, forceRequest); + processWebAppBot(intentAccount, username, group, sticker, emoji, botUser, botChat, botChannel, botChatAdminParams, message, contactToken, folderSlug, text, hasUrl, messageId, channelId, threadId, commentId, game, auth, lang, unsupportedUrl, code, loginToken, wallPaper, inputInvoiceSlug, theme, voicechat, videochat, livestream, state, videoTimestamp, setAsAttachBot, attachMenuBotToOpen, attachMenuBotChoose, botAppMaybe, botAppStartParam, progress, forceNotInternalForApps, storyId, isBoost, chatLinkSlug, user, dismissLoading, false, false, botCompact, botFullscreen, openedTelegram, openProfile, forceRequest, referrer); }); } else { - processWebAppBot(intentAccount, username, group, sticker, emoji, botUser, botChat, botChannel, botChatAdminParams, message, contactToken, folderSlug, text, hasUrl, messageId, channelId, threadId, commentId, game, auth, lang, unsupportedUrl, code, loginToken, wallPaper, inputInvoiceSlug, theme, voicechat, videochat, livestream, state, videoTimestamp, setAsAttachBot, attachMenuBotToOpen, attachMenuBotChoose, botAppMaybe, botAppStartParam, progress, forceNotInternalForApps, storyId, isBoost, chatLinkSlug, user, dismissLoading, false, false, botCompact, botFullscreen, openedTelegram, openProfile, forceRequest); + processWebAppBot(intentAccount, username, group, sticker, emoji, botUser, botChat, botChannel, botChatAdminParams, message, contactToken, folderSlug, text, hasUrl, messageId, channelId, threadId, commentId, game, auth, lang, unsupportedUrl, code, loginToken, wallPaper, inputInvoiceSlug, theme, voicechat, videochat, livestream, state, videoTimestamp, setAsAttachBot, attachMenuBotToOpen, attachMenuBotChoose, botAppMaybe, botAppStartParam, progress, forceNotInternalForApps, storyId, isBoost, chatLinkSlug, user, dismissLoading, false, false, botCompact, botFullscreen, openedTelegram, openProfile, forceRequest, referrer); } } })); } else { - processWebAppBot(intentAccount, username, group, sticker, emoji, botUser, botChat, botChannel, botChatAdminParams, message, contactToken, folderSlug, text, hasUrl, messageId, channelId, threadId, commentId, game, auth, lang, unsupportedUrl, code, loginToken, wallPaper, inputInvoiceSlug, theme, voicechat, videochat, livestream, state, videoTimestamp, setAsAttachBot, attachMenuBotToOpen, attachMenuBotChoose, botAppMaybe, botAppStartParam, progress, forceNotInternalForApps, storyId, isBoost, chatLinkSlug, user, dismissLoading, false, false, botCompact, botFullscreen, openedTelegram, openProfile, forceRequest); + processWebAppBot(intentAccount, username, group, sticker, emoji, botUser, botChat, botChannel, botChatAdminParams, message, contactToken, folderSlug, text, hasUrl, messageId, channelId, threadId, commentId, game, auth, lang, unsupportedUrl, code, loginToken, wallPaper, inputInvoiceSlug, theme, voicechat, videochat, livestream, state, videoTimestamp, setAsAttachBot, attachMenuBotToOpen, attachMenuBotChoose, botAppMaybe, botAppStartParam, progress, forceNotInternalForApps, storyId, isBoost, chatLinkSlug, user, dismissLoading, false, false, botCompact, botFullscreen, openedTelegram, openProfile, forceRequest, referrer); } return; } @@ -5198,7 +5248,7 @@ public class LaunchActivity extends BasePermissionsActivity implements INavigati final boolean isBoost, final String chatLinkSlug, TLRPC.User user, - Runnable dismissLoading, boolean botAttachable, boolean ignoreInactive, boolean botCompact, boolean botFullscreen, boolean openedTelegram, boolean openProfile, boolean forceRequest) { + Runnable dismissLoading, boolean botAttachable, boolean ignoreInactive, boolean botCompact, boolean botFullscreen, boolean openedTelegram, boolean openProfile, boolean forceRequest, String referrer) { TLRPC.TL_messages_getBotApp getBotApp = new TLRPC.TL_messages_getBotApp(); TLRPC.TL_inputBotAppShortName app = new TLRPC.TL_inputBotAppShortName(); @@ -5210,7 +5260,7 @@ public class LaunchActivity extends BasePermissionsActivity implements INavigati progress.end(); } if (error1 != null) { - AndroidUtilities.runOnUIThread(() -> runLinkRequest(intentAccount, username, group, sticker, emoji, botUser, botChat, botChannel, botChatAdminParams, message, contactToken, folderSlug, text, hasUrl, messageId, channelId, threadId, commentId, game, auth, lang, unsupportedUrl, code, loginToken, wallPaper, inputInvoiceSlug, theme, voicechat, videochat, livestream, state, videoTimestamp, setAsAttachBot, attachMenuBotToOpen, attachMenuBotChoose, null, null, progress, forceNotInternalForApps, storyId, isBoost, chatLinkSlug, botCompact, botFullscreen, openedTelegram, openProfile, forceRequest)); + AndroidUtilities.runOnUIThread(() -> runLinkRequest(intentAccount, username, group, sticker, emoji, botUser, botChat, botChannel, botChatAdminParams, message, contactToken, folderSlug, text, hasUrl, messageId, channelId, threadId, commentId, game, auth, lang, unsupportedUrl, code, loginToken, wallPaper, inputInvoiceSlug, theme, voicechat, videochat, livestream, state, videoTimestamp, setAsAttachBot, attachMenuBotToOpen, attachMenuBotChoose, null, null, progress, forceNotInternalForApps, storyId, isBoost, chatLinkSlug, botCompact, botFullscreen, openedTelegram, openProfile, forceRequest, referrer)); } else { TLRPC.TL_messages_botApp botApp = (TLRPC.TL_messages_botApp) response1; AndroidUtilities.runOnUIThread(() -> { @@ -5977,13 +6027,13 @@ public class LaunchActivity extends BasePermissionsActivity implements INavigati arrayList.add(videoPath); SendMessagesHelper.prepareSendingDocuments(accountInstance, arrayList, arrayList, null, captionToSend, null, did, replyToMsg, replyToMsg, null, null, null, notify, scheduleDate, null, null, 0, 0, false); } - } - if (photoPathsArray != null && !photosEditorOpened) { - if (sendingText != null && sendingText.length() <= 1024 && photoPathsArray.size() == 1) { - photoPathsArray.get(0).caption = sendingText; - sendingText = null; + if (photoPathsArray != null && !photosEditorOpened) { + if (sendingText != null && sendingText.length() <= 1024 && photoPathsArray.size() == 1) { + photoPathsArray.get(0).caption = sendingText; + sendingText = null; + } + SendMessagesHelper.prepareSendingMedia(accountInstance, photoPathsArray, did, replyToMsg, replyToMsg, null, null, false, false, null, notify, scheduleDate, 0, false, null, null, 0, 0, false); } - SendMessagesHelper.prepareSendingMedia(accountInstance, photoPathsArray, did, replyToMsg, replyToMsg, null, null, false, false, null, notify, scheduleDate, 0, false, null, null, 0, 0, false); } if (documentsPathsArray != null || documentsUrisArray != null) { if (sendingText != null && sendingText.length() <= 1024 && ((documentsPathsArray != null ? documentsPathsArray.size() : 0) + (documentsUrisArray != null ? documentsUrisArray.size() : 0)) == 1) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java index ff60591b7..33e527b18 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/PhotoViewer.java @@ -82,6 +82,7 @@ import android.transition.TransitionManager; import android.transition.TransitionSet; import android.transition.TransitionValues; import android.util.FloatProperty; +import android.util.Log; import android.util.Pair; import android.util.Property; import android.util.Range; @@ -238,7 +239,6 @@ import org.telegram.ui.Components.LinkPath; import org.telegram.ui.Components.LinkSpanDrawable; import org.telegram.ui.Components.LoadingDrawable; import org.telegram.ui.Components.MediaActivity; -import org.telegram.ui.Components.OptionsSpeedIconDrawable; import org.telegram.ui.Components.OtherDocumentPlaceholderDrawable; import org.telegram.ui.Components.Paint.Views.LPhotoPaintView; import org.telegram.ui.Components.Paint.Views.MaskPaintView; @@ -282,6 +282,7 @@ import org.telegram.ui.Components.VideoTimelinePlayView; import org.telegram.ui.Components.ViewHelper; import org.telegram.ui.Components.spoilers.SpoilersTextView; import org.telegram.ui.Stories.DarkThemeResourceProvider; +import org.telegram.ui.Stories.recorder.CaptionContainerView; import org.telegram.ui.Stories.recorder.KeyboardNotifier; import org.telegram.ui.Stories.recorder.StoryEntry; @@ -664,7 +665,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private String getOf() { lng = LocaleController.getInstance().getCurrentLocaleInfo().shortName; - String text = getString("Of"); // %1$d of %2$d + String text = getString(R.string.Of); // %1$d of %2$d text = text.replace("%1$d", ""); text = text.replace("%2$d", ""); return text; @@ -802,7 +803,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private WindowManager.LayoutParams windowLayoutParams; private FrameLayoutDrawer containerView; private PhotoViewerWebView photoViewerWebView; - private FrameLayout windowView; + public FrameLayout windowView; private ClippingImageView animatingImageView; private FrameLayout bottomLayout; private View navigationBar; @@ -835,6 +836,8 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private CheckBox checkImageView; private CounterView photosCounterView; private FrameLayout pickerView; + private FrameLayout topBulletinUnderCaption; + private FrameLayout bottomBulletinUnderCaption; private ImageView pickerViewSendButton; private PickerBottomLayoutViewer editorDoneLayout; private TextView resetButton; @@ -890,8 +893,10 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat private TextView adButtonTextView; private CaptionScrollView captionScrollView; private CaptionPhotoViewer captionEdit; + private CaptionPhotoViewer topCaptionEdit; private float shiftDp = -8; private FrameLayout captionEditContainer; + private FrameLayout topCaptionEditContainer; private FrameLayout captionContainer; private ChatAttachAlert parentAlert; // private PhotoViewerCaptionEnterView captionEditText; @@ -2834,6 +2839,14 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat default boolean forceAllInGroup() { return false; } + + default boolean isCaptionAbove() { + return false; + } + default boolean canMoveCaptionAbove() { + return false; + } + default void moveCaptionAbove(boolean above) {} } private class FrameLayoutDrawer extends SizeNotifierFrameLayoutPhoto { @@ -2929,7 +2942,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat height = heightSize; } paintingOverlay.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); - } else if (captionEdit.editText.isPopupView(child)) { + } else if (captionEdit.editText.isPopupView(child) || topCaptionEdit.editText.isPopupView(child)) { int inputFieldHeight = 0; if (inBubbleMode) { child.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(heightSize - inputFieldHeight, MeasureSpec.EXACTLY)); @@ -2958,6 +2971,14 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat final int height = heightSize - topMargin - bottomMargin; ((MarginLayoutParams) captionScrollView.getLayoutParams()).bottomMargin = bottomMargin; child.measure(MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); + } else if (child == topCaptionEditContainer || child == topBulletinUnderCaption) { + final int topMargin = (isStatusBarVisible() ? AndroidUtilities.statusBarHeight : 0) + ActionBar.getCurrentActionBarHeight(); + int heightSpec = MeasureSpec.makeMeasureSpec(heightSize - topMargin, MeasureSpec.EXACTLY); + child.measure(widthMeasureSpec, heightSpec); + } else if (child == topCaptionEdit.mentionContainer) { + final int topMargin = (isStatusBarVisible() ? AndroidUtilities.statusBarHeight : 0) + ActionBar.getCurrentActionBarHeight(); + int heightSpec = MeasureSpec.makeMeasureSpec(heightSize - topMargin, MeasureSpec.EXACTLY); + child.measure(widthMeasureSpec, heightSpec); } else { measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); } @@ -3028,14 +3049,10 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } if (child == captionEdit.mentionContainer) { - childTop -= captionEdit.getEditTextHeight(); - } else if (captionEdit.editText.isPopupView(child)) { + } else if (child == topCaptionEdit.mentionContainer) { + childTop += actionBar.getMeasuredHeight(); + } else if (captionEdit.editText.isPopupView(child) || topCaptionEdit.editText.isPopupView(child)) { childTop = (_b - t) - height + (!inBubbleMode && !AndroidUtilities.isInMultiwindow ? AndroidUtilities.navigationBarHeight : 0); -// if (AndroidUtilities.isInMultiwindow) { -// childTop = captionEditText.getTop() - child.getMeasuredHeight() + dp(1); -// } else { -// childTop = captionEditText.getBottom(); -// } } else if (child == selectedPhotosListView) { childTop = actionBar.getMeasuredHeight() + dp(5); } else if (child == muteItem) { @@ -3058,6 +3075,11 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } else if (child == captionEditContainer) { childTop = (b - t) - height - (lp.bottomMargin); childTop -= pickerView.getHeight(); + } else if (child == topCaptionEditContainer || child == topBulletinUnderCaption) { + childTop = actionBar.getMeasuredHeight(); + } else if (child == bottomBulletinUnderCaption) { + childTop = (b - t) - height - (lp.bottomMargin); + childTop -= pickerView.getHeight(); } else if (child == videoAvatarTooltip) { childTop -= pickerView.getHeight() + dp(31); } @@ -3133,14 +3155,14 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (child == leftPaintingOverlay || child == rightPaintingOverlay) { return false; } - if (child != navigationBar && (captionEdit == null || !captionEdit.editText.isPopupView(child))) { - canvas.save(); -// canvas.clipRect(0, 0, getWidth(), getHeight()); - } +// if (child != navigationBar && (captionEdit == null || !captionEdit.editText.isPopupView(child))) { +// canvas.save(); +//// canvas.clipRect(0, 0, getWidth(), getHeight()); +// } boolean result = this.drawChildInternal(canvas, child, drawingTime); - if (child != navigationBar && (captionEdit == null || !captionEdit.editText.isPopupView(child))) { - canvas.restore(); - } +// if (child != navigationBar && (captionEdit == null || !captionEdit.editText.isPopupView(child))) { +// canvas.restore(); +// } return result; } @@ -3179,10 +3201,13 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat public int getBottomOffset(int tag) { int offset = 0; if (editing) { - if (captionEdit != null && captionEdit.getVisibility() == VISIBLE) { - offset += captionEdit.getEditTextHeight() + dp(12); + if (captionEdit != null) { + offset += captionEdit.keyboardNotifier.getKeyboardHeight(); + if (captionEdit.getVisibility() == VISIBLE && !(placeProvider != null && placeProvider.isCaptionAbove())) { + offset += captionEdit.getEditTextHeight() + dp(12); + } } - if (pickerView != null && pickerView.getVisibility() == VISIBLE) { + if (pickerView != null && pickerView.getVisibility() == VISIBLE && (captionEdit == null || !captionEdit.keyboardNotifier.keyboardVisible())) { offset += pickerView.getHeight(); } } else { @@ -3195,6 +3220,12 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } return offset; } + + @Override + public int getTopOffset(int tag) { + final int topMargin = (isStatusBarVisible() ? AndroidUtilities.statusBarHeight : 0) + ActionBar.getCurrentActionBarHeight(); + return topMargin + (int) (topCaptionEdit.getAlpha() * topCaptionEdit.getEditTextHeight()); + } }); } @@ -5938,7 +5969,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat public void setTranslationY(float translationY) { super.setTranslationY(translationY); if (videoTimelineViewContainer != null && videoTimelineViewContainer.getVisibility() != GONE) { - videoTimelineViewContainer.setTranslationY(translationY - Math.max(0, captionEdit.getEditTextHeight() - dp(46))); + videoTimelineViewContainer.setTranslationY(pickerView.getTranslationY() - Math.max(0, captionEdit.getEditTextHeight() - dp(46)) * captionEdit.getAlpha()); } if (captionEditContainer != null) { captionEditContainer.setTranslationY(translationY); @@ -5955,7 +5986,10 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat videoTimelineViewContainer.setAlpha(alpha); } if (captionEdit != null && captionEdit.getVisibility() != GONE) { - captionEdit.setAlpha(alpha); + captionEdit.setAlpha(alpha * captionEditAlpha[0]); + } + if (topCaptionEdit != null && topCaptionEdit.getVisibility() != GONE) { + topCaptionEdit.setAlpha(alpha * topCaptionEditAlpha[0]); } } @@ -6200,7 +6234,35 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } @Override - protected void drawBlur(BlurringShader.StoryBlurDrawer blur, Canvas canvas, RectF rect, float r, boolean text, float ox, float oy, boolean thisView) { + public void updateKeyboard(int keyboardHeight) { + super.updateKeyboard(keyboardHeight); + final Bulletin bulletin = Bulletin.getVisibleBulletin(); + if (bulletin != null) { + bulletin.updatePosition(); + } + updateMoveCaptionButton(); + if (bottomBulletinUnderCaption != null) { + bottomBulletinUnderCaption.animate() + .translationY(-Math.max(0, keyboardHeight - pickerView.getHeight())) + .setDuration(AdjustPanLayoutHelper.keyboardDuration) + .setInterpolator(AdjustPanLayoutHelper.keyboardInterpolator) + .start(); + } + } + + @Override + public void setText(CharSequence text) { + super.setText(text); + updateMoveCaptionButton(); + } + + private void updateMoveCaptionButton() { + final boolean show = (placeProvider != null && placeProvider.canMoveCaptionAbove()) && (captionEdit.keyboardNotifier.keyboardVisible() || !isCurrentVideo && !TextUtils.isEmpty(getCaptionView().getText())); + setShowMoveButtonVisible(show, true); + } + + @Override + protected void drawBlur(BlurringShader.StoryBlurDrawer blur, Canvas canvas, RectF rect, float r, boolean text, float ox, float oy, boolean thisView, float alpha) { canvas.save(); path.rewind(); path.addRoundRect(rect, r, r, Path.Direction.CW); @@ -6210,7 +6272,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } else { canvas.translate(ox, oy); } - drawCaptionBlur(canvas, blur, text ? 0xFF787878 : 0xFF262626, thisView ? (text ? 0 : 0x33000000) : 0x44000000, false, !text, !text && thisView); + drawCaptionBlur(canvas, blur, Theme.multAlpha(text ? 0xFF787878 : 0xFF262626, alpha), Theme.multAlpha(thisView ? (text ? 0 : 0x33000000) : 0x44000000, alpha), false, !text, !text && thisView); canvas.restore(); } @@ -6238,6 +6300,13 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat mentionContainer.getAdapter().setNeedBotContext(false); } + @Override + public void updateMentionsLayoutPosition() { + if (mentionContainer != null) { + mentionContainer.setTranslationY(-getEditTextHeight() - keyboardNotifier.getKeyboardHeight()); + } + } + @Override protected void onUpdateShowKeyboard(float keyboardT) { super.onUpdateShowKeyboard(keyboardT); @@ -6252,6 +6321,29 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } super.invalidate(); } + + @Override + protected boolean showMoveButton() { + return placeProvider != null && placeProvider.canMoveCaptionAbove(); + } + + @Override + protected boolean isAtTop() { + return false; + } + + @Override + protected void onMoveButtonClick() { + toggleCaptionAbove(); + } + + @Override + protected void openedKeyboard() { + expandMoveButton(); + if (topCaptionEdit != null) { + topCaptionEdit.expandMoveButton(); + } + } }; captionEdit.setOnTimerChange(seconds -> { Object object1 = imagesArrLocals.get(currentIndex); @@ -6263,13 +6355,17 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (seconds != 0 && !placeProvider.isPhotoChecked(currentIndex)) { setPhotoChecked(); } + topCaptionEdit.setTimer(seconds); }); captionEdit.setAccount(currentAccount); captionEdit.setOnHeightUpdate(height -> { if (videoTimelineViewContainer != null && videoTimelineViewContainer.getVisibility() != View.GONE) { - videoTimelineViewContainer.setTranslationY(pickerView.getTranslationY() - Math.max(0, captionEdit.getEditTextHeight() - dp(46))); + videoTimelineViewContainer.setTranslationY(pickerView.getTranslationY() - Math.max(0, captionEdit.getEditTextHeight() - dp(46)) * captionEdit.getAlpha()); + } + muteItem.setTranslationY(-Math.max(0, height - dp(46)) * captionEdit.getAlpha()); + if (captionEdit.mentionContainer != null) { + captionEdit.mentionContainer.setTranslationY(-height); } - muteItem.setTranslationY(-Math.max(0, height - dp(46))); }); captionEdit.setOnAddPhotoClick(v -> { if (placeProvider == null || isCaptionOpen()) { @@ -6279,6 +6375,125 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat closePhoto(true, false); }); + topCaptionEdit = new CaptionPhotoViewer(activityContext, windowView, containerView, containerView, resourcesProvider, blurManager, this::applyCaption) { + private final Path path = new Path(); + + @Override + protected boolean customBlur() { + return true; + } + + @Override + protected boolean ignoreTouches(float x, float y) { + return !keyboardShown && currentEditMode != EDIT_MODE_NONE; + } + + @Override + protected void drawBlur(BlurringShader.StoryBlurDrawer blur, Canvas canvas, RectF rect, float r, boolean text, float ox, float oy, boolean thisView, float alpha) { + canvas.save(); + path.rewind(); + path.addRoundRect(rect, r, r, Path.Direction.CW); + canvas.clipPath(path); + if (thisView) { + canvas.translate(-getX() - topCaptionEditContainer.getX() + ox, -getY() - topCaptionEditContainer.getY() + oy); + } else { + canvas.translate(ox, oy); + } + drawCaptionBlur(canvas, blur, Theme.multAlpha(text ? 0xFF787878 : 0xFF262626, alpha), Theme.multAlpha(thisView ? (text ? 0 : 0x33000000) : 0x44000000, alpha), false, !text, !text && thisView); + canvas.restore(); + } + + @Override + protected boolean captionLimitToast() { + if (limitBulletin != null && Bulletin.getVisibleBulletin() == limitBulletin) { + return false; + } + return showCaptionLimitBulletin(containerView); + } + + @Override + public void invalidate() { + if (SharedConfig.photoViewerBlur && (animationInProgress == 1 || animationInProgress == 2 || animationInProgress == 3)) { + return; + } + super.invalidate(); + } + + @Override + protected void setupMentionContainer() { + mentionContainer.setReversed(true); + mentionContainer.getAdapter().setAllowStickers(false); + mentionContainer.getAdapter().setAllowBots(false); + mentionContainer.getAdapter().setAllowChats(false); + mentionContainer.getAdapter().setSearchInDailogs(true); + if (parentChatActivity != null) { + mentionContainer.getAdapter().setChatInfo(parentChatActivity.chatInfo); + mentionContainer.getAdapter().setNeedUsernames(parentChatActivity.currentChat != null); + } else { + mentionContainer.getAdapter().setChatInfo(null); + mentionContainer.getAdapter().setNeedUsernames(false); + } + mentionContainer.getAdapter().setNeedBotContext(false); + mentionContainer.setLayoutParams(LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP)); + } + + @Override + public void updateMentionsLayoutPosition() { + if (mentionContainer != null) { + mentionContainer.setTranslationY(getEditTextHeight()); + } + } + + @Override + protected boolean showMoveButton() { + return placeProvider != null && placeProvider.canMoveCaptionAbove(); + } + + @Override + protected boolean isAtTop() { + return true; + } + + @Override + protected void onMoveButtonClick() { + toggleCaptionAbove(); + } + + @Override + protected void openedKeyboard() { + expandMoveButton(); + if (captionEdit != null) { + captionEdit.expandMoveButton(); + } + } + }; + topCaptionEdit.setShowMoveButtonVisible(true, false); + topCaptionEdit.setOnTimerChange(seconds -> { + Object object1 = imagesArrLocals.get(currentIndex); + if (object1 instanceof MediaController.PhotoEntry) { + ((MediaController.PhotoEntry) object1).ttl = seconds; + } else if (object1 instanceof MediaController.SearchImage) { + ((MediaController.SearchImage) object1).ttl = seconds; + } + if (seconds != 0 && !placeProvider.isPhotoChecked(currentIndex)) { + setPhotoChecked(); + } + captionEdit.setTimer(seconds); + }); + topCaptionEdit.setAccount(currentAccount); + topCaptionEdit.setOnHeightUpdate(height -> { + if (topCaptionEdit.mentionContainer != null) { + topCaptionEdit.mentionContainer.setTranslationY(height); + } + }); + topCaptionEdit.setOnAddPhotoClick(v -> { + if (placeProvider == null || isCaptionOpen()) { + return; + } + placeProvider.needAddMorePhotos(); + closePhoto(true, false); + }); + stickerMakerBackgroundView = new StickerMakerBackgroundView(activityContext) { @Override public void setAlpha(float alpha) { @@ -6512,6 +6727,16 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat captionEditContainer.addView(captionEdit, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.BOTTOM)); containerView.addView(captionEditContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.BOTTOM, 0, 8, 0, 0)); + topCaptionEditContainer = new FrameLayout(parentActivity); + topCaptionEditContainer.addView(topCaptionEdit, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP)); + containerView.addView(topCaptionEditContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.TOP, 0, 8, 0, 0)); + + topBulletinUnderCaption = new FrameLayout(parentActivity); + containerView.addView(topBulletinUnderCaption, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 120, Gravity.FILL_HORIZONTAL | Gravity.TOP, 0, 0, 0, 0)); + + bottomBulletinUnderCaption = new FrameLayout(parentActivity); + containerView.addView(bottomBulletinUnderCaption, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 120, Gravity.FILL_HORIZONTAL | Gravity.BOTTOM, 0, 0, 0, 0)); + videoAvatarTooltip = new TextView(parentActivity); videoAvatarTooltip.setSingleLine(true); videoAvatarTooltip.setVisibility(View.GONE); @@ -7541,9 +7766,10 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } private boolean isCaptionOpen() { + final CaptionContainerView captionView = getCaptionView(); return ( - captionEdit != null && - (captionEdit.keyboardNotifier.keyboardVisible() || captionEdit.editText.isPopupShowing()) + captionView != null && + (captionView.keyboardNotifier.keyboardVisible() || captionView.editText.isPopupShowing()) ); } @@ -9020,11 +9246,14 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (currentIndex < 0 || currentIndex >= imagesArrLocals.size() || !isCaptionOpen()) { return; } - Object object = imagesArrLocals.get(currentIndex); if (apply) { applyCaption(); } - captionEdit.onBackPressed(); + getCaptionView().onBackPressed(); + } + + private CaptionContainerView getCaptionView() { + return placeProvider != null && placeProvider.isCaptionAbove() ? topCaptionEdit : captionEdit; } private CharSequence applyCaption() { @@ -9033,7 +9262,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } Object object = imagesArrLocals.get(currentIndex); - CharSequence caption = captionEdit.getText(); + CharSequence caption = getCaptionView().getText(); CharSequence[] result = new CharSequence[] { caption }; if (hasCaptionForAllMedia && !TextUtils.equals(captionForAllMedia, caption) && placeProvider.getPhotoIndex(currentIndex) != 0 && placeProvider.getSelectedCount() > 0) { @@ -9212,6 +9441,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (captionEdit != null) { captionEdit.updateColors(resourcesProvider); } + if (topCaptionEdit != null) { + topCaptionEdit.updateColors(resourcesProvider); + } if (videoTimelineView != null) { videoTimelineView.invalidate(); } @@ -10117,11 +10349,11 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat caption = ((MediaController.SearchImage) object).caption; } if (TextUtils.isEmpty(caption)) { - captionEdit.setText(""); + getCaptionView().setText(""); } else { - captionEdit.setText(AnimatedEmojiSpan.cloneSpans(caption, AnimatedEmojiDrawable.CACHE_TYPE_ALERT_PREVIEW)); + getCaptionView().setText(AnimatedEmojiSpan.cloneSpans(caption, AnimatedEmojiDrawable.CACHE_TYPE_ALERT_PREVIEW)); } - captionEdit.editText.getEditText().setAllowTextEntitiesIntersection(supportsSendingNewEntities()); + getCaptionView().editText.getEditText().setAllowTextEntitiesIntersection(supportsSendingNewEntities()); } public void showAlertDialog(AlertDialog.Builder builder) { @@ -11184,7 +11416,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat imageMoveAnimation = null; final int fromEditMode = currentEditMode; currentEditMode = mode; - captionEdit.keyboardNotifier.ignore(currentEditMode != EDIT_MODE_NONE); + getCaptionView().keyboardNotifier.ignore(currentEditMode != EDIT_MODE_NONE); if (paintKeyboardNotifier != null) { paintKeyboardNotifier.ignore(currentEditMode != EDIT_MODE_PAINT); } @@ -11415,7 +11647,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat imageMoveAnimation = null; currentEditMode = mode; - captionEdit.keyboardNotifier.ignore(currentEditMode != EDIT_MODE_NONE); + getCaptionView().keyboardNotifier.ignore(currentEditMode != EDIT_MODE_NONE); if (paintKeyboardNotifier != null) { paintKeyboardNotifier.ignore(currentEditMode != EDIT_MODE_PAINT); } @@ -11599,7 +11831,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat photoFilterView.init(); imageMoveAnimation = null; currentEditMode = mode; - captionEdit.keyboardNotifier.ignore(currentEditMode != EDIT_MODE_NONE); + getCaptionView().keyboardNotifier.ignore(currentEditMode != EDIT_MODE_NONE); if (paintKeyboardNotifier != null) { paintKeyboardNotifier.ignore(currentEditMode != EDIT_MODE_PAINT); } @@ -12034,7 +12266,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat paintingOverlay.hideEntities(); imageMoveAnimation = null; currentEditMode = EDIT_MODE_PAINT; - captionEdit.keyboardNotifier.ignore(currentEditMode != EDIT_MODE_NONE); + getCaptionView().keyboardNotifier.ignore(currentEditMode != EDIT_MODE_NONE); if (paintKeyboardNotifier != null) { paintKeyboardNotifier.ignore(currentEditMode != EDIT_MODE_PAINT); } @@ -12078,10 +12310,15 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat animatorSet.setDuration(200); animatorSet.start(); if (!show && isCaptionOpen()) { + closeCaptionEnter(true); if (captionEdit.editText.isPopupShowing()) { captionEdit.editText.hidePopup(true); } + if (topCaptionEdit.editText.isPopupShowing()) { + topCaptionEdit.editText.hidePopup(true); + } captionEdit.editText.closeKeyboard(); + topCaptionEdit.editText.closeKeyboard(); } } @@ -12772,7 +13009,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat totalImagesCount = 0; totalImagesCountMerge = 0; currentEditMode = EDIT_MODE_NONE; - captionEdit.keyboardNotifier.ignore(currentEditMode != EDIT_MODE_NONE); + getCaptionView().keyboardNotifier.ignore(currentEditMode != EDIT_MODE_NONE); if (paintKeyboardNotifier != null) { paintKeyboardNotifier.ignore(currentEditMode != EDIT_MODE_PAINT); } @@ -12892,6 +13129,8 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat tuneItem.setVisibility(View.GONE); tuneItem.setTag(null); captionEdit.setTimerVisible(false, false); + captionEdit.setShowMoveButtonVisible(false, false); + topCaptionEdit.setTimerVisible(false, false); rotateItem.setVisibility(View.GONE); mirrorItem.setVisibility(View.GONE); @@ -13115,6 +13354,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat updateActionBarTitlePadding(); } captionEdit.setAddPhotoVisible(sendPhotoType != SELECT_TYPE_NO_SELECT && (sendPhotoType == 2 || sendPhotoType == 5) && placeProvider.canCaptureMorePhotos(), false); + topCaptionEdit.setAddPhotoVisible(sendPhotoType != SELECT_TYPE_NO_SELECT && (sendPhotoType == 2 || sendPhotoType == 5) && placeProvider.canCaptureMorePhotos(), false); menuItem.setVisibility(View.GONE); imagesArrLocals.addAll(photos); Object obj = imagesArrLocals.get(index); @@ -13161,6 +13401,11 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat captionEdit.mentionContainer.getAdapter().setNeedUsernames(parentChatActivity.currentChat != null); captionEdit.mentionContainer.getAdapter().setNeedBotContext(false); } + if (parentChatActivity != null && topCaptionEdit.mentionContainer != null) { + topCaptionEdit.mentionContainer.getAdapter().setChatInfo(parentChatActivity.chatInfo); + topCaptionEdit.mentionContainer.getAdapter().setNeedUsernames(parentChatActivity.currentChat != null); + topCaptionEdit.mentionContainer.getAdapter().setNeedBotContext(false); + } // if (parentChatActivity != null) { // mentionsAdapter.setChatInfo(parentChatActivity.chatInfo); // mentionsAdapter.setNeedUsernames(parentChatActivity.currentChat != null); @@ -13268,21 +13513,19 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat playerAutoStarted = true; onActionClick(false); } else if (!imagesArrLocals.isEmpty()) { - Object entry = imagesArrLocals.get(index); - CharSequence caption = null; - TLRPC.User user = parentChatActivity != null ? parentChatActivity.getCurrentUser() : null; + final Object entry = imagesArrLocals.get(index); + final TLRPC.User user = parentChatActivity != null ? parentChatActivity.getCurrentUser() : null; boolean allowTimeItem = !isDocumentsPicker && parentChatActivity != null && !parentChatActivity.isSecretChat() && !parentChatActivity.isInScheduleMode() && user != null && !user.bot && !UserObject.isUserSelf(user) && !parentChatActivity.isEditingMessageMedia(); if (placeProvider != null && placeProvider.getEditingMessageObject() != null) { allowTimeItem = false; } if (entry instanceof TLRPC.BotInlineResult) { allowTimeItem = false; - } else if (entry instanceof MediaController.PhotoEntry) { - } else if (allowTimeItem && entry instanceof MediaController.SearchImage) { allowTimeItem = ((MediaController.SearchImage) entry).type == 0; } captionEdit.setTimerVisible(allowTimeItem, true); + topCaptionEdit.setTimerVisible(allowTimeItem, true); } checkFullscreenButton(); } @@ -13890,6 +14133,8 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat PorterDuffColorFilter filter = new PorterDuffColorFilter(getThemedColor(Theme.key_chat_editMediaButton), PorterDuff.Mode.MULTIPLY); captionEdit.setIsVideo(isVideo); captionEdit.setTimer(ttl); + topCaptionEdit.setIsVideo(isVideo); + topCaptionEdit.setTimer(ttl); paintItem.setColorFilter(isPainted ? filter : null); cropItem.setColorFilter(isCropped ? filter : null); tuneItem.setColorFilter(isFiltered ? filter : null); @@ -14213,43 +14458,63 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat eraseBtn.setTag(show ? 1 : null); } - private ObjectAnimator captionAnimator; private void showEditCaption(boolean show, boolean animated) { + final boolean top = placeProvider != null && placeProvider.isCaptionAbove(); + showEditCaption(captionEdit, show && !top, animated, captionEditAlpha); + showEditCaption(topCaptionEdit, show && top, animated, topCaptionEditAlpha); + } + + private final float[] captionEditAlpha = new float[] { 1.0f }; + private final float[] topCaptionEditAlpha = new float[] { 1.0f }; + private void showEditCaption(View view, boolean show, boolean animated, float[] alphaHolder) { + final float tr = dp(placeProvider != null && placeProvider.canMoveCaptionAbove() ? 175 : 58) * (view == topCaptionEdit ? -1.0f : 1.0f); if (!animated) { - captionEdit.animate().setListener(null).cancel(); - captionEdit.setVisibility(show ? View.VISIBLE : View.GONE); - captionEdit.setTranslationY(0); - captionEdit.setAlpha(pickerView.getAlpha()); + view.animate().setListener(null).cancel(); + view.setVisibility(show ? View.VISIBLE : View.GONE); + view.setTranslationY(show ? 0 : tr); + view.setAlpha(pickerView.getAlpha() * (alphaHolder[0] = show ? 1.0f : 0.0f)); } else { - if (show && captionEdit.getTag() == null) { - if (captionEdit.getVisibility() != View.VISIBLE) { - captionEdit.setVisibility(View.VISIBLE); - captionEdit.setAlpha(pickerView.getAlpha()); - captionEdit.setTranslationY(dp(58)); + if (show && view.getTag() == null) { + if (view.getVisibility() != View.VISIBLE) { + view.setVisibility(View.VISIBLE); + view.setAlpha(pickerView.getAlpha()); + view.setTranslationY(tr); } - if (captionAnimator != null) { - captionAnimator.removeAllListeners(); - captionAnimator.cancel(); - } - captionAnimator = ObjectAnimator.ofFloat(captionEdit, View.TRANSLATION_Y, captionEdit.getTranslationY(), 0); - captionAnimator.setDuration(220); - captionAnimator.setInterpolator(CubicBezierInterpolator.DEFAULT); - captionAnimator.start(); - - } else if (!show && captionEdit.getTag() != null) { - if (captionAnimator != null) { - captionAnimator.removeAllListeners(); - captionAnimator.cancel(); - } - - captionAnimator = ObjectAnimator.ofFloat(captionEdit, View.TRANSLATION_Y, captionEdit.getTranslationY(), dp(58)); - captionAnimator.addListener(new HideViewAfterAnimation(captionEdit)); - captionAnimator.setDuration(220); - captionAnimator.setInterpolator(CubicBezierInterpolator.DEFAULT); - captionAnimator.start(); + view.animate() + .translationY(0) + .setUpdateListener(v -> { + view.setAlpha(pickerView.getAlpha() * (alphaHolder[0] = v.getAnimatedFraction())); + if (view == captionEdit) { + if (videoTimelineViewContainer != null) { + videoTimelineViewContainer.setTranslationY(pickerView.getTranslationY() - Math.max(0, captionEdit.getEditTextHeight() - dp(46)) * captionEdit.getAlpha()); + } + muteItem.setTranslationY(-Math.max(0, captionEdit.getEditTextHeight() - dp(46)) * captionEdit.getAlpha()); + } + invalidateBlur(); + }) + .setDuration(420) + .setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT) + .start(); + } else if (!show && view.getTag() != null) { + view.animate() + .translationY(tr) + .setUpdateListener(v -> { + view.setAlpha(pickerView.getAlpha() * (alphaHolder[0] = (1.0f - v.getAnimatedFraction()))); + if (view == captionEdit) { + if (videoTimelineViewContainer != null) { + videoTimelineViewContainer.setTranslationY(pickerView.getTranslationY() - Math.max(0, captionEdit.getEditTextHeight() - dp(46)) * captionEdit.getAlpha()); + } + muteItem.setTranslationY(-Math.max(0, captionEdit.getEditTextHeight() - dp(46)) * captionEdit.getAlpha()); + } + invalidateBlur(); + }) + .setDuration(420) + .setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT) + .withEndAction(() -> view.setVisibility(View.GONE)) + .start(); } } - captionEdit.setTag(show ? 1 : null); + view.setTag(show ? 1 : null); } private ObjectAnimator videoTimelineAnimator; @@ -14289,7 +14554,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } } if (videoTimelineViewContainer != null && videoTimelineViewContainer.getVisibility() != View.GONE) { - videoTimelineViewContainer.setTranslationY(pickerView.getTranslationY() - Math.max(0, captionEdit.getEditTextHeight() - dp(46))); + videoTimelineViewContainer.setTranslationY(pickerView.getTranslationY() - Math.max(0, captionEdit.getEditTextHeight() - dp(46)) * captionEdit.getAlpha()); } videoTimelineViewContainer.setTag(show ? 1 : null); } @@ -14689,6 +14954,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (dialogId == 0 && currentMessageObject != null) dialogId = currentMessageObject.getDialogId(); captionEdit.setDialogId(dialogId); + if (topCaptionEdit != null) { + topCaptionEdit.setDialogId(dialogId); + } } } @@ -14703,11 +14971,12 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat final CharSequence caption = AnimatedEmojiSpan.cloneSpans(_caption, AnimatedEmojiDrawable.CACHE_TYPE_ALERT_PREVIEW); showEditCaption(editing, animated); if (editing || sendPhotoType == SELECT_TYPE_AVATAR) { - captionEdit.setText(caption); + getCaptionView().setText(caption); captionTextViewSwitcher.setVisibility(View.GONE); return; } else { captionEdit.setVisibility(View.GONE); + topCaptionEdit.setVisibility(View.GONE); } if (needCaptionLayout) { if (captionTextViewSwitcher.getParent() != pickerView) { @@ -15206,7 +15475,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat } public int getSelectionLength() { - return captionEdit.editText != null ? captionEdit.getSelectionLength() : 0; + return getCaptionView().editText != null ? getCaptionView().getSelectionLength() : 0; } private void setIndexToPaintingOverlay(int index, PaintingOverlay paintingOverlay) { @@ -16647,7 +16916,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat maskPaintView = null; } currentEditMode = EDIT_MODE_NONE; - captionEdit.keyboardNotifier.ignore(false); + getCaptionView().keyboardNotifier.ignore(false); if (paintKeyboardNotifier != null) { paintKeyboardNotifier.ignore(currentEditMode != EDIT_MODE_PAINT); } @@ -17373,7 +17642,7 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat return true; } - if (captionEdit.editText.isPopupShowing() || captionEdit.editText.isKeyboardVisible()) { + if (getCaptionView().editText.isPopupShowing() || getCaptionView().editText.isKeyboardVisible()) { if (ev.getAction() == MotionEvent.ACTION_UP) { closeCaptionEnter(true); } @@ -20867,6 +21136,9 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat if (captionEdit != null) { captionEdit.invalidateBlur(); } + if (topCaptionEdit != null) { + topCaptionEdit.invalidateBlur(); + } if (cutOutBtn != null) { cutOutBtn.invalidateBlur(); } @@ -21442,4 +21714,43 @@ public class PhotoViewer implements NotificationCenter.NotificationCenterDelegat videoItem.toggleSubMenu(); } } + + private void toggleCaptionAbove() { + if (placeProvider == null) return; + if (!placeProvider.canMoveCaptionAbove()) return; + + final CaptionContainerView fromView = getCaptionView(); + placeProvider.moveCaptionAbove(!placeProvider.isCaptionAbove()); + showEditCaption(true, true); + final CaptionContainerView toView = getCaptionView(); + final boolean above = placeProvider.isCaptionAbove(); + + if (fromView != toView) { + fromView.editText.hidePopup(true); + toView.setText(AnimatedEmojiSpan.cloneSpans(fromView.getText())); + toView.editText.getEditText().setAllowTextEntitiesIntersection(fromView.editText.getEditText().getAllowTextEntitiesIntersection()); + if (fromView.editText.getEditText().isFocused()) { + toView.editText.getEditText().requestFocus(); + toView.editText.getEditText().setSelection( + fromView.editText.getEditText().getSelectionStart(), + fromView.editText.getEditText().getSelectionEnd() + ); + } + if (fromView.mentionContainer != null) { + AndroidUtilities.removeFromParent(fromView.mentionContainer); + fromView.mentionContainer = null; + } + } + if (MessagesController.getInstance(currentAccount).shouldShowMoveCaptionHint()) { + MessagesController.getInstance(currentAccount).incrementMoveCaptionHint(); + BulletinFactory.of(above ? bottomBulletinUnderCaption : topBulletinUnderCaption, new DarkThemeResourceProvider()) + .createSimpleBulletin( + above ? R.raw.caption_up : R.raw.caption_down, + getString(above ? R.string.MovedCaptionUp : R.string.MovedCaptionDown), + getString(above ? R.string.MovedCaptionUpText : R.string.MovedCaptionDownText) + ) + .setImageScale(.8f) + .show(!above); + } + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java index c2236a1c6..00ed59e33 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/ProfileActivity.java @@ -14,6 +14,8 @@ import static org.telegram.messenger.ContactsController.PRIVACY_RULES_TYPE_ADDED import static org.telegram.messenger.LocaleController.formatPluralString; import static org.telegram.messenger.LocaleController.formatString; import static org.telegram.messenger.LocaleController.getString; +import static org.telegram.ui.Stars.StarsIntroActivity.formatStarsAmountShort; +import static org.telegram.ui.bots.AffiliateProgramFragment.percents; import android.Manifest; import android.animation.Animator; @@ -161,6 +163,8 @@ import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.tgnet.tl.TL_bots; import org.telegram.tgnet.tl.TL_fragment; +import org.telegram.tgnet.tl.TL_payments; +import org.telegram.tgnet.tl.TL_stars; import org.telegram.tgnet.tl.TL_stories; import org.telegram.ui.ActionBar.ActionBar; import org.telegram.ui.ActionBar.ActionBarMenu; @@ -269,10 +273,12 @@ import org.telegram.ui.Stories.StoryViewer; import org.telegram.ui.Stories.recorder.ButtonWithCounterView; import org.telegram.ui.Stories.recorder.DualCameraView; import org.telegram.ui.Stories.recorder.StoryRecorder; +import org.telegram.ui.bots.AffiliateProgramFragment; import org.telegram.ui.bots.BotBiometry; import org.telegram.ui.bots.BotDownloads; import org.telegram.ui.bots.BotLocation; import org.telegram.ui.bots.BotWebViewAttachedSheet; +import org.telegram.ui.bots.ChannelAffiliateProgramsFragment; import org.telegram.ui.bots.SetupEmojiStatusSheet; import java.io.BufferedInputStream; @@ -593,6 +599,8 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. private int notificationsSimpleRow; private int infoStartRow, infoEndRow; private int infoSectionRow; + private int affiliateRow; + private int infoAffiliateRow; private int sendMessageRow; private int reportRow; private int reportReactionRow; @@ -3713,7 +3721,21 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. return; } listView.stopScroll(); - if (position == notificationsSimpleRow) { + if (position == affiliateRow) { + TLRPC.User user = getMessagesController().getUser(userId); + if (userInfo != null && userInfo.starref_program != null) { + final long selfId = getUserConfig().getClientUserId(); + BotStarsController.getInstance(currentAccount).getConnectedBot(getContext(), selfId, userId, connectedBot -> { + if (connectedBot == null) { + ChannelAffiliateProgramsFragment.showConnectAffiliateAlert(context, currentAccount, userInfo.starref_program, getUserConfig().getClientUserId(), resourcesProvider, false); + } else { + ChannelAffiliateProgramsFragment.showShareAffiliateAlert(context, currentAccount, connectedBot, selfId, resourcesProvider); + } + }); + } else if (user != null && user.bot_can_edit) { + presentFragment(new AffiliateProgramFragment(userId)); + } + } else if (position == notificationsSimpleRow) { boolean muted = getMessagesController().isDialogMuted(did, topicId); getNotificationsController().muteDialog(did, topicId, !muted); BulletinFactory.createMuteBulletin(ProfileActivity.this, !muted, null).show(); @@ -4239,7 +4261,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. PersistColorPalette.getInstance(currentAccount).cleanup(); SharedPreferences prefs = getMessagesController().getMainSettings(); editor = prefs.edit(); - editor.remove("peerColors").remove("profilePeerColors").remove("boostingappearance").remove("bizbothint"); + editor.remove("peerColors").remove("profilePeerColors").remove("boostingappearance").remove("bizbothint").remove("movecaptionhint"); for (String key : prefs.getAll().keySet()) { if (key.contains("show_gift_for_") || key.contains("bdayhint_") || key.contains("bdayanim_")) { editor.remove(key); @@ -8755,6 +8777,8 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. bizLocationRow = -1; bizHoursRow = -1; infoSectionRow = -1; + affiliateRow = -1; + infoAffiliateRow = -1; secretSettingsSectionRow = -1; bottomPaddingRow = -1; addToGroupButtonRow = -1; @@ -8940,6 +8964,11 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. infoEndRow = rowCount - 1; infoSectionRow = rowCount++; + if (isBot && userInfo != null && userInfo.starref_program != null && (userInfo.starref_program.flags & 2) == 0 && getMessagesController().starrefConnectAllowed) { + affiliateRow = rowCount++; + infoAffiliateRow = rowCount++; + } + if (isBot) { if (botLocation == null && getContext() != null) botLocation = BotLocation.get(getContext(), currentAccount, userId); if (botBiometry == null && getContext() != null) botBiometry = BotBiometry.get(getContext(), currentAccount, userId); @@ -8981,7 +9010,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if (userInfo != null && userInfo.can_view_revenue && BotStarsController.getInstance(currentAccount).getTONBalance(userId) > 0) { botTonBalanceRow = rowCount++; } - if (BotStarsController.getInstance(currentAccount).getBotStarsBalance(userId) > 0 || BotStarsController.getInstance(currentAccount).hasTransactions(userId)) { + if (BotStarsController.getInstance(currentAccount).getBotStarsBalance(userId).amount > 0 || BotStarsController.getInstance(currentAccount).hasTransactions(userId)) { botStarsBalanceRow = rowCount++; } } @@ -9060,7 +9089,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. if ( chatInfo != null && chatInfo.can_view_stars_revenue && ( - BotStarsController.getInstance(currentAccount).getBotStarsBalance(did) > 0 || + BotStarsController.getInstance(currentAccount).getBotStarsBalance(did).amount > 0 || BotStarsController.getInstance(currentAccount).hasTransactions(did) ) || chatInfo != null && @@ -10875,7 +10904,8 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. VIEW_TYPE_CHANNEL = 23, VIEW_TYPE_STARS_TEXT_CELL = 24, VIEW_TYPE_BOT_APP = 25, - VIEW_TYPE_SHADOW_TEXT = 26; + VIEW_TYPE_SHADOW_TEXT = 26, + VIEW_TYPE_COLORFUL_TEXT = 27; private Context mContext; @@ -10968,6 +10998,11 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. view = new TextInfoPrivacyCell(mContext, resourcesProvider); break; } + case VIEW_TYPE_COLORFUL_TEXT: { + view = new AffiliateProgramFragment.ColorfulTextCell(mContext, resourcesProvider); + view.setBackgroundColor(getThemedColor(Theme.key_windowBackgroundWhite)); + break; + } case VIEW_TYPE_USER: { view = new UserCell(mContext, addMemberRow == -1 ? 9 : 6, 0, true, resourcesProvider); view.setBackgroundColor(getThemedColor(Theme.key_windowBackgroundWhite)); @@ -11441,8 +11476,8 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } else if (position == settingsRow) { textCell.setTextAndIcon(LocaleController.getString(R.string.ChannelAdminSettings), R.drawable.msg_customize, position != membersSectionRow - 1); } else if (position == channelBalanceRow) { - long stars_balance = BotStarsController.getInstance(currentAccount).getBotStarsBalance(-chatId); - long ton_balance = BotStarsController.getInstance(currentAccount).getTONBalance(-chatId); + final TL_stars.StarsAmount stars_balance = BotStarsController.getInstance(currentAccount).getBotStarsBalance(-chatId); + final long ton_balance = BotStarsController.getInstance(currentAccount).getTONBalance(-chatId); SpannableStringBuilder ssb = new SpannableStringBuilder(); if (ton_balance > 0) { if (ton_balance / 1_000_000_000.0 > 1000.0) { @@ -11457,16 +11492,16 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. ssb.append("TON ").append(formatterTON.format(ton_balance / 1_000_000_000.0)); } } - if (stars_balance > 0) { + if (stars_balance.amount > 0) { if (ssb.length() > 0) ssb.append(" "); - ssb.append("XTR ").append(AndroidUtilities.formatWholeNumber((int) stars_balance, 0)); + ssb.append("XTR ").append(formatStarsAmountShort(stars_balance)); } textCell.setTextAndValueAndIcon(getString(R.string.ChannelStars), ChannelMonetizationLayout.replaceTON(StarsIntroActivity.replaceStarsWithPlain(ssb, .7f), textCell.getTextView().getPaint()), R.drawable.menu_feature_paid, true); } else if (position == botStarsBalanceRow) { - long stars_balance = BotStarsController.getInstance(currentAccount).getBotStarsBalance(userId); + final TL_stars.StarsAmount stars_balance = BotStarsController.getInstance(currentAccount).getBotStarsBalance(userId); SpannableStringBuilder ssb = new SpannableStringBuilder(); - if (stars_balance > 0) { - ssb.append("XTR ").append(AndroidUtilities.formatWholeNumber((int) stars_balance, 0)); + if (stars_balance.amount > 0) { + ssb.append("XTR ").append(formatStarsAmountShort(stars_balance)); } textCell.setTextAndValueAndIcon(getString(R.string.BotBalanceStars), ChannelMonetizationLayout.replaceTON(StarsIntroActivity.replaceStarsWithPlain(ssb, .7f), textCell.getTextView().getPaint()), R.drawable.menu_premium_main, true); } else if (position == botTonBalanceRow) { @@ -11563,7 +11598,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. textCell.setImageLeft(23); } else if (position == starsRow) { StarsController c = StarsController.getInstance(currentAccount); - long balance = c.getBalance(); + long balance = c.getBalance().amount; textCell.setTextAndValueAndIcon(LocaleController.getString(R.string.MenuTelegramStars), c.balanceAvailable() && balance > 0 ? LocaleController.formatNumber((int) balance, ',') : "", new AnimatedEmojiDrawable.WrapSizeDrawable(PremiumGradient.getInstance().goldenStarMenuDrawable, dp(24), dp(24)), true); textCell.setImageLeft(23); } else if (position == businessRow) { @@ -11668,6 +11703,15 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. cell.setFixedSize(14); cell.setText(null); } + } else if (position == infoAffiliateRow) { + final TLRPC.User botUser = getMessagesController().getUser(userId); + if (botUser != null && botUser.bot && botUser.bot_can_edit) { + cell.setFixedSize(0); + cell.setText(formatString(R.string.ProfileBotAffiliateProgramInfoOwner, UserObject.getUserName(botUser), percents(userInfo != null && userInfo.starref_program != null ? userInfo.starref_program.commission_permille : 0))); + } else { + cell.setFixedSize(0); + cell.setText(formatString(R.string.ProfileBotAffiliateProgramInfo, UserObject.getUserName(botUser), percents(userInfo != null && userInfo.starref_program != null ? userInfo.starref_program.commission_permille : 0))); + } } if (position == infoSectionRow && lastSectionRow == -1 && secretSettingsSectionRow == -1 && sharedMediaRow == -1 && membersSectionRow == -1 || position == secretSettingsSectionRow || position == lastSectionRow || position == membersSectionRow && lastSectionRow == -1 && sharedMediaRow == -1) { cell.setBackgroundDrawable(Theme.getThemedDrawable(mContext, R.drawable.greydivider_bottom, getThemedColor(Theme.key_windowBackgroundGrayShadow))); @@ -11676,6 +11720,12 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. } break; } + case VIEW_TYPE_COLORFUL_TEXT: { + AffiliateProgramFragment.ColorfulTextCell cell = (AffiliateProgramFragment.ColorfulTextCell) holder.itemView; + cell.set(getThemedColor(Theme.key_color_green), R.drawable.filled_affiliate, getString(R.string.ProfileBotAffiliateProgram), null); + cell.setPercent(userInfo != null && userInfo.starref_program != null ? percents(userInfo.starref_program.commission_permille) : null); + break; + } case VIEW_TYPE_USER: UserCell userCell = (UserCell) holder.itemView; TLRPC.ChatParticipant part; @@ -11897,7 +11947,7 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. int type = holder.getItemViewType(); return type != VIEW_TYPE_HEADER && type != VIEW_TYPE_DIVIDER && type != VIEW_TYPE_SHADOW && type != VIEW_TYPE_EMPTY && type != VIEW_TYPE_BOTTOM_PADDING && type != VIEW_TYPE_SHARED_MEDIA && - type != 9 && type != 10; // These are legacy ones, left for compatibility + type != 9 && type != 10 && type != VIEW_TYPE_BOT_APP; // These are legacy ones, left for compatibility } @Override @@ -11966,8 +12016,10 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. return VIEW_TYPE_CHANNEL; } else if (position == botAppRow) { return VIEW_TYPE_BOT_APP; - } else if (position == infoSectionRow) { + } else if (position == infoSectionRow || position == infoAffiliateRow) { return VIEW_TYPE_SHADOW_TEXT; + } else if (position == affiliateRow) { + return VIEW_TYPE_COLORFUL_TEXT; } return 0; } @@ -13231,6 +13283,8 @@ public class ProfileActivity extends BaseFragment implements NotificationCenter. put(++pointer, reportDividerRow, sparseIntArray); put(++pointer, notificationsRow, sparseIntArray); put(++pointer, infoSectionRow, sparseIntArray); + put(++pointer, affiliateRow, sparseIntArray); + put(++pointer, infoAffiliateRow, sparseIntArray); put(++pointer, sendMessageRow, sparseIntArray); put(++pointer, reportRow, sparseIntArray); put(++pointer, reportReactionRow, sparseIntArray); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/SelectAnimatedEmojiDialog.java b/TMessagesProj/src/main/java/org/telegram/ui/SelectAnimatedEmojiDialog.java index 6f105d3ed..da7e9a0ac 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/SelectAnimatedEmojiDialog.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/SelectAnimatedEmojiDialog.java @@ -1900,6 +1900,20 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati next.run(); }); }; + final Utilities.Callback serverSearch = next -> { + if (ConnectionsManager.getInstance(currentAccount).getConnectionState() != ConnectionsManager.ConnectionStateConnected) { + next.run(); + return; + } + final String lang_code = newLanguage == null || newLanguage.length == 0 ? "" : newLanguage[0]; + MediaDataController.getInstance(currentAccount).searchStickers(true, lang_code, query, documents -> { + AnimatedEmojiDrawable.getDocumentFetcher(currentAccount).putDocuments(documents); + for (TLRPC.Document doc : documents) { + documentIds.add(doc.id); + } + next.run(); + }); + }; final Utilities.Callback searchEmojiSuggestions = next -> { if (queryFullyConsistsOfEmojis) { ArrayList stickerSets = MediaDataController.getInstance(currentAccount).getStickerSets(MediaDataController.TYPE_EMOJIPACKS); @@ -2059,7 +2073,7 @@ public class SelectAnimatedEmojiDialog extends FrameLayout implements Notificati next.run(); }; - Utilities.doCallbacks(searchCategories, searchByKeywords, searchEmojiSuggestions, searchAvatarConstructor, searchFromSets, applySearch); + Utilities.doCallbacks(searchCategories, searchByKeywords, serverSearch, searchEmojiSuggestions, searchAvatarConstructor, searchFromSets, applySearch); } }, delay ? 425 : 0); if (searchBox != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stars/BotStarsActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/Stars/BotStarsActivity.java index a354c3905..41199aaa1 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stars/BotStarsActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stars/BotStarsActivity.java @@ -5,6 +5,7 @@ import static org.telegram.messenger.AndroidUtilities.dp; import static org.telegram.messenger.LocaleController.formatString; import static org.telegram.messenger.LocaleController.getString; import static org.telegram.ui.ChannelMonetizationLayout.replaceTON; +import static org.telegram.ui.ChatEditActivity.applyNewSpan; import android.app.Activity; import android.content.Context; @@ -75,6 +76,8 @@ import org.telegram.ui.StatisticActivity; import org.telegram.ui.Stories.recorder.ButtonWithCounterView; import org.telegram.ui.TwoStepVerificationActivity; import org.telegram.ui.TwoStepVerificationSetupActivity; +import org.telegram.ui.bots.AffiliateProgramFragment; +import org.telegram.ui.bots.ChannelAffiliateProgramsFragment; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; @@ -439,6 +442,7 @@ public class BotStarsActivity extends BaseFragment implements NotificationCenter } private final int BALANCE = 1; + private final int BUTTON_AFFILIATE = 2; private CharSequence titleInfo; private CharSequence proceedsInfo; @@ -455,15 +459,24 @@ public class BotStarsActivity extends BaseFragment implements NotificationCenter items.add(UItem.asBlackHeader(getString(R.string.BotStarsOverview))); TLRPC.TL_payments_starsRevenueStats stats = s.getStarsRevenueStats(bot_id); if (stats != null && stats.status != null) { - availableValue.crypto_amount = stats.status.available_balance; + availableValue.contains1 = false; + availableValue.contains2 = true; + availableValue.crypto_amount2 = stats.status.available_balance; + availableValue.crypto_currency2 = "XTR"; availableValue.currency = "USD"; - availableValue.amount = (long) (stats.status.available_balance * rate * 100.0); - totalValue.crypto_amount = stats.status.current_balance; + availableValue.amount2 = (long) (stats.status.available_balance.amount * rate * 100.0); + totalValue.contains1 = false; + totalValue.contains2 = true; + totalValue.crypto_amount2 = stats.status.current_balance; + totalValue.crypto_currency2 = "XTR"; + totalValue.amount2 = (long) (stats.status.current_balance.amount * rate * 100.0); totalValue.currency = "USD"; - totalValue.amount = (long) (stats.status.current_balance * rate * 100.0); - totalProceedsValue.crypto_amount = stats.status.overall_revenue; + totalProceedsValue.contains1 = false; + totalProceedsValue.contains2 = true; + totalProceedsValue.crypto_amount2 = stats.status.overall_revenue; + totalProceedsValue.crypto_currency2 = "XTR"; + totalProceedsValue.amount2 = (long) (stats.status.overall_revenue.amount * rate * 100.0); totalProceedsValue.currency = "USD"; - totalProceedsValue.amount = (long) (stats.status.overall_revenue * rate * 100.0); setStarsBalance(stats.status.available_balance, stats.status.next_withdrawal_at); balanceButtonsLayout.setVisibility(stats.status.withdrawal_enabled ? View.VISIBLE : View.GONE); @@ -475,6 +488,10 @@ public class BotStarsActivity extends BaseFragment implements NotificationCenter items.add(UItem.asBlackHeader(getString(R.string.BotStarsAvailableBalance))); items.add(UItem.asCustom(BALANCE, balanceLayout)); items.add(UItem.asShadow(-3, withdrawInfo)); + if (getMessagesController().starrefConnectAllowed) { + items.add(AffiliateProgramFragment.ColorfulTextCell.Factory.as(BUTTON_AFFILIATE, Theme.getColor(Theme.key_color_green, resourceProvider), R.drawable.filled_earn_stars, applyNewSpan(getString(R.string.BotAffiliateProgramRowTitle)), getString(R.string.BotAffiliateProgramRowText))); + items.add(UItem.asShadow(-4, null)); + } items.add(UItem.asFullscreenCustom(transactionsLayout, 0)); } else if (type == TYPE_TON) { TL_stats.TL_broadcastRevenueStats stats = s.getTONRevenueStats(bot_id, true); @@ -600,14 +617,16 @@ public class BotStarsActivity extends BaseFragment implements NotificationCenter StarsIntroActivity.showTransactionSheet(getContext(), true, bot_id, currentAccount, t, getResourceProvider()); } else if (item.object instanceof TL_stats.BroadcastRevenueTransaction) { ChannelMonetizationLayout.showTransactionSheet(getContext(), currentAccount, (TL_stats.BroadcastRevenueTransaction) item.object, bot_id, resourceProvider); + } else if (item.id == BUTTON_AFFILIATE) { + presentFragment(new ChannelAffiliateProgramsFragment(bot_id)); } } - private void setStarsBalance(long crypto_amount, int blockedUntil) { + private void setStarsBalance(TL_stars.StarsAmount crypto_amount, int blockedUntil) { if (balanceTitle == null || balanceSubtitle == null) return; - long amount = (long) (rate * crypto_amount * 100.0); - SpannableStringBuilder ssb = new SpannableStringBuilder(StarsIntroActivity.replaceStarsWithPlain("XTR " + LocaleController.formatNumber(crypto_amount, ' '), 1f)); + long amount = (long) (rate * crypto_amount.amount * 100.0); + SpannableStringBuilder ssb = new SpannableStringBuilder(StarsIntroActivity.replaceStarsWithPlain(TextUtils.concat("XTR ", StarsIntroActivity.formatStarsAmount(crypto_amount, 0.8f, ' ')), 1f)); int index = TextUtils.indexOf(ssb, "."); if (index >= 0) { ssb.setSpan(balanceTitleSizeSpan, index, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); @@ -617,7 +636,7 @@ public class BotStarsActivity extends BaseFragment implements NotificationCenter balanceEditTextContainer.setVisibility(amount > 0 ? View.VISIBLE : View.GONE); if (balanceEditTextAll) { balanceEditTextIgnore = true; - balanceEditText.setText(Long.toString(balanceEditTextValue = crypto_amount)); + balanceEditText.setText(Long.toString(balanceEditTextValue = crypto_amount.amount)); balanceEditText.setSelection(balanceEditText.getText().length()); balanceEditTextIgnore = false; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stars/BotStarsController.java b/TMessagesProj/src/main/java/org/telegram/ui/Stars/BotStarsController.java index fd30ccaef..8a0e8ce46 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stars/BotStarsController.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stars/BotStarsController.java @@ -2,6 +2,9 @@ package org.telegram.ui.Stars; import static org.telegram.messenger.LocaleController.getString; +import android.content.Context; +import android.text.TextUtils; + import androidx.annotation.NonNull; import org.telegram.messenger.AndroidUtilities; @@ -9,10 +12,15 @@ import org.telegram.messenger.DialogObject; import org.telegram.messenger.MessagesController; import org.telegram.messenger.NotificationCenter; 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.tgnet.tl.TL_bots; +import org.telegram.tgnet.tl.TL_payments; import org.telegram.tgnet.tl.TL_stars; import org.telegram.tgnet.tl.TL_stats; +import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.Theme; import org.telegram.ui.ChannelMonetizationLayout; @@ -54,9 +62,9 @@ public class BotStarsController { private final HashMap lastLoadedTonStats = new HashMap<>(); private final HashMap tonStats = new HashMap<>(); - public long getBotStarsBalance(long did) { + public TL_stars.StarsAmount getBotStarsBalance(long did) { TLRPC.TL_payments_starsRevenueStats botStats = getStarsRevenueStats(did); - return botStats == null ? 0 : botStats.status.current_balance; + return botStats == null ? new TL_stars.StarsAmount(0) : botStats.status.current_balance; } public long getTONBalance(long did) { @@ -66,7 +74,7 @@ public class BotStarsController { public long getAvailableBalance(long did) { TLRPC.TL_payments_starsRevenueStats botStats = getStarsRevenueStats(did); - return botStats == null ? 0 : botStats.status.available_balance; + return botStats == null ? 0 : botStats.status.available_balance.amount; } public boolean isStarsBalanceAvailable(long did) { @@ -83,7 +91,7 @@ public class BotStarsController { public boolean botHasStars(long did) { TLRPC.TL_payments_starsRevenueStats stats = getStarsRevenueStats(did); - return stats != null && stats.status != null && (stats.status.available_balance > 0 || stats.status.overall_revenue > 0 || stats.status.current_balance > 0); + return stats != null && stats.status != null && (stats.status.available_balance.amount > 0 || stats.status.overall_revenue.amount > 0 || stats.status.current_balance.amount > 0); } public boolean botHasTON(long did) { @@ -238,8 +246,8 @@ public class BotStarsController { } ConnectionsManager.getInstance(currentAccount).sendRequest(req, (res, err) -> AndroidUtilities.runOnUIThread(() -> { state.loading[type] = false; - if (res instanceof TL_stars.TL_payments_starsStatus) { - TL_stars.TL_payments_starsStatus r = (TL_stars.TL_payments_starsStatus) res; + if (res instanceof TL_stars.StarsStatus) { + TL_stars.StarsStatus r = (TL_stars.StarsStatus) res; MessagesController.getInstance(currentAccount).putUsers(r.users, false); MessagesController.getInstance(currentAccount).putChats(r.chats, false); @@ -273,4 +281,350 @@ public class BotStarsController { return !state.transactions[type].isEmpty(); } + private final HashMap connectedBots = new HashMap<>(); + public static class ChannelConnectedBots { + + public final int currentAccount; + public final long dialogId; + public int count; + public boolean endReached; + public final ArrayList bots = new ArrayList<>(); + public long lastRequestTime; + + public ChannelConnectedBots(int currentAccount, long dialogId) { + this.currentAccount = currentAccount; + this.dialogId = dialogId; + check(); + } + + public void clear() { + count = 0; + error = false; + endReached = false; + } + + public void check() { + if ((System.currentTimeMillis() - lastRequestTime) > 1000 * 60 * 15) { + clear(); + cancel(); + load(); + } + } + + public void cancel() { + if (reqId != 0) { + ConnectionsManager.getInstance(currentAccount).cancelRequest(reqId, true); + reqId = 0; + } + loading = false; + } + + public boolean isLoading() { + return loading; + } + + private boolean loading = false; + private boolean error = false; + private int reqId; + public void load() { + if (loading || error || endReached) return; + + lastRequestTime = System.currentTimeMillis(); + TL_payments.getConnectedStarRefBots req = new TL_payments.getConnectedStarRefBots(); + req.peer = MessagesController.getInstance(currentAccount).getInputPeer(dialogId); + req.limit = 20; + if (!bots.isEmpty()) { + TL_payments.connectedBotStarRef bot = bots.get(bots.size() - 1); + req.flags |= 4; + req.offset_date = bot.date; + req.offset_link = bot.url; + } + reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (res, err) -> AndroidUtilities.runOnUIThread(() -> { + reqId = 0; + if (res instanceof TL_payments.connectedStarRefBots) { + TL_payments.connectedStarRefBots r = (TL_payments.connectedStarRefBots) res; + MessagesController.getInstance(currentAccount).putUsers(r.users, false); + if (count <= 0) { + bots.clear(); + } + count = r.count; + bots.addAll(r.connected_bots); + endReached = r.connected_bots.isEmpty() || bots.size() >= count; + } else { + error = true; + endReached = true; + } + loading = false; + NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.channelConnectedBotsUpdate, dialogId); + })); + } + + public void apply(TL_payments.connectedStarRefBots res) { + MessagesController.getInstance(currentAccount).putUsers(res.users, false); + clear(); + bots.clear(); + cancel(); + count = res.count; + bots.addAll(res.connected_bots); + endReached = res.connected_bots.isEmpty() || bots.size() >= count; + error = false; + NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.channelConnectedBotsUpdate, dialogId); + load(); + } + + public void applyEdit(TL_payments.connectedStarRefBots res) { + MessagesController.getInstance(currentAccount).putUsers(res.users, false); + for (int a = 0; a < res.connected_bots.size(); ++a) { + TL_payments.connectedBotStarRef bot = res.connected_bots.get(a); + for (int i = 0; i < bots.size(); ++i) { + if (bots.get(i).bot_id == bot.bot_id) { + if (bot.revoked) { + bots.remove(i); + count = Math.max(count - 1, 0); + } else { + bots.set(i, bot); + } + break; + } + } + } + NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.channelConnectedBotsUpdate, dialogId); + load(); + } + } + + public ChannelConnectedBots getChannelConnectedBots(long dialogId) { + ChannelConnectedBots bots = connectedBots.get(dialogId); + if (bots == null) { + connectedBots.put(dialogId, bots = new ChannelConnectedBots(currentAccount, dialogId)); + } + return bots; + } + + public boolean channelHasConnectedBots(long dialogId) { + final ChannelConnectedBots bots = getChannelConnectedBots(dialogId); + return bots != null && bots.count > 0; + } + + + private final HashMap suggestedBots = new HashMap<>(); + public static class ChannelSuggestedBots { + + public final int currentAccount; + public final long dialogId; + public int count; + public boolean endReached; + public final ArrayList bots = new ArrayList<>(); + public long lastRequestTime; + + public ChannelSuggestedBots(int currentAccount, long dialogId) { + this.currentAccount = currentAccount; + this.dialogId = dialogId; + check(); + } + + public void clear() { + count = 0; + endReached = false; + error = false; + lastRequestTime = 0; + lastOffset = null; + } + + public void check() { + if ((System.currentTimeMillis() - lastRequestTime) > 1000 * 60 * 15) { + clear(); + cancel(); + load(); + } + } + + public void cancel() { + if (reqId != 0) { + ConnectionsManager.getInstance(currentAccount).cancelRequest(reqId, true); + reqId = 0; + } + loading = false; + } + + public boolean isLoading() { + return loading; + } + + public int getCount() { + return Math.max(count, bots.size()); + } + + public enum Sort { + BY_PROFITABILITY, + BY_REVENUE, + BY_DATE + }; + + private Sort sorting = Sort.BY_PROFITABILITY; + public void setSort(Sort sort) { + if (sorting != sort) { + sorting = sort; + reload(); + } + } + + public Sort getSort() { + return sorting; + } + + private boolean loading = false; + private boolean error = false; + private String lastOffset = null; + private int reqId; + public void load() { + if (loading || error || endReached) return; + + lastRequestTime = System.currentTimeMillis(); + TL_payments.getSuggestedStarRefBots req = new TL_payments.getSuggestedStarRefBots(); + req.peer = MessagesController.getInstance(currentAccount).getInputPeer(dialogId); + req.limit = 20; + req.order_by_date = sorting == Sort.BY_DATE; + req.order_by_revenue = sorting == Sort.BY_REVENUE; + if (!TextUtils.isEmpty(lastOffset)) { + req.offset = lastOffset; + } else { + req.offset = ""; + } + ConnectionsManager.getInstance(currentAccount).sendRequest(req, (res, err) -> AndroidUtilities.runOnUIThread(() -> { + if (res instanceof TL_payments.suggestedStarRefBots) { + TL_payments.suggestedStarRefBots r = (TL_payments.suggestedStarRefBots) res; + MessagesController.getInstance(currentAccount).putUsers(r.users, false); + if (count <= 0) { + bots.clear(); + } + count = r.count; + bots.addAll(r.suggested_bots); + lastOffset = r.next_offset; + endReached = r.suggested_bots.isEmpty() || bots.size() >= count; + } else { + error = true; + endReached = true; + } + loading = false; + NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.channelSuggestedBotsUpdate, dialogId); + })); + } + + public void remove(long did) { + for (int i = 0; i < bots.size(); ++i) { + if (bots.get(i).bot_id == did) { + bots.remove(i); + count--; + NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.channelSuggestedBotsUpdate, dialogId); + break; + } + } + } + + public void reload() { + clear(); + cancel(); + load(); + } + } + + public ChannelSuggestedBots getChannelSuggestedBots(long dialogId) { + ChannelSuggestedBots bots = suggestedBots.get(dialogId); + if (bots == null) { + suggestedBots.put(dialogId, bots = new ChannelSuggestedBots(currentAccount, dialogId)); + } + return bots; + } + + public boolean channelHasSuggestedBots(long dialogId) { + final ChannelConnectedBots bots = getChannelConnectedBots(dialogId); + return bots != null && bots.count > 0; + } + + private boolean loadingAdminedBots; + public ArrayList adminedBots; + private boolean loadingAdminedChannels; + public ArrayList adminedChannels; + + public void loadAdmined() { + if (!loadingAdminedBots || adminedBots != null) { + loadingAdminedBots = true; + TL_bots.getAdminedBots req1 = new TL_bots.getAdminedBots(); + ConnectionsManager.getInstance(currentAccount).sendRequest(req1, (res, err) -> AndroidUtilities.runOnUIThread(() -> { + adminedBots = new ArrayList<>(); + loadingAdminedBots = false; + if (res instanceof TLRPC.Vector) { + TLRPC.Vector vector = (TLRPC.Vector) res; + for (int i = 0; i < vector.objects.size(); ++i) { + adminedBots.add((TLRPC.User) vector.objects.get(i)); + } + MessagesController.getInstance(currentAccount).putUsers(adminedBots, false); + } + })); + } + + if (!loadingAdminedChannels || adminedChannels != null) { + loadingAdminedChannels = true; + TLRPC.TL_channels_getAdminedPublicChannels req2 = new TLRPC.TL_channels_getAdminedPublicChannels(); + ConnectionsManager.getInstance(currentAccount).sendRequest(req2, (res, err) -> AndroidUtilities.runOnUIThread(() -> { + adminedChannels = new ArrayList<>(); + loadingAdminedBots = false; + if (res instanceof TLRPC.messages_Chats) { + TLRPC.messages_Chats chats = (TLRPC.messages_Chats) res; + MessagesController.getInstance(currentAccount).putChats(chats.chats, false); + adminedChannels.addAll(chats.chats); + } + })); + } + } + + public ArrayList getAdmined() { + loadAdmined(); + ArrayList list = new ArrayList<>(); + if (adminedBots != null) { + list.addAll(adminedBots); + } + if (adminedChannels != null) { + list.addAll(adminedChannels); + } + return list; + } + + public void getConnectedBot(Context context, long dialogId, long botId, Utilities.Callback whenDone) { + if (whenDone == null) return; + ChannelConnectedBots bots = connectedBots.get(dialogId); + if (bots != null) { + for (int i = 0; i < bots.bots.size(); ++i) { + if (!bots.bots.get(i).revoked && bots.bots.get(i).bot_id == botId) { + whenDone.run(bots.bots.get(i)); + return; + } + } + } + final AlertDialog progressDialog = new AlertDialog(context, AlertDialog.ALERT_TYPE_SPINNER); + TL_payments.getConnectedStarRefBot req = new TL_payments.getConnectedStarRefBot(); + req.peer = MessagesController.getInstance(currentAccount).getInputPeer(dialogId); + req.bot = MessagesController.getInstance(currentAccount).getInputUser(botId); + int reqId = ConnectionsManager.getInstance(currentAccount).sendRequest(req, (res, err) -> AndroidUtilities.runOnUIThread(() -> { + progressDialog.dismiss(); + if (res instanceof TL_payments.connectedStarRefBots) { + TL_payments.connectedStarRefBots r = (TL_payments.connectedStarRefBots) res; + MessagesController.getInstance(currentAccount).putUsers(r.users, false); + for (int i = 0; i < r.connected_bots.size(); ++i) { + if (r.connected_bots.get(i).bot_id == botId && !r.connected_bots.get(i).revoked) { + whenDone.run(r.connected_bots.get(i)); + return; + } + } + } + whenDone.run(null); + })); + progressDialog.setCanCancel(true); + progressDialog.setOnCancelListener(d -> { + ConnectionsManager.getInstance(currentAccount).cancelRequest(reqId, true); + }); + progressDialog.showDelayed(200); + } + } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stars/StarsController.java b/TMessagesProj/src/main/java/org/telegram/ui/Stars/StarsController.java index 805f294fa..d7fe3d9e2 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stars/StarsController.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stars/StarsController.java @@ -7,12 +7,12 @@ import static org.telegram.messenger.LocaleController.getString; import android.app.Activity; import android.content.Context; -import android.content.SharedPreferences; import android.graphics.drawable.Drawable; import android.text.TextUtils; import android.util.LongSparseArray; import android.view.Gravity; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.billingclient.api.BillingClient; @@ -105,22 +105,23 @@ public class StarsController { private long lastBalanceLoaded; private boolean balanceLoading, balanceLoaded; - public long balance; + @NonNull + public TL_stars.StarsAmount balance = new TL_stars.StarsAmount(0); public long minus; - public long getBalance() { + public TL_stars.StarsAmount getBalance() { return getBalance(null); } public long getBalance(boolean withMinus) { - return getBalance(withMinus, null); + return getBalance(withMinus, null).amount; } - public long getBalance(Runnable loaded) { + public TL_stars.StarsAmount getBalance(Runnable loaded) { return getBalance(true, loaded); } - public long getBalance(boolean withMinus, Runnable loaded) { + public TL_stars.StarsAmount getBalance(boolean withMinus, Runnable loaded) { if ((!balanceLoaded || System.currentTimeMillis() - lastBalanceLoaded > 1000 * 60) && !balanceLoading) { balanceLoading = true; TL_stars.TL_payments_getStarsStatus req = new TL_stars.TL_payments_getStarsStatus(); @@ -130,15 +131,15 @@ public class StarsController { boolean updatedSubscriptions = false; boolean updatedBalance = !balanceLoaded; lastBalanceLoaded = System.currentTimeMillis(); - if (res instanceof TL_stars.TL_payments_starsStatus) { - TL_stars.TL_payments_starsStatus r = (TL_stars.TL_payments_starsStatus) res; + if (res instanceof TL_stars.StarsStatus) { + TL_stars.StarsStatus r = (TL_stars.StarsStatus) res; MessagesController.getInstance(currentAccount).putUsers(r.users, false); MessagesController.getInstance(currentAccount).putChats(r.chats, false); if (transactions[ALL_TRANSACTIONS].isEmpty()) { for (TL_stars.StarsTransaction t : r.history) { transactions[ALL_TRANSACTIONS].add(t); - transactions[t.stars > 0 ? INCOMING_TRANSACTIONS : OUTGOING_TRANSACTIONS].add(t); + transactions[t.stars.amount > 0 ? INCOMING_TRANSACTIONS : OUTGOING_TRANSACTIONS].add(t); } for (int i = 0; i < 3; ++i) { transactionsExist[i] = !transactions[i].isEmpty() || transactionsExist[i]; @@ -159,7 +160,7 @@ public class StarsController { updatedSubscriptions = true; } - if (this.balance != r.balance) { + if (this.balance.amount != r.balance.amount) { updatedBalance = true; } this.balance = r.balance; @@ -182,7 +183,13 @@ public class StarsController { } })); } - return Math.max(0, balance - (withMinus ? minus : 0)); + if (withMinus && minus > 0) { + TL_stars.StarsAmount stars = new TL_stars.StarsAmount(); + stars.amount = Math.max(0, balance.amount - minus); + stars.nanos = balance.nanos; + return stars; + } + return balance; } public void invalidateBalance() { @@ -191,8 +198,8 @@ public class StarsController { balanceLoaded = true; } - public void updateBalance(long balance) { - if (this.balance != balance) { + public void updateBalance(TL_stars.StarsAmount balance) { + if (!this.balance.equals(balance)) { this.balance = balance; this.minus = 0; NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.starBalanceUpdated); @@ -528,8 +535,8 @@ public class StarsController { } ConnectionsManager.getInstance(currentAccount).sendRequest(req, (res, err) -> AndroidUtilities.runOnUIThread(() -> { loading[type] = false; - if (res instanceof TL_stars.TL_payments_starsStatus) { - TL_stars.TL_payments_starsStatus r = (TL_stars.TL_payments_starsStatus) res; + if (res instanceof TL_stars.StarsStatus) { + TL_stars.StarsStatus r = (TL_stars.StarsStatus) res; MessagesController.getInstance(currentAccount).putUsers(r.users, false); MessagesController.getInstance(currentAccount).putChats(r.chats, false); @@ -588,8 +595,8 @@ public class StarsController { } ConnectionsManager.getInstance(currentAccount).sendRequest(req, (res, err) -> AndroidUtilities.runOnUIThread(() -> { subscriptionsLoading = false; - if (res instanceof TL_stars.TL_payments_starsStatus) { - TL_stars.TL_payments_starsStatus r = (TL_stars.TL_payments_starsStatus) res; + if (res instanceof TL_stars.StarsStatus) { + TL_stars.StarsStatus r = (TL_stars.StarsStatus) res; MessagesController.getInstance(currentAccount).putUsers(r.users, false); MessagesController.getInstance(currentAccount).putChats(r.chats, false); @@ -620,8 +627,8 @@ public class StarsController { req.offset = ""; ConnectionsManager.getInstance(currentAccount).sendRequest(req, (res, err) -> AndroidUtilities.runOnUIThread(() -> { insufficientSubscriptionsLoading = false; - if (res instanceof TL_stars.TL_payments_starsStatus) { - TL_stars.TL_payments_starsStatus r = (TL_stars.TL_payments_starsStatus) res; + if (res instanceof TL_stars.StarsStatus) { + TL_stars.StarsStatus r = (TL_stars.StarsStatus) res; MessagesController.getInstance(currentAccount).putUsers(r.users, false); MessagesController.getInstance(currentAccount).putChats(r.chats, false); insufficientSubscriptions.addAll(r.subscriptions); @@ -660,7 +667,7 @@ public class StarsController { } private void showStarsTopupInternal(Activity activity, long amount, String purpose) { - if (getBalance() >= amount || amount <= 0) { + if (getBalance().amount >= amount || amount <= 0) { BaseFragment lastFragment = LaunchActivity.getSafeLastFragment(); if (lastFragment == null) return; BulletinFactory.of(lastFragment).createSimpleBulletin(R.raw.stars_topup, getString(R.string.StarsTopupLinkEnough), getString(R.string.StarsTopupLinkTopupAnyway), () -> { @@ -1206,7 +1213,7 @@ public class StarsController { final int subscription_period = form.invoice.subscription_period; final boolean[] allDone = new boolean[] { false }; StarsIntroActivity.openConfirmPurchaseSheet(context, resourcesProvider, currentAccount, messageObject, dialogId, product, stars, form.photo, subscription_period, whenDone -> { - if (balance < stars) { + if (balance.amount < stars) { if (!MessagesController.getInstance(currentAccount).starsPurchaseAvailable()) { paymentFormOpened = false; if (whenDone != null) { @@ -1282,7 +1289,7 @@ public class StarsController { final boolean[] allDone = new boolean[] { false }; StarsIntroActivity.openStarsChannelInviteSheet(context, resourcesProvider, currentAccount, chatInvite, whenDone -> { - if (balance < stars) { + if (balance.amount < stars) { if (!MessagesController.getInstance(currentAccount).starsPurchaseAvailable()) { paymentFormOpened = false; if (whenDone != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stars/StarsIntroActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/Stars/StarsIntroActivity.java index 3e5084f9b..b8b26162c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stars/StarsIntroActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stars/StarsIntroActivity.java @@ -6,7 +6,9 @@ import static org.telegram.messenger.LocaleController.formatPluralStringComma; import static org.telegram.messenger.LocaleController.formatPluralStringSpaced; import static org.telegram.messenger.LocaleController.formatString; import static org.telegram.messenger.LocaleController.getString; +import static org.telegram.ui.ChatEditActivity.applyNewSpan; import static org.telegram.ui.Stars.StarsIntroActivity.StarsTransactionView.getPlatformDrawable; +import static org.telegram.ui.bots.AffiliateProgramFragment.percents; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -34,6 +36,7 @@ import android.text.TextUtils; import android.text.TextWatcher; import android.text.style.ClickableSpan; import android.text.style.ImageSpan; +import android.text.style.RelativeSizeSpan; import android.text.style.ReplacementSpan; import android.util.TypedValue; import android.view.Gravity; @@ -84,6 +87,7 @@ import org.telegram.messenger.browser.Browser; import org.telegram.tgnet.ConnectionsManager; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; +import org.telegram.tgnet.tl.TL_payments; import org.telegram.tgnet.tl.TL_stars; import org.telegram.tgnet.tl.TL_stories; import org.telegram.ui.ActionBar.ActionBar; @@ -135,10 +139,14 @@ import org.telegram.ui.GradientHeaderActivity; import org.telegram.ui.ImageReceiverSpan; import org.telegram.ui.LaunchActivity; import org.telegram.ui.PhotoViewer; +import org.telegram.ui.PremiumFeatureCell; import org.telegram.ui.ProfileActivity; import org.telegram.ui.Stories.recorder.ButtonWithCounterView; import org.telegram.ui.Stories.recorder.HintView2; +import org.telegram.ui.bots.AffiliateProgramFragment; +import org.telegram.ui.bots.ChannelAffiliateProgramsFragment; +import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -350,10 +358,10 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi SpannableStringBuilder sb = new SpannableStringBuilder(); sb.append(starBalanceIcon); - sb.append(LocaleController.formatNumber(s.getBalance(), ',')); + sb.append(formatStarsAmount(s.getBalance(), 0.66f, ' ')); starBalanceTextView.setText(sb); - buyButton.setText(LocaleController.getString(s.getBalance() > 0 ? R.string.StarsBuyMore : R.string.StarsBuy), true); + buyButton.setText(LocaleController.getString(s.getBalance().amount > 0 ? R.string.StarsBuyMore : R.string.StarsBuy), true); } @Override @@ -579,6 +587,7 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi private final int BUTTON_EXPAND = -1; private final int BUTTON_GIFT = -2; private final int BUTTON_SUBSCRIPTIONS_EXPAND = -3; + private final int BUTTON_AFFILIATE = -4; public void fillItems(ArrayList items, UniversalAdapter adapter) { if (getContext() == null) { @@ -594,40 +603,12 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi giftButton.setVisibility(getMessagesController().starsGiftsEnabled ? View.VISIBLE : View.GONE); } -// items.add(UItem.asAnimatedHeader(-99, getString(R.string.TelegramStarsChoose))); -// -// int stars = 1; -// ArrayList options = c.getOptions(); -// if (options != null && !options.isEmpty()) { -// int hidden = 0; -// for (int id = 0; id < options.size(); ++id) { -// TL_stars.TL_starsTopupOption option = options.get(id); -// if (option.extended && !expanded) { -// hidden++; -// continue; -// } -// items.add(StarTierView.Factory.asStarTier(id, stars++, option)); -// } -// if (!expanded && hidden > 0) { -// items.add(ExpandView.Factory.asExpand(BUTTON_EXPAND, getString(expanded ? R.string.NotifyLessOptions : R.string.NotifyMoreOptions), !expanded).accent()); -// } -// } else { -// items.add(UItem.asFlicker(FlickerLoadingView.STAR_TIER)); -// items.add(UItem.asFlicker(FlickerLoadingView.STAR_TIER)); -// items.add(UItem.asFlicker(FlickerLoadingView.STAR_TIER)); -// items.add(UItem.asFlicker(FlickerLoadingView.STAR_TIER)); -// items.add(UItem.asFlicker(FlickerLoadingView.STAR_TIER)); -// } -// -// items.add(UItem.asShadow(AndroidUtilities.replaceSingleTag(getString(R.string.StarsTOS), () -> { -// Browser.openUrl(getContext(), getString(R.string.StarsTOSLink)); -// }))); items.add(UItem.asShadow(null)); -// -// if (getMessagesController().starsGiftsEnabled) { -// items.add(UItem.asButton(BUTTON_GIFT, R.drawable.menu_stars_gift, getString(R.string.TelegramStarsGift)).accent()); -// items.add(UItem.asShadow(null)); -// } + + if (getMessagesController().starrefConnectAllowed) { + items.add(AffiliateProgramFragment.ColorfulTextCell.Factory.as(BUTTON_AFFILIATE, getThemedColor(Theme.key_color_green), R.drawable.filled_earn_stars, applyNewSpan(getString(R.string.UserAffiliateProgramRowTitle)), getString(R.string.UserAffiliateProgramRowText))); + items.add(UItem.asShadow(null)); + } if (c.hasSubscriptions()) { items.add(UItem.asHeader(getString(R.string.StarMySubscriptions))); @@ -659,6 +640,8 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi } else if (item.id == BUTTON_SUBSCRIPTIONS_EXPAND) { StarsController.getInstance(currentAccount).loadSubscriptions(); adapter.update(true); + } else if (item.id == BUTTON_AFFILIATE) { + presentFragment(new ChannelAffiliateProgramsFragment(getUserConfig().getClientUserId())); } else if (item.instanceOf(StarTierView.Factory.class)) { if (item.object instanceof TL_stars.TL_starsTopupOption) { StarsController.getInstance(currentAccount).buy(getParentActivity(), (TL_stars.TL_starsTopupOption) item.object, (success, error) -> { @@ -751,7 +734,7 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi StarsController c = StarsController.getInstance(currentAccount); amountTextView.cancelAnimation(); - long balance = StarsController.getInstance(currentAccount).getBalance(); + long balance = StarsController.getInstance(currentAccount).getBalance().amount; if (balance > lastBalance && lastBalance != -1) { bounce(); } @@ -1414,6 +1397,8 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi public void set(TL_stars.StarsTransaction transaction, boolean bot, boolean divider) { long did = DialogObject.getPeerDialogId(transaction.peer.peer); + final boolean affiliate_to_bot = (transaction.flags & 131072) != 0; + final boolean affiliate_to_channel = !affiliate_to_bot && (transaction.flags & 65536) != 0; threeLines = did != 0 || transaction.subscription || transaction.floodskip || transaction.stargift != null || transaction.gift && transaction.peer instanceof TL_stars.TL_starsTransactionPeerFragment; titleTextViewParams.bottomMargin = threeLines ? 0 : dp(4.33f); subtitleTextView.setVisibility(threeLines ? View.VISIBLE : View.GONE); @@ -1465,9 +1450,9 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi setGiftImage(span.imageReceiver, transaction.stargift, 16); titleTextView.setText(username); if (transaction.refund) { - subtitleTextView.setText(TextUtils.concat(spanString, " ", LocaleController.getString(transaction.stars > 0 ? R.string.Gift2TransactionRefundedSent : R.string.Gift2TransactionRefundedConverted))); + subtitleTextView.setText(TextUtils.concat(spanString, " ", LocaleController.getString(transaction.stars.amount > 0 ? R.string.Gift2TransactionRefundedSent : R.string.Gift2TransactionRefundedConverted))); } else { - subtitleTextView.setText(TextUtils.concat(spanString, " ", LocaleController.getString(transaction.stars > 0 ? R.string.Gift2TransactionConverted : R.string.Gift2TransactionSent))); + subtitleTextView.setText(TextUtils.concat(spanString, " ", LocaleController.getString(transaction.stars.amount > 0 ? R.string.Gift2TransactionConverted : R.string.Gift2TransactionSent))); } } else if (transaction.subscription) { titleTextView.setText(username); @@ -1479,6 +1464,10 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi subtitleTextView.setVisibility(VISIBLE); subtitleTextView.setText(String.format(Locale.US, "%s subscription fee", period)); } + } else if (affiliate_to_channel) { + titleTextView.setText(username); + subtitleTextView.setVisibility(deleted ? GONE : VISIBLE); + subtitleTextView.setText(LocaleController.formatString(R.string.StarTransactionCommission, percents(transaction.starref_commission_permille))); } else if (transaction.gift) { titleTextView.setText(username); subtitleTextView.setVisibility(deleted ? GONE : VISIBLE); @@ -1570,14 +1559,14 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi imageView.setImageDrawable(null); } - if (transaction.stars > 0) { + if (transaction.stars.amount > 0) { amountTextView.setVisibility(View.VISIBLE); amountTextView.setTextColor(Theme.getColor(Theme.key_color_green)); - amountTextView.setText(TextUtils.concat("+", LocaleController.formatNumber(transaction.stars, ' '), " ", star)); - } else if (transaction.stars < 0) { + amountTextView.setText(TextUtils.concat("+", formatStarsAmount(transaction.stars), " ", star)); + } else if (transaction.stars.amount < 0) { amountTextView.setVisibility(View.VISIBLE); amountTextView.setTextColor(Theme.getColor(Theme.key_color_red)); - amountTextView.setText(TextUtils.concat("-", LocaleController.formatNumber(-transaction.stars, ' '), " ", star)); + amountTextView.setText(TextUtils.concat(formatStarsAmount(transaction.stars), " ", star)); } else { amountTextView.setVisibility(View.GONE); } @@ -2262,7 +2251,7 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi @Override public void show() { - long balance = StarsController.getInstance(currentAccount).getBalance(); + long balance = StarsController.getInstance(currentAccount).getBalance().amount; BaseFragment lastFragment = LaunchActivity.getLastFragment(); if (lastFragment instanceof ChatActivity) { ChatActivity chatActivity = (ChatActivity) lastFragment; @@ -2419,7 +2408,7 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi if (adapter != null) { adapter.update(true); } - long balance = StarsController.getInstance(currentAccount).getBalance(); + long balance = StarsController.getInstance(currentAccount).getBalance().amount; headerView.titleView.setText(formatPluralStringComma("StarsNeededTitle", (int) (starsNeeded - balance))); if (actionBar != null) { actionBar.setTitle(getTitle()); @@ -2436,7 +2425,7 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi @Override public void show() { - long balance = StarsController.getInstance(currentAccount).getBalance(); + long balance = StarsController.getInstance(currentAccount).getBalance().amount; if (balance >= starsNeeded) { if (whenPurchased != null) { whenPurchased.run(); @@ -2506,7 +2495,7 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi this.starsNeeded = starsNeeded; headerView = new HeaderView(context, currentAccount, resourcesProvider); - long balance = StarsController.getInstance(currentAccount).getBalance(); + long balance = StarsController.getInstance(currentAccount).getBalance().amount; headerView.titleView.setText(formatPluralString("StarsNeededTitle", (int) Math.max(0, starsNeeded - balance))); String stringRes; if (type == TYPE_SUBSCRIPTION_BUY) { @@ -3074,11 +3063,16 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi if (!t.extended_media.isEmpty()) { return getString(R.string.StarMediaPurchase); } + final boolean affiliate_to_bot = (t.flags & 131072) != 0; + final boolean affiliate_to_channel = !affiliate_to_bot && (t.flags & 65536) != 0; + if (affiliate_to_channel) { + return LocaleController.formatString(R.string.StarTransactionCommission, percents(t.starref_commission_permille)); + } if (t.stargift != null) { if (t.refund) { - return LocaleController.getString(t.stars > 0 ? R.string.Gift2TransactionRefundedSent : R.string.Gift2TransactionRefundedConverted); + return LocaleController.getString(t.stars.amount > 0 ? R.string.Gift2TransactionRefundedSent : R.string.Gift2TransactionRefundedConverted); } else { - return LocaleController.getString(t.stars > 0 ? R.string.Gift2TransactionConverted : R.string.Gift2TransactionSent); + return LocaleController.getString(t.stars.amount > 0 ? R.string.Gift2TransactionConverted : R.string.Gift2TransactionSent); } } if (t.subscription) { @@ -3130,7 +3124,7 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi t.peer = new TL_stars.TL_starsTransactionPeer(); t.peer.peer = action.boost_peer; t.date = date; - t.stars = action.stars; + t.stars = new TL_stars.StarsAmount(action.stars); t.id = action.transaction_id; t.gift = true; t.flags |= 8192; @@ -3148,7 +3142,7 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi t.peer = new TL_stars.TL_starsTransactionPeer(); t.peer.peer = sent_by; t.date = date; - t.stars = action.stars; + t.stars = new TL_stars.StarsAmount(action.stars); t.id = action.transaction_id; t.gift = true; t.sent_by = sent_by; @@ -3164,7 +3158,7 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi t.peer = new TL_stars.TL_starsTransactionPeer(); t.peer.peer = action.peer; t.date = date; - t.stars = action.total_amount; + t.stars = new TL_stars.StarsAmount(action.total_amount); t.id = action.charge.id; t.refund = true; return showTransactionSheet(context, false, 0, currentAccount, t, resourcesProvider); @@ -3178,7 +3172,7 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi t.peer = new TL_stars.TL_starsTransactionPeer(); t.peer.peer = MessagesController.getInstance(currentAccount).getPeer(receipt.bot_id); t.date = receipt.date; - t.stars = -receipt.total_amount; + t.stars = new TL_stars.StarsAmount(-receipt.total_amount); t.id = receipt.transaction_id; return showTransactionSheet(context, bot, 0, currentAccount, t, resourcesProvider); } @@ -3283,6 +3277,8 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi return null; final boolean giveaway = (transaction.flags & 8192) != 0; + final boolean affiliate_to_bot = (transaction.flags & 131072) != 0; + final boolean affiliate_to_channel = !affiliate_to_bot && (transaction.flags & 65536) != 0; BottomSheet.Builder b = new BottomSheet.Builder(context, false, resourcesProvider); BottomSheet[] sheet = new BottomSheet[1]; @@ -3298,7 +3294,7 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi setGiftImage(imageView.getImageReceiver(), transaction.stargift, 160); linearLayout.addView(imageView, LayoutHelper.createLinear(160, 160, Gravity.CENTER, 0, -8, 0, 10)); } else if (giveaway || transaction.gift) { - setGiftImage(imageView, imageView.getImageReceiver(), transaction.stars); + setGiftImage(imageView, imageView.getImageReceiver(), transaction.stars.amount); linearLayout.addView(imageView, LayoutHelper.createLinear(160, 160, Gravity.CENTER, 0, -8, 0, 10)); } else if (!transaction.extended_media.isEmpty()) { imageView.setRoundRadius(dp(30)); @@ -3374,7 +3370,7 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi imageView.setImage(ImageLocation.getForWebFile(WebFile.createWithWebDocument(transaction.photo)), "100_100", null, 0, null); } else { imageView.setRoundRadius(dp(50)); - final long did = transaction.subscription && bot ? dialogId : DialogObject.getPeerDialogId(transaction.peer.peer); + final long did = affiliate_to_channel ? DialogObject.getPeerDialogId(transaction.starref_peer) : transaction.subscription && bot ? dialogId : DialogObject.getPeerDialogId(transaction.peer.peer); AvatarDrawable avatarDrawable = new AvatarDrawable(); if (did >= 0) { TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(did); @@ -3420,8 +3416,8 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); textView.setTypeface(AndroidUtilities.bold()); textView.setGravity(Gravity.CENTER); - textView.setTextColor(Theme.getColor(transaction.stars >= 0 ? Theme.key_color_green : Theme.key_color_red, resourcesProvider)); - textView.setText(replaceStarsWithPlain((transaction.stars >= 0 ? "+" : "-") + LocaleController.formatNumber((int) Math.abs(transaction.stars), ' ') + " ⭐️", .8f)); + textView.setTextColor(Theme.getColor(transaction.stars.amount >= 0 ? Theme.key_color_green : Theme.key_color_red, resourcesProvider)); + textView.setText(replaceStarsWithPlain(TextUtils.concat((transaction.stars.amount >= 0 ? "+" : ""), formatStarsAmount(transaction.stars), " ⭐️"), .8f)); SpannableStringBuilder s = new SpannableStringBuilder(textView.getText()); if (transaction.refund) { appendStatus(s, textView, getString(R.string.StarsRefunded)); @@ -3442,7 +3438,7 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi if (self) { textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourcesProvider)); - textView.setText(replaceStarsWithPlain(LocaleController.formatNumber((int) Math.abs(transaction.stars), ' ') + " ⭐️", .8f)); + textView.setText(replaceStarsWithPlain(TextUtils.concat(formatStarsAmount(transaction.stars), " ⭐️"), .8f)); } textView = new LinkSpanDrawable.LinksTextView(context); @@ -3477,7 +3473,7 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi if (!transaction.refund) { final long did = DialogObject.getPeerDialogId(transaction.peer.peer); final TLRPC.User didUser = MessagesController.getInstance(currentAccount).getUser(did); - if (transaction.stars > 0) { // converted + if (transaction.stars.amount > 0) { // converted tableView.addRowUser(getString(R.string.StarGiveawayPrizeFrom), currentAccount, did, () -> { sheet[0].dismiss(); final BaseFragment lastFragment = LaunchActivity.getSafeLastFragment(); @@ -3531,7 +3527,49 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi } } else if (transaction.peer instanceof TL_stars.TL_starsTransactionPeer) { final long did = DialogObject.getPeerDialogId(transaction.peer.peer); - if (giveaway) { + if (affiliate_to_bot) { + final long botId = dialogId; + final long channelId = DialogObject.getPeerDialogId(transaction.starref_peer); + final long referredUserId = did; + tableView.addRowLink(getString(R.string.StarAffiliateReason), getString(R.string.StarAffiliateReasonProgram), () -> { + sheet[0].dismiss(); + final BaseFragment lastFragment = LaunchActivity.getSafeLastFragment(); + if (lastFragment != null) { + lastFragment.presentFragment(new AffiliateProgramFragment(botId)); + } + }); + tableView.addRowUser(getString(R.string.StarAffiliate), currentAccount, channelId, () -> { + sheet[0].dismiss(); + final BaseFragment lastFragment = LaunchActivity.getSafeLastFragment(); + if (lastFragment != null) { + lastFragment.presentFragment(ProfileActivity.of(channelId)); + } + }); + tableView.addRowUser(getString(R.string.StarAffiliateReferredUser), currentAccount, referredUserId, () -> { + sheet[0].dismiss(); + final BaseFragment lastFragment = LaunchActivity.getSafeLastFragment(); + if (lastFragment != null) { + lastFragment.presentFragment(ProfileActivity.of(referredUserId)); + } + }); + tableView.addRow(getString(R.string.StarAffiliateCommission), percents(transaction.starref_commission_permille)); + } else if (affiliate_to_channel) { + final long botId = did; + final long channelId = dialogId; + tableView.addRowLink(getString(R.string.StarAffiliateReason), getString(R.string.StarAffiliateReasonProgram), () -> { + BotStarsController.getInstance(currentAccount).getConnectedBot(context, dialogId, botId, connectedBot -> { + sheet[0].dismiss(); + ChannelAffiliateProgramsFragment.showShareAffiliateAlert(context, currentAccount, connectedBot, dialogId, resourcesProvider); + }); + }); + tableView.addRowUser(getString(R.string.StarAffiliateMiniApp), currentAccount, botId, () -> { + sheet[0].dismiss(); + final BaseFragment lastFragment = LaunchActivity.getSafeLastFragment(); + if (lastFragment != null) { + lastFragment.presentFragment(ProfileActivity.of(botId)); + } + }); + } else if (giveaway) { tableView.addRowUser(getString(R.string.StarGiveawayPrizeFrom), currentAccount, did, () -> { sheet[0].dismiss(); final BaseFragment lastFragment = LaunchActivity.getSafeLastFragment(); @@ -3564,7 +3602,7 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi } } }); - tableView.addRow(getString(R.string.StarGiveawayGift), formatPluralStringComma("Stars", (int) transaction.stars)); + tableView.addRow(getString(R.string.StarGiveawayGift), formatStarsAmountString(transaction.stars)); } else if (transaction.subscription && !bot) { tableView.addRowUser(getString(R.string.StarSubscriptionTo), currentAccount, did, () -> { sheet[0].dismiss(); @@ -3916,12 +3954,12 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi if (did >= 0) { TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(did); deleted = user == null || UserObject.isDeleted(user); - username = UserObject.getPublicUsername(user); + username = UserObject.getUserName(user); avatarSpan.setUser(user); } else { TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-did); deleted = chat == null; - username = ChatObject.getPublicUsername(chat); + username = chat != null ? chat.title : ""; avatarSpan.setChat(chat); } SpannableStringBuilder ssb = new SpannableStringBuilder("x " + username); @@ -4006,7 +4044,7 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi lastFragment.presentFragment(ChatActivity.of(did)); })); }; - if (c.balance < subscription.pricing.amount) { + if (c.balance.amount < subscription.pricing.amount) { new StarsNeededSheet(context, resourcesProvider, subscription.pricing.amount, business ? StarsNeededSheet.TYPE_BIZ_SUBSCRIPTION_KEEP : did < 0 ? StarsNeededSheet.TYPE_SUBSCRIPTION_KEEP : StarsNeededSheet.TYPE_BOT_SUBSCRIPTION_KEEP, peerName, refulfill).show(); } else { refulfill.run(); @@ -5160,4 +5198,84 @@ public class StarsIntroActivity extends GradientHeaderActivity implements Notifi rowTextView.setText(gift.availability_remains <= 0 ? formatPluralStringComma("Gift2Availability2ValueNone", gift.availability_total) : formatPluralStringComma("Gift2Availability4Value", gift.availability_remains, LocaleController.formatNumber(gift.availability_total, ','))); } } + + private static DecimalFormat floatFormat; + + public static CharSequence formatStarsAmount(TL_stars.StarsAmount starsAmount) { + return formatStarsAmount(starsAmount, 0.777f, ','); + } + + public static CharSequence formatStarsAmount(TL_stars.StarsAmount starsAmount, float relativeSize, char symbol) { + SpannableStringBuilder ssb = new SpannableStringBuilder(); + final long amount = starsAmount.amount + (starsAmount.nanos < 0 && starsAmount.amount > 0 ? -1 : (starsAmount.nanos > 0 && starsAmount.amount < 0 ? +1 : 0)); + final boolean negative = amount < 0; + if (starsAmount.nanos != 0) { + ssb.append((negative ? "-" : "") + LocaleController.formatNumber(Math.abs(amount), symbol)); + if (floatFormat == null) floatFormat = new DecimalFormat("0.################"); + String str = floatFormat.format((starsAmount.nanos < 0 ? 1e9 + starsAmount.nanos : starsAmount.nanos) / 1e9d); + int index; + if ((index = str.indexOf(".")) >= 0) { + int fromIndex = ssb.length(); + ssb.append(str.substring(index)); + ssb.setSpan(new RelativeSizeSpan(relativeSize), fromIndex + 1, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } else { + ssb.append((negative ? "-" : "") + LocaleController.formatNumber(Math.abs(amount), ' ')); + } + return ssb; + } + + public static CharSequence formatStarsAmountShort(TL_stars.StarsAmount starsAmount) { + return formatStarsAmountShort(starsAmount, 0.777f, ' '); + } + + public static CharSequence formatStarsAmountShort(TL_stars.StarsAmount starsAmount, float relativeSize, char symbol) { + SpannableStringBuilder ssb = new SpannableStringBuilder(); + final long amount = starsAmount.amount + (starsAmount.nanos < 0 && starsAmount.amount > 0 ? -1 : (starsAmount.nanos > 0 && starsAmount.amount < 0 ? +1 : 0)); + final boolean negative = amount < 0; + if (Math.abs(amount) <= 1000 && starsAmount.nanos != 0) { + ssb.append((negative ? "-" : "") + LocaleController.formatNumber(Math.abs(amount), symbol)); + if (floatFormat == null) floatFormat = new DecimalFormat("0.################"); + String str = floatFormat.format((starsAmount.nanos < 0 ? 1e9 + starsAmount.nanos : starsAmount.nanos) / 1e9d); + int index; + if ((index = str.indexOf(".")) >= 0) { + int fromIndex = ssb.length(); + String part = str.substring(index); + if (part.length() > 1) { + ssb.append(part.substring(0, Math.min(part.length(), 3))); + ssb.setSpan(new RelativeSizeSpan(relativeSize), fromIndex + 1, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + } else if (starsAmount.amount <= 1000) { + ssb.append((negative ? "-" : "") + LocaleController.formatNumber(Math.abs(amount), symbol)); + } else { + ssb.append((negative ? "-" : "") + AndroidUtilities.formatWholeNumber((int) Math.abs(amount), 0)); + } + return ssb; + } + + public static CharSequence formatStarsAmountString(TL_stars.StarsAmount starsAmount) { + return formatStarsAmountString(starsAmount, 0.777f, ','); + } + + public static CharSequence formatStarsAmountString(TL_stars.StarsAmount starsAmount, float relativeSize, char symbol) { + SpannableStringBuilder ssb = new SpannableStringBuilder(); + final long amount = starsAmount.amount + (starsAmount.nanos < 0 && starsAmount.amount > 0 ? -1 : (starsAmount.nanos > 0 && starsAmount.amount < 0 ? +1 : 0)); + final boolean negative = amount < 0; + if (starsAmount.nanos != 0) { + ssb.append((negative ? "-" : "") + LocaleController.formatNumber(Math.abs(amount), symbol)); + if (floatFormat == null) floatFormat = new DecimalFormat("0.################"); + String str = floatFormat.format((starsAmount.nanos < 0 ? 1e9 + starsAmount.nanos : starsAmount.nanos) / 1e9d); + int index; + if ((index = str.indexOf(".")) >= 0) { + int fromIndex = ssb.length(); + ssb.append(str.substring(index)); + ssb.setSpan(new RelativeSizeSpan(relativeSize), fromIndex + 1, ssb.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + ssb.append(" ").append(getString(R.string.StarsNano)); + } else { + ssb.append(formatPluralStringComma("Stars", (int) starsAmount.amount)); + } + return ssb; + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stars/StarsReactionsSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/Stars/StarsReactionsSheet.java index 6b60536d3..76f0d4183 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stars/StarsReactionsSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stars/StarsReactionsSheet.java @@ -345,7 +345,7 @@ public class StarsReactionsSheet extends BottomSheet { }); }; - if (starsController.balanceAvailable() && starsController.getBalance() < totalStars) { + if (starsController.balanceAvailable() && starsController.getBalance().amount < totalStars) { new StarsIntroActivity.StarsNeededSheet(context, resourcesProvider, totalStars, StarsIntroActivity.StarsNeededSheet.TYPE_REACTIONS, chat == null ? "" : chat.title, send).show(); } else { send.run(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/StatisticActivity.java b/TMessagesProj/src/main/java/org/telegram/ui/StatisticActivity.java index 0ca3878ee..7dbb526c8 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/StatisticActivity.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/StatisticActivity.java @@ -491,6 +491,7 @@ public class StatisticActivity extends BaseFragment implements NotificationCente } } } else if (id == NotificationCenter.boostByChannelCreated) { + if (getParentLayout() == null) return; TLRPC.Chat chat = (TLRPC.Chat) args[0]; boolean isGiveaway = (boolean) args[1]; List fragmentStack = getParentLayout().getFragmentStack(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/PeerStoriesView.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/PeerStoriesView.java index 4fd546af7..2bd278f71 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stories/PeerStoriesView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/PeerStoriesView.java @@ -2991,7 +2991,7 @@ public class PeerStoriesView extends SizeNotifierFrameLayout implements Notifica chatAttachAlert.setMaxSelectedPhotos(-1, true); chatAttachAlert.setDialogId(dialogId); chatAttachAlert.init(); - chatAttachAlert.getCommentTextView().setText(chatActivityEnterView.getFieldText()); + chatAttachAlert.getCommentView().setText(chatActivityEnterView.getFieldText()); delegate.showDialog(chatAttachAlert); } @@ -3135,7 +3135,7 @@ public class PeerStoriesView extends SizeNotifierFrameLayout implements Notifica } } }); - chatAttachAlert.getCommentTextView().setText(chatActivityEnterView.getFieldText()); + chatAttachAlert.getCommentView().setText(chatActivityEnterView.getFieldText()); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/StoriesViewPager.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/StoriesViewPager.java index 16ee91026..51bb7a8bc 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stories/StoriesViewPager.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/StoriesViewPager.java @@ -291,7 +291,7 @@ public class StoriesViewPager extends ViewPager { } return true; } - private static boolean eq(ArrayList a, ArrayList b) { + public static boolean eq(ArrayList a, ArrayList b) { if (a == null && b == null) return true; if (a == null || b == null) return false; if (a.size() != b.size()) return false; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/StoryViewer.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/StoryViewer.java index af0e30a69..cc4ba13d5 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stories/StoryViewer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/StoryViewer.java @@ -1381,7 +1381,7 @@ public class StoryViewer implements NotificationCenter.NotificationCenterDelegat currentPlayerScope.firstFrameRendered = true; } FileLog.d("StoryViewer requestPlayer: currentPlayerScope.player start " + uri); - currentPlayerScope.player.start(isPaused(), uri, t, isInSilentMode, currentSpeed); + currentPlayerScope.player.start(false, isPaused(), uri, t, isInSilentMode, currentSpeed); currentPlayerScope.invalidate(); } else { FileLog.d("StoryViewer requestPlayer: url is null (1)"); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/CaptionContainerView.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/CaptionContainerView.java index a949406f2..7bf4a818f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/CaptionContainerView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/CaptionContainerView.java @@ -197,7 +197,7 @@ public class CaptionContainerView extends FrameLayout { if (blurDrawer == null) { blurDrawer = new BlurringShader.StoryBlurDrawer(blurManager, view, BlurringShader.StoryBlurDrawer.BLUR_TYPE_EMOJI_VIEW); } - drawBlur(blurDrawer, canvas, rectF, 0, false, 0, -view.getY(), false); + drawBlur(blurDrawer, canvas, rectF, 0, false, 0, -view.getY(), false, 1.0f); } else { drawBackground(canvas, rectF, 0, .95f, view); } @@ -246,6 +246,9 @@ public class CaptionContainerView extends FrameLayout { editText.getEditText().setHintText(LocaleController.getString(R.string.AddCaption), false); hintTextBitmapPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); editText.getEditText().setTranslationX(AndroidUtilities.dp(-40 + 18)); + if (isAtTop()) { + editText.getEditText().setGravity(Gravity.TOP); + } editText.getEmojiButton().setAlpha(0f); editText.getEditText().addTextChangedListener(new TextWatcher() { @@ -309,7 +312,7 @@ public class CaptionContainerView extends FrameLayout { } }); editText.getEditText().setLinkTextColor(Color.WHITE); - addView(editText, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.FILL_HORIZONTAL, 12, 12, 12 + additionalRightMargin(), 12)); + addView(editText, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, (isAtTop() ? Gravity.TOP : Gravity.BOTTOM) | Gravity.FILL_HORIZONTAL, 12, 12, 12 + additionalRightMargin(), 12)); applyButton = new BounceableImageView(context); ScaleStateListAnimator.apply(applyButton, 0.05f, 1.25f); @@ -327,7 +330,7 @@ public class CaptionContainerView extends FrameLayout { textChangeRunnable.run(); }); applyButton.setTranslationY(-AndroidUtilities.dp(1)); - addView(applyButton, LayoutHelper.createFrame(44, 44, Gravity.RIGHT | Gravity.BOTTOM)); + addView(applyButton, LayoutHelper.createFrame(44, 44, Gravity.RIGHT | (isAtTop() ? Gravity.TOP : Gravity.BOTTOM))); limitTextView = new AnimatedTextView(context, false, true, true); limitTextView.setGravity(Gravity.CENTER); @@ -337,8 +340,8 @@ public class CaptionContainerView extends FrameLayout { limitTextView.setTypeface(AndroidUtilities.bold()); limitTextContainer = new FrameLayout(context); limitTextContainer.setTranslationX(dp(2)); - limitTextContainer.addView(limitTextView, LayoutHelper.createFrame(52, 16, Gravity.RIGHT | Gravity.BOTTOM)); - addView(limitTextContainer, LayoutHelper.createFrame(52, 16, Gravity.RIGHT | Gravity.BOTTOM, 0, 0, 0, 50)); + limitTextContainer.addView(limitTextView, LayoutHelper.createFrame(52, 16, Gravity.RIGHT | (isAtTop() ? Gravity.TOP : Gravity.BOTTOM))); + addView(limitTextContainer, LayoutHelper.createFrame(52, 16, Gravity.RIGHT | (isAtTop() ? Gravity.TOP : Gravity.BOTTOM), 0, (isAtTop() ? 50 : 0), 0, (isAtTop() ? 0 : 50))); fadePaint.setShader(fadeGradient); fadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); @@ -402,6 +405,7 @@ public class CaptionContainerView extends FrameLayout { return super.dispatchTouchEvent(ev); } } + keyboardNotifier.ignore(false); editText.getEditText().setForceCursorEnd(true); editText.getEditText().requestFocus(); editText.openKeyboard(); @@ -449,7 +453,7 @@ public class CaptionContainerView extends FrameLayout { public void drawRoundRect(Canvas canvas, Rect rectTmp, float radius) { rectF.set(rectTmp); if (customBlur()) { - drawBlur(mentionBackgroundBlur, canvas, rectF, radius, false, -mentionContainer.getX(), -mentionContainer.getY(), false); + drawBlur(mentionBackgroundBlur, canvas, rectF, radius, false, -mentionContainer.getX(), -mentionContainer.getY(), false, 1.0f); } else { Paint blurPaint = mentionBackgroundBlur.getPaint(1f); if (blurPaint == null) { @@ -468,7 +472,6 @@ public class CaptionContainerView extends FrameLayout { } }; mentionBackgroundBlur = new BlurringShader.StoryBlurDrawer(blurManager, mentionContainer, BlurringShader.StoryBlurDrawer.BLUR_TYPE_BACKGROUND); - setupMentionContainer(); mentionContainer.withDelegate(new MentionsContainerView.Delegate() { @Override public void replaceText(int start, int len, CharSequence replacingString, boolean allowShort) { @@ -480,6 +483,7 @@ public class CaptionContainerView extends FrameLayout { } }); containerView.addView(mentionContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.LEFT | Gravity.BOTTOM)); + setupMentionContainer(); } protected void setupMentionContainer() { @@ -538,7 +542,7 @@ public class CaptionContainerView extends FrameLayout { return AndroidUtilities.navigationBarHeight; } - private void updateKeyboard(int keyboardHeight) { + public void updateKeyboard(int keyboardHeight) { if (sizeNotifierFrameLayout != null) { sizeNotifierFrameLayout.notifyHeightChanged(); } @@ -551,21 +555,23 @@ public class CaptionContainerView extends FrameLayout { View parent = (View) getParent(); parent.clearAnimation(); - if (parentKeyboardAnimator != null) { - parentKeyboardAnimator.removeAllListeners(); - parentKeyboardAnimator.cancel(); - parentKeyboardAnimator = null; - } + if (!isAtTop()) { + if (parentKeyboardAnimator != null) { + parentKeyboardAnimator.removeAllListeners(); + parentKeyboardAnimator.cancel(); + parentKeyboardAnimator = null; + } - parentKeyboardAnimator = ObjectAnimator.ofFloat(parent, TRANSLATION_Y, parent.getTranslationY(), -keyboardHeight); - if (keyboardHeight > AndroidUtilities.dp(20)) { - parentKeyboardAnimator.setInterpolator(AdjustPanLayoutHelper.keyboardInterpolator); - parentKeyboardAnimator.setDuration(AdjustPanLayoutHelper.keyboardDuration); - } else { - parentKeyboardAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT); - parentKeyboardAnimator.setDuration(640); + parentKeyboardAnimator = ObjectAnimator.ofFloat(parent, TRANSLATION_Y, parent.getTranslationY(), -keyboardHeight); + if (keyboardHeight > AndroidUtilities.dp(20)) { + parentKeyboardAnimator.setInterpolator(AdjustPanLayoutHelper.keyboardInterpolator); + parentKeyboardAnimator.setDuration(AdjustPanLayoutHelper.keyboardDuration); + } else { + parentKeyboardAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT); + parentKeyboardAnimator.setDuration(640); + } + parentKeyboardAnimator.start(); } - parentKeyboardAnimator.start(); toKeyboardShow = keyboardHeight > AndroidUtilities.dp(20); AndroidUtilities.cancelRunOnUIThread(updateShowKeyboard); @@ -576,7 +582,7 @@ public class CaptionContainerView extends FrameLayout { } } - private boolean toKeyboardShow; + public boolean toKeyboardShow; private Runnable updateShowKeyboard = () -> { updateShowKeyboard(toKeyboardShow, true); }; @@ -619,7 +625,7 @@ public class CaptionContainerView extends FrameLayout { keyboardT = (float) anm.getAnimatedValue(); editText.getEditText().setTranslationX(lerp(dp(-40 + 18) + getEditTextLeft(), dp(2), keyboardT)); editText.setTranslationX(lerp(0, dp(-8), keyboardT)); - editText.setTranslationY(lerp(0, dp(10), keyboardT)); + editText.setTranslationY(lerp(0, dp(isAtTop() ? -10 : 10), keyboardT)); limitTextContainer.setTranslationX(lerp(-dp(8), dp(2), keyboardT)); limitTextContainer.setTranslationY(lerp(-dp(8), 0, keyboardT)); editText.getEmojiButton().setAlpha(keyboardT); @@ -661,7 +667,7 @@ public class CaptionContainerView extends FrameLayout { keyboardT = show ? 1 : 0; editText.getEditText().setTranslationX(lerp(AndroidUtilities.dp(-40 + 18) + getEditTextLeft(), AndroidUtilities.dp(2), keyboardT)); editText.setTranslationX(lerp(0, AndroidUtilities.dp(-8), keyboardT)); - editText.setTranslationY(lerp(0, AndroidUtilities.dp(10), keyboardT)); + editText.setTranslationY(lerp(0, AndroidUtilities.dp(isAtTop() ? -10 : 10), keyboardT)); limitTextContainer.setTranslationX(lerp(-dp(8), dp(2), keyboardT)); limitTextContainer.setTranslationY(lerp(-dp(8), 0, keyboardT)); editText.getEmojiButton().setAlpha(keyboardT); @@ -783,7 +789,7 @@ public class CaptionContainerView extends FrameLayout { private boolean ignoreDraw = false; private final RectF rectF = new RectF(); - private final RectF bounds = new RectF(); + public final RectF bounds = new RectF(); private final RectF clickBounds = new RectF(); protected void onEditHeightChange(int height) {} @@ -877,6 +883,10 @@ public class CaptionContainerView extends FrameLayout { } } + protected float forceRound() { + return 0.0f; + } + @Override protected void dispatchDraw(Canvas canvas) { if (ignoreDraw) { @@ -902,32 +912,47 @@ public class CaptionContainerView extends FrameLayout { lastHeight = height; } updateMentionsLayoutPosition(); - final float heightTranslation = dpf2(-1) * keyboardT + height - heightAnimated; - if (Math.abs(lastHeightTranslation - heightTranslation) >= 1 && !collapsed) { - editText.getEditText().setTranslationY(lastHeightTranslation = heightTranslation); - } - final float pad = lerp(dp(12), 0, keyboardT); - bounds.set( - pad, - getHeight() - pad - heightAnimated, - getWidth() - pad, - getHeight() - pad - ); - clickBounds.set( - 0, - getHeight() - heightAnimated - dp(24), - getWidth(), - getHeight() - ); + final float pad = lerp(dp(12), 0, keyboardT * (1.0f - forceRound())); + if (isAtTop()) { + bounds.set( + pad, + pad, + getWidth() - pad, + pad + heightAnimated + ); + clickBounds.set( + pad, + pad, + getWidth() - pad, + pad + heightAnimated + dp(24) + ); + } else { + final float heightTranslation = dpf2(-1) * keyboardT + height - heightAnimated; + if (Math.abs(lastHeightTranslation - heightTranslation) >= 1 && !collapsed) { + editText.getEditText().setTranslationY(lastHeightTranslation = heightTranslation); + } + bounds.set( + pad, + getHeight() - pad - heightAnimated, + getWidth() - pad, + getHeight() - pad + ); + clickBounds.set( + 0, + getHeight() - heightAnimated - dp(24), + getWidth(), + getHeight() + ); + } canvas.save(); final float s = bounce.getScale(.018f); canvas.scale(s, s, bounds.centerX(), bounds.centerY()); - final float r = lerp(dp(21), 0, keyboardT); + final float r = lerp(dp(21), 0, keyboardT * (1.0f - forceRound())); if (customBlur()) { - drawBlur(backgroundBlur, canvas, bounds, r, false, 0, 0, true); + drawBlur(backgroundBlur, canvas, bounds, r, false, 0, 0, true, 1.0f); backgroundPaint.setAlpha((int) (lerp(0x26, 0x40, keyboardT))); canvas.drawRoundRect(bounds, r, r, backgroundPaint); } else { @@ -1064,7 +1089,7 @@ public class CaptionContainerView extends FrameLayout { canvas.translate(-e.hintLayoutX, 0); canvas.saveLayerAlpha(0, 0, hintTextBitmap.getWidth(), hintTextBitmap.getHeight(), 0xff, Canvas.ALL_SAVE_FLAG); rectF.set(0, 1, hintTextBitmap.getWidth(), hintTextBitmap.getHeight() - 1); - drawBlur(captionBlur, canvas, rectF, 0, true, -editText.getX() - e.getPaddingLeft(), -editText.getY() - e.getPaddingTop() - e.getExtendedPaddingTop(), true); + drawBlur(captionBlur, canvas, rectF, 0, true, -editText.getX() - e.getPaddingLeft(), -editText.getY() - e.getPaddingTop() - e.getExtendedPaddingTop(), true, 1.0f); canvas.save(); hintTextBitmapPaint.setAlpha(0xa5); canvas.drawBitmap(hintTextBitmap, 0, 0, hintTextBitmapPaint); @@ -1089,7 +1114,7 @@ public class CaptionContainerView extends FrameLayout { return false; } - protected void drawBlur(BlurringShader.StoryBlurDrawer blur, Canvas canvas, RectF rect, float r, boolean text, float ox, float oy, boolean thisView) { + protected void drawBlur(BlurringShader.StoryBlurDrawer blur, Canvas canvas, RectF rect, float r, boolean text, float ox, float oy, boolean thisView, float alpha) { } @@ -1116,7 +1141,7 @@ public class CaptionContainerView extends FrameLayout { @Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { if (child == editText) { - float ty = Math.max(0, editText.getHeight() - dp(82) - editText.getScrollY()) * (1f - keyboardT); + float ty = isAtTop() ? 0 : Math.max(0, editText.getHeight() - dp(82) - editText.getScrollY()) * (1f - keyboardT); canvas.saveLayerAlpha(0, 0, getWidth(), getHeight(), 0xFF, Canvas.ALL_SAVE_FLAG); canvas.save(); @@ -1434,4 +1459,8 @@ public class CaptionContainerView extends FrameLayout { } } + protected boolean isAtTop() { + return false; + } + } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/CollageLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/CollageLayout.java new file mode 100644 index 000000000..b89382ff2 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/CollageLayout.java @@ -0,0 +1,126 @@ +package org.telegram.ui.Stories.recorder; + +import android.graphics.RectF; +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.telegram.messenger.BuildVars; + +import java.util.ArrayList; + +public class CollageLayout { + + private static ArrayList layouts; + public static ArrayList getLayouts() { + if (layouts == null) { + layouts = new ArrayList<>(); + layouts.add(new CollageLayout("./.")); + layouts.add(new CollageLayout("..")); + layouts.add(new CollageLayout("../.")); + layouts.add(new CollageLayout("./..")); + layouts.add(new CollageLayout("././.")); + layouts.add(new CollageLayout("...")); + layouts.add(new CollageLayout("../..")); + layouts.add(new CollageLayout("./../..")); + layouts.add(new CollageLayout("../../.")); + layouts.add(new CollageLayout("../../..")); + if (BuildVars.DEBUG_PRIVATE_VERSION) { + layouts.add(new CollageLayout("../../../..")); + layouts.add(new CollageLayout(".../.../...")); + layouts.add(new CollageLayout("..../..../....")); + layouts.add(new CollageLayout(".../.../.../...")); + } + } + return layouts; + } + + private final String src; + public final int w, h; + public final int[] columns; + public final ArrayList parts = new ArrayList<>(); + + public CollageLayout(@Nullable String schema) { + if (schema == null) schema = "."; + src = schema; + final String[] rows = src.split("/"); + h = rows.length; + columns = new int[h]; + int maxW = 0; + for (int y = 0; y < rows.length; ++y) { + columns[y] = rows[y].length(); + maxW = Math.max(maxW, rows[y].length()); + } + w = maxW; + for (int y = 0; y < rows.length; ++y) { + for (int x = 0; x < rows[y].length(); ++x) { + parts.add(new Part(this, x, y)); + } + } + } + + public CollageLayout delete(int index) { + if (index < 0 || index >= parts.size()) return null; + ArrayList newParts = new ArrayList<>(parts); + newParts.remove(index); + final StringBuilder newSource = new StringBuilder(); + for (int i = 0, y = 0; i < newParts.size(); ++i) { + final Part p = newParts.get(i); + if (p.y != y) { + newSource.append("/"); + y = p.y; + } + newSource.append("."); + } + return new CollageLayout(newSource.toString()); + } + + public static class Part { + public final CollageLayout layout; + public final int x, y; + private Part(CollageLayout layout, int x, int y) { + this.layout = layout; + this.x = x; + this.y = y; + } + + public final float l(float w) { + return ((float) w / layout.columns[y] * x); + } + public final float t(float h) { + return ((float) h / layout.h * y); + } + public final float r(float w) { + return ((float) w / layout.columns[y] * (x + 1)); + } + public final float b(float h) { + return ((float) h / layout.h * (y + 1)); + } + + public final float w(float w) { + return (float) w / layout.columns[y]; + } + public final float h(float h) { + return (float) h / layout.h; + } + + public final void bounds(RectF rect, float w, float h) { + rect.set(l(w), t(h), r(w), b(h)); + } + } + + @NonNull + @Override + public String toString() { + return src; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof CollageLayout) { + return TextUtils.equals(src, ((CollageLayout) obj).src); + } + return false; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/CollageLayoutButton.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/CollageLayoutButton.java new file mode 100644 index 000000000..490198a7b --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/CollageLayoutButton.java @@ -0,0 +1,303 @@ +package org.telegram.ui.Stories.recorder; + +import static org.telegram.messenger.AndroidUtilities.dp; +import static org.telegram.messenger.AndroidUtilities.dpf2; +import static org.telegram.messenger.AndroidUtilities.lerp; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Xfermode; +import android.graphics.drawable.Drawable; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.Utilities; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.CubicBezierInterpolator; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.Components.UniversalAdapter; +import org.telegram.ui.Components.UniversalRecyclerView; +import org.telegram.ui.GradientClip; + +public class CollageLayoutButton extends ToggleButton2 { + + public CollageLayoutButton(Context context) { + super(context); + } + + public static class CollageLayoutListView extends FrameLayout { + + public final RecyclerListView listView; + + private CollageLayout selectedLayout; + + public void setSelected(CollageLayout layout) { + selectedLayout = layout; + AndroidUtilities.updateVisibleRows(listView); + } + + public CollageLayoutListView(Context context, final FlashViews flashViews) { + super(context); + + listView = new RecyclerListView(context) { + @Override + public boolean onInterceptTouchEvent(MotionEvent e) { + if (e.getX() <= getPaddingLeft() || e.getX() >= getWidth() - getPaddingRight()) { + return false; + } + getParent().requestDisallowInterceptTouchEvent(true); + return super.onInterceptTouchEvent(e); + } + @Override + public boolean dispatchTouchEvent(MotionEvent e) { + if (e.getX() <= getPaddingLeft() || e.getX() >= getWidth() - getPaddingRight()) { + return false; + } + return super.dispatchTouchEvent(e); + } + + private final GradientClip clip = new GradientClip(); + + @Override + protected void dispatchDraw(Canvas canvas) { + canvas.saveLayerAlpha(0, 0, getWidth(), getHeight(), (int) (0xFF * visibleProgress), Canvas.ALL_SAVE_FLAG); + canvas.save(); + final float l = getPaddingLeft(), r = getWidth() - getPaddingRight(); + canvas.clipRect(l, 0, r, getHeight()); + canvas.translate(r * (1.0f - visibleProgress), 0); + super.dispatchDraw(canvas); + canvas.restore(); + canvas.save(); + AndroidUtilities.rectTmp.set(l, 0, l + dp(12), getHeight()); + clip.draw(canvas, AndroidUtilities.rectTmp, GradientClip.LEFT, visibleProgress); + AndroidUtilities.rectTmp.set(r - dp(12), 0, r, getHeight()); + clip.draw(canvas, AndroidUtilities.rectTmp, GradientClip.RIGHT, visibleProgress); + canvas.restore(); + canvas.restore(); + } + }; + listView.setAdapter(new RecyclerView.Adapter() { + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + final Button imageView = new Button(context); + imageView.setLayoutParams(new RecyclerView.LayoutParams(dp(46), dp(56))); + imageView.setBackground(Theme.createSelectorDrawable(0x20ffffff)); + return new RecyclerListView.Holder(imageView); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + final Button imageView = (Button) holder.itemView; + final CollageLayout layout = CollageLayout.getLayouts().get(position); + final boolean animated = position == imageView.position; + imageView.setDrawable(new CollageLayoutDrawable(layout)); + imageView.setSelected(layout.equals(selectedLayout), animated); + imageView.position = position; + } + + @Override + public void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder) { + Button button = (Button) holder.itemView; + flashViews.add(button); + if (button.position >= 0 && button.position < CollageLayout.getLayouts().size()) { + final CollageLayout layout = CollageLayout.getLayouts().get(button.position); + button.setDrawable(new CollageLayoutDrawable(layout)); + button.setSelected(layout.equals(selectedLayout), false); + } + super.onViewAttachedToWindow(holder); + } + + @Override + public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) { + flashViews.remove((Button) holder.itemView); + super.onViewDetachedFromWindow(holder); + } + + @Override + public int getItemCount() { + return CollageLayout.getLayouts().size(); + } + }); + listView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)); + listView.setClipToPadding(false); + listView.setVisibility(View.GONE); + listView.setWillNotDraw(false); + listView.setOnItemClickListener((view, position) -> { + if (onLayoutClick != null) { + onLayoutClick.run(CollageLayout.getLayouts().get(position)); + } + }); + addView(listView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 56)); + } + + private static class Button extends ToggleButton2 { + public int position; + public Button(Context context) { + super(context); + } + } + + private Utilities.Callback onLayoutClick; + public void setOnLayoutClick(Utilities.Callback onLayoutClick) { + this.onLayoutClick = onLayoutClick; + } + + public void setBounds(float left, float right) { + listView.setPadding((int) left, 0, (int) right, 0); + listView.invalidate(); + } + + public boolean isVisible() { + return visible; + } + + private float visibleProgress; + private boolean visible; + private ValueAnimator visibleAnimator; + public void setVisible(final boolean visible, boolean animated) { + if (visibleAnimator != null) { + visibleAnimator.cancel(); + } + if (this.visible == visible) return; + this.visible = visible; + if (animated) { + listView.setVisibility(View.VISIBLE); + visibleAnimator = ValueAnimator.ofFloat(this.visibleProgress, visible ? 1.0f : 0.0f); + visibleAnimator.addUpdateListener(anm -> { + visibleProgress = (float) anm.getAnimatedValue(); + listView.invalidate(); + }); + visibleAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + visibleProgress = visible ? 1.0f : 0.0f; + listView.invalidate(); + listView.setVisibility(visible ? View.VISIBLE : View.GONE); + } + }); + visibleAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT); + visibleAnimator.setDuration(340); + visibleAnimator.start(); + } else { + this.visibleProgress = visible ? 1.0f : 0.0f; + listView.invalidate(); + listView.setVisibility(visible ? View.VISIBLE : View.GONE); + } + } + + } + + public static class CollageLayoutDrawable extends Drawable { + + public final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); + public final Paint crossXferPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + public final Paint crossPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + public final Path path = new Path(); + private final float[] radii = new float[8]; + private boolean cross; + + public CollageLayoutDrawable(final CollageLayout layout) { + this(layout, false); + } + + public CollageLayoutDrawable(final CollageLayout layout, boolean cross) { + this.cross = cross; + + paint.setColor(0xFFFFFFFF); + final float ow = dpf2(40 / 3f), oh = dpf2(56 / 3f), or = dpf2(3); + final float iw = dpf2(30 / 3f), ih = dpf2(46 / 3f), ir = dpf2(1); + final float p = dpf2(1.33f); + path.setFillType(Path.FillType.EVEN_ODD); + AndroidUtilities.rectTmp.set(-ow / 2f, -oh / 2f, ow / 2f, oh / 2f); + path.addRoundRect(AndroidUtilities.rectTmp, or, or, Path.Direction.CW); + + for (CollageLayout.Part part : layout.parts) { + final int cols = layout.columns[part.y]; + final float pw = (iw - Math.max(0, cols - 1) * p) / cols; + final float ph = (ih - Math.max(0, layout.h - 1) * p) / layout.h; + AndroidUtilities.rectTmp.set( + -iw / 2f + pw * part.x + p * part.x, + -ih / 2f + ph * part.y + p * part.y, + -iw / 2f + pw * (part.x + 1) + p * part.x, + -ih / 2f + ph * (part.y + 1) + p * part.y + ); + radii[0] = radii[1] = part.x == 0 && part.y == 0 ? ir : 0; // top left + radii[2] = radii[3] = part.x == cols - 1 && part.y == 0 ? ir : 0; // top right + radii[4] = radii[5] = part.x == cols - 1 && part.y == layout.h - 1 ? ir : 0; // bottom right + radii[6] = radii[7] = part.x == 0 && part.y == layout.h - 1 ? ir : 0; // bottom left + path.addRoundRect(AndroidUtilities.rectTmp, radii, Path.Direction.CW); + } + + crossXferPaint.setStyle(Paint.Style.STROKE); + crossXferPaint.setStrokeWidth(dp(3.33f)); + crossXferPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + crossPaint.setStyle(Paint.Style.STROKE); + crossPaint.setStrokeWidth(dp(1.33f)); + crossPaint.setColor(0xFFFFFFFF); + crossPaint.setStrokeCap(Paint.Cap.ROUND); + crossPaint.setStrokeJoin(Paint.Join.ROUND); + } + + @Override + public void draw(@NonNull Canvas canvas) { + if (cross) { + canvas.saveLayerAlpha(getBounds().left, getBounds().top, getBounds().right, getBounds().bottom, 0xFF, Canvas.ALL_SAVE_FLAG); + } else { + canvas.save(); + } + canvas.translate(getBounds().centerX(), getBounds().centerY()); + canvas.drawPath(path, paint); + if (cross) { + canvas.drawLine(-dp(8.66f), -dp(8.66f), dp(8.66f), dp(8.66f), crossXferPaint); + canvas.drawLine(-dp(8.66f), -dp(8.66f), dp(8.66f), dp(8.66f), crossPaint); + } + canvas.restore(); + } + + @Override + public void setAlpha(int alpha) { + paint.setAlpha(alpha); + } + + @Override + public void setColorFilter(@Nullable ColorFilter colorFilter) { + paint.setColorFilter(colorFilter); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSPARENT; + } + + @Override + public int getIntrinsicWidth() { + return dp(32); + } + + @Override + public int getIntrinsicHeight() { + return dp(32); + } + } + +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/CollageLayoutView2.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/CollageLayoutView2.java new file mode 100644 index 000000000..a96928f9b --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/CollageLayoutView2.java @@ -0,0 +1,1317 @@ +package org.telegram.ui.Stories.recorder; + +import static org.telegram.messenger.AndroidUtilities.dp; +import static org.telegram.messenger.AndroidUtilities.lerp; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.LinearGradient; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.RectF; +import android.graphics.RenderNode; +import android.graphics.Shader; +import android.net.Uri; +import android.util.Log; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.HapticFeedbackConstants; +import android.view.MotionEvent; +import android.view.TextureView; +import android.view.View; +import android.view.ViewConfiguration; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.zxing.common.detector.MathUtils; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.ImageLoader; +import org.telegram.messenger.ImageReceiver; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.R; +import org.telegram.messenger.Utilities; +import org.telegram.messenger.camera.CameraView; +import org.telegram.messenger.video.VideoPlayerHolderBase; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.AnimatedFloat; +import org.telegram.ui.Components.BlurringShader; +import org.telegram.ui.Components.CubicBezierInterpolator; +import org.telegram.ui.Components.ItemOptions; +import org.telegram.ui.Components.LayoutHelper; + +import java.util.ArrayList; +import java.util.Collections; + +public class CollageLayoutView2 extends FrameLayout implements ItemOptions.ScrimView { + + private final FrameLayout containerView; + private final Theme.ResourcesProvider resourcesProvider; + + public CameraView cameraView; + private Object cameraViewBlurRenderNode; + + @NonNull + private CollageLayout currentLayout = new CollageLayout("."); + public final ArrayList parts = new ArrayList<>(); + public final ArrayList removingParts = new ArrayList<>(); + public Part currentPart; + @Nullable + public Part nextPart; + + private final Paint highlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Path highlightPath = new Path(); + private final float[] radii = new float[8]; + private final int gradientWidth; + private final LinearGradient gradient; + private final Matrix gradientMatrix; + + private final BlurringShader.BlurManager blurManager; + + public CollageLayoutView2(Context context, BlurringShader.BlurManager blurManager, FrameLayout containerView, Theme.ResourcesProvider resourcesProvider) { + super(context); + this.blurManager = blurManager; + this.containerView = containerView; + this.resourcesProvider = resourcesProvider; + + setBackgroundColor(0xFF1F1F1F); + + final Part firstPartView = new Part(); + firstPartView.setPart(currentLayout.parts.get(0), false); + firstPartView.setCurrent(true); + if (attached) { + firstPartView.imageReceiver.onAttachedToWindow(); + } +// addView(firstPartView); + parts.add(firstPartView); + currentPart = firstPartView; + nextPart = null; + + highlightPaint.setStyle(Paint.Style.STROKE); + highlightPaint.setColor(0xFFFFFFFF); + highlightPaint.setStrokeWidth(dp(8)); + gradient = new LinearGradient(0, 0, gradientWidth = dp(300), 0, new int[] { 0, 0xFFFFFFFF, 0xFFFFFFFF, 0 }, new float[] { 0, 0.2f, 0.8f, 1.0f }, Shader.TileMode.CLAMP); + gradientMatrix = new Matrix(); + highlightPaint.setShader(gradient); + + setWillNotDraw(false); + } + + @Nullable + public Part getCurrent() { + return currentPart; + } + + @Nullable + public Part getNext() { + return nextPart; + } + + private final Runnable resetReordering = () -> { + if (this.reordering) { + this.reordering = false; + invalidate(); + } + }; + public void setLayout(CollageLayout layout, boolean animated) { + layout = layout == null ? new CollageLayout(".") : layout; + this.currentLayout = layout; + AndroidUtilities.cancelRunOnUIThread(resetReordering); + for (int i = 0; i < Math.max(layout.parts.size(), parts.size()); ++i) { + final CollageLayout.Part part = i < layout.parts.size() ? layout.parts.get(i) : null; + Part partView = i < parts.size() ? parts.get(i) : null; + if (partView == null && part != null) { + partView = new Part(); + if (attached) partView.imageReceiver.onAttachedToWindow(); + partView.setPart(part, animated); + parts.add(partView); + } else if (part != null) { + partView.setPart(part, animated); + } else if (partView != null) { + removingParts.add(partView); + parts.remove(partView); + partView.setPart(null, animated); + i--; + } + } + updatePartsState(); + invalidate(); + if (animated) { + AndroidUtilities.runOnUIThread(resetReordering, 360); + } + } + + public void highlight(int i) { + for (Part part : parts) { + if (part.index == i) { + part.highlightAnimated.set(1.0f, true); + invalidate(); + break; + } + } + } + + public ArrayList getOrder() { + ArrayList order = new ArrayList<>(); + for (int i = 0; i < parts.size(); ++i) { + order.add(parts.get(i).index); + } + return order; + } + + protected void onLayoutUpdate(CollageLayout layout) { + + } + + public void swap(int from, int to) { + Collections.swap(parts, from, to); + setLayout(currentLayout, true); + reordering = true; + invalidate(); + } + + private void layoutOut(RectF rect, CollageLayout.Part part) { + int w = getMeasuredWidth(); + int h = getMeasuredHeight(); + if (w <= 0 || h <= 0) { + w = AndroidUtilities.displaySize.x; + h = AndroidUtilities.displaySize.y; + } + layout(rect, part); + final boolean l = rect.left <= 0, t = rect.top <= 0; + final boolean r = rect.right >= w, b = rect.bottom >= h; + if (l && r && !t && !b) { + rect.offset(0, h - rect.top); + } else if (t && b && !l && !r) { + rect.offset(0, w - rect.left); + } else { + if (l && !r) { +// rect.offset(-rect.width(), 0); + } + if (r && !l) { + rect.offset(+rect.width(), 0); + } + if (t && !b) { +// rect.offset(0, -rect.height()); + } + if (b && !t) { + rect.offset(0, +rect.height()); + } + } + } + + private void layout(RectF rect, CollageLayout.Part part) { + int w = getMeasuredWidth(); + int h = getMeasuredHeight(); + if (w <= 0 || h <= 0) { + w = AndroidUtilities.displaySize.x; + h = AndroidUtilities.displaySize.y; + } + rect.set( + ((float) w / part.layout.columns[part.y] * part.x), + ((float) h / part.layout.h * part.y), + ((float) w / part.layout.columns[part.y] * (part.x + 1)), + ((float) h / part.layout.h * (part.y + 1)) + ); + } + + @Override + protected boolean drawChild(@NonNull Canvas canvas, View child, long drawingTime) { + if ((child == cameraView) && AndroidUtilities.makingGlobalBlurBitmap) + return false; + return super.drawChild(canvas, child, drawingTime); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int w = MeasureSpec.getSize(widthMeasureSpec); + int h = MeasureSpec.getSize(heightMeasureSpec); + setMeasuredDimension(w, h); + for (int i = 0; i < getChildCount(); ++i) { + View child = getChildAt(i); + if (child == cameraView) { + child.measure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY)); + } else { + Part part = null; + for (int j = 0; j < parts.size(); ++j) { + if (child == parts.get(j).textureView) { + part = parts.get(j); + break; + } + } + if (part != null && part.content != null && part.content.width > 0 && part.content.height > 0) { + int cw = part.content.width; + int ch = part.content.height; + if ((part.content.orientation % 90) == 1) { + int _cw = cw; + cw = ch; + ch = _cw; + } + final float scale = Math.min(1.0f, Math.max((float) cw / w, (float) ch / h)); + child.measure(MeasureSpec.makeMeasureSpec((int) (cw * scale), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((int) (ch * scale), MeasureSpec.EXACTLY)); + } else { + child.measure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY)); + } + } + } + } + + public void set(StoryEntry entry, boolean animated) { + if (entry == null || entry.collageContent == null) { + clear(true); + return; + } + setLayout(entry.collage, animated); + for (int i = 0; i < parts.size(); ++i) { + parts.get(i).setContent(entry.collageContent.get(i)); + } + } + + private final AnimatedFloat animatedRows = new AnimatedFloat(this, 0, 320, CubicBezierInterpolator.EASE_OUT_QUINT); + private final AnimatedFloat[] animatedColumns = new AnimatedFloat[] { + new AnimatedFloat(this, 0, 320, CubicBezierInterpolator.EASE_OUT_QUINT), + new AnimatedFloat(this, 0, 320, CubicBezierInterpolator.EASE_OUT_QUINT), + new AnimatedFloat(this, 0, 320, CubicBezierInterpolator.EASE_OUT_QUINT), + new AnimatedFloat(this, 0, 320, CubicBezierInterpolator.EASE_OUT_QUINT), + new AnimatedFloat(this, 0, 320, CubicBezierInterpolator.EASE_OUT_QUINT) + }; + private final AnimatedFloat animatedReordering = new AnimatedFloat(this, 0, 320, CubicBezierInterpolator.EASE_OUT_QUINT); + private final float[] lefts = new float[5]; + private final float[] rights = new float[5]; + + @Override + public void drawScrim(Canvas canvas, float progress) { + if (longPressedPart != null) { + final CollageLayout.Part p = longPressedPart.part; + final float H = p.layout.h; + final float cols = animatedColumns[p.y].set(p.layout.columns[p.y]); + rect.set( + ((float) getMeasuredWidth() / cols * p.x), + ((float) getMeasuredHeight() / H * p.y), + ((float) getMeasuredWidth() / cols * (p.x + 1)), + ((float) getMeasuredHeight() / H * (p.y + 1)) + ); + drawPart(canvas, rect, longPressedPart); + } + } + + @Override + public void getBounds(RectF bounds) { + if (longPressedPart != null) { + final CollageLayout.Part p = longPressedPart.part; + final float H = p.layout.h; + final float cols = animatedColumns[p.y].set(p.layout.columns[p.y]); + bounds.set( + ((float) getMeasuredWidth() / cols * p.x), + ((float) getMeasuredHeight() / H * p.y), + ((float) getMeasuredWidth() / cols * (p.x + 1)), + ((float) getMeasuredHeight() / H * (p.y + 1)) + ); + } else { + bounds.set(0, 0, getWidth(), getHeight()); + } + } + + private final RectF rect = new RectF(); + @Override + protected void dispatchDraw(@NonNull Canvas canvas) { + super.dispatchDraw(canvas); + if (!hasLayout() && !reordering && !this.reorderingTouch && animatedRows.get() == currentLayout.h && animatedColumns[0].get() == currentLayout.columns[0]) { + setCameraNeedsBlur(false); + return; + } else if (preview) { + setCameraNeedsBlur(false); + } + canvas.drawColor(0xFF1F1F1F); + boolean blurNeedsInvalidate = false; + final float reordering = animatedReordering.set(this.reorderingTouch); + final float H = animatedRows.set(currentLayout.h); + float bottom = 0; + for (int y = 0; y < Math.ceil(H); ++y) { + lefts[y] = getMeasuredWidth(); + rights[y] = 0; + } + for (int y = currentLayout.h; y < animatedColumns.length; ++y) { + animatedColumns[y].set(1); + } + for (int i = 0; i < parts.size(); ++i) { + final Part part = parts.get(i); + final CollageLayout.Part p = part.part; + final float cols = animatedColumns[p.y].set(p.layout.columns[p.y]); + if (this.reordering || this.reorderingTouch) { + AndroidUtilities.lerp(part.fromBounds, part.bounds, part.boundsTransition, rect); + } else { + rect.set( + ((float) getMeasuredWidth() / cols * p.x), + ((float) getMeasuredHeight() / H * p.y), + ((float) getMeasuredWidth() / cols * (p.x + 1)), + ((float) getMeasuredHeight() / H * (p.y + 1)) + ); + } + lefts[p.y] = Math.min(lefts[p.y], rect.left); + rights[p.y] = Math.max(rights[p.y], rect.right); + bottom = Math.max(bottom, rect.bottom); + if (reordering > 0 && part == reorderingPart) continue; + if (preview && part.videoPlayer != null) { + blurNeedsInvalidate = true; + } + drawPart(canvas, rect, part); + } + for (int i = 0; i < removingParts.size(); ++i) { + final Part part = removingParts.get(i); + final CollageLayout.Part p = part.part; + final float cols = animatedColumns[p.y].set(p.y >= currentLayout.columns.length ? 1 : currentLayout.columns[p.y]); + rect.set( + ((float) getMeasuredWidth() / cols * p.x), + ((float) getMeasuredHeight() / H * p.y), + ((float) getMeasuredWidth() / cols * (p.x + 1)), + ((float) getMeasuredHeight() / H * (p.y + 1)) + ); + lefts[p.y] = Math.min(lefts[p.y], rect.left); + rights[p.y] = Math.max(rights[p.y], rect.right); + bottom = Math.max(bottom, rect.bottom); + if (preview && part.videoPlayer != null) { + blurNeedsInvalidate = true; + } + drawPart(canvas, rect, part); + } + if (!this.reorderingTouch) { + for (int y = 0; y < Math.ceil(H); ++y) { + if (lefts[y] >= 0) { + rect.set( + 0, + ((float) getMeasuredHeight() / H * y), + lefts[y], + ((float) getMeasuredHeight() / H * (y + 1)) + ); + drawPart(canvas, rect, null); + } + if (rights[y] < getMeasuredWidth()) { + rect.set( + rights[y], + ((float) getMeasuredHeight() / H * y), + (float) getMeasuredWidth(), + ((float) getMeasuredHeight() / H * (y + 1)) + ); + drawPart(canvas, rect, null); + } + } + if (bottom < getMeasuredHeight()) { + rect.set(0, bottom, getMeasuredWidth(), getMeasuredHeight()); + drawPart(canvas, rect, null); + } + } + if (reordering > 0 && reorderingPart != null) { + final Part part = reorderingPart; + final CollageLayout.Part p = part.part; + final float cols = animatedColumns[p.y].set(currentLayout.columns[p.y]); + if (this.reorderingTouch) { + AndroidUtilities.lerp(part.fromBounds, part.bounds, part.boundsTransition, rect); + } else { + rect.set( + ((float) getMeasuredWidth() / cols * p.x), + ((float) getMeasuredHeight() / H * p.y), + ((float) getMeasuredWidth() / cols * (p.x + 1)), + ((float) getMeasuredHeight() / H * (p.y + 1)) + ); + } + canvas.save(); + canvas.translate(lerp(ldx, dx, part.boundsTransition) * reordering, lerp(ldy, dy, part.boundsTransition) * reordering); + drawPart(canvas, rect, part); + canvas.restore(); + } + for (int i = 0; i < parts.size(); ++i) { + final Part part = parts.get(i); + final CollageLayout.Part p = part.part; + final float highlight = part.highlightAnimated.set(0); + if (highlight <= 0) continue; + final float cols = animatedColumns[p.y].set(p.layout.columns[p.y]); + if (this.reordering || this.reorderingTouch) { + AndroidUtilities.lerp(part.fromBounds, part.bounds, part.boundsTransition, rect); + } else { + rect.set( + ((float) getMeasuredWidth() / cols * p.x), + ((float) getMeasuredHeight() / H * p.y), + ((float) getMeasuredWidth() / cols * (p.x + 1)), + ((float) getMeasuredHeight() / H * (p.y + 1)) + ); + } + AndroidUtilities.rectTmp.set(rect); + AndroidUtilities.rectTmp.inset(dp(4), dp(4)); + gradientMatrix.reset(); + gradientMatrix.postTranslate(rect.left + lerp(-1.4f*(float)Math.sqrt(gradientWidth*gradientWidth+gradientWidth*gradientWidth), (float)Math.sqrt(rect.width()*rect.width()+rect.height()*rect.height()), 1.0f - highlight), 0); + gradientMatrix.postRotate(-25); + gradient.setLocalMatrix(gradientMatrix); + final float alpha = 1.0f; // (float) Math.pow(4 * highlight * (1 - highlight), 0.05f); + highlightPaint.setAlpha((int) (0xFF * alpha)); + highlightPath.rewind(); + radii[0] = radii[1] = part.part.x == 0 && part.part.y == 0 ? dp(8) : 0; + radii[1] = radii[2] = part.part.x == part.part.layout.w - 1 && part.part.y == 0 ? dp(8) : 0; + radii[3] = radii[4] = part.part.x == part.part.layout.w - 1 && part.part.y == part.part.layout.h - 1 ? dp(8) : 0; + radii[5] = radii[6] = part.part.x == 0 && part.part.y == part.part.layout.h - 1 ? dp(8) : 0; + highlightPath.addRoundRect(AndroidUtilities.rectTmp, radii, Path.Direction.CW); + canvas.drawPath(highlightPath, highlightPaint); + } + if (blurNeedsInvalidate && blurManager != null) { + blurManager.invalidate(); + } + } + + public float getFilledProgress() { + int done = 0, total = 0; + for (int i = 0; i < parts.size(); ++i) { + if (parts.get(i).hasContent()) + done++; + total++; + } + return (float) done / total; + } + + private final Path clipPath = new Path(); + private void drawPart(Canvas canvas, RectF rect, Part part) { + if (AndroidUtilities.makingGlobalBlurBitmap && part == longPressedPart) { + return; + } + boolean restore = false; + if (part == reorderingPart && animatedReordering.get() > 0) { + canvas.save(); + clipPath.rewind(); + AndroidUtilities.rectTmp.set(rect); + AndroidUtilities.rectTmp.inset(dp(10) * animatedReordering.get(), dp(10) * animatedReordering.get()); + final float r = dp(12) * animatedReordering.get(); + clipPath.addRoundRect(AndroidUtilities.rectTmp, r, r, Path.Direction.CW); + canvas.clipPath(clipPath); + restore = true; + } + if (part != null && part.content != null) { + if (part.textureView != null && part.textureViewReady) { + drawView(canvas, part.textureView, rect, 0); + } else { + part.imageReceiver.setImageCoords(rect.left, rect.top, rect.width(), rect.height()); + if (!part.imageReceiver.draw(canvas)) { + drawView(canvas, cameraView, rect, 0); + } + } + } else if (part != null && part.current || AndroidUtilities.makingGlobalBlurBitmap) { + drawView(canvas, cameraView, rect, !(part != null && part.current) ? 0.4f : 0); + } else { + setCameraNeedsBlur(!preview); + if (cameraViewBlurRenderNode != null && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q && canvas.isHardwareAccelerated()) { + final RenderNode node = (RenderNode) cameraViewBlurRenderNode; + final float scale = Math.max(rect.width() / node.getWidth(), rect.height() / node.getHeight()); + canvas.save(); + canvas.translate(rect.centerX(), rect.centerY()); + canvas.clipRect(-rect.width() / 2.0f, -rect.height() / 2.0f, rect.width() / 2.0f, rect.height() / 2.0f); + canvas.scale(scale, scale); + canvas.translate(-node.getWidth() / 2.0f, -node.getHeight() / 2.0f); + canvas.drawRenderNode(node); + canvas.drawColor(0x64000000); + canvas.restore(); + } else { + drawView(canvas, cameraView, rect, 0.75f); + } + if (cameraView != null && cameraView.blurredStubView != null && cameraView.blurredStubView.getVisibility() == View.VISIBLE && cameraView.blurredStubView.getAlpha() > 0) { + drawView(canvas, cameraView.blurredStubView, rect, 0.4f); + } + } + if (restore) { + canvas.restore(); + } + } + + private void drawView(Canvas canvas, View view, RectF rect, float overlayAlpha) { + if (view == null) return; + final float scale = Math.max(rect.width() / view.getWidth(), rect.height() / view.getHeight()); + canvas.save(); + canvas.translate(rect.centerX(), rect.centerY()); + canvas.clipRect(-rect.width() / 2.0f, -rect.height() / 2.0f, rect.width() / 2.0f, rect.height() / 2.0f); + canvas.scale(scale, scale); + canvas.translate(-view.getWidth() / 2.0f, -view.getHeight() / 2.0f); + if (AndroidUtilities.makingGlobalBlurBitmap) { + TextureView textureView; + if (view instanceof TextureView) { + textureView = (TextureView) view; + } else if (view instanceof CameraView) { + textureView = ((CameraView) view).getTextureView(); + } else { + textureView = null; + } + if (textureView != null) { + Bitmap bitmap = textureView.getBitmap(); + if (bitmap != null) { + canvas.scale((float) view.getWidth() / bitmap.getWidth(), (float) view.getHeight() / bitmap.getHeight()); + canvas.drawBitmap(bitmap, 0, 0, null); + } + } + } else { + view.draw(canvas); + } + if (overlayAlpha > 0) { + canvas.drawColor(Theme.multAlpha(0xFF000000, view.getAlpha() * overlayAlpha)); + } + canvas.restore(); + } + + public void updatePartsState() { + currentPart = null; + nextPart = null; + for (int i = 0; i < parts.size(); ++i) { + Part partView = parts.get(i); + if (!partView.hasContent()) { + if (currentPart == null) { + currentPart = partView; + } else { + nextPart = partView; + break; + } + } + } + for (int i = 0; i < parts.size(); ++i) { + Part partView = parts.get(i); + partView.setCurrent(partView == currentPart); + } + } + + public boolean swap(Part a, Part b) { + if (a == null || b == null) return false; + final CollageLayout.Part partA = a.part; + a.setPart(b.part, false); + b.setPart(partA, false); + Collections.swap(parts, parts.indexOf(a), parts.indexOf(b)); + return true; + } + + public boolean push(StoryEntry content) { + if (content != null && content.isVideo) { + boolean hasUnmuted = false; + for (Part part : parts) { + if (part.content != null && part.content.isVideo && part.content.videoVolume > 0) { + hasUnmuted = true; + break; + } + } + if (hasUnmuted) { + content.videoVolume = 0.0f; + } + } + if (currentPart != null) { + currentPart.setContent(content); + } + updatePartsState(); + requestLayout(); + return currentPart == null; + } + + public ArrayList getContent() { + final ArrayList array = new ArrayList<>(); + for (Part partView : parts) { + if (partView.hasContent()) { + array.add(partView.content); + } + } + return array; + } + + public void clear(boolean destroy) { + for (Part part : parts) { + part.setContent(null); + } + updatePartsState(); + } + + @NonNull + public CollageLayout getLayout() { + return currentLayout; + } + + public boolean hasLayout() { + return currentLayout.parts.size() > 1; + } + + public boolean hasContent() { + for (Part part : parts) { + if (part.hasContent()) return true; + } + return false; + } + + public void setCameraView(CameraView cameraView) { + if (this.cameraView != cameraView && this.cameraView != null) { + this.cameraView.unlistenDraw(this::invalidate); + AndroidUtilities.removeFromParent(this.cameraView); + this.cameraView = null; + updateCameraNeedsBlur(); + } + this.cameraView = cameraView; + if (cameraView != null) { + addView(cameraView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.FILL)); + } + + if (this.cameraView != null) { + this.cameraView.unlistenDraw(this::invalidate); + } + this.cameraView = cameraView; + if (cameraView != null) { + cameraView.listenDraw(this::invalidate); + } + updateCameraNeedsBlur(); + + invalidate(); + } + + private boolean needsBlur; + public void setCameraNeedsBlur(boolean needsBlur) { + if (this.needsBlur == needsBlur) return; + this.needsBlur = needsBlur; + updateCameraNeedsBlur(); + } + public void updateCameraNeedsBlur() { + final boolean canDoBlur = cameraView != null && needsBlur; + final boolean hasBlur = cameraViewBlurRenderNode != null; + if (canDoBlur == hasBlur) return; + if (canDoBlur) { + cameraViewBlurRenderNode = cameraView.getBlurRenderNode(); + } else { + cameraViewBlurRenderNode = null; + } + } + + public Part getPartAt(float x, float y) { + final float H = animatedRows.get(); + for (int i = 0; i < parts.size(); ++i) { + final Part part = parts.get(i); + final CollageLayout.Part p = part.part; + final float cols = animatedColumns[p.y].get(); + rect.set( + ((float) getMeasuredWidth() / cols * p.x), + ((float) getMeasuredHeight() / H * p.y), + ((float) getMeasuredWidth() / cols * (p.x + 1)), + ((float) getMeasuredHeight() / H * (p.y + 1)) + ); + if (rect.contains(x, y)) return part; + } + return null; + } + + public int getPartIndexAt(float x, float y) { + final float H = animatedRows.get(); + for (int i = 0; i < parts.size(); ++i) { + final Part part = parts.get(i); + final CollageLayout.Part p = part.part; + final float cols = animatedColumns[p.y].get(); + rect.set( + ((float) getMeasuredWidth() / cols * p.x), + ((float) getMeasuredHeight() / H * p.y), + ((float) getMeasuredWidth() / cols * (p.x + 1)), + ((float) getMeasuredHeight() / H * (p.y + 1)) + ); + if (rect.contains(x, y)) return i; + } + return -1; + } + + private void onLongPress() { + if (reorderingTouch || preview) return; + if (longPressedPart != null && longPressedPart.videoPlayer != null) { + longPressedPart.videoPlayer.setVolume(0.0f); + } + longPressedPart = pressedPart; + if (longPressedPart == null || longPressedPart.content == null) { + return; + } + if (cancelGestures != null) { + cancelGestures.run(); + } + if (longPressedPart.videoPlayer != null) { + longPressedPart.videoPlayer.setVolume(longPressedPart.content.videoVolume); + } + + FrameLayout hintLayout = new FrameLayout(getContext()); + ImageView imageView = new ImageView(getContext()); + imageView.setImageResource(R.drawable.menu_lightbulb); + imageView.setColorFilter(new PorterDuffColorFilter(0xFFFFFFFF, PorterDuff.Mode.SRC_IN)); + imageView.setScaleType(ImageView.ScaleType.CENTER); + hintLayout.addView(imageView, LayoutHelper.createFrame(24, 24, Gravity.LEFT | Gravity.CENTER_VERTICAL, 12, 12, 12, 12)); + TextView textView = new TextView(getContext()); + textView.setText(LocaleController.getString(R.string.StoryCollageMenuHint)); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); + textView.setTextColor(0xFFFFFFFF); + hintLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.FILL_HORIZONTAL | Gravity.CENTER_VERTICAL, 47, 8, 24, 8)); + + ItemOptions i = ItemOptions.makeOptions(containerView, resourcesProvider, this); + if (longPressedPart.content.isVideo) { + final SliderView volumeSlider = + new SliderView(getContext(), SliderView.TYPE_VOLUME) + .setMinMax(0, 1.5f) + .setValue(longPressedPart.content.videoVolume) + .setOnValueChange(volume -> { + longPressedPart.content.videoVolume = volume; + if (longPressedPart.videoPlayer != null) { + longPressedPart.videoPlayer.setVolume(longPressedPart.content.videoVolume); + } + }); + volumeSlider.fixWidth = dp(220); + i.addView(volumeSlider).addSpaceGap(); + } + + i + .setFixedWidth(220) + .add(R.drawable.menu_camera_retake, LocaleController.getString(R.string.StoreCollageRetake), () -> { + retake(longPressedPart); + }) + .add(R.drawable.msg_delete, LocaleController.getString(R.string.Delete), true, () -> { + delete(longPressedPart); + }) + .addSpaceGap() + .addView(hintLayout, LayoutHelper.createLinear(220, LayoutHelper.WRAP_CONTENT)) + .setOnDismiss(() -> { + + }) + .setGravity(Gravity.CENTER_HORIZONTAL) + .allowCenter(true) + .setBlur(true) + .setRoundRadius(dp(12), dp(10)) + .setOnDismiss(() -> { + if (longPressedPart != null && longPressedPart.videoPlayer != null) { + longPressedPart.videoPlayer.setVolume(0.0f); + } + }) + .show(); + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + } + + public void retake(Part part) { + if (part == null) return; + part.setContent(null); + updatePartsState(); + invalidate(); + if (onResetState != null) { + onResetState.run(); + } + } + + public void delete(Part part) { + if (part == null) return; + int index = parts.indexOf(part); + if (index < 0) return; + final CollageLayout newLayout = currentLayout.delete(currentLayout.parts.indexOf(part.part)); + if (newLayout.parts.size() <= 1) { + clear(true); + invalidate(); + } + setLayout(newLayout, true); + reordering = true; + updatePartsState(); + invalidate(); + if (onResetState != null) { + onResetState.run(); + } + onLayoutUpdate(newLayout); + } + + public float tx, ty; + public float ldx, ldy; + public float dx, dy; + public boolean reorderingTouch, reordering; + public Part pressedPart; + public Part reorderingPart; + public Runnable onLongPressPart; + + public Part longPressedPart; + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + if (!hasLayout() || preview) { + return super.dispatchTouchEvent(event); + } + final Part hitPart = getPartAt(event.getX(), event.getY()); + if (event.getAction() == MotionEvent.ACTION_DOWN) { + tx = event.getX(); + ty = event.getY(); + reorderingTouch = false; + ldx = dx = 0; + ldy = dy = 0; + pressedPart = hitPart; + if (pressedPart != null) { + AndroidUtilities.runOnUIThread(onLongPressPart = this::onLongPress, ViewConfiguration.getLongPressTimeout()); + } + } else if (event.getAction() == MotionEvent.ACTION_MOVE) { + if (MathUtils.distance(event.getX(), event.getY(), tx, ty) > AndroidUtilities.touchSlop * 1.2f) { + if (onLongPressPart != null) { + AndroidUtilities.cancelRunOnUIThread(onLongPressPart); + onLongPressPart = null; + } + } + if (!reorderingTouch && getFilledProgress() >= 1 && pressedPart != null && hitPart != null && MathUtils.distance(event.getX(), event.getY(), tx, ty) > AndroidUtilities.touchSlop * 1.2f) { + reorderingTouch = true; + reorderingPart = pressedPart; + ldx = dx = 0; + ldy = dy = 0; + invalidate(); + if (onLongPressPart != null) { + AndroidUtilities.cancelRunOnUIThread(onLongPressPart); + onLongPressPart = null; + } + } else if (reorderingTouch && reorderingPart != null) { + int newIndex = getPartIndexAt(event.getX(), event.getY()); + int thisIndex = parts.indexOf(reorderingPart); + if (newIndex >= 0 && thisIndex >= 0 && newIndex != thisIndex) { + swap(thisIndex, newIndex); + final float H = currentLayout.h; + final CollageLayout.Part p = reorderingPart.part; + final float cols = animatedColumns[p.y].get(); + rect.set( + ((float) getMeasuredWidth() / cols * p.x), + ((float) getMeasuredHeight() / H * p.y), + ((float) getMeasuredWidth() / cols * (p.x + 1)), + ((float) getMeasuredHeight() / H * (p.y + 1)) + ); + ldx = dx; + ldy = dy; + tx = rect.centerX(); + ty = rect.centerY(); + } + dx = event.getX() - tx; + dy = event.getY() - ty; + invalidate(); + } else if (pressedPart != hitPart) { + pressedPart = null; + if (onLongPressPart != null) { + AndroidUtilities.cancelRunOnUIThread(onLongPressPart); + onLongPressPart = null; + } + return true; + } + } else if (event.getAction() == MotionEvent.ACTION_UP) { + if (pressedPart != null) { + pressedPart = null; + reorderingTouch = false; + invalidate(); + if (onLongPressPart != null) { + AndroidUtilities.cancelRunOnUIThread(onLongPressPart); + onLongPressPart = null; + } + return true; + } + } else if (event.getAction() == MotionEvent.ACTION_CANCEL) { + if (cancelTouch()) { + return true; + } + } + return pressedPart != null || super.dispatchTouchEvent(event); + } + + public boolean cancelTouch() { + if (pressedPart != null) { + pressedPart = null; + reorderingTouch = false; + invalidate(); + if (onLongPressPart != null) { + AndroidUtilities.cancelRunOnUIThread(onLongPressPart); + onLongPressPart = null; + } + return true; + } + return false; + } + + public class Part { + + private int index; + private final AnimatedFloat highlightAnimated = new AnimatedFloat(CollageLayoutView2.this, 0, 1200, CubicBezierInterpolator.EASE_OUT); + + public final ImageReceiver imageReceiver = new ImageReceiver(CollageLayoutView2.this); + public VideoPlayerHolderBase videoPlayer; + public TextureView textureView; + public boolean textureViewReady; + + private volatile long pendingSeek = -1; + + public CollageLayout.Part part; + + public boolean hasBounds = false; + public RectF fromBounds = new RectF(); + public RectF bounds = new RectF(); + public float boundsTransition = 1.0f; + + private boolean current; + private StoryEntry content; + + public Part() {} + + private ValueAnimator animator; + public void setPart(CollageLayout.Part part, boolean animated) { + final CollageLayout.Part oldPart = this.part; + if (part != null) this.part = part; + if (animator != null) { + animator.cancel(); + animator = null; + } + if (animated) { + if (!hasBounds) { + layoutOut(fromBounds, part); + } else { + AndroidUtilities.lerp(fromBounds, bounds, boundsTransition, fromBounds); + } + if (part == null) { + layoutOut(bounds, oldPart); + } else { + layout(bounds, part); + } + boundsTransition = 0.0f; + animator = ValueAnimator.ofFloat(0, 1.0f); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(@NonNull ValueAnimator animation) { + boundsTransition = (float) animation.getAnimatedValue(); + invalidate(); + } + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + boundsTransition = 1.0f; + if (removingParts.contains(Part.this)) { + imageReceiver.onDetachedFromWindow(); + destroyContent(); + removingParts.remove(Part.this); + } + invalidate(); + } + }); + animator.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT); + animator.setDuration(360); + animator.start(); + } else { + layout(bounds, part); + boundsTransition = 1.0f; + if (part == null) { + imageReceiver.onDetachedFromWindow(); + destroyContent(); + removingParts.remove(Part.this); + } + } + invalidate(); + hasBounds = true; + } + + public void setCurrent(boolean current) { + this.current = current; + } + + public boolean isEmpty() { + return !current && !hasContent(); + } + + public void setContent(StoryEntry entry) { + destroyContent(); + + content = entry; + final String filter = ((int) Math.ceil(AndroidUtilities.displaySize.x / AndroidUtilities.density)) + "_" + ((int) Math.ceil(AndroidUtilities.displaySize.y / AndroidUtilities.density)) + (entry != null && entry.isVideo ? "_" + ImageLoader.AUTOPLAY_FILTER : "") + "_exif"; + if (content == null) { + imageReceiver.clearImage(); + } else if (content.isVideo) { + if (content.thumbBitmap != null) { + imageReceiver.setImageBitmap(content.thumbBitmap); + } else if (content.thumbPath != null) { + imageReceiver.setImage(content.thumbPath, filter, null, null, 0); + } else { + imageReceiver.clearImage(); + } + textureView = new TextureView(getContext()); + addView(textureView); + + videoPlayer = new VideoPlayerHolderBase() { + @Override + public void onRenderedFirstFrame() { + textureViewReady = true; + invalidate(); + } + + @Override + protected void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { + AndroidUtilities.runOnUIThread(() -> { + StoryEntry e = content; + if (e == null) return; + if (e.width != width || e.height != height || e.orientation != unappliedRotationDegrees) { + e.width = width; + e.height = height; + e.orientation = unappliedRotationDegrees; + if (textureView != null) { + textureView.requestLayout(); + } + } + }); + } + + @Override + public boolean needRepeat() { + return !preview; + } + }; + videoPlayer.allowMultipleInstances(true); + videoPlayer.with(textureView); + videoPlayer.preparePlayer(Uri.fromFile(content.file), false, 1.0f); + videoPlayer.setVolume(isMuted || content.muted || !preview ? 0.0f : content.videoVolume); + if (!preview || playing) { + videoPlayer.play(); + } else { + videoPlayer.pause(); + } + } else { + imageReceiver.setImage(content.file.getAbsolutePath(), filter, null, null, 0); + } + invalidate(); + } + + public boolean hasContent() { + return content != null; + } + + public void destroyContent() { + if (videoPlayer != null) { + videoPlayer.pause(); + videoPlayer.release(null); + videoPlayer = null; + } + if (textureView != null) { + AndroidUtilities.removeFromParent(textureView); + textureView = null; + } + textureViewReady = false; + } + + } + + private boolean attached; + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + for (int i = 0; i < parts.size(); ++i) { + parts.get(i).imageReceiver.onAttachedToWindow(); + } + attached = true; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + for (int i = 0; i < parts.size(); ++i) { + parts.get(i).imageReceiver.onDetachedFromWindow(); + } + attached = false; + AndroidUtilities.cancelRunOnUIThread(syncRunnable); + } + + private Runnable cancelGestures; + public void setCancelGestures(Runnable cancelGestures) { + this.cancelGestures = cancelGestures; + } + + private Runnable onResetState; + public void setResetState(Runnable onResetState) { + this.onResetState = onResetState; + } + + private boolean preview; + private long previewStartTime; + private boolean fastSeek; + private boolean playing = true; + + + public void setPreview(boolean preview) { + if (this.preview == preview) return; + this.preview = preview; + if (preview) { + if (blurManager != null) { + blurManager.invalidate(); + } + for (int i = 0; i < parts.size(); ++i) { + parts.get(i).index = i; + } + } + fastSeek = false; + lastPausedPosition = 0; + for (Part part : parts) { + if (part.videoPlayer != null) { + part.videoPlayer.setAudioEnabled(preview, true); + if (!preview || playing) { + part.videoPlayer.play(); + } else { + part.videoPlayer.pause(); + } + } + } + AndroidUtilities.cancelRunOnUIThread(syncRunnable); + if (preview) { + this.previewStartTime = System.currentTimeMillis(); + AndroidUtilities.runOnUIThread(syncRunnable, (long) (1000L / AndroidUtilities.screenRefreshRate)); + } else { + + } + } + + public Part getMainPart() { + if (!this.preview) return null; + long maxDuration = 0; + Part maxPart = null; + for (Part part : parts) { + if (part.content == null) continue; + if (!part.content.isVideo) continue; + long duration = part.content.duration; + if (part.videoPlayer != null && part.videoPlayer.getDuration() > 0) { + duration = part.videoPlayer.getDuration(); + } + if (duration > maxDuration) { + maxDuration = duration; + maxPart = part; + } + } + return maxPart; + } + + private TimelineView timelineView; + public void setTimelineView(TimelineView timelineView) { + this.timelineView = timelineView; + } + + private PreviewView previewView; + public void setPreviewView(PreviewView previewView) { + this.previewView = previewView; + } + + public long getPosition() { + if (!preview) return 0; + if (!playing) return lastPausedPosition; + final long now = System.currentTimeMillis(); + final long d = now - previewStartTime; + if (d > getDuration()) { + previewStartTime = now - (d % getDuration()); + } + return d; + } + + public long getPositionWithOffset() { + if (!preview) return 0; + long position = getPosition(); + final Part mainPart = getMainPart(); + final long generalOffset = mainPart == null ? 0 : mainPart.content.videoOffset + (long) (mainPart.content.videoLeft * mainPart.content.duration); + return getPosition() + generalOffset; + } + + private boolean restorePositionOnPlaying = true; + public void forceNotRestorePosition() { +// restorePositionOnPlaying = false; + } + + private long lastPausedPosition; + public void setPlaying(boolean playing) { + final boolean restore = restorePositionOnPlaying; + restorePositionOnPlaying = true; + if (this.playing == playing) return; + this.playing = playing; + if (!playing) { + lastPausedPosition = getPosition(); + } else if (restore) { + seekTo(lastPausedPosition, false); + } else { + fastSeek = false; + } + AndroidUtilities.cancelRunOnUIThread(syncRunnable); + syncRunnable.run(); + } + + public boolean isPlaying() { + return playing; + } + + public boolean isMuted; + public void setMuted(boolean muted) { + if (isMuted == muted) return; + isMuted = muted; + } + + public boolean hasVideo() { + for (Part part : parts) { + if (part.content != null && part.content.isVideo) + return true; + } + return false; + } + + public long getDuration() { + if (!preview) return 1; + Part mainPart = getMainPart(); + if (mainPart == null || mainPart.content == null) return 1; + long duration = (long) (mainPart.content.duration * (mainPart.content.videoRight - mainPart.content.videoLeft)); + duration = Math.min(duration, 59_500); + duration = Math.max(duration, 1); + return duration; + } + + public void seekTo(long progress) { + seekTo(progress, false); + } + + public void seekTo(long progress, boolean fast) { + if (!preview) return; + progress = Utilities.clamp(progress, getDuration(), 0); + if (!playing) lastPausedPosition = progress; + final long now = System.currentTimeMillis(); + previewStartTime = now - progress; + fastSeek = fast; + AndroidUtilities.cancelRunOnUIThread(syncRunnable); + syncRunnable.run(); + } + + private final Runnable syncRunnable = () -> { + final long position = getPosition(); + final Part mainPart = getMainPart(); + final long generalOffset = mainPart == null ? 0 : mainPart.content.videoOffset + (long) (mainPart.content.videoLeft * mainPart.content.duration); + for (int i = 0; i < parts.size(); ++i) { + final Part part = parts.get(i); + if (part.content != null && part.videoPlayer != null) { + final long duration = part.videoPlayer.getDuration(); + long frame = Utilities.clamp(position + generalOffset - part.content.videoOffset, duration, 0); + final boolean shouldPlay = (!preview || playing) && frame > part.content.videoLeft * duration && frame < part.content.videoRight * duration; + frame = Utilities.clamp(frame, (long) (part.content.videoRight * duration), (long) (part.content.videoLeft * duration)); + if (part.videoPlayer.isPlaying() != shouldPlay) { + if (shouldPlay) { + part.videoPlayer.play(); + } else { + part.videoPlayer.pause(); + } + } + part.videoPlayer.setVolume(isMuted || part.content.muted || !preview ? 0.0f : part.content.videoVolume); + final long currentPosition = part.pendingSeek >= 0 ? part.pendingSeek : part.videoPlayer.getCurrentPosition(); + if (Math.abs(currentPosition - frame) > 450 && part.pendingSeek < 0) { + part.videoPlayer.seekTo(part.pendingSeek = frame, fastSeek, () -> { + part.pendingSeek = -1; + }); + } + } + } + if (timelineView != null) { + timelineView.setProgress(position); + } + if (previewView != null) { + previewView.updateAudioPlayer(true); + previewView.updateRoundPlayer(true); + } + if (preview && playing) { + AndroidUtilities.runOnUIThread(this.syncRunnable, (long) (1000L / AndroidUtilities.screenRefreshRate)); + } + }; + +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/DraftsController.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/DraftsController.java index ef56eac07..07950b815 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/DraftsController.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/DraftsController.java @@ -108,8 +108,10 @@ public class DraftsController { continue; } if ( - entry.file == null || - !entry.file.exists() || + !entry.isCollage() && ( + entry.file == null || + !entry.file.exists() + ) || (entry.isEdit ? (now > entry.editExpireDate) : (now - entry.draftDate > EXPIRATION_PERIOD) @@ -148,8 +150,10 @@ public class DraftsController { continue; } if ( - entry.file == null || - !entry.file.exists() || + !entry.isCollage() && ( + entry.file == null || + !entry.file.exists() + ) || now - entry.draftDate > EXPIRATION_PERIOD ) { deleteEntries.add(entry); @@ -520,6 +524,9 @@ public class DraftsController { public String botLang; public TLRPC.InputMedia botEdit; + public CollageLayout collage; + public ArrayList collageParts; + public StoryDraft(@NonNull StoryEntry entry) { this.id = entry.draftId; this.date = entry.draftDate; @@ -579,6 +586,9 @@ public class DraftsController { this.botId = entry.botId; this.botLang = entry.botLang; this.botEdit = entry.editingBotPreview; + + this.collage = entry.collage; + this.collageParts = VideoEditedInfo.Part.toParts(entry); } public StoryEntry toEntry() { @@ -677,6 +687,9 @@ public class DraftsController { entry.botLang = botLang; entry.editingBotPreview = botEdit; + entry.collage = collage; + entry.collageContent = VideoEditedInfo.Part.toStoryEntries(collageParts); + return entry; } @@ -811,6 +824,15 @@ public class DraftsController { botEdit.serializeToStream(stream); } + if (collage == null || collage.parts.size() <= 1 || collageParts == null || collageParts.size() <= 1) { + stream.writeInt32(TLRPC.TL_null.constructor); + } else { + stream.writeInt32(0xdeadbeef); + stream.writeString(collage.toString()); + for (VideoEditedInfo.Part part : collageParts) { + part.serializeToStream(stream); + } + } } public int getObjectSize() { @@ -1004,6 +1026,19 @@ public class DraftsController { botEdit = TLRPC.InputMedia.TLdeserialize(stream, magic, exception); } } + if (stream.remaining() > 0) { + magic = stream.readInt32(exception); + if (magic == 0xdeadbeef) { + collage = new CollageLayout(stream.readString(exception)); + collageParts = new ArrayList<>(); + for (int i = 0; i < collage.parts.size(); ++i) { + VideoEditedInfo.Part part = new VideoEditedInfo.Part(); + part.readParams(stream, exception); + part.part = collage.parts.get(i); + collageParts.add(part); + } + } + } } } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/FfmpegAudioWaveformLoader.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/FfmpegAudioWaveformLoader.java index 767a07a93..1e7d9049f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/FfmpegAudioWaveformLoader.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/FfmpegAudioWaveformLoader.java @@ -1,10 +1,14 @@ package org.telegram.ui.Stories.recorder; +import androidx.annotation.Keep; + import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.Utilities; +@Keep public class FfmpegAudioWaveformLoader { + @Keep private volatile boolean running = true; private native void init(String path, int count); @@ -17,6 +21,7 @@ public class FfmpegAudioWaveformLoader { } private Utilities.Callback2 onChunkReceived; + @Keep private void receiveChunk(short[] data, int len) { AndroidUtilities.runOnUIThread(() -> { onChunkReceived.run(data, len); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/FlashViews.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/FlashViews.java index 77c2626f7..277633925 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/FlashViews.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/FlashViews.java @@ -203,6 +203,10 @@ public class FlashViews { invertableViews.add(view); } + public void remove(Invertable view) { + invertableViews.remove(view); + } + private int lastWidth, lastHeight, lastColor; private float lastInvert; private int color; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/HintView2.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/HintView2.java index 631e48627..6f59f1336 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/HintView2.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/HintView2.java @@ -517,6 +517,11 @@ public class HintView2 extends View { private final Runnable hideRunnable = this::hide; + public void show(boolean show) { + if (show) show(); + else hide(); + } + public HintView2 show() { prepareBlur(); if (shown) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/MuteButtonHint.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/MuteButtonHint.java deleted file mode 100644 index f47a04a61..000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/MuteButtonHint.java +++ /dev/null @@ -1,119 +0,0 @@ -package org.telegram.ui.Stories.recorder; - -import static org.telegram.messenger.AndroidUtilities.dp; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.CornerPathEffect; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.drawable.Drawable; -import android.view.Gravity; -import android.view.View; - -import androidx.annotation.NonNull; - -import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.LocaleController; -import org.telegram.ui.Components.AnimatedFloat; -import org.telegram.ui.Components.AnimatedTextView; -import org.telegram.ui.Components.CubicBezierInterpolator; - -public class MuteButtonHint extends View { - - private final Paint backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private final AnimatedTextView.AnimatedTextDrawable textDrawable = new AnimatedTextView.AnimatedTextDrawable(false, false, false); - - private final Path path = new Path(); - - public MuteButtonHint(Context context) { - super(context); - - backgroundPaint.setColor(0xcc282828); - backgroundPaint.setPathEffect(new CornerPathEffect(dp(6))); - - textDrawable.updateAll = true; - textDrawable.setAnimationProperties(.4f, 0, 450, CubicBezierInterpolator.EASE_OUT_QUINT); - textDrawable.setGravity(Gravity.RIGHT); - textDrawable.setCallback(this); - textDrawable.setTextSize(dp(14)); - textDrawable.setTextColor(0xffffffff); - textDrawable.setOverrideFullWidth(AndroidUtilities.displaySize.x); - } - - private Runnable hideRunnable; - public void setMuted(boolean muted) { - textDrawable.setText(muted ? LocaleController.getString("StorySoundMuted") : LocaleController.getString("StorySoundNotMuted"), !LocaleController.isRTL && shown); - show(true, true); - if (hideRunnable != null) { - AndroidUtilities.cancelRunOnUIThread(hideRunnable); - } - AndroidUtilities.runOnUIThread(hideRunnable = () -> hide(true), 3500); - } - - public void hide(boolean animated) { - show(false, animated); - } - - public void show(boolean show, boolean animated) { - if (!show && hideRunnable != null) { - AndroidUtilities.cancelRunOnUIThread(hideRunnable); - hideRunnable = null; - } - shown = show; - if (!animated) { - showT.set(show, true); - } - invalidate(); - } - - @Override - protected boolean verifyDrawable(@NonNull Drawable who) { - return who == textDrawable || super.verifyDrawable(who); - } - - private boolean shown; - private AnimatedFloat showT = new AnimatedFloat(this, 0, 350, CubicBezierInterpolator.EASE_OUT_QUINT); - - @Override - protected void dispatchDraw(Canvas canvas) { - final float showT = this.showT.set(shown); - - if (showT <= 0) { - return; - } - - final float W = getMeasuredWidth(), H = getMeasuredHeight(); - final float width = dp(11 + 11) + textDrawable.getCurrentWidth(); - - final float cx = W - dp(56 + 28 - 12); - path.rewind(); - path.moveTo(W - width - dp(8), dp(6)); - path.lineTo(W - width - dp(8), H); - path.lineTo(W - dp(8), H); - path.lineTo(W - dp(8), dp(6)); - path.lineTo(cx + dp(7), dp(6)); - path.lineTo(cx + dp(1), 0); - path.lineTo(cx - dp(1), 0); - path.lineTo(cx - dp(7), dp(6)); - path.close(); - - backgroundPaint.setAlpha((int) (0xcc * showT)); - canvas.drawPath(path, backgroundPaint); - - textDrawable.setAlpha((int) (0xFF * showT)); - AndroidUtilities.rectTmp2.set((int) (W - width + dp(-8 + 11)), dp(6 + 7), (int) (W - dp(8 + 11)), (int) (H - dp(7))); - canvas.save(); - canvas.clipRect(AndroidUtilities.rectTmp2); - textDrawable.setBounds(AndroidUtilities.rectTmp2); - textDrawable.draw(canvas); - canvas.restore(); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int width = MeasureSpec.getSize(widthMeasureSpec); - textDrawable.setOverrideFullWidth(width - dp(22)); - setMeasuredDimension(width, dp(38)); - } -} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/PaintView.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/PaintView.java index 204643758..d73bae09c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/PaintView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/PaintView.java @@ -160,8 +160,8 @@ public class PaintView extends SizeNotifierFrameLayoutPhoto implements IPhotoPai private PaintDoneView doneButton; private float offsetTranslationY; - private final Bitmap bitmapToEdit; - private final Bitmap blurBitmapToEdit; + private Bitmap bitmapToEdit; + private Bitmap blurBitmapToEdit; private Bitmap facesBitmap; private UndoStore undoStore; @@ -841,7 +841,7 @@ public class PaintView extends SizeNotifierFrameLayoutPhoto implements IPhotoPai bottomLayout.setBackground(new GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, new int [] {0x00000000, 0x80000000} )); addView(bottomLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 44 + 60, Gravity.BOTTOM)); - paintToolsView = new PaintToolsView(context, entry != null && !entry.isRepostMessage && blurManager != null); + paintToolsView = new PaintToolsView(context, entry != null && !entry.isCollage() && !entry.isRepostMessage && blurManager != null); paintToolsView.setPadding(dp(16), 0, dp(16), 0); paintToolsView.setDelegate(this); // paintToolsView.setSelectedIndex(MathUtils.clamp(palette.getCurrentBrush(), 0, Brush.BRUSHES_LIST.size()) + 1); @@ -1145,6 +1145,18 @@ public class PaintView extends SizeNotifierFrameLayoutPhoto implements IPhotoPai previewViewTranslationAnimator.start(); } + public void destroy() { + AndroidUtilities.removeFromParent(renderView); + if (bitmapToEdit != null) { + bitmapToEdit.recycle(); + bitmapToEdit = null; + } + if (blurBitmapToEdit != null) { + blurBitmapToEdit.recycle(); + blurBitmapToEdit = null; + } + } + @Override public void onAnimationStateChanged(boolean isStart) { weightChooserView.setLayerType(isStart ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, null); @@ -1571,28 +1583,42 @@ public class PaintView extends SizeNotifierFrameLayoutPhoto implements IPhotoPai return true; } + private final Path clipPath = new Path(); + @Override public void drawRoundRect(Canvas canvas, RectF rect, float radius, float offsetX, float offsetY, int alpha, boolean isWindow) { - Paint paint; - if (isWindow) { - if (windowBackgroundBlur == null) { - windowBackgroundBlur = new BlurringShader.StoryBlurDrawer(blurManager, reactionLayout.getReactionsWindow().windowView, BlurringShader.StoryBlurDrawer.BLUR_TYPE_BACKGROUND); - } - windowBackgroundBlur.setBounds(-offsetX, -offsetY, - -offsetX + getMeasuredWidth(), - -offsetY + getMeasuredHeight()); - paint = windowBackgroundBlur.paint; + if (!isWindow && blurManager != null && blurManager.hasRenderNode()) { + final BlurringShader.StoryBlurDrawer drawer = isWindow ? windowBackgroundBlur : reactionBackgroundBlur; + clipPath.rewind(); + clipPath.addRoundRect(rect, radius, radius, Path.Direction.CW); + canvas.save(); + canvas.clipPath(clipPath); + drawer.drawRect(canvas); + backgroundPaint.setAlpha((int) (0.4f * alpha)); + canvas.drawPaint(backgroundPaint); + canvas.restore(); } else { - reactionBackgroundBlur.setBounds(-offsetX, -offsetY, - -offsetX + getMeasuredWidth(), - -offsetY + getMeasuredHeight()); - paint = reactionBackgroundBlur.paint; + Paint paint; + if (isWindow) { + if (windowBackgroundBlur == null) { + windowBackgroundBlur = new BlurringShader.StoryBlurDrawer(blurManager, reactionLayout.getReactionsWindow().windowView, BlurringShader.StoryBlurDrawer.BLUR_TYPE_BACKGROUND); + } + windowBackgroundBlur.setBounds(-offsetX, -offsetY, + -offsetX + getMeasuredWidth(), + -offsetY + getMeasuredHeight()); + paint = windowBackgroundBlur.paint; + } else { + reactionBackgroundBlur.setBounds(-offsetX, -offsetY, + -offsetX + getMeasuredWidth(), + -offsetY + getMeasuredHeight()); + paint = reactionBackgroundBlur.paint; + } + paint.setAlpha(alpha); + backgroundPaint.setAlpha((int) (0.4f * alpha)); + canvas.drawRoundRect(rect, radius, radius, paint); + canvas.drawRoundRect(rect, radius, radius, backgroundPaint); + //ReactionsContainerLayout.ReactionsContainerDelegate.super.drawRoundRect(canvas, rect, radius, offsetX, offsetY); } - paint.setAlpha(alpha); - backgroundPaint.setAlpha((int) (0.4f * alpha)); - canvas.drawRoundRect(rect, radius, radius, paint); - canvas.drawRoundRect(rect, radius, radius, backgroundPaint); - //ReactionsContainerLayout.ReactionsContainerDelegate.super.drawRoundRect(canvas, rect, radius, offsetX, offsetY); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/PhotoVideoSwitcherView.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/PhotoVideoSwitcherView.java index f64aeb7b9..f860de146 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/PhotoVideoSwitcherView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/PhotoVideoSwitcherView.java @@ -180,6 +180,10 @@ public class PhotoVideoSwitcherView extends View implements FlashViews.Invertabl canvas.restore(); } + protected boolean allowTouch() { + return true; + } + @Override public boolean onTouchEvent(MotionEvent event) { if (mVelocityTracker == null) { @@ -189,6 +193,9 @@ public class PhotoVideoSwitcherView extends View implements FlashViews.Invertabl switch (event.getAction()) { case MotionEvent.ACTION_DOWN: + if (!allowTouch()) { + return false; + } mIsTouch = true; modeAtTouchDown = mode; mLastTouchTime = System.currentTimeMillis(); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/PreviewView.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/PreviewView.java index b6bdc1b35..e23e00129 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/PreviewView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/PreviewView.java @@ -46,6 +46,8 @@ import com.google.zxing.common.detector.MathUtils; import org.telegram.messenger.AndroidUtilities; import org.telegram.messenger.ApplicationLoader; import org.telegram.messenger.ChatThemeController; +import org.telegram.messenger.ImageLoader; +import org.telegram.messenger.ImageReceiver; import org.telegram.messenger.MessageObject; import org.telegram.messenger.MessagesController; import org.telegram.messenger.Utilities; @@ -64,6 +66,7 @@ import org.telegram.ui.Components.VideoEditTextureView; import org.telegram.ui.Components.VideoPlayer; import java.io.File; +import java.util.ArrayList; import java.util.HashSet; public class PreviewView extends FrameLayout { @@ -85,6 +88,7 @@ public class PreviewView extends FrameLayout { private VideoPlayer audioPlayer; + private CollageLayoutView2 collage; // private VideoTimelinePlayView videoTimelineView; private TimelineView timelineView; @@ -129,14 +133,20 @@ public class PreviewView extends FrameLayout { if (entry == null) { setupVideoPlayer(null, whenReady, seekTo); setupImage(null); + setupCollage(null); setupWallpaper(null, false); gradientPaint.setShader(null); setupAudio((StoryEntry) null, false); setupRound(null, null, false); return; } - if (entry.isVideo) { + if (entry.isCollage()) { + setupImage(null); + setupVideoPlayer(null, whenReady, seekTo); + setupCollage(entry); + } else if (entry.isVideo) { setupImage(entry); + setupCollage(null); setupVideoPlayer(entry, whenReady, seekTo); if (entry.gradientTopColor != 0 || entry.gradientBottomColor != 0) { setupGradient(); @@ -144,6 +154,7 @@ public class PreviewView extends FrameLayout { entry.setupGradient(this::setupGradient); } } else { + setupCollage(null); setupVideoPlayer(null, whenReady, 0); setupImage(entry); setupGradient(); @@ -154,6 +165,14 @@ public class PreviewView extends FrameLayout { setupRound(entry, null, false); } + public void setCollageView(CollageLayoutView2 collage) { + this.collage = collage; + } + + public boolean isCollage() { + return collage != null && entry != null && entry.isCollage(); + } + // set without video for faster transition public void preset(StoryEntry entry) { this.entry = entry; @@ -278,8 +297,15 @@ public class PreviewView extends FrameLayout { entry.audioOffset = (long) (entry.left * getDuration()); } entry.audioLeft = 0; - long scrollDuration = Math.min(entry != null && entry.isVideo ? getDuration() : entry.audioDuration, TimelineView.MAX_SCROLL_DURATION); - entry.audioRight = entry.audioDuration == 0 ? 1 : Math.min(1, Math.min(scrollDuration, TimelineView.MAX_SELECT_DURATION) / (float) entry.audioDuration); + long duration; + if (isCollage() && collage.hasVideo()) { + duration = collage.getDuration(); + } else if (entry.isVideo) { + duration = getDuration(); + } else { + duration = entry.audioDuration; + } + entry.audioRight = entry.audioDuration == 0 ? 1 : Math.min(1, Math.min(duration, TimelineView.MAX_SELECT_DURATION) / (float) entry.audioDuration); } } setupAudio(entry, animated); @@ -300,6 +326,8 @@ public class PreviewView extends FrameLayout { public void seekTo(long position, boolean fast) { if (videoPlayer != null) { videoPlayer.seekTo(position, fast); + } else if (isCollage()) { + collage.seekTo(position, fast); } else if (roundPlayer != null) { roundPlayer.seekTo(position, fast); } else if (audioPlayer != null) { @@ -393,6 +421,9 @@ public class PreviewView extends FrameLayout { timelineView.setDelegate(new TimelineView.TimelineDelegate() { @Override public void onProgressDragChange(boolean dragging) { + if (isCollage()) { + collage.forceNotRestorePosition(); + } updatePauseReason(-4, dragging); } @@ -402,6 +433,8 @@ public class PreviewView extends FrameLayout { seekTo(progress); } else if (videoPlayer != null) { videoPlayer.seekTo(progress, true); + } else if (isCollage()) { + collage.seekTo(progress, true); } else if (audioPlayer != null) { audioPlayer.seekTo(progress, false); } @@ -532,6 +565,41 @@ public class PreviewView extends FrameLayout { public void onRoundSelectChange(boolean selected) { PreviewView.this.onRoundSelectChange(selected); } + + @Override + public void onVideoVolumeChange(int i, float volume) { + if (entry != null && entry.collageContent != null && i >= 0 && i < entry.collageContent.size()) { + entry.collageContent.get(i).videoVolume = volume; + } + } + + @Override + public void onVideoLeftChange(int i, float left) { + if (entry != null && entry.collageContent != null && i >= 0 && i < entry.collageContent.size()) { + entry.collageContent.get(i).videoLeft = left; + } + } + + @Override + public void onVideoRightChange(int i, float right) { + if (entry != null && entry.collageContent != null && i >= 0 && i < entry.collageContent.size()) { + entry.collageContent.get(i).videoRight = right; + } + } + + @Override + public void onVideoOffsetChange(int i, long offset) { + if (entry != null && entry.collageContent != null && i >= 0 && i < entry.collageContent.size()) { + entry.collageContent.get(i).videoOffset = offset; + } + } + + @Override + public void onVideoSelected(int i) { + if (collage != null) { + collage.highlight(i); + } + } }); } } @@ -630,6 +698,12 @@ public class PreviewView extends FrameLayout { invalidate(); } + private void setupCollage(StoryEntry entry) { + if (timelineView != null) { + timelineView.setCollage(entry != null ? entry.collageContent : null); + } + } + private void setupGradient() { final int height = getMeasuredHeight() > 0 ? getMeasuredHeight() : AndroidUtilities.displaySize.y; if (entry.gradientTopColor == 0 || entry.gradientBottomColor == 0) { @@ -678,7 +752,7 @@ public class PreviewView extends FrameLayout { } public void setupVideoPlayer(StoryEntry entry, Runnable whenReady, long seekTo) { - if (entry == null) { + if (entry == null || entry.isCollage()) { if (videoPlayer != null) { videoPlayer.pause(); videoPlayer.releasePlayer(true); @@ -1043,7 +1117,7 @@ public class PreviewView extends FrameLayout { }; private final Runnable updateAudioProgressRunnable = () -> { - if (audioPlayer == null || videoPlayer != null || roundPlayer != null || timelineView == null) { + if (audioPlayer == null || videoPlayer != null || roundPlayer != null || timelineView == null || isCollage()) { return; } @@ -1061,7 +1135,7 @@ public class PreviewView extends FrameLayout { }; private final Runnable updateRoundProgressRunnable = () -> { - if (roundPlayer == null || videoPlayer != null || timelineView == null) { + if (roundPlayer == null || videoPlayer != null || isCollage() || timelineView == null) { return; } @@ -1079,12 +1153,12 @@ public class PreviewView extends FrameLayout { } }; - private void updateAudioPlayer(boolean updateSeek) { + public void updateAudioPlayer(boolean updateSeek) { if (audioPlayer == null || entry == null) { return; } - if (videoPlayer == null && roundPlayer == null) { + if (videoPlayer == null && roundPlayer == null && !isCollage()) { audioPlayer.setPlayWhenReady(pauseLinks.isEmpty()); audioPlayer.setLooping(true); @@ -1099,26 +1173,34 @@ public class PreviewView extends FrameLayout { return; } - VideoPlayer player = videoPlayer != null ? videoPlayer : roundPlayer; + final long pos; + final boolean playing; + if (isCollage()) { + pos = collage.getPositionWithOffset(); + playing = collage.isPlaying(); + } else { + VideoPlayer player = videoPlayer != null ? videoPlayer : roundPlayer; + pos = player.getCurrentPosition(); + playing = player.isPlaying(); + } - final long pos = player.getCurrentPosition(); final long duration = (long) ((entry.audioRight - entry.audioLeft) * entry.audioDuration); - boolean shouldPlaying = player.isPlaying() && pos >= entry.audioOffset && pos <= entry.audioOffset + duration; + boolean shouldPlaying = playing && pos >= entry.audioOffset && pos <= entry.audioOffset + duration; long audioPos = pos - entry.audioOffset + (long) (entry.audioLeft * entry.audioDuration); if (audioPlayer.isPlaying() != shouldPlaying) { audioPlayer.setPlayWhenReady(shouldPlaying); audioPlayer.seekTo(audioPos); - } else if (updateSeek && Math.abs(audioPlayer.getCurrentPosition() - audioPos) > 120) { + } else if (updateSeek && Math.abs(audioPlayer.getCurrentPosition() - audioPos) > (isCollage() ? 300 : 120)) { audioPlayer.seekTo(audioPos); } } - private void updateRoundPlayer(boolean updateSeek) { + public void updateRoundPlayer(boolean updateSeek) { if (roundPlayer == null || entry == null) { return; } - if (videoPlayer == null) { + if (videoPlayer == null && !isCollage()) { roundPlayer.setPlayWhenReady(pauseLinks.isEmpty()); roundPlayer.setLooping(true); if (roundView != null) { @@ -1136,10 +1218,19 @@ public class PreviewView extends FrameLayout { return; } - final long pos = videoPlayer.getCurrentPosition(); + final long pos; + final boolean playing; + if (isCollage()) { + pos = collage.getPositionWithOffset(); + playing = collage.isPlaying(); + } else { + pos = videoPlayer.getCurrentPosition(); + playing = videoPlayer.isPlaying(); + } + final long duration = (long) ((entry.roundRight - entry.roundLeft) * entry.roundDuration); boolean shouldPlayingInSeek = pos >= entry.roundOffset && pos <= entry.roundOffset + duration; - boolean shouldPlaying = videoPlayer.isPlaying() && shouldPlayingInSeek; + boolean shouldPlaying = playing && shouldPlayingInSeek; long roundPos = pos - entry.roundOffset + (long) (entry.roundLeft * entry.roundDuration); if (roundView != null) { roundView.setShown(shouldPlayingInSeek, true); @@ -1147,7 +1238,7 @@ public class PreviewView extends FrameLayout { if (roundPlayer.isPlaying() != shouldPlaying) { roundPlayer.setPlayWhenReady(shouldPlaying); roundPlayer.seekTo(roundPos); - } else if (updateSeek && Math.abs(roundPlayer.getCurrentPosition() - roundPos) > 120) { + } else if (updateSeek && Math.abs(roundPlayer.getCurrentPosition() - roundPos) > (isCollage() ? 300 : 120)) { roundPlayer.seekTo(roundPos); } } @@ -1172,6 +1263,9 @@ public class PreviewView extends FrameLayout { if (audioPlayer != null) { audioPlayer.setVolume(isMuted ? 0 : (entry != null ? entry.audioVolume : 1f)); } + if (collage != null) { + collage.setMuted(isMuted); + } } private AnimatedFloat wallpaperDrawableCrossfade = new AnimatedFloat(this, 0, 350, CubicBezierInterpolator.EASE_OUT_QUINT); @@ -1245,7 +1339,7 @@ public class PreviewView extends FrameLayout { } else { canvas.drawRect(0, 0, getWidth(), getHeight(), gradientPaint); } - if (draw && entry != null) { + if (draw && entry != null && !isCollage()) { float alpha = this.thumbAlpha.set(bitmap != null); if (thumbBitmap != null && (1f - alpha) > 0) { matrix.set(entry.matrix); @@ -1520,6 +1614,9 @@ public class PreviewView extends FrameLayout { if (videoPlayer != null) { videoPlayer.setPlayWhenReady(pauseLinks.isEmpty()); } + if (collage != null) { + collage.setPlaying(pauseLinks.isEmpty()); + } updateAudioPlayer(true); updateRoundPlayer(true); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/RecordControl.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/RecordControl.java index 6081706dd..0bd6cdf2d 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/RecordControl.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/RecordControl.java @@ -4,6 +4,7 @@ import static android.graphics.Color.BLACK; import static org.telegram.messenger.AndroidUtilities.dp; import static org.telegram.messenger.AndroidUtilities.dpf2; import static org.telegram.messenger.AndroidUtilities.lerp; +import static org.telegram.messenger.Utilities.clamp; import android.animation.ValueAnimator; import android.content.Context; @@ -13,10 +14,13 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; +import android.graphics.PorterDuffXfermode; import android.graphics.RadialGradient; import android.graphics.Shader; import android.graphics.SurfaceTexture; +import android.graphics.Xfermode; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.SystemClock; import android.util.Log; import android.view.MotionEvent; @@ -62,6 +66,7 @@ public class RecordControl extends View implements FlashViews.Invertable { void onZoom(float zoom); void onVideoRecordLocked(); boolean canRecordAudio(); + void onCheckClick(); } public void startAsVideo(boolean isVideo) { @@ -99,6 +104,7 @@ public class RecordControl extends View implements FlashViews.Invertable { private final Paint redPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint hintLinePaintWhite = new Paint(Paint.ANTI_ALIAS_FLAG); private final Paint hintLinePaintBlack = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint checkPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final Matrix redMatrix = new Matrix(); private RadialGradient redGradient; @@ -115,6 +121,11 @@ public class RecordControl extends View implements FlashViews.Invertable { private long recordingStart; private long lastDuration; + private final Path checkPath = new Path(); + private final Point check1 = new Point(-dpf2(29/3.0f), dpf2(7/3.0f)); + private final Point check2 = new Point(-dpf2(8.5f/3.0f), dpf2(26/3.0f)); + private final Point check3 = new Point(dpf2(29/3.0f), dpf2(-11/3.0f)); + public RecordControl(Context context) { super(context); @@ -125,6 +136,7 @@ public class RecordControl extends View implements FlashViews.Invertable { redPaint.setShader(redGradient); outlinePaint.setColor(WHITE); outlinePaint.setStyle(Paint.Style.STROKE); + outlinePaint.setStrokeCap(Paint.Cap.ROUND); outlineFilledPaint.setColor(RED); outlineFilledPaint.setStrokeCap(Paint.Cap.ROUND); outlineFilledPaint.setStyle(Paint.Style.STROKE); @@ -136,6 +148,14 @@ public class RecordControl extends View implements FlashViews.Invertable { hintLinePaintWhite.setStrokeCap(Paint.Cap.ROUND); hintLinePaintBlack.setStyle(Paint.Style.STROKE); hintLinePaintBlack.setStrokeCap(Paint.Cap.ROUND); + checkPaint.setStyle(Paint.Style.STROKE); + checkPaint.setStrokeJoin(Paint.Join.ROUND); + checkPaint.setStrokeCap(Paint.Cap.ROUND); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + checkPaint.setBlendMode(android.graphics.BlendMode.CLEAR); + } else { + checkPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); + } galleryImage.setParentView(this); galleryImage.setCrossfadeWithOldImage(true); @@ -283,8 +303,23 @@ public class RecordControl extends View implements FlashViews.Invertable { private final AnimatedFloat touchIsButtonT = new AnimatedFloat(this, 0, 650, CubicBezierInterpolator.EASE_OUT_QUINT); private final AnimatedFloat lockedT = new AnimatedFloat(this, 0, 320, CubicBezierInterpolator.EASE_OUT_QUINT); + private float collageProgress; + private final AnimatedFloat collage = new AnimatedFloat(this, 0, 320, CubicBezierInterpolator.EASE_OUT_QUINT); + private final AnimatedFloat collageProgressAnimated = new AnimatedFloat(this, 0, 320, CubicBezierInterpolator.EASE_OUT_QUINT); + private final AnimatedFloat checkAnimated = new AnimatedFloat(this, 0, 320, CubicBezierInterpolator.EASE_OUT_QUINT); + + public void setCollageProgress(float collageProgress, boolean animated) { + if (Math.abs(collageProgress - this.collageProgress) < 0.01f) return; + this.collageProgress = collageProgress; + if (!animated) { + this.collage.set(collageProgress > 0 && !recording, true); + this.collageProgressAnimated.set(collageProgress, true); + } + invalidate(); + } + private final Runnable onRecordLongPressRunnable = () -> { - if (recording) { + if (recording || hasCheck()) { return; } if (!delegate.canRecordAudio()) { @@ -304,7 +339,7 @@ public class RecordControl extends View implements FlashViews.Invertable { }; private final Runnable onFlipLongPressRunnable = () -> { - if (!recording) { + if (!recording && !hasCheck()) { delegate.onFlipLongClick(); rotateFlip(360); @@ -315,8 +350,8 @@ public class RecordControl extends View implements FlashViews.Invertable { } }; - private Path metaballsPath = new Path(); - private Path circlePath = new Path(); + private final Path metaballsPath = new Path(); + private final Path circlePath = new Path(); private final float HALF_PI = (float) Math.PI / 2; @@ -328,12 +363,16 @@ public class RecordControl extends View implements FlashViews.Invertable { float scale; - float touchT = this.touchT.set(touch ? 1 : 0); - float touchIsCenterT = touchT * this.touchIsCenterT.set(Math.abs(touchX - cx) < dp(64) && (recording || recordButton.isPressed()) ? 1 : 0); - float touchIsCenter2T = touchT * this.touchIsCenter2T.set(Math.abs(touchX - cx) < dp(64) ? 1 : 0); - float touchCenterT16 = Utilities.clamp((touchX - cx) / dp(16), 1, -1); - float touchCenterT96 = Utilities.clamp((touchX - cx) / dp(64), 1, -1); - float touchIsButtonT = touchT * this.touchIsButtonT.set(Math.min(Math.abs(touchX - rightCx), Math.abs(touchX - leftCx)) < dp(16) ? 1 : 0); + final float touchT = this.touchT.set(touch ? 1 : 0); + final float touchIsCenterT = touchT * this.touchIsCenterT.set(Math.abs(touchX - cx) < dp(64) && (recording || recordButton.isPressed()) ? 1 : 0); + final float touchIsCenter2T = touchT * this.touchIsCenter2T.set(Math.abs(touchX - cx) < dp(64) ? 1 : 0); + final float touchCenterT16 = clamp((touchX - cx) / dp(16), 1, -1); + final float touchCenterT96 = clamp((touchX - cx) / dp(64), 1, -1); + final float touchIsButtonT = touchT * this.touchIsButtonT.set(Math.min(Math.abs(touchX - rightCx), Math.abs(touchX - leftCx)) < dp(16) ? 1 : 0); + + final float collage = this.collage.set(collageProgress > 0) * (1.0f - recordingT); + final float collageProgress = this.collageProgressAnimated.set(this.collageProgress); + final float check = checkAnimated.set(hasCheck()); float hintLineT = longpressRecording ? recordingT * isVideo * touchT : 0; if (hintLineT > 0) { @@ -348,32 +387,58 @@ public class RecordControl extends View implements FlashViews.Invertable { canvas.drawLine(lcx, cy, lerp(lcx, leftCx + dp(22 + 8), hintLineT), cy, hintLinePaintWhite); } - canvas.save(); - scale = lerp(recordButton.getScale(startModeIsVideo ? 0 : .2f), 1 + .2f * animatedAmplitude.set(amplitude), recordingT); - canvas.scale(scale, scale, cx, cy); - mainPaint.setColor(ColorUtils.blendARGB(WHITE, RED, isVideo)); float acx = lerp(cx, recordCx.set(cx + dp(4) * touchCenterT16), touchIsCenterT); float r = lerp(lerp(dp(29), dp(12), recordingT), dp(32) - dp(4) * Math.abs(touchCenterT96), touchIsCenterT); float rad = lerp(lerp(dp(32), dp(7), recordingT), dp(32), touchIsCenterT); + scale = lerp(recordButton.getScale(startModeIsVideo ? 0 : .2f), 1 + .2f * animatedAmplitude.set(amplitude), recordingT); AndroidUtilities.rectTmp.set(acx - r, cy - r, acx + r, cy + r); + mainPaint.setColor(ColorUtils.blendARGB(WHITE, RED, isVideo * (1.0f - check))); + if (check > 0) { + canvas.save(); + canvas.scale(scale, scale, cx, cy); + mainPaint.setAlpha((int) (0xFF * (1.0f - check))); + canvas.drawRoundRect(AndroidUtilities.rectTmp, rad, rad, mainPaint); + canvas.restore(); + canvas.saveLayerAlpha(0, 0, getWidth(), getHeight(), 0xFF, Canvas.ALL_SAVE_FLAG); + } else { + canvas.save(); + } + canvas.scale(scale, scale, cx, cy); + mainPaint.setAlpha(0xFF); canvas.drawRoundRect(AndroidUtilities.rectTmp, rad, rad, mainPaint); + if (check > 0) { + checkPaint.setStrokeWidth(dp(4)); + checkPath.rewind(); + checkPath.moveTo(check1.x, check1.y); + checkPath.lineTo(lerp(check1.x, check2.x, clamp(check / .3f, 1.0f, 0.0f)), lerp(check1.y, check2.y, clamp(check / .3f, 1.0f, 0.0f))); + if (check > .3f) checkPath.lineTo(lerp(check2.x, check3.x, clamp((check-.3f) / .7f, 1.0f, 0.0f)), lerp(check2.y, check3.y, clamp((check-.3f) / .7f, 1.0f, 0.0f))); + canvas.translate(cx, cy); + canvas.drawPath(checkPath, checkPaint); + } canvas.restore(); canvas.save(); scale = Math.max(scale, 1); canvas.scale(scale, scale, cx, cy); - outlinePaint.setStrokeWidth(dp(3)); - float or = Math.max(dpf2(33.5f), r + lerp(dpf2(4.5f), dp(9), touchIsCenterT)); + float or = Math.max(dpf2(33.5f), r + lerp(dpf2(4.5f), dp(9), touchIsCenterT) + dp(5) * collage * (1.0f - touchIsCenterT)); + final float strokeWidth = lerp(dp(3), dp(4), collage); + or = lerp(or, r - strokeWidth - dp(4), check); + AndroidUtilities.rectTmp.set(cx - or, cy - or, cx + or, cy + or); + outlinePaint.setStrokeWidth(strokeWidth); + outlinePaint.setAlpha((int) (0xFF * lerp(1.0f, 0.3f, collage) * (1.0f - check))); canvas.drawCircle(cx, cy, or, outlinePaint); + if (collage > 0 & collageProgress > 0) { + outlinePaint.setAlpha(0xFF); + canvas.drawArc(AndroidUtilities.rectTmp, -90, 360 * collageProgress, false, outlinePaint); + } long duration = System.currentTimeMillis() - recordingStart; - AndroidUtilities.rectTmp.set(cx - or, cy - or, cx + or, cy + or); float recordEndT = recording ? 0 : 1f - recordingLongT; float sweepAngle = duration / (float) MAX_DURATION * 360; float recordingLoading = this.recordingLoadingT.set(this.recordingLoading); - outlineFilledPaint.setStrokeWidth(dp(3)); + outlineFilledPaint.setStrokeWidth(strokeWidth); outlineFilledPaint.setAlpha((int) (0xFF * Math.max(.7f * recordingLoading, 1f - recordEndT))); if (recordingLoading <= 0) { @@ -430,7 +495,7 @@ public class RecordControl extends View implements FlashViews.Invertable { } } - scale = lockButton.getScale(.2f) * (1f - recordingT); + scale = lockButton.getScale(.2f) * (1f - recordingT) * (1.0f - check); if (scale > 0) { canvas.save(); canvas.scale(scale, scale, leftCx, cy); @@ -441,7 +506,7 @@ public class RecordControl extends View implements FlashViews.Invertable { float dualT = this.dualT.set(dual ? 1f : 0f); if (dualT > 0) { canvas.save(); - scale = flipButton.getScale(.2f) * dualT; + scale = flipButton.getScale(.2f) * dualT * (1.0f - check); canvas.scale(scale, scale, rightCx, cy); canvas.rotate(flipDrawableRotateT.set(flipDrawableRotate), rightCx, cy); canvas.drawCircle(rightCx, cy, dp(22), buttonPaintWhite); @@ -450,7 +515,7 @@ public class RecordControl extends View implements FlashViews.Invertable { } if (dualT < 1) { canvas.save(); - scale = flipButton.getScale(.2f) * (1f - dualT); + scale = flipButton.getScale(.2f) * (1f - dualT) * (1.0f - check); canvas.scale(scale, scale, rightCx, cy); canvas.rotate(flipDrawableRotateT.set(flipDrawableRotate), rightCx, cy); canvas.drawCircle(rightCx, cy, dp(22), buttonPaint); @@ -459,7 +524,7 @@ public class RecordControl extends View implements FlashViews.Invertable { } final float tr; - if (longpressRecording) { + if (longpressRecording && !hasCheck()) { tr = ( touchT * isVideo * @@ -484,7 +549,7 @@ public class RecordControl extends View implements FlashViews.Invertable { float x1 = acx, x2 = touchX; final float handleSize = 2.4f; - final float v = Utilities.clamp(1f - touchT * Math.abs(touchCenterT96) / 1.3f, 1, 0); + final float v = clamp(1f - touchT * Math.abs(touchCenterT96) / 1.3f, 1, 0); final float d = Math.abs(x1 - x2); final float maxdist = r + tr * 2f; if (d < maxdist && v < .6f) { @@ -542,7 +607,7 @@ public class RecordControl extends View implements FlashViews.Invertable { } } if (tr > 0 || locked > 0) { - scale = lockButton.getScale(.2f) * recordingT; + scale = lockButton.getScale(.2f) * recordingT * (1.0f - check);; canvas.save(); circlePath.rewind(); if (tr > 0) { @@ -561,7 +626,7 @@ public class RecordControl extends View implements FlashViews.Invertable { canvas.restore(); } - scale = flipButton.getScale(.2f); + scale = flipButton.getScale(.2f) * (1.0f - check); canvas.save(); canvas.scale(scale, scale, rightCx, cy); canvas.rotate(flipDrawableRotateT.set(flipDrawableRotate), rightCx, cy); @@ -573,6 +638,10 @@ public class RecordControl extends View implements FlashViews.Invertable { } } + public boolean hasCheck() { + return collageProgress >= 1.0f; + } + private final Point p1 = new Point(), p2 = new Point(), p3 = new Point(), p4 = new Point(), h1 = new Point(), h2 = new Point(), h3 = new Point(), h4 = new Point(); private void getVector(float cx, float cy, double a, float r, Point point) { point.x = (float) (cx + Math.cos(a) * r); @@ -617,7 +686,7 @@ public class RecordControl extends View implements FlashViews.Invertable { float ox = 0, oy = 0; final int action = event.getAction(); - final float x = Utilities.clamp(event.getX() + ox, rightCx, leftCx), y = event.getY() + oy; + final float x = clamp(event.getX() + ox, rightCx, leftCx), y = event.getY() + oy; final boolean innerFlipButton = isPressed(x, y, rightCx, cy, dp(7), true); if (recordingLoading) { @@ -626,8 +695,8 @@ public class RecordControl extends View implements FlashViews.Invertable { lockButton.setPressed(false); } else if (action == MotionEvent.ACTION_DOWN || touch) { recordButton.setPressed(isPressed(x, y, cx, cy, dp(60), false)); - flipButton.setPressed(isPressed(x, y, rightCx, cy, dp(30), true)); - lockButton.setPressed(isPressed(x, y, leftCx, cy, dp(30), false)); + flipButton.setPressed(isPressed(x, y, rightCx, cy, dp(30), true) && !hasCheck()); + lockButton.setPressed(isPressed(x, y, leftCx, cy, dp(30), false) && !hasCheck()); } boolean r = false; @@ -651,7 +720,7 @@ public class RecordControl extends View implements FlashViews.Invertable { if (!touch) { return false; } - touchX = Utilities.clamp(x, rightCx, leftCx); + touchX = clamp(x, rightCx, leftCx); touchY = y; invalidate(); @@ -662,7 +731,7 @@ public class RecordControl extends View implements FlashViews.Invertable { if (recording && longpressRecording) { final float dy = cy - dp(48) - y; - final float zoom = Utilities.clamp(dy / (AndroidUtilities.displaySize.y / 2f), 1, 0); + final float zoom = clamp(dy / (AndroidUtilities.displaySize.y / 2f), 1, 0); delegate.onZoom(zoom); } @@ -692,7 +761,9 @@ public class RecordControl extends View implements FlashViews.Invertable { delegate.onVideoRecordEnd(false); } } else if (recordButton.isPressed()) { - if (!startModeIsVideo && !recording && !longpressRecording) { + if (hasCheck()) { + delegate.onCheckClick(); + } else if (!startModeIsVideo && !recording && !longpressRecording) { delegate.onPhotoShoot(); } else if (!recording) { if (delegate.canRecordAudio()) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/RecordControlRenderer.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/RecordControlRenderer.java deleted file mode 100644 index edc5dd597..000000000 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/RecordControlRenderer.java +++ /dev/null @@ -1,314 +0,0 @@ -package org.telegram.ui.Stories.recorder; - -import android.content.Context; -import android.graphics.SurfaceTexture; -import android.opengl.EGL14; -import android.opengl.GLES11Ext; -import android.opengl.GLES20; -import android.opengl.GLUtils; -import android.os.Build; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; -import android.view.Display; -import android.view.WindowManager; - -import org.telegram.messenger.AndroidUtilities; -import org.telegram.messenger.ApplicationLoader; -import org.telegram.messenger.BuildVars; -import org.telegram.messenger.DispatchQueue; -import org.telegram.messenger.FileLog; -import org.telegram.messenger.Intro; -import org.telegram.messenger.camera.CameraSession; -import org.telegram.messenger.camera.CameraView; - -import java.io.File; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.nio.FloatBuffer; -import java.util.concurrent.TimeUnit; - -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; -import javax.microedition.khronos.egl.EGLSurface; -import javax.microedition.khronos.opengles.GL; - -public class RecordControlRenderer extends DispatchQueue { - - private static final String VERTEX_SHADER = - "attribute vec4 aPosition;\n" + - "void main() {\n" + - " gl_Position = aPosition;\n" + - "}\n"; - private final static String FRAGMENT_SHADER = - "precision lowp float;\n" + - "void main() {\n" + - " gl_FragColor = vec4(1., 0., 0., 1.);\n" + - "}\n"; - - private final Object layoutLock = new Object(); - private FloatBuffer vertexBuffer; - - private final static int EGL_CONTEXT_CLIENT_VERSION = 0x3098; - private final static int EGL_OPENGL_ES2_BIT = 4; - private SurfaceTexture surfaceTexture; - private EGL10 egl10; - private EGLDisplay eglDisplay; - private EGLContext eglContext; - private EGLSurface eglSurface; - private EGLConfig eglConfig; - private boolean initied; - - private final int DO_RENDER_MESSAGE = 0; - private final int DO_SHUTDOWN_MESSAGE = 1; - - private int drawProgram; - private int positionHandle; - private int resolutionUniform; - - private int width, height; - - private Integer cameraId = 0; - - public RecordControlRenderer(SurfaceTexture surface) { - super("RecordControlRenderer"); - surfaceTexture = surface; - } - - private boolean initGL() { - if (BuildVars.LOGS_ENABLED) { - FileLog.d("RecordControlRenderer " + "start init gl"); - } - egl10 = (EGL10) EGLContext.getEGL(); - - eglDisplay = egl10.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); - if (eglDisplay == EGL10.EGL_NO_DISPLAY) { - if (BuildVars.LOGS_ENABLED) { - FileLog.e("eglGetDisplay failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); - } - eglDisplay = null; - finish(); - return false; - } - - int[] version = new int[2]; - if (!egl10.eglInitialize(eglDisplay, version)) { - if (BuildVars.LOGS_ENABLED) { - FileLog.e("eglInitialize failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); - } - finish(); - return false; - } - - int[] configsCount = new int[1]; - EGLConfig[] configs = new EGLConfig[1]; - int[] configSpec = new int[]{ - EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL10.EGL_RED_SIZE, 8, - EGL10.EGL_GREEN_SIZE, 8, - EGL10.EGL_BLUE_SIZE, 8, - EGL10.EGL_ALPHA_SIZE, 0, - EGL10.EGL_DEPTH_SIZE, 0, - EGL10.EGL_STENCIL_SIZE, 0, - EGL10.EGL_NONE - }; - if (!egl10.eglChooseConfig(eglDisplay, configSpec, configs, 1, configsCount)) { - if (BuildVars.LOGS_ENABLED) { - FileLog.e("eglChooseConfig failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); - } - finish(); - return false; - } else if (configsCount[0] > 0) { - eglConfig = configs[0]; - } else { - if (BuildVars.LOGS_ENABLED) { - FileLog.e("eglConfig not initialized"); - } - finish(); - return false; - } - int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE}; - eglContext = egl10.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, attrib_list); - if (eglContext == null || eglContext == EGL10.EGL_NO_CONTEXT) { - eglContext = null; - if (BuildVars.LOGS_ENABLED) { - FileLog.e("eglCreateContext failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); - } - finish(); - return false; - } - - if (surfaceTexture != null) { - eglSurface = egl10.eglCreateWindowSurface(eglDisplay, eglConfig, surfaceTexture, null); - } else { - finish(); - return false; - } - - if (eglSurface == null || eglSurface == EGL10.EGL_NO_SURFACE) { - if (BuildVars.LOGS_ENABLED) { - FileLog.e("createWindowSurface failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); - } - finish(); - return false; - } - if (!egl10.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) { - if (BuildVars.LOGS_ENABLED) { - FileLog.e("eglMakeCurrent failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); - } - finish(); - return false; - } - GL gl = eglContext.getGL(); - - int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER); - int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER); - if (vertexShader != 0 && fragmentShader != 0) { - drawProgram = GLES20.glCreateProgram(); - GLES20.glAttachShader(drawProgram, vertexShader); - GLES20.glAttachShader(drawProgram, fragmentShader); - GLES20.glLinkProgram(drawProgram); - int[] linkStatus = new int[1]; - GLES20.glGetProgramiv(drawProgram, GLES20.GL_LINK_STATUS, linkStatus, 0); - if (linkStatus[0] == 0) { - if (BuildVars.LOGS_ENABLED) { - FileLog.e("failed link shader"); - } - GLES20.glDeleteProgram(drawProgram); - drawProgram = 0; - } else { - positionHandle = GLES20.glGetAttribLocation(drawProgram, "aPosition"); - } - } else { - if (BuildVars.LOGS_ENABLED) { - FileLog.e("failed creating shader"); - } - finish(); - return false; - } - - if (BuildVars.LOGS_ENABLED) { - FileLog.e("gl initied"); - } - - float[] verticesData = { - -1.0f, -1.0f, 0, - 1.0f, -1.0f, 0, - -1.0f, 1.0f, 0, - 1.0f, 1.0f, 0 - }; - - vertexBuffer = ByteBuffer.allocateDirect(verticesData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer(); - vertexBuffer.put(verticesData).position(0); - - return true; - } - - private int loadShader(int type, String shaderCode) { - int shader = GLES20.glCreateShader(type); - GLES20.glShaderSource(shader, shaderCode); - GLES20.glCompileShader(shader); - int[] compileStatus = new int[1]; - GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0); - if (compileStatus[0] == 0) { - if (BuildVars.LOGS_ENABLED) { - FileLog.e(GLES20.glGetShaderInfoLog(shader)); - } - GLES20.glDeleteShader(shader); - shader = 0; - } - return shader; - } - - public void finish() { - if (eglSurface != null) { - egl10.eglMakeCurrent(eglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); - egl10.eglDestroySurface(eglDisplay, eglSurface); - eglSurface = null; - } - if (eglContext != null) { - egl10.eglDestroyContext(eglDisplay, eglContext); - eglContext = null; - } - if (eglDisplay != null) { - egl10.eglTerminate(eglDisplay); - eglDisplay = null; - } - } - - final int array[] = new int[1]; - - private void draw() { - if (!initied) { - return; - } - - if (!eglContext.equals(egl10.eglGetCurrentContext()) || !eglSurface.equals(egl10.eglGetCurrentSurface(EGL10.EGL_DRAW))) { - if (!egl10.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) { - if (BuildVars.LOGS_ENABLED) { - FileLog.e("eglMakeCurrent failed " + GLUtils.getEGLErrorString(egl10.eglGetError())); - } - return; - } - } - - egl10.eglQuerySurface(eglDisplay, eglSurface, EGL10.EGL_WIDTH, array); - int drawnWidth = array[0]; - egl10.eglQuerySurface(eglDisplay, eglSurface, EGL10.EGL_HEIGHT, array); - int drawnHeight = array[0]; - - GLES20.glViewport(0, 0, drawnWidth, drawnHeight); - - GLES20.glUseProgram(drawProgram); - - GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 12, vertexBuffer); - GLES20.glEnableVertexAttribArray(positionHandle); - - GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); - - GLES20.glDisableVertexAttribArray(positionHandle); - GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0); - GLES20.glUseProgram(0); - - egl10.eglSwapBuffers(eglDisplay, eglSurface); - } - - - @Override - public void run() { - initied = initGL(); - super.run(); - } - - @Override - public void handleMessage(Message inputMessage) { - int what = inputMessage.what; - - switch (what) { - case DO_RENDER_MESSAGE: - draw( - - ); - break; - } - } - - public void shutdown() { - postRunnable(() -> { - finish(); - Looper looper = Looper.myLooper(); - if (looper != null) { - looper.quit(); - } - }); - } - - public void requestRender() { - Handler handler = getHandler(); - if (handler != null) { - sendMessage(handler.obtainMessage(DO_RENDER_MESSAGE, cameraId), 0); - } - } -} \ No newline at end of file diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/SliderView.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/SliderView.java index f11b2f6cf..e1f2c2a83 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/SliderView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/SliderView.java @@ -251,6 +251,8 @@ public class SliderView extends View { private int w, h; private final TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); + public int fixWidth; + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (currentType == TYPE_DIMMING) { @@ -260,7 +262,10 @@ public class SliderView extends View { } textPaint.setTextSize(dp(16)); text.setTextSize(dp(15)); - if (currentType == TYPE_VOLUME) { + if (fixWidth > 0) { + w = fixWidth; + h = dp(48); + } else if (currentType == TYPE_VOLUME) { // TODO: fix this nonsense w = (int) Math.min(textPaint.measureText(LocaleController.getString(R.string.StoryAudioRemove)) + dp(88), MeasureSpec.getSize(widthMeasureSpec)); h = dp(48); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/StoryEntry.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/StoryEntry.java index 456fc313c..97ec0b546 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/StoryEntry.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/StoryEntry.java @@ -8,6 +8,7 @@ import android.graphics.LinearGradient; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.Shader; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; @@ -100,6 +101,12 @@ public class StoryEntry { public float videoVolume = 1f; public int orientation, invert; + public CollageLayout collage; + public ArrayList collageContent; + public boolean videoLoop = false; + public float videoLeft = 0f, videoRight = 1f; + public long videoOffset; + public boolean muted; public float left, right = 1; @@ -286,17 +293,46 @@ public class StoryEntry { tempMatrix.postScale(scale, scale); canvas.drawBitmap(mainFileBitmap, tempMatrix, bitmapPaint); } else { - File file = filterFile != null ? filterFile : this.file; - if (file != null) { - try { - Bitmap fileBitmap = getScaledBitmap(opts -> BitmapFactory.decodeFile(file.getPath(), opts), w, h, true, true); - final float s = (float) width / fileBitmap.getWidth(); - tempMatrix.preScale(s, s); - tempMatrix.postScale(scale, scale); - canvas.drawBitmap(fileBitmap, tempMatrix, bitmapPaint); - fileBitmap.recycle(); - } catch (Exception e) { - FileLog.e(e); + if (isCollage()) { + for (int i = 0; i < collageContent.size(); ++i) { + StoryEntry entry = collageContent.get(i); + final File file = entry.filterFile != null ? entry.filterFile : entry.file; + if (file != null) { + try { + final Bitmap fileBitmap = getScaledBitmap(opts -> BitmapFactory.decodeFile(file.getPath(), opts), w, h, true, true); + canvas.save(); + final RectF bounds = new RectF(); + collage.parts.get(i).bounds(bounds, w, h); + canvas.translate(bounds.centerX(), bounds.centerY()); + canvas.clipRect(-bounds.width() / 2.0f, -bounds.height() / 2.0f, bounds.width() / 2.0f, bounds.height() / 2.0f); + final float s = Math.max(bounds.width() / fileBitmap.getWidth(), bounds.height() / fileBitmap.getHeight()); + canvas.scale(s, s); + canvas.translate(-fileBitmap.getWidth() / 2.0f, -fileBitmap.getHeight() / 2.0f); + canvas.drawBitmap(fileBitmap, 0, 0, null); + canvas.restore(); +// final float s = (float) width / fileBitmap.getWidth(); +// tempMatrix.preScale(s, s); +// tempMatrix.postScale(scale, scale); +// canvas.drawBitmap(fileBitmap, tempMatrix, bitmapPaint); +// fileBitmap.recycle(); + } catch (Exception e) { + FileLog.e(e); + } + } + } + } else { + final File file = filterFile != null ? filterFile : this.file; + if (file != null) { + try { + Bitmap fileBitmap = getScaledBitmap(opts -> BitmapFactory.decodeFile(file.getPath(), opts), w, h, true, true); + final float s = (float) width / fileBitmap.getWidth(); + tempMatrix.preScale(s, s); + tempMatrix.postScale(scale, scale); + canvas.drawBitmap(fileBitmap, tempMatrix, bitmapPaint); + fileBitmap.recycle(); + } catch (Exception e) { + FileLog.e(e); + } } } @@ -594,6 +630,11 @@ public class StoryEntry { thumbPathBitmap.recycle(); thumbPathBitmap = null; } + if (collageContent != null) { + for (int i = 0; i < collageContent.size(); ++i) { + collageContent.get(i).destroy(draft); + } + } cancelCheckStickers(); } @@ -837,6 +878,49 @@ public class StoryEntry { entry.gradientTopColor = photoEntry.gradientTopColor; entry.gradientBottomColor = photoEntry.gradientBottomColor; entry.decodeBounds(entry.file.getAbsolutePath()); + if (photoEntry.width > 0 && photoEntry.height > 0) { + entry.width = photoEntry.width; + entry.height = photoEntry.height; + } + entry.setupMatrix(); + return entry; + } + + public boolean isCollage() { + return collage != null && collageContent != null; + } + + public boolean hasVideo() { + if (!isCollage()) return false; + for (int i = 0; i < collageContent.size(); ++i) { + if (collageContent.get(i).isVideo) + return true; + } + return false; + } + + public static StoryEntry asCollage(CollageLayout layout, ArrayList entries) { + StoryEntry entry = new StoryEntry(); + entry.collage = layout; + entry.collageContent = entries; + for (StoryEntry e : entries) { + if (e.isVideo) { + entry.isVideo = true; + e.videoLeft = 0; + e.videoRight = Math.min(1.0f, 59_000.0f / e.duration); + } + } + if (entry.isVideo) { + entry.width = 720; + entry.height = 1280; + entry.resultWidth = 720; + entry.resultHeight = 1280; + } else { + entry.width = 1080; + entry.height = 1920; + entry.resultWidth = 1080; + entry.resultHeight = 1920; + } entry.setupMatrix(); return entry; } @@ -1014,7 +1098,8 @@ public class StoryEntry { resultHeight = 1280; } final String videoPath = file == null ? null : file.getAbsolutePath(); - final int[] params = new int[AnimatedFileDrawable.PARAM_NUM_COUNT]; + final int[][] params = new int[Math.max(1, isCollage() ? collageContent.size() : 0)][AnimatedFileDrawable.PARAM_NUM_COUNT]; + params[0] = new int[AnimatedFileDrawable.PARAM_NUM_COUNT]; Runnable fill = () -> { VideoEditedInfo info = new VideoEditedInfo(); @@ -1029,13 +1114,14 @@ public class StoryEntry { info.messageVideoMaskPath = messageVideoMaskFile == null ? null : messageVideoMaskFile.getPath(); info.backgroundPath = backgroundFile == null ? null : backgroundFile.getPath(); + long generalOffset = 0; final int encoderBitrate = MediaController.extractRealEncoderBitrate(info.resultWidth, info.resultHeight, info.bitrate, true); - if (isVideo && videoPath != null) { + if (isVideo && videoPath != null && !isCollage()) { info.originalPath = videoPath; info.isPhoto = false; - info.framerate = Math.min(59, params[AnimatedFileDrawable.PARAM_NUM_FRAMERATE]); + info.framerate = Math.min(59, params[0][AnimatedFileDrawable.PARAM_NUM_FRAMERATE]); int videoBitrate = MediaController.getVideoBitrate(videoPath); - info.originalBitrate = videoBitrate == -1 ? params[AnimatedFileDrawable.PARAM_NUM_BITRATE] : videoBitrate; + info.originalBitrate = videoBitrate == -1 ? params[0][AnimatedFileDrawable.PARAM_NUM_BITRATE] : videoBitrate; if (info.originalBitrate < 1_000_000 && (mediaEntities != null && !mediaEntities.isEmpty())) { info.bitrate = 2_000_000; info.originalBitrate = -1; @@ -1046,13 +1132,13 @@ public class StoryEntry { info.bitrate = Utilities.clamp(info.originalBitrate, 3_000_000, 500_000); } FileLog.d("story bitrate, original = " + info.originalBitrate + " => " + info.bitrate); - info.originalDuration = (duration = params[AnimatedFileDrawable.PARAM_NUM_DURATION]) * 1000L; + info.originalDuration = (duration = params[0][AnimatedFileDrawable.PARAM_NUM_DURATION]) * 1000L; info.startTime = (long) (left * duration) * 1000L; info.endTime = (long) (right * duration) * 1000L; info.estimatedDuration = info.endTime - info.startTime; info.volume = videoVolume; info.muted = muted; - info.estimatedSize = (long) (params[AnimatedFileDrawable.PARAM_NUM_AUDIO_FRAME_SIZE] + params[AnimatedFileDrawable.PARAM_NUM_DURATION] / 1000.0f * encoderBitrate / 8); + info.estimatedSize = (long) (params[0][AnimatedFileDrawable.PARAM_NUM_AUDIO_FRAME_SIZE] + params[0][AnimatedFileDrawable.PARAM_NUM_DURATION] / 1000.0f * encoderBitrate / 8); info.estimatedSize = Math.max(file.length(), info.estimatedSize); info.filterState = filterState; info.blurPath = paintBlurFile == null ? null : paintBlurFile.getPath(); @@ -1063,7 +1149,42 @@ public class StoryEntry { info.originalPath = videoPath; } info.isPhoto = true; - if (round != null) { + info.collage = collage; + if (isCollage()) { + boolean hasVideo = false; + for (int i = 0; i < collageContent.size(); ++i) { + StoryEntry e = collageContent.get(i); + if (e.isVideo) { + hasVideo = true; + e.width = Math.max(e.width, params[i][AnimatedFileDrawable.PARAM_NUM_WIDTH]); + e.height = Math.max(e.height, params[i][AnimatedFileDrawable.PARAM_NUM_HEIGHT]); + e.duration = Math.max(e.duration, params[i][AnimatedFileDrawable.PARAM_NUM_DURATION]); + } + } + info.collageParts = VideoEditedInfo.Part.toParts(this); + if (!hasVideo) { + info.estimatedDuration = info.originalDuration = duration = averageDuration; + } else { + long maxPartDuration = 0; + VideoEditedInfo.Part maxPart = null; + for (VideoEditedInfo.Part part : info.collageParts) { + if (part.isVideo && part.duration > maxPartDuration) { + maxPartDuration = part.duration; + maxPart = part; + } + } + if (maxPart != null) { + info.estimatedDuration = info.originalDuration = duration = (long) (maxPart.duration * (maxPart.right - maxPart.left)); + generalOffset = -(maxPart.offset + (long) (maxPart.left * maxPart.duration)); + maxPart.offset = generalOffset; + for (VideoEditedInfo.Part part : info.collageParts) { + if (part.isVideo && part != maxPart) { + part.offset += generalOffset; + } + } + } + } + } else if (round != null) { info.estimatedDuration = info.originalDuration = duration = (long) ((roundRight - roundLeft) * roundDuration); } else if (audioPath != null) { info.estimatedDuration = info.originalDuration = duration = (long) ((audioRight - audioLeft) * audioDuration); @@ -1098,6 +1219,18 @@ public class StoryEntry { info.hdrInfo = hdrInfo; info.mixedSoundInfos.clear(); + if (isCollage() && !muted) { + for (VideoEditedInfo.Part part : info.collageParts) { + if (part.isVideo && part.volume > 0.0f && !part.muted) { + final MediaCodecVideoConvertor.MixedSoundInfo soundInfo = new MediaCodecVideoConvertor.MixedSoundInfo(part.path); + soundInfo.volume = part.volume; + soundInfo.audioOffset = (long) (part.left * part.duration) * 1000L; + soundInfo.startTime = (long) (part.offset) * 1000L; + soundInfo.duration = (long) ((part.right - part.left) * part.duration) * 1000L; + info.mixedSoundInfos.add(soundInfo); + } + } + } if (round != null) { final MediaCodecVideoConvertor.MixedSoundInfo soundInfo = new MediaCodecVideoConvertor.MixedSoundInfo(round.getAbsolutePath()); soundInfo.volume = roundVolume; @@ -1107,6 +1240,7 @@ public class StoryEntry { } else { soundInfo.startTime = 0; } + soundInfo.startTime += generalOffset; soundInfo.duration = (long) ((roundRight - roundLeft) * roundDuration) * 1000L; info.mixedSoundInfos.add(soundInfo); } @@ -1119,17 +1253,30 @@ public class StoryEntry { } else { soundInfo.startTime = 0; } + soundInfo.startTime += generalOffset; soundInfo.duration = (long) ((audioRight - audioLeft) * audioDuration) * 1000L; info.mixedSoundInfos.add(soundInfo); } whenDone.run(info); }; - if (file == null) { + if (isCollage()) { + final String[] paths = new String[collageContent.size()]; + for (int i = 0; i < collageContent.size(); ++i) { + paths[i] = collageContent.get(i).file == null ? null : collageContent.get(i).file.getAbsolutePath(); + params[i] = new int[AnimatedFileDrawable.PARAM_NUM_COUNT]; + } + Utilities.globalQueue.postRunnable(() -> { + for (int i = 0; i < paths.length; ++i) + if (paths[i] != null) + AnimatedFileDrawable.getVideoInfo(paths[i], params[i]); + AndroidUtilities.runOnUIThread(fill); + }); + } else if (file == null) { fill.run(); } else { Utilities.globalQueue.postRunnable(() -> { - AnimatedFileDrawable.getVideoInfo(videoPath, params); + AnimatedFileDrawable.getVideoInfo(videoPath, params[0]); AndroidUtilities.runOnUIThread(fill); }); } @@ -1400,6 +1547,10 @@ public class StoryEntry { newEntry.botLang = botLang; newEntry.editingBotPreview = editingBotPreview; newEntry.cover = cover; + newEntry.collageContent = collageContent; + newEntry.collage = collage; + newEntry.videoLoop = videoLoop; + newEntry.videoOffset = videoOffset; return newEntry; } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/StoryPrivacyBottomSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/StoryPrivacyBottomSheet.java index fad73ee6c..84f531528 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/StoryPrivacyBottomSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/StoryPrivacyBottomSheet.java @@ -137,6 +137,7 @@ public class StoryPrivacyBottomSheet extends BottomSheet implements Notification private boolean allowScreenshots = true; private boolean keepOnMyPage = false; + private boolean allowCover = true; private boolean canChangePeer = true; private HashSet mergeUsers(ArrayList users, HashMap> usersByGroup) { @@ -1105,7 +1106,7 @@ public class StoryPrivacyBottomSheet extends BottomSheet implements Notification items.add(ItemInner.asShadow(LocaleController.formatPluralString(containsPrivacy ? "StoryKeepInfo" : (isChannel ? "StoryKeepChannelInfo" : "StoryKeepGroupInfo"), (storyPeriod == Integer.MAX_VALUE ? 86400 : storyPeriod) / 3600))); pad.subtractHeight += dp(80); } - if (keepOnMyPage && whenCoverClicked != null) { + if (keepOnMyPage && allowCover && whenCoverClicked != null) { items.add(ItemInner.asButton(getString(R.string.StoryEditCover), coverDrawable)); pad.subtractHeight += dp(50); items.add(ItemInner.asShadow(getString(R.string.StoryEditCoverInfo))); @@ -2312,6 +2313,19 @@ public class StoryPrivacyBottomSheet extends BottomSheet implements Notification this.onSelectedPeer = onSelectedPeer; return this; } + public StoryPrivacyBottomSheet allowCover(boolean allowCover) { + this.allowCover = allowCover; + if (viewPager != null) { + View[] viewPages = viewPager.getViewPages(); + for (int i = 0; i < viewPages.length; ++i) { + View view = viewPages[i]; + if (view instanceof Page) { + ((Page) view).updateButton(false); + } + } + } + return this; + } public StoryPrivacyBottomSheet enableSharing(boolean enable) { this.sendAsMessageEnabled = enable; if (viewPager != null) { diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/StoryRecorder.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/StoryRecorder.java index f67ef08b2..cee06221c 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/StoryRecorder.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/StoryRecorder.java @@ -36,6 +36,7 @@ import android.graphics.PorterDuffColorFilter; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.RenderNode; import android.graphics.Shader; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; @@ -56,6 +57,7 @@ import android.text.style.ClickableSpan; import android.text.style.ForegroundColorSpan; import android.text.style.ImageSpan; import android.text.style.URLSpan; +import android.util.Log; import android.util.Pair; import android.view.Gravity; import android.view.HapticFeedbackConstants; @@ -107,7 +109,6 @@ import org.telegram.messenger.camera.CameraController; import org.telegram.tgnet.TLObject; import org.telegram.tgnet.TLRPC; import org.telegram.ui.ActionBar.ActionBar; -import org.telegram.ui.ActionBar.ActionBarLayout; import org.telegram.ui.ActionBar.AlertDialog; import org.telegram.ui.ActionBar.BaseFragment; import org.telegram.ui.ActionBar.SimpleTextView; @@ -989,6 +990,9 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg if (captionContainer != null) { Bulletin.removeDelegate(captionContainer); } + if (collageLayoutView != null) { + collageLayoutView.clear(true); + } } private Runnable onCloseListener; @@ -1148,10 +1152,24 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg } private boolean flingDetected; + private boolean touchInCollageList; @Override public boolean dispatchTouchEvent(MotionEvent ev) { flingDetected = false; + if (collageListView != null && collageListView.isVisible()) { + final float y = containerView.getY() + actionBarContainer.getY() + collageListView.getY(); + if (ev.getY() >= y && ev.getY() <= y + collageListView.getHeight() || touchInCollageList) { + touchInCollageList = ev.getAction() != MotionEvent.ACTION_UP && ev.getAction() != MotionEvent.ACTION_CANCEL; + return super.dispatchTouchEvent(ev); + } else { + collageListView.setVisible(false, true); + updateActionBarButtons(true); + } + } + if (touchInCollageList && (ev.getAction() == MotionEvent.ACTION_UP || ev.getAction() == MotionEvent.ACTION_CANCEL)) { + touchInCollageList = false; + } scaleGestureDetector.onTouchEvent(ev); gestureDetector.onTouchEvent(ev); if (ev.getAction() == MotionEvent.ACTION_UP && !flingDetected) { @@ -1173,6 +1191,11 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg return super.dispatchTouchEvent(ev); } + public void cancelGestures() { + scaleGestureDetector.onTouchEvent(AndroidUtilities.emptyMotionEvent()); + gestureDetector.onTouchEvent(AndroidUtilities.emptyMotionEvent()); + } + @Override public boolean dispatchKeyEventPreIme(KeyEvent event) { if (event != null && event.getKeyCode() @@ -1187,7 +1210,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg private final class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { @Override public boolean onScale(ScaleGestureDetector detector) { - if (!scaling || cameraView == null || currentPage != PAGE_CAMERA || cameraView.isDualTouch()) { + if (!scaling || cameraView == null || currentPage != PAGE_CAMERA || cameraView.isDualTouch() || collageLayoutView.getFilledProgress() >= 1) { return false; } final float deltaScaleFactor = (detector.getScaleFactor() - 1.0f) * .75f; @@ -1253,7 +1276,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg @Override public boolean onScroll(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY) { - if (openCloseAnimator != null && openCloseAnimator.isRunning() || galleryOpenCloseSpringAnimator != null || galleryOpenCloseAnimator != null || recordControl.isTouch() || cameraView != null && cameraView.isDualTouch() || scaling || zoomControlView != null && zoomControlView.isTouch()) { + if (openCloseAnimator != null && openCloseAnimator.isRunning() || galleryOpenCloseSpringAnimator != null || galleryOpenCloseAnimator != null || recordControl.isTouch() || cameraView != null && cameraView.isDualTouch() || scaling || zoomControlView != null && zoomControlView.isTouch() || inCheck()) { return false; } if (takingVideo || takingPhoto || currentPage != PAGE_CAMERA) { @@ -1262,6 +1285,9 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg if (!scrollingX) { sty += distanceY; if (!scrollingY && Math.abs(sty) >= touchSlop) { + if (collageLayoutView != null) { + collageLayoutView.cancelTouch(); + } scrollingY = true; } } @@ -1296,6 +1322,9 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg if (!scrollingY) { stx += distanceX; if (!scrollingX && Math.abs(stx) >= touchSlop) { + if (collageLayoutView != null) { + collageLayoutView.cancelTouch(); + } scrollingX = true; } } @@ -1312,7 +1341,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg @Override public boolean onFling(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY) { - if (openCloseAnimator != null && openCloseAnimator.isRunning() || recordControl.isTouch() || cameraView != null && cameraView.isDualTouch() || scaling || zoomControlView != null && zoomControlView.isTouch()) { + if (openCloseAnimator != null && openCloseAnimator.isRunning() || recordControl.isTouch() || cameraView != null && cameraView.isDualTouch() || scaling || zoomControlView != null && zoomControlView.isTouch() || inCheck()) { return false; } flingDetected = true; @@ -1342,6 +1371,9 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg galleryClosing = false; scrollingY = false; scrollingX = false; + if (r && collageLayoutView != null) { + collageLayoutView.cancelTouch(); + } return r; } @@ -1779,6 +1811,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg public static final int EDIT_MODE_NONE = -1; public static final int EDIT_MODE_PAINT = 0; public static final int EDIT_MODE_FILTER = 1; + public static final int EDIT_MODE_TIMELINE = 2; private int currentEditMode = EDIT_MODE_NONE; private FrameLayout previewContainer; @@ -1800,13 +1833,19 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg private String botLang; private TLRPC.InputMedia botEdit; + private CollageLayout lastCollageLayout; + /* PAGE_CAMERA */ + private CollageLayoutView2 collageLayoutView; private ImageView cameraViewThumb; private DualCameraView cameraView; private int flashButtonResId; private ToggleButton2 flashButton; private ToggleButton dualButton; + private CollageLayoutButton collageButton; + private ToggleButton2 collageRemoveButton; + private CollageLayoutButton.CollageLayoutListView collageListView; private VideoTimerView videoTimerView; private boolean wasGalleryOpen; private boolean galleryClosing; @@ -1815,6 +1854,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg private RecordControl recordControl; private PhotoVideoSwitcherView modeSwitcherView; private HintTextView hintTextView; + private HintTextView collageHintTextView; private ZoomControlView zoomControlView; private HintView2 cameraHint; private StoryThemeSheet themeSheet; @@ -1835,6 +1875,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg private HintView2 muteHint; private HintView2 dualHint; private HintView2 savedDualHint; + private HintView2 removeCollageHint; // private StoryPrivacySelector privacySelector; // private boolean privacySelectorHintOpened; // private StoryPrivacySelector.StoryPrivacyHint privacySelectorHint; @@ -1894,18 +1935,13 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg @NonNull @Override public WindowInsets onApplyWindowInsets(@NonNull View v, @NonNull WindowInsets insets) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - Insets r = insets.getInsets(WindowInsetsCompat.Type.displayCutout() | WindowInsetsCompat.Type.systemBars()); - insetTop = r.top; - insetBottom = r.bottom; - insetLeft = r.left; - insetRight = r.right; - } else { - insetTop = insets.getStableInsetTop(); - insetBottom = insets.getStableInsetBottom(); - insetLeft = insets.getStableInsetLeft(); - insetRight = insets.getStableInsetRight(); - } + final WindowInsetsCompat insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets, v); + final androidx.core.graphics.Insets i = insetsCompat.getInsets(WindowInsetsCompat.Type.displayCutout() | WindowInsetsCompat.Type.systemBars()); + insetTop = Math.max(i.top, insets.getStableInsetTop()); + insetBottom = Math.max(i.bottom, insets.getStableInsetBottom()); + insetLeft = Math.max(i.left, insets.getStableInsetLeft()); + insetRight = Math.max(i.right, insets.getStableInsetRight()); + insetTop = Math.max(insetTop, AndroidUtilities.statusBarHeight); windowView.requestLayout(); if (Build.VERSION.SDK_INT >= 30) { return WindowInsets.CONSUMED; @@ -1973,6 +2009,29 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg } super.invalidate(); } + + private RenderNode renderNode; + @Override + protected void dispatchDraw(@NonNull Canvas c) { + boolean endRecording = false; + Canvas canvas = c; + if (Build.VERSION.SDK_INT >= 31 && c.isHardwareAccelerated() && !AndroidUtilities.makingGlobalBlurBitmap) { + if (renderNode == null) { + renderNode = new RenderNode("StoryRecorder.PreviewView"); + } + renderNode.setPosition(0, 0, getWidth(), getHeight()); + canvas = renderNode.beginRecording(); + endRecording = true; + } + super.dispatchDraw(canvas); + if (endRecording && Build.VERSION.SDK_INT >= 31) { + renderNode.endRecording(); + if (blurManager != null) { + blurManager.setRenderNode(this, renderNode, 0xFF1F1F1F); + } + c.drawRenderNode(renderNode); + } + } }); containerView.addView(flashViews.foregroundView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); @@ -2010,6 +2069,25 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg } }); + collageLayoutView = new CollageLayoutView2(context, blurManager, containerView, resourcesProvider) { + @Override + protected void onLayoutUpdate(CollageLayout layout) { + collageListView.setVisible(false, true); + if (layout != null && layout.parts.size() > 1) { + collageButton.setIcon(new CollageLayoutButton.CollageLayoutDrawable(lastCollageLayout = layout), true); + collageButton.setSelected(true, true); + } else { + collageButton.setSelected(false, true); + } + updateActionBarButtons(true); + } + }; + collageLayoutView.setCancelGestures(windowView::cancelGestures); + collageLayoutView.setResetState(() -> { + updateActionBarButtons(true); + }); + previewContainer.addView(collageLayoutView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.FILL)); + cameraViewThumb = new ImageView(context); cameraViewThumb.setScaleType(ImageView.ScaleType.CENTER_CROP); cameraViewThumb.setOnClickListener(v -> { @@ -2018,7 +2096,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg } }); cameraViewThumb.setClickable(true); - previewContainer.addView(cameraViewThumb, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.FILL)); +// previewContainer.addView(cameraViewThumb, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.FILL)); previewContainer.setBackgroundColor(openType == 1 || openType == 0 ? 0 : 0xff1f1f1f); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { @@ -2147,6 +2225,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg } } }; + previewView.setCollageView(collageLayoutView); previewView.invalidateBlur = this::invalidateBlur; previewView.setOnTapListener(() -> { if (currentEditMode != EDIT_MODE_NONE || currentPage != PAGE_PREVIEW || captionEdit.keyboardShown || captionEdit != null && captionEdit.isRecording()) { @@ -2341,10 +2420,30 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg @Override public int getTimelineHeight() { if (videoTimelineContainerView != null && timelineView != null && timelineView.getVisibility() == View.VISIBLE) { - return timelineView.getContentHeight(); + return timelineView.getTimelineHeight(); } return 0; } + + @Override + protected boolean customBlur() { + return blurManager.hasRenderNode(); + } + + private final Path path = new Path(); + @Override + protected void drawBlur(BlurringShader.StoryBlurDrawer blur, Canvas canvas, RectF rect, float r, boolean text, float ox, float oy, boolean thisView, float alpha) { + if (!canvas.isHardwareAccelerated()) { + return; + } + canvas.save(); + path.rewind(); + path.addRoundRect(rect, r, r, Path.Direction.CW); + canvas.clipPath(path); + canvas.translate(ox, oy); + blur.drawRect(canvas, 0, 0, alpha); + canvas.restore(); + } }; captionEdit.setAccount(currentAccount); captionEdit.setUiBlurBitmap(this::getUiBlurBitmap); @@ -2356,7 +2455,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg }); captionEdit.setOnHeightUpdate(height -> { if (videoTimelineContainerView != null) { - videoTimelineContainerView.setTranslationY(-(captionEdit.getEditTextHeight() + AndroidUtilities.dp(12)) + AndroidUtilities.dp(64)); + videoTimelineContainerView.setTranslationY(currentEditMode == EDIT_MODE_TIMELINE ? dp(68) : -(captionEdit.getEditTextHeight() + dp(12)) + dp(64)); } Bulletin visibleBulletin = Bulletin.getVisibleBulletin(); if (visibleBulletin != null && visibleBulletin.tag == 2) { @@ -2398,6 +2497,10 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg containerView.addView(captionEditOverlay); timelineView = new TimelineView(context, containerView, previewContainer, resourcesProvider, blurManager); + timelineView.setOnTimelineClick(() -> { + if (currentPage != PAGE_PREVIEW) return; + switchToEditMode(EDIT_MODE_TIMELINE, true); + }); previewView.setVideoTimelineView(timelineView); timelineView.setVisibility(View.GONE); timelineView.setAlpha(0f); @@ -2409,6 +2512,8 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg videoTimelineContainerView.addView(videoTimeView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 25, Gravity.FILL_HORIZONTAL | Gravity.TOP, 0, 0, 0, 0)); captionContainer.addView(videoTimelineContainerView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, TimelineView.heightDp() + 25, Gravity.FILL_HORIZONTAL | Gravity.BOTTOM, 0, 0, 0, 68)); captionContainer.addView(captionEdit, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.FILL_HORIZONTAL | Gravity.BOTTOM, 0, 200, 0, 0)); + collageLayoutView.setTimelineView(timelineView); + collageLayoutView.setPreviewView(previewView); coverTimelineView = new TimelineView(context, containerView, previewContainer, resourcesProvider, blurManager); coverTimelineView.setCover(); @@ -2473,15 +2578,22 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg return; } outputEntry.muted = !outputEntry.muted; + if (outputEntry.collageContent != null) { + for (StoryEntry entry : outputEntry.collageContent) { + entry.muted = outputEntry.muted; + } + } final boolean hasMusic = !TextUtils.isEmpty(outputEntry.audioPath); final boolean hasRound = outputEntry.round != null; - muteHint.setText( - outputEntry.muted ? - getString(hasMusic || hasRound ? R.string.StoryOriginalSoundMuted : R.string.StorySoundMuted) : - getString(hasMusic || hasRound ? R.string.StoryOriginalSoundNotMuted : R.string.StorySoundNotMuted), - muteHint.shown() - ); - muteHint.show(); + if (currentEditMode == EDIT_MODE_NONE) { + muteHint.setText( + outputEntry.muted ? + getString(hasMusic || hasRound ? R.string.StoryOriginalSoundMuted : R.string.StorySoundMuted) : + getString(hasMusic || hasRound ? R.string.StoryOriginalSoundNotMuted : R.string.StorySoundNotMuted), + muteHint.shown() + ); + muteHint.show(); + } setIconMuted(outputEntry.muted, true); previewView.checkVolumes(); }); @@ -2573,10 +2685,74 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg } savedDualHint.hide(); }); - dualButton.setVisibility(DualCameraView.dualAvailableStatic(context) ? View.VISIBLE : View.GONE); + final boolean dualCameraAvailable = DualCameraView.dualAvailableStatic(context); + dualButton.setVisibility(dualCameraAvailable ? View.VISIBLE : View.GONE); + dualButton.setAlpha(dualCameraAvailable ? 1.0f : 0.0f); flashViews.add(dualButton); actionBarContainer.addView(dualButton, LayoutHelper.createFrame(56, 56, Gravity.TOP | Gravity.RIGHT)); + collageButton = new CollageLayoutButton(context); + collageButton.setBackground(Theme.createSelectorDrawable(0x20ffffff)); + if (lastCollageLayout == null) { + lastCollageLayout = CollageLayout.getLayouts().get(6); + } + collageButton.setOnClickListener(v -> { + if (currentPage != PAGE_CAMERA || animatedRecording) return; + if (cameraView != null && cameraView.isDual()) { + cameraView.toggleDual(); + } + if (!collageListView.isVisible() && !collageLayoutView.hasLayout()) { + collageLayoutView.setLayout(lastCollageLayout, true); + collageListView.setSelected(lastCollageLayout); + collageButton.setIcon(new CollageLayoutButton.CollageLayoutDrawable(lastCollageLayout), true); + collageButton.setSelected(true); + if (cameraView != null) { + cameraView.recordHevc = !collageLayoutView.hasLayout(); + } + } + collageListView.setVisible(!collageListView.isVisible(), true); + updateActionBarButtons(true); + }); + collageButton.setIcon(new CollageLayoutButton.CollageLayoutDrawable(lastCollageLayout), false); + collageButton.setSelected(false); + collageButton.setVisibility(View.VISIBLE); + collageButton.setAlpha(1.0f); + flashViews.add(collageButton); + actionBarContainer.addView(collageButton, LayoutHelper.createFrame(56, 56, Gravity.TOP | Gravity.RIGHT)); + + collageRemoveButton = new ToggleButton2(context); + collageRemoveButton.setBackground(Theme.createSelectorDrawable(0x20ffffff)); + collageRemoveButton.setIcon(new CollageLayoutButton.CollageLayoutDrawable(new CollageLayout("../../.."), true), false); + collageRemoveButton.setVisibility(View.GONE); + collageRemoveButton.setAlpha(0.0f); + collageRemoveButton.setOnClickListener(v -> { + collageLayoutView.setLayout(null, true); + collageLayoutView.clear(true); + collageListView.setSelected(null); + if (cameraView != null) { + cameraView.recordHevc = !collageLayoutView.hasLayout(); + } + collageListView.setVisible(false, true); + updateActionBarButtons(true); + }); + flashViews.add(collageRemoveButton); + actionBarContainer.addView(collageRemoveButton, LayoutHelper.createFrame(56, 56, Gravity.TOP | Gravity.RIGHT)); + + collageListView = new CollageLayoutButton.CollageLayoutListView(context, flashViews); + collageListView.listView.scrollToPosition(6); + collageListView.setSelected(null); + collageListView.setOnLayoutClick(layout -> { + collageLayoutView.setLayout(lastCollageLayout = layout, true); + collageListView.setSelected(layout); + if (cameraView != null) { + cameraView.recordHevc = !collageLayoutView.hasLayout(); + } + collageButton.setDrawable(new CollageLayoutButton.CollageLayoutDrawable(layout)); + setActionBarButtonVisible(collageRemoveButton, collageListView.isVisible(), true); + recordControl.setCollageProgress(collageLayoutView.hasLayout() ? collageLayoutView.getFilledProgress() : 0.0f, true); + }); + actionBarContainer.addView(collageListView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 56, Gravity.TOP | Gravity.RIGHT)); + dualHint = new HintView2(activity, HintView2.DIRECTION_TOP) .setJoint(1, -20) .setDuration(5000) @@ -2592,6 +2768,13 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg .setMultilineText(true); actionBarContainer.addView(savedDualHint, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP, 0, 0, 52, 0)); + removeCollageHint = new HintView2(activity, HintView2.DIRECTION_TOP) + .setJoint(1, -20) + .setDuration(5000) + .setText(LocaleController.getString(R.string.StoryCollageRemoveGrid)); + removeCollageHint.setPadding(dp(8), 0, dp(8), 0); + actionBarContainer.addView(removeCollageHint, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.TOP, 0, 52, 0, 0)); + videoTimerView = new VideoTimerView(context); showVideoTimer(false, false); actionBarContainer.addView(videoTimerView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 45, Gravity.TOP | Gravity.FILL_HORIZONTAL, 56, 0, 56, 0)); @@ -2606,6 +2789,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg recordControl.startAsVideo(isVideo); controlContainer.addView(recordControl, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 100, Gravity.BOTTOM | Gravity.FILL_HORIZONTAL)); flashViews.add(recordControl); + recordControl.setCollageProgress(collageLayoutView.hasLayout() ? collageLayoutView.getFilledProgress() : 0.0f, true); cameraHint = new HintView2(activity, HintView2.DIRECTION_BOTTOM) .setMultilineText(true) @@ -2627,14 +2811,19 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg }); zoomControlView.setZoom(cameraZoom = 0, false); - modeSwitcherView = new PhotoVideoSwitcherView(context); + modeSwitcherView = new PhotoVideoSwitcherView(context) { + @Override + protected boolean allowTouch() { + return !inCheck(); + } + }; modeSwitcherView.setOnSwitchModeListener(newIsVideo -> { if (takingPhoto || takingVideo) { return; } isVideo = newIsVideo; - showVideoTimer(isVideo, true); + showVideoTimer(isVideo && !collageListView.isVisible(), true); modeSwitcherView.switchMode(isVideo); recordControl.startAsVideo(isVideo); }); @@ -2648,6 +2837,12 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg navbarContainer.addView(hintTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 32, Gravity.CENTER, 8, 0, 8, 8)); flashViews.add(hintTextView); + collageHintTextView = new HintTextView(context); + collageHintTextView.setText(LocaleController.getString(R.string.StoryCollageReorderHint), false); + collageHintTextView.setAlpha(0.0f); + navbarContainer.addView(collageHintTextView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 32, Gravity.CENTER, 8, 0, 8, 8)); + flashViews.add(collageHintTextView); + coverButton = new ButtonWithCounterView(context, resourcesProvider); coverButton.setVisibility(View.GONE); coverButton.setAlpha(0f); @@ -2716,6 +2911,8 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg previewHighlight = new PreviewHighlightView(context, currentAccount, resourcesProvider); previewContainer.addView(previewHighlight, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.FILL)); + + updateActionBarButtonsOffsets(); } private void processDone() { @@ -2772,6 +2969,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg outputEntry.privacy = privacy; } }) + .allowCover(!collageLayoutView.hasLayout()) .isEdit(false) .setWarnUsers(getUsersFrom(captionEdit.getText())) .whenSelectedPeer(peer -> { @@ -2994,6 +3192,8 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg file = null; } + View previewView = collageLayoutView.hasLayout() ? collageLayoutView : this.previewView; + final float scale = forDraft ? 1 / 3f : 1f; final int w = (int) (previewView.getWidth() * scale); final int h = (int) (previewView.getHeight() * scale); @@ -3002,12 +3202,14 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg canvas.save(); canvas.scale(scale, scale); + AndroidUtilities.makingGlobalBlurBitmap = true; previewView.draw(canvas); + AndroidUtilities.makingGlobalBlurBitmap = false; canvas.restore(); final Paint bitmapPaint = new Paint(Paint.FILTER_BITMAP_FLAG); - TextureView textureView = previewView.getTextureView(); + TextureView textureView = this.previewView.getTextureView(); if (storyEntry.isVideo && !storyEntry.isRepostMessage && textureView != null) { Bitmap previewTextureView = textureView.getBitmap(); Matrix matrix = textureView.getTransform(null); @@ -3077,21 +3279,15 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg return file; } + private String flashButtonMode; private void setCameraFlashModeIcon(String mode, boolean animated) { flashButton.clearAnimation(); if (cameraView != null && cameraView.isDual() || animatedRecording) { mode = null; } + flashButtonMode = mode; if (mode == null) { - if (animated) { - flashButton.setVisibility(View.VISIBLE); - flashButton.animate().alpha(0).withEndAction(() -> { - flashButton.setVisibility(View.GONE); - }).start(); - } else { - flashButton.setVisibility(View.GONE); - flashButton.setAlpha(0f); - } + setActionBarButtonVisible(flashButton, false, animated); return; } final int resId; @@ -3111,12 +3307,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg break; } flashButton.setIcon(flashButtonResId = resId, animated && flashButtonResId != resId); - flashButton.setVisibility(View.VISIBLE); - if (animated) { - flashButton.animate().alpha(1f).start(); - } else { - flashButton.setAlpha(1f); - } + setActionBarButtonVisible(flashButton, currentPage == PAGE_CAMERA && !collageListView.isVisible() && flashButtonMode != null && !inCheck(), animated); } private final RecordControl.Delegate recordControlDelegate = new RecordControl.Delegate() { @@ -3151,13 +3342,31 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg } } + @Override + public void onCheckClick() { + ArrayList entries = collageLayoutView.getContent(); + if (entries.size() == 1) { + outputEntry = entries.get(0); + } else { + outputEntry = StoryEntry.asCollage(collageLayoutView.getLayout(), collageLayoutView.getContent()); + } + isVideo = outputEntry != null && outputEntry.isVideo; + if (modeSwitcherView != null) { + modeSwitcherView.switchMode(isVideo); + } + StoryPrivacySelector.applySaved(currentAccount, outputEntry); + navigateTo(PAGE_PREVIEW, true); + } + private void takePicture(Utilities.Callback done) { boolean savedFromTextureView = false; if (!useDisplayFlashlight()) { cameraView.startTakePictureAnimation(true); } - if (cameraView.isDual() && TextUtils.equals(cameraView.getCameraSession().getCurrentFlashMode(), Camera.Parameters.FLASH_MODE_OFF)) { - cameraView.pauseAsTakingPicture(); + if (cameraView.isDual() && TextUtils.equals(cameraView.getCameraSession().getCurrentFlashMode(), Camera.Parameters.FLASH_MODE_OFF) || collageLayoutView.hasLayout()) { + if (!collageLayoutView.hasLayout()) { + cameraView.pauseAsTakingPicture(); + } final Bitmap bitmap = cameraView.getTextureView().getBitmap(); try (FileOutputStream out = new FileOutputStream(outputFile.getAbsoluteFile())) { bitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); @@ -3195,11 +3404,67 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg } else if (h > w && rotate != 0) { rotate = 0; } - outputEntry = StoryEntry.fromPhotoShoot(outputFile, rotate); - if (outputEntry != null) { - outputEntry.botId = botId; - outputEntry.botLang = botLang; + StoryEntry entry = StoryEntry.fromPhotoShoot(outputFile, rotate); + if (entry != null) { + entry.botId = botId; + entry.botLang = botLang; } + if (collageLayoutView.hasLayout()) { + outputFile = null; + if (collageLayoutView.push(entry)) { + outputEntry = StoryEntry.asCollage(collageLayoutView.getLayout(), collageLayoutView.getContent()); + StoryPrivacySelector.applySaved(currentAccount, outputEntry); + fromGallery = false; + + if (done != null) { + done.run(null); + } +// if (done != null) { +// done.run(() -> navigateTo(PAGE_PREVIEW, true)); +// } else { +// navigateTo(PAGE_PREVIEW, true); +// } + } else if (done != null) { + done.run(null); + } + updateActionBarButtons(true); + } else { + outputEntry = entry; + StoryPrivacySelector.applySaved(currentAccount, outputEntry); + fromGallery = false; + + if (done != null) { + done.run(() -> navigateTo(PAGE_PREVIEW, true)); + } else { + navigateTo(PAGE_PREVIEW, true); + } + } + }); + } else { + takingPhoto = false; + final StoryEntry entry = StoryEntry.fromPhotoShoot(outputFile, 0); + entry.botId = botId; + entry.botLang = botLang; + if (collageLayoutView.hasLayout()) { + outputFile = null; + if (collageLayoutView.push(entry)) { + outputEntry = StoryEntry.asCollage(collageLayoutView.getLayout(), collageLayoutView.getContent()); + StoryPrivacySelector.applySaved(currentAccount, outputEntry); + fromGallery = false; + if (done != null) { + done.run(null); + } +// if (done != null) { +// done.run(() -> navigateTo(PAGE_PREVIEW, true)); +// } else { +// navigateTo(PAGE_PREVIEW, true); +// } + } else if (done != null) { + done.run(null); + } + updateActionBarButtons(true); + } else { + outputEntry = entry; StoryPrivacySelector.applySaved(currentAccount, outputEntry); fromGallery = false; @@ -3208,21 +3473,6 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg } else { navigateTo(PAGE_PREVIEW, true); } - }); - } else { - takingPhoto = false; - outputEntry = StoryEntry.fromPhotoShoot(outputFile, 0); - if (outputEntry != null) { - outputEntry.botId = botId; - outputEntry.botLang = botLang; - } - StoryPrivacySelector.applySaved(currentAccount, outputEntry); - fromGallery = false; - - if (done != null) { - done.run(() -> navigateTo(PAGE_PREVIEW, true)); - } else { - navigateTo(PAGE_PREVIEW, true); } } } @@ -3302,22 +3552,44 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg showVideoTimer(false, true); - outputEntry = StoryEntry.fromVideoShoot(outputFile, thumbPath, duration); - if (outputEntry != null) { - outputEntry.botId = botId; - outputEntry.botLang = botLang; + StoryEntry entry = StoryEntry.fromVideoShoot(outputFile, thumbPath, duration); + entry.botId = botId; + entry.botLang = botLang; + animateRecording(false, true); + setAwakeLock(false); + videoTimerView.setRecording(false, true); + if (recordControl != null) { + recordControl.stopRecordingLoading(true); } - StoryPrivacySelector.applySaved(currentAccount, outputEntry); - fromGallery = false; - int width = cameraView.getVideoWidth(), height = cameraView.getVideoHeight(); - if (width > 0 && height > 0) { - outputEntry.width = width; - outputEntry.height = height; - outputEntry.setupMatrix(); + if (collageLayoutView.hasLayout()) { + outputFile = null; + entry.videoVolume = 1.0f; + if (collageLayoutView.push(entry)) { + outputEntry = StoryEntry.asCollage(collageLayoutView.getLayout(), collageLayoutView.getContent()); + StoryPrivacySelector.applySaved(currentAccount, outputEntry); + fromGallery = false; + int width = cameraView.getVideoWidth(), height = cameraView.getVideoHeight(); + if (width > 0 && height > 0) { + outputEntry.width = width; + outputEntry.height = height; + outputEntry.setupMatrix(); + } + } + updateActionBarButtons(true); + } else { + outputEntry = entry; + StoryPrivacySelector.applySaved(currentAccount, outputEntry); + fromGallery = false; + int width = cameraView.getVideoWidth(), height = cameraView.getVideoHeight(); + if (width > 0 && height > 0) { + outputEntry.width = width; + outputEntry.height = height; + outputEntry.setupMatrix(); + } + navigateToPreviewWithPlayerAwait(() -> { + navigateTo(PAGE_PREVIEW, true); + }, 0); } - navigateToPreviewWithPlayerAwait(() -> { - navigateTo(PAGE_PREVIEW, true); - }, 0); }, () /* onVideoStart */ -> { whenStarted.run(); @@ -3325,12 +3597,14 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg animateRecording(true, true); setAwakeLock(true); + collageListView.setVisible(false, true); videoTimerView.setRecording(true, true); showVideoTimer(true, true); }, cameraView, true); if (!isVideo) { isVideo = true; + collageListView.setVisible(false, true); showVideoTimer(isVideo, true); modeSwitcherView.switchMode(isVideo); recordControl.startAsVideo(isVideo); @@ -3431,6 +3705,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg private AnimatorSet recordingAnimator; private boolean animatedRecording; + private boolean animatedRecordingWasInCheck; private void animateRecording(boolean recording, boolean animated) { if (recording) { if (dualHint != null) { @@ -3446,7 +3721,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg cameraHint.hide(); } } - if (animatedRecording == recording) { + if (animatedRecording == recording && animatedRecordingWasInCheck == inCheck()) { return; } if (recordingAnimator != null) { @@ -3454,45 +3729,31 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg recordingAnimator = null; } animatedRecording = recording; + animatedRecordingWasInCheck = inCheck(); + if (recording && collageListView != null && collageListView.isVisible()) { + collageListView.setVisible(false, animated); + } + updateActionBarButtons(animated); if (animated) { - backButton.setVisibility(View.VISIBLE); - flashButton.setVisibility(View.VISIBLE); - dualButton.setVisibility(cameraView != null && cameraView.dualAvailable() ? View.VISIBLE : View.GONE); recordingAnimator = new AnimatorSet(); recordingAnimator.playTogether( - ObjectAnimator.ofFloat(backButton, View.ALPHA, recording ? 0 : 1), - ObjectAnimator.ofFloat(flashButton, View.ALPHA, recording || currentPage != PAGE_CAMERA ? 0 : 1), - ObjectAnimator.ofFloat(dualButton, View.ALPHA, recording || currentPage != PAGE_CAMERA || cameraView == null || !cameraView.dualAvailable() ? 0 : 1), - ObjectAnimator.ofFloat(hintTextView, View.ALPHA, recording && currentPage == PAGE_CAMERA ? 1 : 0), - ObjectAnimator.ofFloat(hintTextView, View.TRANSLATION_Y, recording || currentPage != PAGE_CAMERA ? 0 : dp(16)), - ObjectAnimator.ofFloat(modeSwitcherView, View.ALPHA, recording || currentPage != PAGE_CAMERA ? 0 : 1), - ObjectAnimator.ofFloat(modeSwitcherView, View.TRANSLATION_Y, recording || currentPage != PAGE_CAMERA ? dp(16) : 0) + ObjectAnimator.ofFloat(hintTextView, View.ALPHA, recording && currentPage == PAGE_CAMERA && !inCheck() ? 1 : 0), + ObjectAnimator.ofFloat(hintTextView, View.TRANSLATION_Y, recording && currentPage == PAGE_CAMERA && !inCheck() ? 0 : dp(16)), + ObjectAnimator.ofFloat(collageHintTextView, View.ALPHA, !recording && currentPage == PAGE_CAMERA && inCheck() ? 0.6f : 0), + ObjectAnimator.ofFloat(collageHintTextView, View.TRANSLATION_Y, !recording && currentPage == PAGE_CAMERA && inCheck() ? 0 : dp(16)), + ObjectAnimator.ofFloat(modeSwitcherView, View.ALPHA, recording || currentPage != PAGE_CAMERA || inCheck() ? 0 : 1), + ObjectAnimator.ofFloat(modeSwitcherView, View.TRANSLATION_Y, recording || currentPage != PAGE_CAMERA || inCheck() ? dp(16) : 0) ); - recordingAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (recording) { - backButton.setVisibility(View.GONE); - } - if (recording || currentPage != PAGE_CAMERA) { - flashButton.setVisibility(View.GONE); - } - } - }); recordingAnimator.setDuration(260); recordingAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT); recordingAnimator.start(); } else { - backButton.setAlpha(recording ? 0 : 1f); - backButton.setVisibility(recording ? View.GONE : View.VISIBLE); - flashButton.setAlpha(recording || currentPage != PAGE_CAMERA ? 0 : 1f); - flashButton.setVisibility(recording || currentPage != PAGE_CAMERA ? View.GONE : View.VISIBLE); - dualButton.setAlpha(recording || currentPage != PAGE_CAMERA ? 0 : 1f); - dualButton.setVisibility(recording || currentPage != PAGE_CAMERA || cameraView == null || !cameraView.dualAvailable() ? View.GONE : View.VISIBLE); - hintTextView.setAlpha(recording && currentPage == PAGE_CAMERA ? 1f : 0); - hintTextView.setTranslationY(recording || currentPage != PAGE_CAMERA ? 0 : dp(16)); - modeSwitcherView.setAlpha(recording || currentPage != PAGE_CAMERA ? 0 : 1f); - modeSwitcherView.setTranslationY(recording || currentPage != PAGE_CAMERA ? dp(16) : 0); + hintTextView.setAlpha(recording && currentPage == PAGE_CAMERA && !inCheck() ? 1f : 0); + hintTextView.setTranslationY(recording && currentPage == PAGE_CAMERA && !inCheck() ? 0 : dp(16)); + collageHintTextView.setAlpha(!recording && currentPage == PAGE_CAMERA && inCheck() ? 0.6f : 0); + collageHintTextView.setTranslationY(!recording && currentPage == PAGE_CAMERA && inCheck() ? 0 : dp(16)); + modeSwitcherView.setAlpha(recording || currentPage != PAGE_CAMERA || inCheck() ? 0 : 1f); + modeSwitcherView.setTranslationY(recording || currentPage != PAGE_CAMERA || inCheck() ? dp(16) : 0); } } @@ -3621,10 +3882,14 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg } else if (currentEditMode > EDIT_MODE_NONE) { switchToEditMode(EDIT_MODE_NONE, true); return false; + } else if (currentPage == PAGE_CAMERA && collageLayoutView.hasContent()) { + collageLayoutView.clear(true); + updateActionBarButtons(true); + return false; } else if (currentPage == PAGE_PREVIEW && (outputEntry == null || !outputEntry.isRepost && !outputEntry.isRepostMessage) && !isReposting && (outputEntry == null || !outputEntry.isEdit || (paintView != null && paintView.hasChanges()) || outputEntry.editedMedia || outputEntry.editedCaption)) { if (paintView != null && paintView.onBackPressed()) { return false; - } else if (botId == 0 && (fromGallery && (paintView == null || !paintView.hasChanges()) && (outputEntry == null || outputEntry.filterFile == null) || !previewButtons.isShareEnabled()) && (outputEntry == null || !outputEntry.isEdit || !outputEntry.isRepost && !outputEntry.isRepostMessage) && !isReposting) { + } else if (botId == 0 && (fromGallery && !collageLayoutView.hasLayout() && (paintView == null || !paintView.hasChanges()) && (outputEntry == null || outputEntry.filterFile == null) || !previewButtons.isShareEnabled()) && (outputEntry == null || !outputEntry.isEdit || !outputEntry.isRepost && !outputEntry.isRepostMessage) && !isReposting) { navigateTo(PAGE_CAMERA, true); } else { if (botId != 0) { @@ -3720,10 +3985,15 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg if (previewButtons != null) { previewButtons.appear(page == PAGE_PREVIEW, animated); } - showVideoTimer(page == PAGE_CAMERA && isVideo, animated); + showVideoTimer(page == PAGE_CAMERA && isVideo && !collageListView.isVisible() && !inCheck(), animated); if (page != PAGE_PREVIEW) { videoTimeView.show(false, animated); } + setActionBarButtonVisible(backButton, !collageListView.isVisible(), animated); + setActionBarButtonVisible(flashButton, page == PAGE_CAMERA && !collageListView.isVisible() && flashButtonMode != null && !inCheck(), animated); + setActionBarButtonVisible(dualButton, page == PAGE_CAMERA && cameraView != null && cameraView.dualAvailable() && !collageListView.isVisible() && !collageLayoutView.hasLayout(), true); + setActionBarButtonVisible(collageButton, page == PAGE_CAMERA && !collageListView.isVisible(), animated); + updateActionBarButtons(animated); if (animated) { pageAnimator = new AnimatorSet(); @@ -3734,17 +4004,19 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg } cameraViewThumb.setVisibility(View.VISIBLE); animators.add(ObjectAnimator.ofFloat(cameraViewThumb, View.ALPHA, page == PAGE_CAMERA ? 1 : 0)); - animators.add(ObjectAnimator.ofFloat(previewView, View.ALPHA, page == PAGE_PREVIEW || page == PAGE_COVER ? 1 : 0)); + animators.add(ObjectAnimator.ofFloat(previewView, View.ALPHA, page == PAGE_PREVIEW && !collageLayoutView.hasLayout() || page == PAGE_COVER ? 1 : 0)); + animators.add(ObjectAnimator.ofFloat(collageLayoutView, View.ALPHA, page == PAGE_CAMERA || page == PAGE_PREVIEW && collageLayoutView.hasLayout() ? 1 : 0)); animators.add(ObjectAnimator.ofFloat(recordControl, View.ALPHA, page == PAGE_CAMERA ? 1 : 0)); - animators.add(ObjectAnimator.ofFloat(flashButton, View.ALPHA, page == PAGE_CAMERA ? 1 : 0)); - animators.add(ObjectAnimator.ofFloat(dualButton, View.ALPHA, page == PAGE_CAMERA && cameraView != null && cameraView.dualAvailable() ? 1 : 0)); +// animators.add(ObjectAnimator.ofFloat(flashButton, View.ALPHA, page == PAGE_CAMERA ? 1 : 0)); +// animators.add(ObjectAnimator.ofFloat(dualButton, View.ALPHA, page == PAGE_CAMERA && cameraView != null && cameraView.dualAvailable() ? 1 : 0)); animators.add(ObjectAnimator.ofFloat(recordControl, View.TRANSLATION_Y, page == PAGE_CAMERA ? 0 : dp(24))); - animators.add(ObjectAnimator.ofFloat(modeSwitcherView, View.ALPHA, page == PAGE_CAMERA ? 1 : 0)); - animators.add(ObjectAnimator.ofFloat(modeSwitcherView, View.TRANSLATION_Y, page == PAGE_CAMERA ? 0 : dp(24))); - backButton.setVisibility(View.VISIBLE); - animators.add(ObjectAnimator.ofFloat(backButton, View.ALPHA, 1)); - animators.add(ObjectAnimator.ofFloat(hintTextView, View.ALPHA, page == PAGE_CAMERA && animatedRecording ? 1 : 0)); + animators.add(ObjectAnimator.ofFloat(modeSwitcherView, View.ALPHA, page == PAGE_CAMERA && !inCheck() ? 1 : 0)); + animators.add(ObjectAnimator.ofFloat(modeSwitcherView, View.TRANSLATION_Y, page == PAGE_CAMERA && !inCheck() ? 0 : dp(24))); +// backButton.setVisibility(View.VISIBLE); +// animators.add(ObjectAnimator.ofFloat(backButton, View.ALPHA, 1)); + animators.add(ObjectAnimator.ofFloat(hintTextView, View.ALPHA, page == PAGE_CAMERA && animatedRecording && !inCheck() ? 1 : 0)); + animators.add(ObjectAnimator.ofFloat(collageHintTextView, View.ALPHA, page == PAGE_CAMERA && !animatedRecording && inCheck() ? 0.6f : 0)); animators.add(ObjectAnimator.ofFloat(captionContainer, View.ALPHA, page == PAGE_PREVIEW && (outputEntry == null || outputEntry.botId == 0) || page == PAGE_COVER ? 1f : 0)); animators.add(ObjectAnimator.ofFloat(captionContainer, View.TRANSLATION_Y, page == PAGE_PREVIEW && (outputEntry == null || outputEntry.botId == 0) || page == PAGE_COVER ? 0 : dp(12))); animators.add(ObjectAnimator.ofFloat(captionEdit, View.ALPHA, page == PAGE_COVER ? 0f : 1f)); @@ -3780,16 +4052,14 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg } cameraViewThumb.setAlpha(page == PAGE_CAMERA ? 1f : 0); cameraViewThumb.setVisibility(page == PAGE_CAMERA ? View.VISIBLE : View.GONE); - previewView.setAlpha(page == PAGE_PREVIEW || page == PAGE_COVER ? 1f : 0); - flashButton.setAlpha(page == PAGE_CAMERA ? 1f : 0); - dualButton.setAlpha(page == PAGE_CAMERA && cameraView != null && cameraView.dualAvailable() ? 1f : 0); + previewView.setAlpha(page == PAGE_PREVIEW && !collageLayoutView.hasLayout() || page == PAGE_COVER ? 1f : 0); + collageLayoutView.setAlpha(page == PAGE_CAMERA || page == PAGE_PREVIEW && collageLayoutView.hasLayout() ? 1 : 0); recordControl.setAlpha(page == PAGE_CAMERA ? 1f : 0); recordControl.setTranslationY(page == PAGE_CAMERA ? 0 : dp(16)); - modeSwitcherView.setAlpha(page == PAGE_CAMERA ? 1f : 0); - modeSwitcherView.setTranslationY(page == PAGE_CAMERA ? 0 : dp(16)); - backButton.setVisibility(View.VISIBLE); - backButton.setAlpha(1f); - hintTextView.setAlpha(page == PAGE_CAMERA && animatedRecording ? 1f : 0); + modeSwitcherView.setAlpha(page == PAGE_CAMERA && !inCheck() ? 1f : 0); + modeSwitcherView.setTranslationY(page == PAGE_CAMERA && !inCheck() ? 0 : dp(16)); + hintTextView.setAlpha(page == PAGE_CAMERA && animatedRecording && !inCheck() ? 1f : 0); + collageHintTextView.setAlpha(page == PAGE_CAMERA && !animatedRecording && inCheck() ? 0.6f : 0); captionContainer.setAlpha(page == PAGE_PREVIEW || page == PAGE_COVER ? 1f : 0); captionContainer.setTranslationY(page == PAGE_PREVIEW || page == PAGE_COVER ? 0 : dp(12)); captionEdit.setAlpha(page == PAGE_COVER ? 0f : 1f); @@ -3948,40 +4218,61 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg } animateGalleryListView(false); } else { - if (entry instanceof MediaController.PhotoEntry) { - MediaController.PhotoEntry photoEntry = (MediaController.PhotoEntry) entry; - isVideo = photoEntry.isVideo; - outputEntry = StoryEntry.fromPhotoEntry(photoEntry); - - StoryPrivacySelector.applySaved(currentAccount, outputEntry); - outputEntry.blurredVideoThumb = blurredBitmap; - fromGallery = true; - } else if (entry instanceof StoryEntry) { - StoryEntry storyEntry = (StoryEntry) entry; - if (storyEntry.file == null) { - downloadButton.showToast(R.raw.error, "Failed to load draft"); - MessagesController.getInstance(currentAccount).getStoriesController().getDraftsController().delete(storyEntry); - return; - } - - isVideo = storyEntry.isVideo; - outputEntry = storyEntry; - outputEntry.blurredVideoThumb = blurredBitmap; - fromGallery = false; - } - - if (outputEntry != null) { - outputEntry.botId = botId; - outputEntry.botLang = botLang; - outputEntry.setupMatrix(); - } - + StoryEntry storyEntry; showVideoTimer(false, true); modeSwitcherView.switchMode(isVideo); recordControl.startAsVideo(isVideo); animateGalleryListView(false); - navigateTo(PAGE_PREVIEW, true); + if (entry instanceof MediaController.PhotoEntry) { + MediaController.PhotoEntry photoEntry = (MediaController.PhotoEntry) entry; + isVideo = photoEntry.isVideo; + storyEntry = StoryEntry.fromPhotoEntry(photoEntry); + storyEntry.blurredVideoThumb = blurredBitmap; + storyEntry.botId = botId; + storyEntry.botLang = botLang; + storyEntry.setupMatrix(); + fromGallery = true; + + if (collageLayoutView.hasLayout()) { + outputFile = null; + storyEntry.videoVolume = 1.0f; + if (collageLayoutView.push(storyEntry)) { + outputEntry = StoryEntry.asCollage(collageLayoutView.getLayout(), collageLayoutView.getContent()); +// StoryPrivacySelector.applySaved(currentAccount, outputEntry); +// navigateTo(PAGE_PREVIEW, true); + } + updateActionBarButtons(true); + } else { + outputEntry = storyEntry; + if (entry instanceof MediaController.PhotoEntry) { + StoryPrivacySelector.applySaved(currentAccount, outputEntry); + } + navigateTo(PAGE_PREVIEW, true); + } + } else if (entry instanceof StoryEntry) { + storyEntry = (StoryEntry) entry; + if (storyEntry.file == null && !storyEntry.isCollage()) { + downloadButton.showToast(R.raw.error, "Failed to load draft"); + MessagesController.getInstance(currentAccount).getStoriesController().getDraftsController().delete(storyEntry); + return; + } + storyEntry.botId = botId; + storyEntry.botLang = botLang; + storyEntry.setupMatrix(); + isVideo = storyEntry.isVideo; + storyEntry.blurredVideoThumb = blurredBitmap; + fromGallery = false; + + collageLayoutView.set(storyEntry, true); + outputEntry = storyEntry; + if (entry instanceof MediaController.PhotoEntry) { + StoryPrivacySelector.applySaved(currentAccount, outputEntry); + } + navigateTo(PAGE_PREVIEW, true); + } else { + return; + } } if (galleryListView != null) { @@ -4117,6 +4408,10 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg outputEntry.destroy(false); outputEntry = null; } + if (collageLayoutView != null) { + collageLayoutView.clear(true); + recordControl.setCollageProgress(0.0f, false); + } } if (fromPage == PAGE_CAMERA) { setCameraFlashModeIcon(null, true); @@ -4168,6 +4463,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg captionEdit.setPeriodVisible(!MessagesController.getInstance(currentAccount).premiumFeaturesBlocked() && (outputEntry == null || !outputEntry.isEdit)); captionEdit.setHasRoundVideo(outputEntry != null && outputEntry.round != null); setReply(); + timelineView.setOpen(outputEntry == null || !outputEntry.isCollage() || !outputEntry.hasVideo(), false); } if (toPage == PAGE_COVER || fromPage == PAGE_COVER) { titleTextView.setVisibility(View.VISIBLE); @@ -4224,7 +4520,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg } else { captionEdit.clear(); } - previewButtons.setFiltersVisible(outputEntry == null || !outputEntry.isRepostMessage || outputEntry.isVideo); + previewButtons.setFiltersVisible(outputEntry == null || (!outputEntry.isRepostMessage || outputEntry.isVideo) && !outputEntry.isCollage()); previewButtons.setShareEnabled(!videoError && !captionEdit.isCaptionOverLimit() && (!MessagesController.getInstance(currentAccount).getStoriesController().hasStoryLimit() || (outputEntry != null && (outputEntry.isEdit || outputEntry.botId != 0)))); muteButton.setImageResource(outputEntry != null && outputEntry.muted ? R.drawable.media_unmute : R.drawable.media_mute); previewView.setVisibility(View.VISIBLE); @@ -4321,6 +4617,10 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg if (paintView != null) { paintView.setCoverPreview(toPage != PAGE_PREVIEW); } + if (removeCollageHint != null) { + removeCollageHint.hide(); + } + collageLayoutView.setPreview(toPage == PAGE_PREVIEW && collageLayoutView.hasLayout()); } private void onNavigateEnd(int fromPage, int toPage) { @@ -4329,7 +4629,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg recordControl.setVisibility(View.GONE); zoomControlView.setVisibility(View.GONE); modeSwitcherView.setVisibility(View.GONE); - dualButton.setVisibility(View.GONE); +// dualButton.setVisibility(View.GONE); animateRecording(false, false); setAwakeLock(false); } @@ -4412,7 +4712,10 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg private AnimatorSet editModeAnimator; public void switchToEditMode(int editMode, boolean animated) { - if (currentEditMode == editMode) { + switchToEditMode(editMode, false, animated); + } + public void switchToEditMode(int editMode, boolean force, boolean animated) { + if (currentEditMode == editMode && !force) { return; } if (editMode != EDIT_MODE_NONE && (captionEdit != null && captionEdit.isRecording())) { @@ -4427,7 +4730,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg editModeAnimator = null; } - previewButtons.appear(editMode == EDIT_MODE_NONE && openProgress > 0, animated); + previewButtons.appear((editMode == EDIT_MODE_NONE || editMode == EDIT_MODE_TIMELINE) && openProgress > 0, animated); ArrayList animators = new ArrayList<>(); @@ -4469,17 +4772,15 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg animators.add(ObjectAnimator.ofFloat(paintView.getWeightChooserView(), View.TRANSLATION_X, -AndroidUtilities.dp(32))); } - animators.add(ObjectAnimator.ofFloat(muteButton, View.ALPHA, editMode == EDIT_MODE_NONE && isVideo ? 1 : 0)); - animators.add(ObjectAnimator.ofFloat(playButton, View.ALPHA, editMode == EDIT_MODE_NONE && (isVideo || outputEntry != null && !TextUtils.isEmpty(outputEntry.audioPath)) ? 1 : 0)); - animators.add(ObjectAnimator.ofFloat(downloadButton, View.ALPHA, editMode == EDIT_MODE_NONE ? 1 : 0)); + animators.add(ObjectAnimator.ofFloat(muteButton, View.ALPHA, (editMode == EDIT_MODE_NONE || editMode == EDIT_MODE_TIMELINE) && isVideo ? 1 : 0)); + animators.add(ObjectAnimator.ofFloat(playButton, View.ALPHA, (editMode == EDIT_MODE_NONE || editMode == EDIT_MODE_TIMELINE) && (isVideo || outputEntry != null && !TextUtils.isEmpty(outputEntry.audioPath)) ? 1 : 0)); + animators.add(ObjectAnimator.ofFloat(downloadButton, View.ALPHA, (editMode == EDIT_MODE_NONE || editMode == EDIT_MODE_TIMELINE) ? 1 : 0)); if (themeButton != null) { - animators.add(ObjectAnimator.ofFloat(themeButton, View.ALPHA, editMode == EDIT_MODE_NONE && (outputEntry != null && outputEntry.isRepostMessage) ? 1f : 0)); + animators.add(ObjectAnimator.ofFloat(themeButton, View.ALPHA, (editMode == EDIT_MODE_NONE || editMode == EDIT_MODE_TIMELINE) && (outputEntry != null && outputEntry.isRepostMessage) ? 1f : 0)); } -// animators.add(ObjectAnimator.ofFloat(privacySelector, View.ALPHA, editMode == EDIT_MODE_NONE ? 1 : 0)); - -// animators.add(ObjectAnimator.ofFloat(videoTimelineView, View.ALPHA, currentPage == PAGE_PREVIEW && isVideo && editMode == EDIT_MODE_NONE ? 1f : 0f)); animators.add(ObjectAnimator.ofFloat(titleTextView, View.ALPHA, (currentPage == PAGE_PREVIEW || currentPage == PAGE_COVER) && editMode == EDIT_MODE_NONE ? 1f : 0f)); + int rightMargin = 0; int bottomMargin = 0; if (editMode == EDIT_MODE_FILTER) { previewContainer.setPivotY(previewContainer.getMeasuredHeight() * .2f); @@ -4487,6 +4788,10 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg } else if (editMode == EDIT_MODE_PAINT) { previewContainer.setPivotY(previewContainer.getMeasuredHeight() * .6f); bottomMargin = dp(40); + } else if (editMode == EDIT_MODE_TIMELINE) { + previewContainer.setPivotY(0); + bottomMargin = timelineView.getContentHeight() + dp(8); +// rightMargin = dp(46); } float scale = 1f; @@ -4494,6 +4799,10 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg final int bottomPivot = previewContainer.getHeight() - (int) previewContainer.getPivotY(); scale = (float) (bottomPivot - bottomMargin) / bottomPivot; } + if (rightMargin > 0) { + final int rightPivot = previewContainer.getWidth() - (int) previewContainer.getPivotX(); + scale = Math.min(scale, (float) (rightPivot - rightMargin) / rightPivot); + } animators.add(ObjectAnimator.ofFloat(previewContainer, View.SCALE_X, scale)); animators.add(ObjectAnimator.ofFloat(previewContainer, View.SCALE_Y, scale)); @@ -4508,9 +4817,34 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg animators.add(ObjectAnimator.ofFloat(photoFilterViewBlurControl, View.ALPHA, editMode == EDIT_MODE_FILTER ? 1f : 0)); } - animators.add(ObjectAnimator.ofFloat(captionContainer, View.ALPHA, editMode == EDIT_MODE_NONE ? 1f : 0)); + animators.add(ObjectAnimator.ofFloat(captionEdit, View.ALPHA, editMode == EDIT_MODE_NONE ? 1f : 0)); + animators.add(ObjectAnimator.ofFloat(videoTimelineContainerView, View.ALPHA, editMode == EDIT_MODE_NONE || editMode == EDIT_MODE_TIMELINE ? 1f : 0)); + animators.add(ObjectAnimator.ofFloat(videoTimelineContainerView, View.TRANSLATION_Y, editMode == EDIT_MODE_TIMELINE ? dp(68) : -(captionEdit.getEditTextHeight() + AndroidUtilities.dp(12)) + AndroidUtilities.dp(64))); + actionBarButtons.setPivotX(actionBarButtons.getMeasuredWidth() - dp(46 / 2.0f)); + animators.add(ObjectAnimator.ofFloat(actionBarButtons, View.ROTATION, editMode == EDIT_MODE_TIMELINE ? -90 : 0)); + animators.add(ObjectAnimator.ofFloat(playButton, View.ROTATION, editMode == EDIT_MODE_TIMELINE ? 90 : 0)); + animators.add(ObjectAnimator.ofFloat(muteButton, View.ROTATION, editMode == EDIT_MODE_TIMELINE ? 90 : 0)); + animators.add(ObjectAnimator.ofFloat(downloadButton, View.ROTATION, editMode == EDIT_MODE_TIMELINE ? 90 : 0)); + if (themeButton != null) { + animators.add(ObjectAnimator.ofFloat(themeButton, View.ROTATION, editMode == EDIT_MODE_TIMELINE ? 90 : 0)); + } + if (blurManager.hasRenderNode()) { + ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1); + valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(@NonNull ValueAnimator animation) { + captionEdit.invalidateBlur(); + } + }); + animators.add(valueAnimator); + } - onSwitchEditModeStart(oldEditMode, editMode); + if (oldEditMode != editMode) { + onSwitchEditModeStart(oldEditMode, editMode); + } + if (timelineView != null) { + timelineView.setOpen(outputEntry == null || !outputEntry.isCollage() || !outputEntry.hasVideo() || editMode == EDIT_MODE_TIMELINE, animated); + } if (animated) { editModeAnimator = new AnimatorSet(); editModeAnimator.playTogether(animators); @@ -4519,7 +4853,9 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg editModeAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - onSwitchEditModeEnd(oldEditMode, editMode); + if (oldEditMode != editMode) { + onSwitchEditModeEnd(oldEditMode, editMode); + } } }); if (delay) { @@ -4532,7 +4868,9 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg a.setDuration(1); a.start(); } - onSwitchEditModeEnd(oldEditMode, editMode); + if (oldEditMode != editMode) { + onSwitchEditModeEnd(oldEditMode, editMode); + } } } @@ -4547,7 +4885,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg paintView.getBottomLayout().setTranslationY(AndroidUtilities.dp(48)); paintView.getWeightChooserView().setTranslationX(-AndroidUtilities.dp(32)); paintView.setVisibility(View.GONE); - paintView.keyboardNotifier.ignore(true); +// paintView.keyboardNotifier.ignore(true); } private void createPhotoPaintView() { @@ -4624,8 +4962,10 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg if (!isEntityDeletable()) { delete = false; } - captionContainer.clearAnimation(); - captionContainer.animate().alpha(1f).setDuration(180).setInterpolator(CubicBezierInterpolator.EASE_OUT).start(); + captionEdit.clearAnimation(); + captionEdit.animate().alpha(currentEditMode == EDIT_MODE_NONE ? 1f : 0).setDuration(180).setInterpolator(CubicBezierInterpolator.EASE_OUT).start(); + videoTimelineContainerView.clearAnimation(); + videoTimelineContainerView.animate().alpha(currentEditMode == EDIT_MODE_NONE || currentEditMode == EDIT_MODE_TIMELINE ? 1f : 0).setDuration(180).setInterpolator(CubicBezierInterpolator.EASE_OUT).start(); showTrash(false, delete); if (delete) { removeCurrentEntity(); @@ -4637,8 +4977,12 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg @Override public void onEntityDragStart() { paintView.showReactionsLayout(false); - captionContainer.clearAnimation(); - captionContainer.animate().alpha(0f).setDuration(180).setInterpolator(CubicBezierInterpolator.EASE_OUT).start(); + captionEdit.clearAnimation(); + captionEdit.animate().alpha(0f).setDuration(180).setInterpolator(CubicBezierInterpolator.EASE_OUT).start(); + if (currentEditMode != EDIT_MODE_TIMELINE) { + videoTimelineContainerView.clearAnimation(); + videoTimelineContainerView.animate().alpha(0f).setDuration(180).setInterpolator(CubicBezierInterpolator.EASE_OUT).start(); + } showTrash(isEntityDeletable(), false); } @@ -4718,7 +5062,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg } }).start(); } - switchToEditMode(EDIT_MODE_NONE, true); + switchToEditMode(collageLayoutView.hasLayout() && collageLayoutView.hasVideo() && !TextUtils.isEmpty(outputEntry.audioPath) ? EDIT_MODE_TIMELINE : EDIT_MODE_NONE, true, true); } @Override @@ -4956,7 +5300,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg private void onSwitchEditModeStart(int fromMode, int toMode) { if (toMode == EDIT_MODE_NONE) { backButton.setVisibility(View.VISIBLE); - captionContainer.setVisibility(View.VISIBLE); + captionEdit.setVisibility(View.VISIBLE); if (paintView != null) { paintView.clearSelection(); } @@ -5008,15 +5352,15 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg paintView.setVisibility(View.GONE); } if (fromMode == EDIT_MODE_NONE) { - captionContainer.setVisibility(View.GONE); - muteButton.setVisibility(View.GONE); - playButton.setVisibility(View.GONE); - downloadButton.setVisibility(View.GONE); + captionEdit.setVisibility(View.GONE); + muteButton.setVisibility(toMode == EDIT_MODE_TIMELINE ? View.VISIBLE : View.GONE); + playButton.setVisibility(toMode == EDIT_MODE_TIMELINE ? View.VISIBLE : View.GONE); + downloadButton.setVisibility(toMode == EDIT_MODE_TIMELINE ? View.VISIBLE : View.GONE); if (themeButton != null) { - themeButton.setVisibility(View.GONE); + themeButton.setVisibility(toMode == EDIT_MODE_TIMELINE ? View.VISIBLE : View.GONE); } // privacySelector.setVisibility(View.GONE); - timelineView.setVisibility(View.GONE); + timelineView.setVisibility(toMode == EDIT_MODE_TIMELINE ? View.VISIBLE : View.GONE); titleTextView.setVisibility(View.GONE); } previewView.setAllowCropping(toMode == EDIT_MODE_NONE); @@ -5600,7 +5944,7 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg if (recordControl != null) { recordControl.setAmplitude(0, false); } - cameraView.isStory = true; + cameraView.recordHevc = !collageLayoutView.hasLayout(); cameraView.setThumbDrawable(getCameraThumb()); cameraView.initTexture(); cameraView.setDelegate(() -> { @@ -5612,10 +5956,12 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg if (zoomControlView != null) { zoomControlView.setZoom(cameraZoom = 0, false); } + updateActionBarButtons(true); }); - dualButton.setVisibility(cameraView.dualAvailable() && currentPage == PAGE_CAMERA ? View.VISIBLE : View.GONE); - flashButton.setTranslationX(cameraView.dualAvailable() ? -dp(46) : 0); - previewContainer.addView(cameraView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.FILL)); + setActionBarButtonVisible(dualButton, cameraView.dualAvailable() && currentPage == PAGE_CAMERA, true); + collageButton.setTranslationX(cameraView.dualAvailable() ? 0 : dp(46)); +// collageLayoutView.getLast().addView(cameraView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.FILL)); + collageLayoutView.setCameraView(cameraView); if (MessagesController.getGlobalMainSettings().getInt("storyhint2", 0) < 1) { cameraHint.show(); MessagesController.getGlobalMainSettings().edit().putInt("storyhint2", MessagesController.getGlobalMainSettings().getInt("storyhint2", 0) + 1).apply(); @@ -5790,7 +6136,10 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg cameraViewThumb.setImageDrawable(getCameraThumb()); if (cameraView != null) { cameraView.destroy(true, null); - previewContainer.removeView(cameraView); + AndroidUtilities.removeFromParent(cameraView); + if (collageLayoutView != null) { + collageLayoutView.setCameraView(null); + } cameraView = null; } }); @@ -5799,7 +6148,10 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg cameraViewThumb.setImageDrawable(getCameraThumb()); }); cameraView.destroy(true, null); - previewContainer.removeView(cameraView); + AndroidUtilities.removeFromParent(cameraView); + if (collageLayoutView != null) { + collageLayoutView.setCameraView(null); + } cameraView = null; } } @@ -6464,4 +6816,68 @@ public class StoryRecorder implements NotificationCenter.NotificationCenterDeleg updateThemeButtonDrawable(true); }); } + + public void setActionBarButtonVisible(View view, boolean visible, boolean animated) { + if (view == null) return; + if (animated) { + view.setVisibility(View.VISIBLE); + view.animate() + .alpha(visible ? 1.0f : 0.0f) + .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(@NonNull ValueAnimator animation) { + updateActionBarButtonsOffsets(); + } + }) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + updateActionBarButtonsOffsets(); + if (!visible) { + view.setVisibility(View.GONE); + } + } + }) + .setDuration(320) + .setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT) + .start(); + } else { + view.animate().cancel(); + view.setVisibility(visible ? View.VISIBLE : View.GONE); + view.setAlpha(visible ? 1.0f : 0.0f); + updateActionBarButtonsOffsets(); + } + } + + private boolean inCheck() { + final float collageProgress = collageLayoutView.hasLayout() ? collageLayoutView.getFilledProgress() : 0.0f; + return !animatedRecording && collageProgress >= 1.0f; + } + + private void updateActionBarButtons(boolean animated) { + showVideoTimer(currentPage == PAGE_CAMERA && isVideo && !collageListView.isVisible() && !inCheck(), animated); + collageButton.setSelected(collageLayoutView.hasLayout()); + setActionBarButtonVisible(backButton, collageListView == null || !collageListView.isVisible(), animated); + setActionBarButtonVisible(flashButton, !animatedRecording && currentPage == PAGE_CAMERA && flashButtonMode != null && !collageListView.isVisible() && !inCheck(), animated); + setActionBarButtonVisible(dualButton, !animatedRecording && currentPage == PAGE_CAMERA && cameraView != null && cameraView.dualAvailable() && !collageListView.isVisible() && !collageLayoutView.hasLayout(), animated); + setActionBarButtonVisible(collageButton, currentPage == PAGE_CAMERA && !collageListView.isVisible(), animated); + setActionBarButtonVisible(collageRemoveButton, collageListView.isVisible(), animated); + final float collageProgress = collageLayoutView.hasLayout() ? collageLayoutView.getFilledProgress() : 0.0f; + recordControl.setCollageProgress(collageProgress, animated); + removeCollageHint.show(collageListView.isVisible()); + animateRecording(animatedRecording, animated); + } + + private void updateActionBarButtonsOffsets() { + float right = 0; + collageRemoveButton.setTranslationX(-right); right += dp(46) * collageRemoveButton.getAlpha(); + dualButton.setTranslationX(-right); right += dp(46) * dualButton.getAlpha(); + collageButton.setTranslationX(-right); right += dp(46) * collageButton.getAlpha(); + flashButton.setTranslationX(-right); right += dp(46) * flashButton.getAlpha(); + + float left = 0; + backButton.setTranslationX(left); left += dp(46) * backButton.getAlpha(); + + collageListView.setBounds(left + dp(8), right + dp(8)); + } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/TimelineView.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/TimelineView.java index ba255c7c3..d7a183694 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/TimelineView.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/TimelineView.java @@ -47,14 +47,23 @@ import org.telegram.ui.Components.BlurringShader; import org.telegram.ui.Components.CubicBezierInterpolator; import org.telegram.ui.Components.ItemOptions; import org.telegram.ui.Components.Scroller; +import org.telegram.ui.Components.Text; +import org.telegram.ui.Stories.StoriesViewPager; import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.Collections; public class TimelineView extends View { // milliseconds when timeline goes out of the box - public static final long MAX_SCROLL_DURATION = 120 * 1000L; + public long getMaxScrollDuration() { + if (collageTracks.isEmpty()) { + return 120 * 1000L; + } else { + return 70 * 1000L; + } + } // minimal allowed duration to select public static final long MIN_SELECT_DURATION = 1 * 1000L; // maximum allowed duration to select @@ -68,6 +77,12 @@ public class TimelineView extends View { default void onVideoRightChange(float right) {}; default void onVideoVolumeChange(float volume) {}; + default void onVideoLeftChange(int i, float left) {}; + default void onVideoRightChange(int i, float right) {}; + default void onVideoVolumeChange(int i, float volume) {}; + default void onVideoOffsetChange(int i, long offset) {}; + default void onVideoSelected(int i) {}; + default void onAudioOffsetChange(long offset) {}; default void onAudioLeftChange(float left) {}; default void onAudioRightChange(float right) {}; @@ -84,18 +99,69 @@ public class TimelineView extends View { } private TimelineDelegate delegate; + private Runnable onTimelineClick; + public void setOnTimelineClick(Runnable click) { + this.onTimelineClick = click; + } private long progress; private long scroll; - private boolean hasVideo; - private boolean isMainVideoRound; - private String videoPath; - private long videoDuration; - private float videoLeft; - private float videoRight; - private float videoVolume; - private VideoThumbsLoader thumbs; + private class Track { + int index; + boolean isRound; + VideoThumbsLoader thumbs; + String path; + long duration; + long offset; + float left, right; + float volume; + + final RectF bounds = new RectF(); + + private final AnimatedFloat selectedT = new AnimatedFloat(TimelineView.this, 360, CubicBezierInterpolator.EASE_OUT_QUINT); + + private void setupThumbs(boolean force) { + if (getMeasuredWidth() <= 0 || thumbs != null && !force) { + return; + } + if (thumbs != null) { + thumbs.destroy(); + thumbs = null; + } + thumbs = new VideoThumbsLoader(isRound, path, w - px - px, dp(38), duration > 2 ? duration : null, getMaxScrollDuration(), coverStart, coverEnd, () -> { + if (thumbs != null && thumbs.getDuration() > 0) { + duration = thumbs.getDuration(); + sortCollage(); + } + }); + } + + private void setupWaveform(boolean force) { + if (index < 0 || index >= collageWaveforms.size()) + return; + AudioWaveformLoader waveform = collageWaveforms.get(index); + if (getMeasuredWidth() <= 0 || waveform != null && !force) { + return; + } + if (waveform != null) { + waveform.destroy(); + waveform = null; + } + waveform = new AudioWaveformLoader(path, getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); + collageWaveforms.set(index, waveform); + } + } + + private Track videoTrack; + + private int collageSelected = 0; + private final ArrayList collageWaveforms = new ArrayList<>(); + private final ArrayList collageTracks = new ArrayList<>(); + private Track collageMain; + private final Paint collageFramePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + private final Path collageClipPath = new Path(); + private final Path selectedCollageClipPath = new Path(); private boolean hasRound; private String roundPath; @@ -120,14 +186,14 @@ public class TimelineView extends View { private AudioWaveformLoader waveform; private long getBaseDuration() { - if (hasVideo) { - return videoDuration; + if (videoTrack != null) { + return Math.max(1, videoTrack.duration); + } + if (collageMain != null) { + return Math.max(1, collageMain.duration); } if (hasRound) { - return roundDuration; - } - if (hasAudio) { - return audioDuration; + return Math.max(1, roundDuration); } return Math.max(1, audioDuration); } @@ -138,16 +204,32 @@ public class TimelineView extends View { private final AnimatedFloat audioT = new AnimatedFloat(this, 0, 360, CubicBezierInterpolator.EASE_OUT_QUINT); private final AnimatedFloat audioSelectedT = new AnimatedFloat(this, 360, CubicBezierInterpolator.EASE_OUT_QUINT); - private final AnimatedFloat videoSelectedT = new AnimatedFloat(this, 360, CubicBezierInterpolator.EASE_OUT_QUINT); - - private final AnimatedFloat waveformLoaded = new AnimatedFloat(this, 0, 600, CubicBezierInterpolator.EASE_OUT_QUINT); private final AnimatedFloat waveformMax = new AnimatedFloat(this, 0, 360, CubicBezierInterpolator.EASE_OUT_QUINT); + private final AnimatedFloat timelineWaveformLoaded = new AnimatedFloat(this, 0, 600, CubicBezierInterpolator.EASE_OUT_QUINT); + private final AnimatedFloat timelineWaveformMax = new AnimatedFloat(this, 0, 360, CubicBezierInterpolator.EASE_OUT_QUINT); + private final AnimatedFloat openT = new AnimatedFloat(this, 0, 320, CubicBezierInterpolator.EASE_OUT_QUINT); + public boolean open = true; + public void setOpen(boolean open, boolean animated) { + if (this.open == open && animated) return; + this.open = open; + if (!animated) { + openT.set(open, true); + } + invalidate(); + } + private final BlurringShader.BlurManager blurManager; private final BlurringShader.StoryBlurDrawer backgroundBlur; private final BlurringShader.StoryBlurDrawer audioBlur; private final BlurringShader.StoryBlurDrawer audioWaveformBlur; + private final RectF timelineBounds = new RectF(); + private final Path timelineClipPath = new Path(); + private final Text timelineText; + private final Drawable timelineIcon; + private final WaveformPath timelineWaveformPath = new WaveformPath(); + private final RectF videoBounds = new RectF(); private final Paint videoFramePaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); private final Path videoClipPath = new Path(); @@ -218,6 +300,10 @@ public class TimelineView extends View { progressWhitePaint.setColor(0xffffffff); progressShadowPaint.setColor(0x26000000); + timelineText = new Text(LocaleController.getString(R.string.StoryTimeline), 12, AndroidUtilities.bold()); + timelineIcon = getContext().getResources().getDrawable(R.drawable.timeline).mutate(); + timelineIcon.setColorFilter(new PorterDuffColorFilter(0xffffffff, PorterDuff.Mode.SRC_IN)); + audioIcon = getContext().getResources().getDrawable(R.drawable.filled_widget_music).mutate(); audioIcon.setColorFilter(new PorterDuffColorFilter(0xffffffff, PorterDuff.Mode.SRC_IN)); @@ -238,9 +324,8 @@ public class TimelineView extends View { delegate.onAudioVolumeChange(volume); } }); - final long videoScrollDuration = Math.min(getBaseDuration(), MAX_SCROLL_DURATION); + final long videoScrollDuration = Math.min(getBaseDuration(), getMaxScrollDuration()); final float uiRight = Math.min(w - px - ph, px + ph + (audioOffset - scroll + lerp(audioRight, 1, audioSelectedT.get()) * audioDuration) / (float) videoScrollDuration * sw); - final float y = h - py - (hasVideo ? getVideoHeight() + dp(4) : 0) - (hasRound ? getRoundHeight() + dp(4) : 0) - (hasAudio ? getAudioHeight() + dp(4) : 0); ItemOptions itemOptions = ItemOptions.makeOptions(container, resourcesProvider, this) .addView(slider) .addSpaceGap() @@ -251,7 +336,7 @@ public class TimelineView extends View { }) .setGravity(Gravity.RIGHT) .forceTop(true) - .translate(-(w - uiRight) + dp(18), y) + .translate(-(w - uiRight) + dp(18), audioBounds.top) .show(); itemOptions.setBlurBackground(blurManager, -previewContainer.getX(), -previewContainer.getY()); @@ -269,9 +354,8 @@ public class TimelineView extends View { delegate.onRoundVolumeChange(volume); } }); - final long videoScrollDuration = Math.min(getBaseDuration(), MAX_SCROLL_DURATION); + final long videoScrollDuration = Math.min(getBaseDuration(), getMaxScrollDuration()); final float uiRight = Math.min(w - px - ph, px + ph + (roundOffset - scroll + lerp(roundRight, 1, roundSelectedT.get()) * roundDuration) / (float) videoScrollDuration * sw); - final float y = h - py - (hasVideo ? getVideoHeight() + dp(4) : 0) - (hasRound ? getRoundHeight() + dp(4) : 0); ItemOptions itemOptions = ItemOptions.makeOptions(container, resourcesProvider, this) .addView(slider) .addSpaceGap() @@ -282,35 +366,56 @@ public class TimelineView extends View { }) .setGravity(Gravity.RIGHT) .forceTop(true) - .translate(-(w - uiRight) + dp(18), y) + .translate(-(w - uiRight) + dp(18), roundBounds.top) .show(); itemOptions.setBlurBackground(blurManager, -previewContainer.getX(), -previewContainer.getY()); try { performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); } catch (Exception e) {} - } else if (pressType == 0 && hasVideo) { + } else if (pressType == 0 && videoTrack != null) { SliderView slider = new SliderView(getContext(), SliderView.TYPE_VOLUME) .setMinMax(0, 1.5f) - .setValue(videoVolume) + .setValue(videoTrack.volume) .setOnValueChange(volume -> { - videoVolume = volume; + videoTrack.volume = volume; if (delegate != null) { delegate.onVideoVolumeChange(volume); } }); - final float y = h - py - (hasVideo ? getVideoHeight() + dp(4) : 0); ItemOptions itemOptions = ItemOptions.makeOptions(container, resourcesProvider, this) .addView(slider) .setGravity(Gravity.RIGHT) .forceTop(true) - .translate(dp(18), y) + .translate(dp(18), videoBounds.top) .show(); itemOptions.setBlurBackground(blurManager, -previewContainer.getX(), -previewContainer.getY()); try { performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); } catch (Exception e) {} + } else if (pressType == 3 && pressCollageIndex >= 0 && pressCollageIndex < collageTracks.size()) { + final Track track = collageTracks.get(pressCollageIndex); + SliderView slider = + new SliderView(getContext(), SliderView.TYPE_VOLUME) + .setMinMax(0, 1.5f) + .setValue(track.volume) + .setOnValueChange(volume -> { + track.volume = volume; + if (delegate != null) { + delegate.onVideoVolumeChange(track.index, volume); + } + }); + ItemOptions itemOptions = ItemOptions.makeOptions(container, resourcesProvider, this) + .addView(slider) + .setGravity(Gravity.RIGHT) + .forceTop(true) + .translate(dp(18), track.bounds.top) + .show(); + itemOptions.setBlurBackground(blurManager, -previewContainer.getX(), -previewContainer.getY()); + try { + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS, HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING); + } catch (Exception e) {} } }; } @@ -323,37 +428,92 @@ public class TimelineView extends View { public void setCoverVideo(long videoStart, long videoEnd) { coverStart = videoStart; coverEnd = videoEnd; - setupVideoThumbs(true); + if (videoTrack != null) { + videoTrack.setupThumbs(true); + } } public void setVideo(boolean isRound, String videoPath, long videoDuration, float videoVolume) { - if (TextUtils.equals(this.videoPath, videoPath)) { + if (TextUtils.equals(videoTrack == null ? null : videoTrack.path, videoPath)) { return; } - if (thumbs != null) { - thumbs.destroy(); - thumbs = null; + if (videoTrack != null) { + if (videoTrack.thumbs != null) { + videoTrack.thumbs.destroy(); + videoTrack.thumbs = null; + } + videoTrack = null; } - isMainVideoRound = isRound; if (videoPath != null) { scroll = 0; - this.videoPath = videoPath; - this.videoDuration = videoDuration; - this.videoVolume = videoVolume; - setupVideoThumbs(false); + videoTrack = new Track(); + videoTrack.isRound = isRound; + videoTrack.path = videoPath; + videoTrack.duration = videoDuration; + videoTrack.volume = videoVolume; + videoTrack.setupThumbs(false); } else { - this.videoPath = null; - this.videoDuration = 1; + videoTrack = null; scroll = 0; } if (!hasRound) { roundSelected = false; } - hasVideo = this.videoPath != null; progress = 0; invalidate(); } + public void setCollage(ArrayList entries) { + for (int i = 0; i < collageTracks.size(); ++i) { + Track track = collageTracks.get(i); + if (track != null && track.thumbs != null) { + track.thumbs.destroy(); + } + } + collageTracks.clear(); + for (int i = 0; i < collageWaveforms.size(); ++i) { + AudioWaveformLoader waveform = collageWaveforms.get(i); + if (waveform != null) { + waveform.destroy(); + } + } + collageWaveforms.clear(); + timelineWaveformMax.set(1, true); + if (entries != null) { + for (int i = 0; i < entries.size(); ++i) { + collageWaveforms.add(null); + StoryEntry entry = entries.get(i); + if (entry.isVideo) { + final Track track = new Track(); + track.index = i; + track.isRound = false; + track.path = entry.file.getAbsolutePath(); + track.duration = entry.duration; + track.offset = entry.videoOffset; + track.volume = entry.videoVolume; + track.left = entry.videoLeft; + track.right = entry.videoRight; + track.setupThumbs(false); + track.setupWaveform(false); + collageTracks.add(track); + } + } + } + sortCollage(); + collageSelected = 0; + } + + public void sortCollage() { + Collections.sort(collageTracks, (a, b) -> (int) (b.duration - a.duration)); + collageMain = collageTracks.isEmpty() ? null : collageTracks.get(0); + if (collageMain != null && collageMain.offset != 0) { +// collageMain.offset = 0; +// if (delegate != null) { +// delegate.onVideoOffsetChange(collageMain.index, 0); +// } + } + } + public void setRoundNull(boolean animated) { setRound(null, 0, 0, 0, 0, 0, animated); } @@ -375,7 +535,7 @@ public class TimelineView extends View { this.roundRight = right; this.roundVolume = volume; setupRoundThumbs(); - if (!hasVideo) { + if (videoTrack == null) { audioSelected = false; roundSelected = true; } @@ -385,11 +545,11 @@ public class TimelineView extends View { roundSelected = false; } hasRound = this.roundPath != null; - if (hadRoundDuration != roundDuration && !hasVideo && waveform != null) { + if (hadRoundDuration != roundDuration && videoTrack == null && waveform != null) { resetWaveform = true; setupAudioWaveform(); } - if (hasAudio && hasRound && !hasVideo) { + if (hasAudio && hasRound && videoTrack == null) { audioLeft = 0; audioRight = Utilities.clamp((float) roundDuration / audioDuration, 1, 0); } @@ -407,43 +567,29 @@ public class TimelineView extends View { audioSelected = false; } else { roundSelected = false; - audioSelected = hasAudio && !hasVideo; + audioSelected = hasAudio && videoTrack == null; } invalidate(); } - private void setupVideoThumbs(boolean force) { - if (getMeasuredWidth() <= 0 || this.thumbs != null && !force) { - return; - } - if (thumbs != null) { - thumbs.destroy(); - thumbs = null; - } - this.thumbs = new VideoThumbsLoader(isMainVideoRound, videoPath, w - px - px, dp(38), videoDuration > 2 ? videoDuration : null, MAX_SCROLL_DURATION, coverStart, coverEnd); - if (this.thumbs.getDuration() > 0) { - videoDuration = this.thumbs.getDuration(); - } - setupRoundThumbs(); - } - private void setupRoundThumbs() { - if (getMeasuredWidth() <= 0 || this.roundThumbs != null || hasVideo && videoDuration < 1) { + if (getMeasuredWidth() <= 0 || this.roundThumbs != null || videoTrack != null && videoTrack.duration < 1) { return; } - this.roundThumbs = new VideoThumbsLoader(false, roundPath, w - px - px, dp(38), roundDuration > 2 ? roundDuration : null, hasVideo ? videoDuration : MAX_SCROLL_DURATION, -1, -1); - if (this.roundThumbs.getDuration() > 0) { - roundDuration = this.roundThumbs.getDuration(); - } + this.roundThumbs = new VideoThumbsLoader(false, roundPath, w - px - px, dp(38), roundDuration > 2 ? roundDuration : null, videoTrack != null ? videoTrack.duration : getMaxScrollDuration(), -1, -1, () -> { + if (this.roundThumbs != null && this.roundThumbs.getDuration() > 0) { + roundDuration = this.roundThumbs.getDuration(); + } + }); } private final AnimatedFloat loopProgress = new AnimatedFloat(0, this, 0, 340, CubicBezierInterpolator.EASE_OUT_QUINT); private long loopProgressFrom = -1; public void setProgress(long progress) { if ( - hasVideo && progress < this.progress && progress <= videoDuration * videoLeft + 240 && this.progress + 240 >= videoDuration * videoRight || - hasAudio && !hasRound && !hasVideo && progress < this.progress && progress <= audioDuration * audioLeft + 240 && this.progress + 240 >= audioDuration * audioRight || - hasRound && !hasVideo && progress < this.progress && progress <= roundDuration * audioLeft + 240 && this.progress + 240 >= roundDuration * audioRight + videoTrack != null && progress < this.progress && progress <= videoTrack.duration * videoTrack.left + 240 && this.progress + 240 >= videoTrack.duration * videoTrack.right || + hasAudio && !hasRound && videoTrack == null && progress < this.progress && progress <= audioDuration * audioLeft + 240 && this.progress + 240 >= audioDuration * audioRight || + hasRound && videoTrack == null && progress < this.progress && progress <= roundDuration * audioLeft + 240 && this.progress + 240 >= roundDuration * audioRight ) { loopProgressFrom = -1; loopProgress.set(1, true); @@ -453,12 +599,14 @@ public class TimelineView extends View { } public void setVideoLeft(float left) { - videoLeft = left; + if (videoTrack == null) return; + videoTrack.left = left; invalidate(); } public void setVideoRight(float right) { - videoRight = right; + if (videoTrack == null) return; + videoTrack.right = right; invalidate(); } @@ -468,7 +616,6 @@ public class TimelineView extends View { waveform.destroy(); waveform = null; waveformIsLoaded = false; - waveformLoaded.set(0, true); } this.audioPath = audioPath; setupAudioWaveform(); @@ -521,7 +668,6 @@ public class TimelineView extends View { } this.waveform = new AudioWaveformLoader(audioPath, getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); waveformIsLoaded = false; - waveformLoaded.set(0, true); waveformMax.set(1, true); } @@ -542,45 +688,76 @@ public class TimelineView extends View { private static final int HANDLE_ROUND_RIGHT = 11; private static final int HANDLE_ROUND_REGION = 12; + private static final int HANDLE_COLLAGE_LEFT = 13; + private static final int HANDLE_COLLAGE_RIGHT = 14; + private static final int HANDLE_COLLAGE_REGION = 15; + private static final int HANDLE_COLLAGE_SCROLL = 16; + private int detectHandle(MotionEvent event) { float x = event.getX(); float y = event.getY(); - final long scrollWidth = Math.min(getBaseDuration(), MAX_SCROLL_DURATION); - final float progressT = (Utilities.clamp(progress, getBaseDuration(), 0) + (!hasVideo ? audioOffset : 0) - scroll) / (float) scrollWidth; + final long scrollWidth = Math.min(getBaseDuration(), getMaxScrollDuration()); + final float progressT = (Utilities.clamp(progress, getBaseDuration(), 0) + (collageMain != null ? collageMain.offset + collageMain.left * collageMain.duration : (videoTrack == null ? audioOffset : 0)) - scroll) / (float) scrollWidth; final float progressX = px + ph + sw * progressT; if (!isCover && x >= progressX - dp(12) && x <= progressX + dp(12)) { return HANDLE_PROGRESS; } - final boolean isInVideo = hasVideo && y > h - py - getVideoHeight() - dp(2); - final boolean isInRound = hasRound && y > h - py - getVideoHeight() - dp(4) - getRoundHeight() - dp(4) - dp(2) && y < h - py - getVideoHeight() - dp(2); + final boolean isInVideo = videoTrack != null && y > h - py - getVideoHeight() - dp(2); + final boolean isInCollage = !collageTracks.isEmpty() && y > h - py - getVideoHeight() - dp(4) - getCollageHeight() - dp(4) - dp(2) && y < h - py - getVideoHeight() - dp(2); + final boolean isInRound = hasRound && y > h - py - getVideoHeight() - dp(4) - getCollageHeight() - dp(collageTracks.isEmpty() ? 0 : 4) - getRoundHeight() - dp(4) - dp(2) && y < h - py - getVideoHeight() - dp(2) - getCollageHeight() - dp(collageTracks.isEmpty() ? 0 : 4); - if (isInVideo) { + if (isInCollage) { + for (int i = 0; i < collageTracks.size(); ++i) { + Track track = collageTracks.get(i); + AndroidUtilities.rectTmp.set(track.bounds); + AndroidUtilities.rectTmp.inset(-dp(2), -dp(2)); + if (AndroidUtilities.rectTmp.contains(x, y)) { + float startX = px + ph + (track.offset) / (float) scrollWidth * sw; + float leftX = px + ph + (track.offset + track.left * track.duration) / (float) scrollWidth * sw; + float rightX = px + ph + (track.offset + track.right * track.duration) / (float) scrollWidth * sw; + float endX = px + ph + (track.offset + track.duration) / (float) scrollWidth * sw; + + pressHandleCollageIndex = i; + if (x >= leftX - dp(10 + 5) && x <= leftX + dp(5)) { + return HANDLE_COLLAGE_LEFT; + } else if (x >= rightX - dp(5) && x <= rightX + dp(10 + 5)) { + return HANDLE_COLLAGE_RIGHT; + } else if (x >= leftX && x <= rightX && (track.left > 0.01f || track.right < .99f)) { + return HANDLE_COLLAGE_REGION; + } else if (x >= startX && x <= endX) { + return HANDLE_COLLAGE_SCROLL; + } else { + return -1; + } + } + } + } else if (isInVideo) { if (isCover) { return HANDLE_VIDEO_REGION; } - final float leftX = px + ph + (videoLeft * videoDuration - scroll) / (float) scrollWidth * sw; - final float rightX = px + ph + (videoRight * videoDuration - scroll) / (float) scrollWidth * sw; + final float leftX = px + ph + (videoTrack.left * videoTrack.duration - scroll) / (float) scrollWidth * sw; + final float rightX = px + ph + (videoTrack.right * videoTrack.duration - scroll) / (float) scrollWidth * sw; if (x >= leftX - dp(10 + 5) && x <= leftX + dp(5)) { return HANDLE_VIDEO_LEFT; } else if (x >= rightX - dp(5) && x <= rightX + dp(10 + 5)) { return HANDLE_VIDEO_RIGHT; - } else if (x >= leftX && x <= rightX && (videoLeft > 0.01f || videoRight < .99f)) { + } else if (x >= leftX && x <= rightX && (videoTrack.left > 0.01f || videoTrack.right < .99f)) { return HANDLE_VIDEO_REGION; } } else if (isInRound) { float leftX = px + ph + (roundOffset + roundLeft * roundDuration - scroll) / (float) scrollWidth * sw; float rightX = px + ph + (roundOffset + roundRight * roundDuration - scroll) / (float) scrollWidth * sw; - if (roundSelected || !hasVideo) { + if (roundSelected || videoTrack == null) { if (x >= leftX - dp(10 + 5) && x <= leftX + dp(5)) { return HANDLE_ROUND_LEFT; } else if (x >= rightX - dp(5) && x <= rightX + dp(10 + 5)) { return HANDLE_ROUND_RIGHT; } else if (x >= leftX && x <= rightX) { - if (!hasVideo) { + if (videoTrack == null) { return HANDLE_ROUND_REGION; } else { return HANDLE_ROUND_SCROLL; @@ -595,13 +772,13 @@ public class TimelineView extends View { } else if (hasAudio) { float leftX = px + ph + (audioOffset + audioLeft * audioDuration - scroll) / (float) scrollWidth * sw; float rightX = px + ph + (audioOffset + audioRight * audioDuration - scroll) / (float) scrollWidth * sw; - if (audioSelected || !hasVideo && !hasRound) { + if (audioSelected || videoTrack == null && !hasRound) { if (x >= leftX - dp(10 + 5) && x <= leftX + dp(5)) { return HANDLE_AUDIO_LEFT; } else if (x >= rightX - dp(5) && x <= rightX + dp(10 + 5)) { return HANDLE_AUDIO_RIGHT; } else if (x >= leftX && x <= rightX) { - if (!hasVideo) { + if (videoTrack == null) { return HANDLE_AUDIO_REGION; } else { return HANDLE_AUDIO_SCROLL; @@ -615,7 +792,7 @@ public class TimelineView extends View { } } - if (videoDuration > MAX_SCROLL_DURATION && isInVideo) { + if (videoTrack != null && videoTrack.duration > getMaxScrollDuration() && isInVideo) { return HANDLE_VIDEO_SCROLL; } @@ -625,7 +802,7 @@ public class TimelineView extends View { public boolean onBackPressed() { if (audioSelected) { audioSelected = false; - if (hasRound && !hasVideo) { + if (hasRound && videoTrack == null) { roundSelected = true; if (delegate != null) { delegate.onRoundSelectChange(true); @@ -642,17 +819,24 @@ public class TimelineView extends View { private Runnable askExactSeek; private boolean setProgressAt(float x, boolean fast) { - if (!hasVideo && !hasAudio) { + if (videoTrack == null && !hasAudio && collageTracks.isEmpty()) { return false; } - final long scrollWidth = Math.min(getBaseDuration(), MAX_SCROLL_DURATION); + final long scrollWidth = Math.min(getBaseDuration(), getMaxScrollDuration()); final float t = (x - px - ph) / sw; - long progress = (long) Utilities.clamp(t * scrollWidth + (!hasVideo ? -audioOffset : 0) + scroll, hasVideo ? videoDuration : audioDuration, 0); - if (hasVideo && (progress / (float) videoDuration < videoLeft || progress / (float) videoDuration > videoRight)) { + long offset = 0; + if (collageMain != null) { + offset = (long) (collageMain.offset + collageMain.left * collageMain.duration); + } + long progress = (long) Utilities.clamp(t * scrollWidth - (collageMain != null ? offset : (videoTrack == null ? audioOffset : 0)) + scroll, getBaseDuration(), 0); + if (videoTrack != null && (progress / (float) videoTrack.duration < videoTrack.left || progress / (float) videoTrack.duration > videoTrack.right)) { return false; } - if (hasAudio && !hasVideo && (progress / (float) audioDuration < audioLeft || progress / (float) audioDuration > audioRight)) { + if (collageMain != null && (progress < 0 || progress >= (long) ((collageMain.right - collageMain.left) * collageMain.duration))) { + return false; + } + if (hasAudio && videoTrack == null && collageTracks.isEmpty() && (progress / (float) audioDuration < audioLeft || progress / (float) audioDuration > audioRight)) { return false; } this.progress = progress; @@ -675,12 +859,26 @@ public class TimelineView extends View { } private float getVideoHeight() { - if (!hasVideo) + if (videoTrack == null) return 0; - float videoSelected = this.videoSelectedT.set(!this.audioSelected && !this.roundSelected); + float videoSelected = videoTrack.selectedT.get(); return lerp(dp(28), dp(38), videoSelected); } + private float getCollageHeight() { + if (collageTracks.isEmpty()) + return 0; + float h = 0; + for (int i = 0; i < collageTracks.size(); ++i) { + if (h > 0) { + h += dp(4); + } + float selected = collageTracks.get(i).selectedT.get(); + h += lerp(dp(28), dp(38), selected); + } + return h; + } + private float getAudioHeight() { float audioSelected = this.audioSelectedT.set(this.audioSelected); return lerp(dp(28), dp(38), audioSelected); @@ -697,20 +895,23 @@ public class TimelineView extends View { private long pressTime; private float lastX; private int pressHandle = -1; + private int pressHandleCollageIndex = -1; private int pressType = -1; + private int pressCollageIndex = -1; private boolean draggingProgress, dragged; private boolean hadDragChange; private VelocityTracker velocityTracker; private boolean scrollingVideo = true; + private int scrollingCollage = -1; private boolean scrolling = false; @Override public boolean onTouchEvent(MotionEvent event) { - if (!hasVideo && !hasAudio && !hasRound) { + if (videoTrack == null && collageTracks.isEmpty() && !hasAudio && !hasRound) { return false; } - final float top = h - py - py - (hasVideo ? getVideoHeight() + dp(4) : 0) - (hasAudio ? getAudioHeight() + dp(4) : 0) - (hasRound ? getRoundHeight() + dp(4) : 0); + final float top = h - getTimelineHeight(); if (event.getAction() == MotionEvent.ACTION_DOWN && event.getY() < top) { return false; } @@ -722,15 +923,33 @@ public class TimelineView extends View { askExactSeek = null; } scroller.abortAnimation(); + pressHandleCollageIndex = -1; pressHandle = detectHandle(event); pressType = -1; + pressCollageIndex = -1; int y = h - py; - if (pressType == -1 && hasVideo) { + if (pressType == -1 && !open && timelineBounds.contains(event.getX(), event.getY())) { + pressType = 10; + pressHandle = -1; + } + if (pressType == -1 && videoTrack != null) { if (event.getY() < y && event.getY() > y - getVideoHeight() - dp(2)) { pressType = 0; } y -= getVideoHeight() + dp(4); } + if (pressType == -1 && !collageTracks.isEmpty()) { + for (int i = 0; i < collageTracks.size(); ++i) { + final Track track = collageTracks.get(i); + final float selected = track.selectedT.get(); + final float h = lerp(dp(28), dp(38), selected); + if (event.getY() < y && event.getY() > y - h - dp(2)) { + pressType = 3; + pressCollageIndex = i; break; + } + y -= h + dp(4); + } + } if (pressType == -1 && hasRound) { if (event.getY() < y && event.getY() > y - getRoundHeight() - dp(2)) { pressType = 1; @@ -760,53 +979,53 @@ public class TimelineView extends View { } } else if (event.getAction() == MotionEvent.ACTION_MOVE) { final float Δx = event.getX() - lastX; - final boolean allowDrag = dragged || Math.abs(Δx) > AndroidUtilities.touchSlop; + final boolean allowDrag = open && (dragged || Math.abs(Δx) > AndroidUtilities.touchSlop); if (allowDrag) { - final long videoScrollDuration = Math.min(getBaseDuration(), MAX_SCROLL_DURATION); - if (pressHandle == HANDLE_VIDEO_SCROLL) { - scroll = (long) Utilities.clamp(scroll - Δx / sw * videoScrollDuration, videoDuration - videoScrollDuration, 0); + final long videoScrollDuration = Math.min(getBaseDuration(), getMaxScrollDuration()); + if (videoTrack != null && pressHandle == HANDLE_VIDEO_SCROLL) { + scroll = (long) Utilities.clamp(scroll - Δx / sw * videoScrollDuration, videoTrack.duration - videoScrollDuration, 0); invalidate(); dragged = true; draggingProgress = false; - } else if (pressHandle == HANDLE_VIDEO_LEFT || pressHandle == HANDLE_VIDEO_RIGHT || pressHandle == HANDLE_VIDEO_REGION) { - float d = Δx / sw * (videoScrollDuration / (float) videoDuration); + } else if (videoTrack != null && (pressHandle == HANDLE_VIDEO_LEFT || pressHandle == HANDLE_VIDEO_RIGHT || pressHandle == HANDLE_VIDEO_REGION)) { + float d = Δx / sw * (videoScrollDuration / (float) videoTrack.duration); if (pressHandle == HANDLE_VIDEO_LEFT) { - videoLeft = Utilities.clamp(videoLeft + d, videoRight - MIN_SELECT_DURATION / (float) videoDuration, 0); + videoTrack.left = Utilities.clamp(videoTrack.left + d, videoTrack.right - MIN_SELECT_DURATION / (float) videoTrack.duration, 0); if (delegate != null) { - delegate.onVideoLeftChange(videoLeft); + delegate.onVideoLeftChange(videoTrack.left); } - if (videoRight - videoLeft > MAX_SELECT_DURATION / (float) videoDuration) { - videoRight = Math.min(1, videoLeft + MAX_SELECT_DURATION / (float) videoDuration); + if (videoTrack.right - videoTrack.left > MAX_SELECT_DURATION / (float) videoTrack.duration) { + videoTrack.right = Math.min(1, videoTrack.left + MAX_SELECT_DURATION / (float) videoTrack.duration); if (delegate != null) { - delegate.onVideoRightChange(videoRight); + delegate.onVideoRightChange(videoTrack.right); } } } else if (pressHandle == HANDLE_VIDEO_RIGHT) { - videoRight = Utilities.clamp(videoRight + d, 1, videoLeft + MIN_SELECT_DURATION / (float) videoDuration); + videoTrack.right = Utilities.clamp(videoTrack.right + d, 1, videoTrack.left + MIN_SELECT_DURATION / (float) videoTrack.duration); if (delegate != null) { - delegate.onVideoRightChange(videoRight); + delegate.onVideoRightChange(videoTrack.right); } - if (videoRight - videoLeft > MAX_SELECT_DURATION / (float) videoDuration) { - videoLeft = Math.max(0, videoRight - MAX_SELECT_DURATION / (float) videoDuration); + if (videoTrack.right - videoTrack.left > MAX_SELECT_DURATION / (float) videoTrack.duration) { + videoTrack.left = Math.max(0, videoTrack.right - MAX_SELECT_DURATION / (float) videoTrack.duration); if (delegate != null) { - delegate.onVideoLeftChange(videoLeft); + delegate.onVideoLeftChange(videoTrack.left); } } } else if (pressHandle == HANDLE_VIDEO_REGION) { if (d > 0) { - d = Math.min(1 - videoRight, d); + d = Math.min(1 - videoTrack.right, d); } else { - d = Math.max(-videoLeft, d); + d = Math.max(-videoTrack.left, d); } - videoLeft += d; - videoRight += d; + videoTrack.left += d; + videoTrack.right += d; if (delegate != null) { - delegate.onVideoLeftChange(videoLeft); - delegate.onVideoRightChange(videoRight); + delegate.onVideoLeftChange(videoTrack.left); + delegate.onVideoRightChange(videoTrack.right); } } - if (progress / (float) videoDuration < videoLeft || progress / (float) videoDuration > videoRight) { - progress = (long) (videoLeft * videoDuration); + if (progress / (float) videoTrack.duration < videoTrack.left || progress / (float) videoTrack.duration > videoTrack.right) { + progress = (long) (videoTrack.left * videoTrack.duration); if (delegate != null) { delegate.onProgressChange(progress, false); } @@ -819,15 +1038,17 @@ public class TimelineView extends View { if (pressHandle == HANDLE_AUDIO_LEFT) { float maxValue = audioRight - minAudioSelect() / (float) audioDuration; float minValue = Math.max(0, scroll - audioOffset) / (float) audioDuration; - if (!hasVideo && !hasRound) { + if (videoTrack != null) { + minValue = Math.max(minValue, (videoTrack.left * videoTrack.duration + scroll - audioOffset) / (float) audioDuration); + } else if (collageMain != null) { + minValue = Math.max(minValue, (collageMain.left * collageMain.duration + scroll - audioOffset) / (float) audioDuration); + } else if (hasRound) { + minValue = Math.max(minValue, (roundLeft * roundDuration + scroll - audioOffset) / (float) audioDuration); + } else { minValue = Math.max(minValue, audioRight - MAX_SELECT_DURATION / (float) audioDuration); if (!hadDragChange && d < 0 && audioLeft <= (audioRight - MAX_SELECT_DURATION / (float) audioDuration)) { pressHandle = HANDLE_AUDIO_REGION; } - } else if (hasVideo) { - minValue = Math.max(minValue, (videoLeft * videoDuration + scroll - audioOffset) / (float) audioDuration); - } else if (hasRound) { - minValue = Math.max(minValue, (roundLeft * roundDuration + scroll - audioOffset) / (float) audioDuration); } float wasAudioLeft = audioLeft; audioLeft = Utilities.clamp(audioLeft + d, maxValue, minValue); @@ -843,15 +1064,17 @@ public class TimelineView extends View { } else if (pressHandle == HANDLE_AUDIO_RIGHT) { float maxValue = Math.min(1, Math.max(0, scroll - audioOffset + videoScrollDuration) / (float) audioDuration); float minValue = audioLeft + minAudioSelect() / (float) audioDuration; - if (!hasVideo && !hasRound) { + if (videoTrack != null) { + maxValue = Math.min(maxValue, (videoTrack.right * videoTrack.duration + scroll - audioOffset) / (float) audioDuration); + } else if (collageMain != null) { + maxValue = Math.min(maxValue, (collageMain.right * collageMain.duration + scroll - audioOffset) / (float) audioDuration); + } else if (hasRound) { + maxValue = Math.min(maxValue, (roundRight * roundDuration + scroll - audioOffset) / (float) audioDuration); + } else { maxValue = Math.min(maxValue, audioLeft + MAX_SELECT_DURATION / (float) audioDuration); if (!hadDragChange && d > 0 && audioRight >= (audioLeft + MAX_SELECT_DURATION / (float) audioDuration)) { pressHandle = HANDLE_AUDIO_REGION; } - } else if (hasVideo) { - maxValue = Math.min(maxValue, (videoRight * videoDuration + scroll - audioOffset) / (float) audioDuration); - } else if (hasRound) { - maxValue = Math.min(maxValue, (roundRight * roundDuration + scroll - audioOffset) / (float) audioDuration); } float wasAudioRight = audioRight; audioRight = Utilities.clamp(audioRight + d, maxValue, minValue); @@ -865,6 +1088,10 @@ public class TimelineView extends View { if (pressHandle == HANDLE_AUDIO_REGION) { float minLeft = Math.max(0, scroll - audioOffset) / (float) audioDuration; float maxRight = Math.min(1, Math.max(0, scroll - audioOffset + videoScrollDuration) / (float) audioDuration); +// if (collageMain != null) { +// minLeft = Math.max(minLeft, (collageMain.left * collageMain.duration + scroll - audioOffset) / (float) audioDuration); +// maxRight = Math.min(maxRight, (collageMain.right * collageMain.duration + scroll - audioOffset) / (float) audioDuration); +// } if (d > 0) { d = Math.min(Math.max(0, maxRight - audioRight), d); } else { @@ -882,7 +1109,7 @@ public class TimelineView extends View { delegate.onProgressDragChange(true); } } - if (!hasVideo && !hasRound) { + if (videoTrack == null && !hasRound) { progress = (long) (audioLeft * audioDuration); if (delegate != null) { delegate.onProgressDragChange(true); @@ -897,13 +1124,15 @@ public class TimelineView extends View { if (pressHandle == HANDLE_ROUND_LEFT) { float maxValue = roundRight - minAudioSelect() / (float) roundDuration; float minValue = Math.max(0, scroll - roundOffset) / (float) roundDuration; - if (!hasVideo) { + if (videoTrack != null) { + minValue = Math.max(minValue, (videoTrack.left * videoTrack.duration + scroll - roundOffset) / (float) roundDuration); + } else if (collageMain != null) { + minValue = Math.max(minValue, (collageMain.left * collageMain.duration + scroll - roundOffset) / (float) roundDuration); + } else { minValue = Math.max(minValue, roundRight - MAX_SELECT_DURATION / (float) roundDuration); if (!hadDragChange && d < 0 && roundLeft <= (roundRight - MAX_SELECT_DURATION / (float) roundDuration)) { pressHandle = HANDLE_AUDIO_REGION; } - } else { - minValue = Math.max(minValue, (videoLeft * videoDuration + scroll - roundOffset) / (float) roundDuration); } float wasAudioLeft = roundLeft; roundLeft = Utilities.clamp(roundLeft + d, maxValue, minValue); @@ -919,13 +1148,15 @@ public class TimelineView extends View { } else if (pressHandle == HANDLE_ROUND_RIGHT) { float maxValue = Math.min(1, Math.max(0, scroll - roundOffset + videoScrollDuration) / (float) roundDuration); float minValue = roundLeft + minAudioSelect() / (float) roundDuration; - if (!hasVideo) { + if (videoTrack != null) { + maxValue = Math.min(maxValue, (videoTrack.right * videoTrack.duration + scroll - roundOffset) / (float) roundDuration); + } if (collageMain != null) { + maxValue = Math.min(maxValue, (collageMain.right * collageMain.duration + scroll - roundOffset) / (float) roundDuration); + } else { maxValue = Math.min(maxValue, roundLeft + MAX_SELECT_DURATION / (float) roundDuration); if (!hadDragChange && d > 0 && roundRight >= (roundLeft + MAX_SELECT_DURATION / (float) roundDuration)) { pressHandle = HANDLE_AUDIO_REGION; } - } else { - maxValue = Math.min(maxValue, (videoRight * videoDuration + scroll - roundOffset) / (float) roundDuration); } float wasAudioRight = roundRight; roundRight = Utilities.clamp(roundRight + d, maxValue, minValue); @@ -939,6 +1170,10 @@ public class TimelineView extends View { if (pressHandle == HANDLE_ROUND_REGION) { float minLeft = Math.max(0, scroll - roundOffset) / (float) roundDuration; float maxRight = Math.min(1, Math.max(0, scroll - roundOffset + videoScrollDuration) / (float) roundDuration); +// if (collageMain != null) { +// minLeft = Math.max(minLeft, (collageMain.left * collageMain.duration + scroll - roundOffset) / (float) roundDuration); +// maxRight = Math.min(maxRight, (collageMain.right * collageMain.duration + scroll - roundOffset) / (float) roundDuration); +// } if (d > 0) { d = Math.min(maxRight - roundRight, d); } else { @@ -956,7 +1191,7 @@ public class TimelineView extends View { delegate.onProgressDragChange(true); } } - if (!hasVideo) { + if (videoTrack == null) { progress = (long) (roundLeft * roundDuration); if (delegate != null) { delegate.onProgressDragChange(true); @@ -966,6 +1201,87 @@ public class TimelineView extends View { invalidate(); dragged = true; draggingProgress = false; + } else if ((pressHandleCollageIndex >= 0 && pressHandleCollageIndex < collageTracks.size()) && (pressHandle == HANDLE_COLLAGE_LEFT || pressHandle == HANDLE_COLLAGE_RIGHT || pressHandle == HANDLE_COLLAGE_REGION)) { + final Track track = collageTracks.get(pressHandleCollageIndex); + float d = Δx / sw * (videoScrollDuration / (float) track.duration); + if (pressHandle == HANDLE_COLLAGE_LEFT) { + float maxValue = track.right - minAudioSelect() / (float) track.duration; + float minValue = Math.max(0, scroll - track.offset) / (float) track.duration; + if (track == collageMain) { + minValue = Math.max(minValue, track.right - MAX_SELECT_DURATION / (float) track.duration); + if (!hadDragChange && d < 0 && track.left <= (track.right - MAX_SELECT_DURATION / (float) track.duration)) { + pressHandle = HANDLE_COLLAGE_REGION; + } + } +// else if (collageMain != null) { +// minValue = Math.max(minValue, (collageMain.left * collageMain.duration + scroll - track.offset) / (float) track.duration); +// } + float wasTrackLeft = track.left; + track.left = Utilities.clamp(track.left + d, maxValue, minValue); + if (Math.abs(wasTrackLeft - track.left) > 0.01f) { + hadDragChange = true; + } + if (delegate != null) { + delegate.onVideoOffsetChange(track.index, track.offset); + } + if (delegate != null) { + delegate.onVideoLeftChange(track.index, track.left); + } + } else if (pressHandle == HANDLE_COLLAGE_RIGHT) { + float maxValue = Math.min(1, Math.max(0, scroll - track.offset + videoScrollDuration) / (float) track.duration); + float minValue = track.left + minAudioSelect() / (float) track.duration; + if (track == collageMain) { + maxValue = Math.min(maxValue, track.left + MAX_SELECT_DURATION / (float) track.duration); + if (!hadDragChange && d > 0 && track.right >= (track.left + MAX_SELECT_DURATION / (float) track.duration)) { + pressHandle = HANDLE_COLLAGE_REGION; + } + } +// else if (collageMain != null) { +// maxValue = Math.min(maxValue, (collageMain.right * collageMain.duration + scroll - track.offset) / (float) track.duration); +// } + float wasTrackRight = track.right; + track.right = Utilities.clamp(track.right + d, maxValue, minValue); + if (Math.abs(wasTrackRight - track.right) > 0.01f) { + hadDragChange = true; + } + if (delegate != null) { + delegate.onVideoRightChange(track.index, track.right); + } + } + if (pressHandle == HANDLE_COLLAGE_REGION) { + float minLeft = Math.max(0, scroll - track.offset) / (float) track.duration; + float maxRight = Math.min(1, Math.max(0, scroll - track.offset + videoScrollDuration) / (float) track.duration); +// if (track != collageMain) { +// minLeft = Math.max(minLeft, (collageMain.left * collageMain.duration + scroll - track.offset) / (float) track.duration); +// maxRight = Math.min(maxRight, (collageMain.right * collageMain.duration + scroll - track.offset) / (float) track.duration); +// } + if (d > 0) { + d = Math.min(maxRight - track.right, d); + } else { + d = Math.max(minLeft - track.left, d); + } + track.left += d; + track.right += d; + + if (delegate != null) { + delegate.onVideoLeftChange(track.index, track.left); + delegate.onVideoOffsetChange(track.index, track.offset); + delegate.onVideoRightChange(track.index, track.right); + } + if (delegate != null) { + delegate.onProgressDragChange(true); + } + } +// if (collageMain == track) { +// progress = (long) (track.left * track.duration); +// if (delegate != null) { +// delegate.onProgressDragChange(true); +// delegate.onProgressChange(progress, false); +// } +// } + invalidate(); + dragged = true; + draggingProgress = false; } else if (pressHandle == HANDLE_AUDIO_SCROLL) { float d = Δx / sw * videoScrollDuration; moveAudioOffset(d); @@ -976,6 +1292,12 @@ public class TimelineView extends View { moveRoundOffset(d); dragged = true; draggingProgress = false; + } else if ((pressHandleCollageIndex >= 0 && pressHandleCollageIndex < collageTracks.size()) && pressHandle == HANDLE_COLLAGE_SCROLL) { + final Track track = collageTracks.get(pressHandleCollageIndex); + float d = Δx / sw * videoScrollDuration; + moveCollageOffset(track, d); + dragged = true; + draggingProgress = false; } else if (draggingProgress) { setProgressAt(event.getX(), now - lastTime < 350); if (!dragged && delegate != null) { @@ -996,14 +1318,29 @@ public class TimelineView extends View { scroller.abortAnimation(); boolean scrollStopped = true; if (event.getAction() == MotionEvent.ACTION_UP) { - if (System.currentTimeMillis() - pressTime <= ViewConfiguration.getTapTimeout() && !dragged) { - if (isCover) { - float d = videoRight - videoLeft; - videoLeft = (event.getX() - px - ph) / sw * (1 - d); - videoRight = videoLeft + d; + if (System.currentTimeMillis() - pressTime <= ViewConfiguration.getTapTimeout() && !dragged || !open) { + if (!open) { + if (pressType == 10) { + if (onTimelineClick != null) { + onTimelineClick.run(); + } + } + } else if (isCover && videoTrack != null) { + float d = videoTrack.right - videoTrack.left; + videoTrack.left = (event.getX() - px - ph) / sw * (1 - d); + videoTrack.right = videoTrack.left + d; if (delegate != null) { - delegate.onVideoLeftChange(videoLeft); - delegate.onVideoRightChange(videoRight); + delegate.onVideoLeftChange(videoTrack.left); + delegate.onVideoRightChange(videoTrack.right); + } + invalidate(); + } else if (pressType == 3 && (audioSelected || roundSelected ? -1 : collageSelected) != pressCollageIndex) { + audioSelected = false; + roundSelected = false; + collageSelected = pressCollageIndex; + if (delegate != null && pressCollageIndex >= 0 && pressCollageIndex < collageTracks.size()) { + Track track = collageTracks.get(pressCollageIndex); + delegate.onVideoSelected(track.index); } invalidate(); } else if (pressType == 2 && !audioSelected) { @@ -1042,54 +1379,66 @@ public class TimelineView extends View { invalidate(); } } + } else if (pressHandle == HANDLE_COLLAGE_SCROLL && velocityTracker != null) { + velocityTracker.computeCurrentVelocity(1000); + final int velocity = (int) velocityTracker.getXVelocity(); + scrollingVideo = true; + if (videoTrack != null && Math.abs(velocity) > dp(100)) { + final long videoScrollDuration = Math.min(videoTrack.duration, getMaxScrollDuration()); + final int scrollX = (int) (px + scroll / (float) videoScrollDuration * sw); + final int maxScrollX = (int) (px + (videoTrack.duration - videoScrollDuration) / (float) videoScrollDuration * sw); + scrolling = true; + scroller.fling(wasScrollX = scrollX, 0, -velocity, 0, px, maxScrollX, 0, 0); + scrollStopped = false; + } } else if (pressHandle == HANDLE_VIDEO_SCROLL && velocityTracker != null) { velocityTracker.computeCurrentVelocity(1000); final int velocity = (int) velocityTracker.getXVelocity(); scrollingVideo = true; - if (Math.abs(velocity) > dp(100)) { - final long videoScrollDuration = Math.min(videoDuration, MAX_SCROLL_DURATION); + if (videoTrack != null && Math.abs(velocity) > dp(100)) { + final long videoScrollDuration = Math.min(videoTrack.duration, getMaxScrollDuration()); final int scrollX = (int) (px + scroll / (float) videoScrollDuration * sw); - final int maxScrollX = (int) (px + (videoDuration - videoScrollDuration) / (float) videoScrollDuration * sw); + final int maxScrollX = (int) (px + (videoTrack.duration - videoScrollDuration) / (float) videoScrollDuration * sw); scrolling = true; scroller.fling(wasScrollX = scrollX, 0, -velocity, 0, px, maxScrollX, 0, 0); scrollStopped = false; } } else if ((pressHandle == HANDLE_AUDIO_SCROLL || pressHandle == HANDLE_AUDIO_REGION && !dragged) && audioSelected && velocityTracker != null) { - velocityTracker.computeCurrentVelocity(hasVideo ? 1000 : 1500); + velocityTracker.computeCurrentVelocity(videoTrack != null ? 1000 : 1500); final int velocity = (int) velocityTracker.getXVelocity(); scrollingVideo = false; if (Math.abs(velocity) > dp(100)) { - final long videoScrollDuration = Math.min(getBaseDuration(), MAX_SCROLL_DURATION); + final long videoScrollDuration = Math.min(getBaseDuration(), getMaxScrollDuration()); final int scrollX = (int) (px + ph + audioOffset / (float) videoScrollDuration * sw); final long mx, mn; - if (hasVideo) { - mx = (long) ((videoRight * videoDuration) - (0 * audioDuration)); - mn = (long) ((videoLeft * videoDuration) - (1 * audioDuration)); + if (videoTrack != null) { + mx = (long) ((videoTrack.right * videoTrack.duration) - (0 * audioDuration)); + mn = (long) ((videoTrack.left * videoTrack.duration) - (1 * audioDuration)); } else if (hasRound) { mx = (long) ((roundRight * roundDuration) - (0 * audioDuration)); mn = (long) ((roundLeft * roundDuration) - (1 * audioDuration)); } else { mx = 0; - mn = (long) -(audioDuration - Math.min(getBaseDuration(), MAX_SCROLL_DURATION)); + mn = (long) -(audioDuration - Math.min(getBaseDuration(), getMaxScrollDuration())); } scrolling = true; scroller.fling(wasScrollX = scrollX, 0, velocity, 0, (int) (px + ph + mn / (float) videoScrollDuration * sw), (int) (px + ph + mx / (float) videoScrollDuration * sw), 0, 0); scrollStopped = false; } } else if ((pressHandle == HANDLE_ROUND_SCROLL || pressHandle == HANDLE_ROUND_REGION && !dragged) && roundSelected && velocityTracker != null) { - velocityTracker.computeCurrentVelocity(hasVideo ? 1000 : 1500); + velocityTracker.computeCurrentVelocity(videoTrack != null ? 1000 : 1500); final int velocity = (int) velocityTracker.getXVelocity(); scrollingVideo = false; if (Math.abs(velocity) > dp(100)) { - final long videoScrollDuration = Math.min(getBaseDuration(), MAX_SCROLL_DURATION); + final long videoScrollDuration = Math.min(getBaseDuration(), getMaxScrollDuration()); final int scrollX = (int) (px + ph + roundOffset / (float) videoScrollDuration * sw); final long mx, mn; - if (hasVideo) { - mx = (long) ((videoRight * videoDuration) - (0 * roundDuration)); - mn = (long) ((videoLeft * videoDuration) - (1 * roundDuration)); + if (videoTrack != null) { + mx = (long) ((videoTrack.right * videoTrack.duration) - (0 * roundDuration)); + mn = (long) ((videoTrack.left * videoTrack.duration) - (1 * roundDuration)); } else { mx = 0; - mn = (long) -(roundDuration - Math.min(getBaseDuration(), MAX_SCROLL_DURATION)); + mn = (long) -(roundDuration - Math.min(getBaseDuration(), getMaxScrollDuration())); } scrolling = true; scroller.fling(wasScrollX = scrollX, 0, velocity, 0, (int) (px + ph + mn / (float) videoScrollDuration * sw), (int) (px + ph + mx / (float) videoScrollDuration * sw), 0, 0); @@ -1118,13 +1467,13 @@ public class TimelineView extends View { } private long minAudioSelect() { - return (long) Math.max(MIN_SELECT_DURATION, Math.min(hasVideo ? videoDuration : (hasRound ? roundDuration : audioDuration), MAX_SELECT_DURATION) * 0.15f); + return (long) Math.max(MIN_SELECT_DURATION, Math.min(getBaseDuration(), MAX_SELECT_DURATION) * 0.15f); } private void moveAudioOffset(final float d) { - if (!hasVideo && !hasRound) { + if (videoTrack == null && !hasRound) { long wasAudioOffset = audioOffset; - audioOffset = Utilities.clamp(audioOffset + (long) d, 0, (long) -(audioDuration - Math.min(getBaseDuration(), MAX_SCROLL_DURATION))); + audioOffset = Utilities.clamp(audioOffset + (long) d, 0, (long) -(audioDuration - Math.min(getBaseDuration(), getMaxScrollDuration()))); long rd = audioOffset - wasAudioOffset; audioLeft = Utilities.clamp(audioLeft - (float) rd / audioDuration, 1, 0); audioRight = Utilities.clamp(audioRight - (float) rd / audioDuration, 1, 0); @@ -1133,9 +1482,9 @@ public class TimelineView extends View { delegate.onAudioRightChange(audioRight); } } else if (audioSelected) { - final float L = hasVideo ? videoLeft * videoDuration : roundLeft * roundDuration; - final float R = hasVideo ? videoRight * videoDuration : roundRight * roundDuration; - final float D = hasVideo ? (videoRight - videoLeft) * videoDuration : (roundRight - roundLeft) * roundDuration; + final float L = videoTrack != null ? videoTrack.left * videoTrack.duration : roundLeft * roundDuration; + final float R = videoTrack != null ? videoTrack.right * videoTrack.duration : roundRight * roundDuration; + final float D = videoTrack != null ? (videoTrack.right - videoTrack.left) * videoTrack.duration : (roundRight - roundLeft) * roundDuration; long mx = (long) (R - (audioRight * audioDuration)); long mn = (long) (L - (audioLeft * audioDuration)); final float wasDuration = Math.min(audioRight - audioLeft, D / (float) audioDuration); @@ -1183,23 +1532,23 @@ public class TimelineView extends View { delegate.onProgressDragChange(true); long progressToStart; - if (hasVideo) { - progressToStart = Utilities.clamp(audioOffset + (long) (audioLeft * audioDuration), (long) (videoRight * videoDuration), (long) (videoLeft * videoDuration)); + if (videoTrack != null) { + progressToStart = Utilities.clamp(audioOffset + (long) (audioLeft * audioDuration), (long) (videoTrack.right * videoTrack.duration), (long) (videoTrack.left * videoTrack.duration)); } else if (hasRound) { progressToStart = Utilities.clamp(audioOffset + (long) (audioLeft * audioDuration), (long) (roundRight * roundDuration), (long) (roundLeft * roundDuration)); } else { progressToStart = Utilities.clamp((long) (audioLeft * audioDuration), audioDuration, 0); } - if (hasVideo && Math.abs(progress - progressToStart) > 400) { + if (videoTrack != null && Math.abs(progress - progressToStart) > 400) { loopProgressFrom = progress; loopProgress.set(1, true); } delegate.onProgressChange(progress = progressToStart, false); } else if (dragged || scrolling) { - if (hasVideo) { - progress = Utilities.clamp(audioOffset + (long) (audioLeft * audioDuration), (long) (videoRight * videoDuration), (long) (videoLeft * videoDuration)); - } else if (hasRound) { - progress = Utilities.clamp(audioOffset + (long) (audioLeft * audioDuration), (long) (roundRight * videoDuration), (long) (roundLeft * videoDuration)); + if (videoTrack != null) { + progress = Utilities.clamp(audioOffset + (long) (audioLeft * audioDuration), (long) (videoTrack.right * videoTrack.duration), (long) (videoTrack.left * videoTrack.duration)); + } else if (hasRound && videoTrack != null) { + progress = Utilities.clamp(audioOffset + (long) (audioLeft * audioDuration), (long) (roundRight * videoTrack.duration), (long) (roundLeft * videoTrack.duration)); } else { progress = Utilities.clamp((long) (audioLeft * audioDuration), audioDuration, 0); } @@ -1210,25 +1559,25 @@ public class TimelineView extends View { } private void moveRoundOffset(final float d) { - if (!hasVideo) { + if (videoTrack == null) { long wasAudioOffset = roundOffset; - roundOffset = Utilities.clamp(roundOffset + (long) d, 0, (long) -(roundDuration - Math.min(getBaseDuration(), MAX_SCROLL_DURATION))); + roundOffset = Utilities.clamp(roundOffset + (long) d, 0, (long) -(roundDuration - Math.min(getBaseDuration(), getMaxScrollDuration()))); long rd = roundOffset - wasAudioOffset; roundLeft = Utilities.clamp(roundLeft - (float) rd / roundDuration, 1, 0); roundRight = Utilities.clamp(roundRight - (float) rd / roundDuration, 1, 0); if (delegate != null) { - delegate.onAudioLeftChange(roundLeft); - delegate.onAudioRightChange(roundRight); + delegate.onRoundLeftChange(roundLeft); + delegate.onRoundRightChange(roundRight); } } else if (roundSelected) { - long mx = (long) ((videoRight * videoDuration) - (roundRight * roundDuration)); - long mn = (long) ((videoLeft * videoDuration) - (roundLeft * roundDuration)); - final float wasDuration = Math.min(roundRight - roundLeft, (videoRight - videoLeft) * videoDuration / (float) roundDuration); + long mx = (long) ((videoTrack.right * videoTrack.duration) - (roundRight * roundDuration)); + long mn = (long) ((videoTrack.left * videoTrack.duration) - (roundLeft * roundDuration)); + final float wasDuration = Math.min(roundRight - roundLeft, (videoTrack.right - videoTrack.left) * videoTrack.duration / (float) roundDuration); if (roundOffset + (long) d > mx) { - roundRight = Utilities.clamp((videoRight * videoDuration - roundOffset - (long) d) / (float) roundDuration, 1, wasDuration); + roundRight = Utilities.clamp((videoTrack.right * videoTrack.duration - roundOffset - (long) d) / (float) roundDuration, 1, wasDuration); roundLeft = Utilities.clamp(roundRight - wasDuration, 1, 0); - long mmx = (long) ((videoRight * videoDuration) - (roundRight * roundDuration)); - long mmn = (long) ((videoLeft * videoDuration) - (roundLeft * roundDuration)); + long mmx = (long) ((videoTrack.right * videoTrack.duration) - (roundRight * roundDuration)); + long mmn = (long) ((videoTrack.left * videoTrack.duration) - (roundLeft * roundDuration)); if (mmx < mmn) { long t = mmx; mmx = mmn; @@ -1240,10 +1589,10 @@ public class TimelineView extends View { delegate.onRoundRightChange(roundRight); } } else if (roundOffset + (long) d < mn) { - roundLeft = Utilities.clamp((videoLeft * videoDuration - roundOffset - (long) d) / (float) roundDuration, 1 - wasDuration, 0); + roundLeft = Utilities.clamp((videoTrack.left * videoTrack.duration - roundOffset - (long) d) / (float) roundDuration, 1 - wasDuration, 0); roundRight = Utilities.clamp(roundLeft + wasDuration, 1, 0); - long mmx = (long) ((videoRight * videoDuration) - (roundRight * roundDuration)); - long mmn = (long) ((videoLeft * videoDuration) - (roundLeft * roundDuration)); + long mmx = (long) ((videoTrack.right * videoTrack.duration) - (roundRight * roundDuration)); + long mmn = (long) ((videoTrack.left * videoTrack.duration) - (roundLeft * roundDuration)); if (mmx < mmn) { long t = mmx; mmx = mmn; @@ -1268,19 +1617,19 @@ public class TimelineView extends View { delegate.onProgressDragChange(true); long progressToStart; - if (hasVideo) { - progressToStart = Utilities.clamp(roundOffset + (long) (roundLeft * roundDuration), (long) (videoRight * videoDuration), (long) (videoLeft * videoDuration)); + if (videoTrack != null) { + progressToStart = Utilities.clamp(roundOffset + (long) (roundLeft * roundDuration), (long) (videoTrack.right * videoTrack.duration), (long) (videoTrack.left * videoTrack.duration)); } else { progressToStart = Utilities.clamp((long) (roundLeft * roundDuration), roundDuration, 0); } - if (hasVideo && Math.abs(progress - progressToStart) > 400) { + if (videoTrack != null && Math.abs(progress - progressToStart) > 400) { loopProgressFrom = progress; loopProgress.set(1, true); } delegate.onProgressChange(progress = progressToStart, false); } else if (dragged || scrolling) { - if (hasVideo) { - progress = Utilities.clamp(roundOffset + (long) (roundLeft * roundDuration), (long) (videoRight * videoDuration), (long) (videoLeft * videoDuration)); + if (videoTrack != null) { + progress = Utilities.clamp(roundOffset + (long) (roundLeft * roundDuration), (long) (videoTrack.right * videoTrack.duration), (long) (videoTrack.left * videoTrack.duration)); } else { progress = Utilities.clamp((long) (roundLeft * roundDuration), roundDuration, 0); } @@ -1290,12 +1639,94 @@ public class TimelineView extends View { } } + private void moveCollageOffset(Track track, final float d) { + if (track == null) return; + if (collageMain == track || collageMain == null) { +// long wasAudioOffset = track.offset; +// track.offset = Utilities.clamp(track.offset + (long) d, 0, (long) -(track.duration - Math.min(getBaseDuration(), getMaxScrollDuration()))); +// long rd = track.offset - wasAudioOffset; +// track.left = Utilities.clamp(track.left - (float) rd / track.duration, 1, 0); +// track.right = Utilities.clamp(track.right - (float) rd / track.duration, 1, 0); +// if (delegate != null) { +// delegate.onVideoLeftChange(track.index, track.left); +// delegate.onVideoRightChange(track.index, track.right); +// } + } else if (collageSelected == collageTracks.indexOf(track)) { + long mx = (long) ((1.0f/*collageMain.right*/ * collageMain.duration) - (track.right * track.duration)); + long mn = (long) ((0.0f/*collageMain.left*/ * collageMain.duration) - (track.left * track.duration)); + final float wasDuration = Math.min(track.right - track.left, (collageMain.right - collageMain.left) * collageMain.duration / (float) track.duration); + if (track.offset + (long) d > mx) { + track.right = Utilities.clamp((collageMain.right * collageMain.duration - track.offset - (long) d) / (float) track.duration, 1, wasDuration); + track.left = Utilities.clamp(track.right - wasDuration, 1, 0); + long mmx = (long) ((collageMain.right * collageMain.duration) - (track.right * track.duration)); + long mmn = (long) ((collageMain.left * collageMain.duration) - (track.left * track.duration)); + if (mmx < mmn) { + long t = mmx; + mmx = mmn; + mmn = t; + } + track.offset = Utilities.clamp(track.offset + (long) d, mmx, mmn); + if (delegate != null) { + delegate.onVideoLeftChange(track.index, track.left); + delegate.onVideoRightChange(track.index, track.right); + } + } else if (track.offset + (long) d < mn) { + track.left = Utilities.clamp((collageMain.left * collageMain.duration - track.offset - (long) d) / (float) track.duration, 1 - wasDuration, 0); + track.right = Utilities.clamp(track.left + wasDuration, 1, 0); + long mmx = (long) ((collageMain.right * collageMain.duration) - (track.right * track.duration)); + long mmn = (long) ((collageMain.left * collageMain.duration) - (track.left * track.duration)); + if (mmx < mmn) { + long t = mmx; + mmx = mmn; + mmn = t; + } + track.offset = Utilities.clamp(track.offset + (long) d, mmx, mmn); + if (delegate != null) { + delegate.onVideoLeftChange(track.index, track.left); + delegate.onVideoRightChange(track.index, track.right); + } + } else { + track.offset += (long) d; + } + } else { + track.offset = Utilities.clamp(track.offset + (long) d, (long) (getBaseDuration() - track.duration * track.right), (long) (-track.left * track.duration)); + } + invalidate(); + if (delegate != null) { + delegate.onVideoOffsetChange(track.index, track.offset); + } + if (!dragged && delegate != null) { + delegate.onProgressDragChange(true); + + long progressToStart; + if (collageMain != track && collageMain != null) { + progressToStart = Utilities.clamp(track.offset + (long) (track.left * track.duration), (long) (collageMain.right * collageMain.duration), (long) (collageMain.left * collageMain.duration)); + } else { + progressToStart = Utilities.clamp((long) (track.left * track.duration), track.duration, 0); + } + if (collageMain != track && collageMain != null && Math.abs(progress - progressToStart) > 400) { + loopProgressFrom = progress; + loopProgress.set(1, true); + } + delegate.onProgressChange(progress = progressToStart, false); + } else if (dragged || scrolling) { + if (collageMain != track && collageMain != null) { + progress = Utilities.clamp(track.offset + (long) (track.left * track.duration), (long) (collageMain.right * collageMain.duration), (long) (collageMain.left * collageMain.duration)); + } else { + progress = Utilities.clamp((long) (track.left * track.duration), track.duration, 0); + } + if (delegate != null) { + delegate.onProgressChange(progress, false); + } + } + } + private int wasScrollX; @Override public void computeScroll() { if (scroller.computeScrollOffset()) { int scrollX = scroller.getCurrX(); - final long videoScrollDuration = Math.min(getBaseDuration(), MAX_SCROLL_DURATION); + final long videoScrollDuration = Math.min(getBaseDuration(), getMaxScrollDuration()); if (scrollingVideo) { scroll = (long) Math.max(0, ((scrollX - px - ph) / (float) sw * videoScrollDuration)); } else { @@ -1316,10 +1747,13 @@ public class TimelineView extends View { } } - class WaveformPath extends Path { + static class WaveformPath extends Path { + private final int ph = dp(10); + private final float[] waveformRadii = new float[8]; - private int lastWaveformCount; - private float lastAnimatedLoaded; + private ArrayList lastWaveforms; + private ArrayList lastWaveformCounts; + private ArrayList lastWaveformLoaded; private long lastScrollDuration; private float lastAudioHeight; private float lastMaxBar; @@ -1329,21 +1763,68 @@ public class TimelineView extends View { private float lastLeft; private float lastRight; + WaveformPath() { + waveformRadii[0] = waveformRadii[1] = waveformRadii[2] = waveformRadii[3] = dp(2); + waveformRadii[4] = waveformRadii[5] = waveformRadii[6] = waveformRadii[7] = 0; + } + + private boolean eqCount(ArrayList counts, ArrayList waveforms) { + if (counts == null && waveforms == null) return true; + if (counts == null || waveforms == null) return false; + if (counts.size() != waveforms.size()) return false; + for (int i = 0; i < counts.size(); ++i) { + if (counts.get(i) != (waveforms.get(i) == null ? 0 : waveforms.get(i).getCount())) + return false; + } + return true; + } + + private boolean eqLoadedCounts(ArrayList loadedCounts, ArrayList waveforms) { + if (loadedCounts == null && waveforms == null) return true; + if (loadedCounts == null || waveforms == null) return false; + if (loadedCounts.size() != waveforms.size()) return false; + for (int i = 0; i < loadedCounts.size(); ++i) { + if (loadedCounts.get(i) != (waveforms.get(i) == null ? 0 : waveforms.get(i).animatedLoaded.set(waveforms.get(i).getLoadedCount()))) + return false; + } + return true; + } + + public static int getMaxBar(ArrayList waveforms) { + if (waveforms == null) return 0; + int sum = 0; + for (int i = 0; i < waveforms.size(); ++i) { + if (waveforms.get(i) == null) continue; + sum += waveforms.get(i).getMaxBar(); + } + return sum; + } + + public static int getMaxLoadedCount(ArrayList waveforms) { + if (waveforms == null) return 0; + int max = 0; + for (int i = 0; i < waveforms.size(); ++i) { + if (waveforms.get(i) == null) continue; + max = Math.max(waveforms.get(i).getLoadedCount(), max); + } + return max; + } + public void check( float start, float left, float right, float audioSelected, - float animatedLoaded, long scrollDuration, float audioHeight, float maxBar, - float bottom + float bottom, + AudioWaveformLoader waveform ) { if (waveform == null) { rewind(); return; } - if (lastWaveformCount != waveform.getCount() || - Math.abs(lastAnimatedLoaded - animatedLoaded) > 0.01f || + final float animatedLoaded = waveform == null ? 0 : waveform.animatedLoaded.set(waveform.getLoadedCount()); + if ( lastScrollDuration != scrollDuration || Math.abs(lastAudioHeight - audioHeight) > 1f || Math.abs(lastMaxBar - maxBar) > 0.01f || @@ -1351,17 +1832,72 @@ public class TimelineView extends View { Math.abs(lastBottom - bottom) > 1f || Math.abs(lastStart - start) > 1f || Math.abs(lastLeft - left) > 1f || - Math.abs(lastRight - right) > 1f + Math.abs(lastRight - right) > 1f || + (waveform != null) != (lastWaveformCounts != null && lastWaveformCounts.size() == 1) || + Math.abs((lastWaveformLoaded == null || lastWaveformLoaded.isEmpty() ? 0 : lastWaveformLoaded.get(0)) - animatedLoaded) > 0.01f ) { - lastWaveformCount = waveform.getCount(); + if (lastWaveformCounts == null) { + lastWaveformCounts = new ArrayList<>(); + } else lastWaveformCounts.clear(); + lastWaveformCounts.add(waveform.getCount()); + if (lastWaveformLoaded == null) { + lastWaveformLoaded = new ArrayList<>(); + } else lastWaveformLoaded.clear(); + lastWaveformLoaded.add(animatedLoaded); layout( lastStart = start, lastLeft = left, lastRight = right, lastAudioSelected = audioSelected, - lastAnimatedLoaded = animatedLoaded, - lastScrollDuration = scrollDuration, lastMaxBar = maxBar, lastAudioHeight = audioHeight, - lastBottom = bottom + lastBottom = bottom, + waveform == null ? 0 : waveform.animatedLoaded.set(waveform.getLoadedCount()), + waveform + ); + } + } + + public void check( + float start, float left, float right, + float audioSelected, + float audioHeight, + float maxBar, + float bottom, + ArrayList waveforms + ) { + if (waveforms == null || waveforms.isEmpty()) { + rewind(); + return; + } + if (Math.abs(lastAudioHeight - audioHeight) > 1f || + Math.abs(lastMaxBar - maxBar) > 0.01f || + Math.abs(lastAudioSelected - audioSelected) > 0.1f || + Math.abs(lastBottom - bottom) > 1f || + Math.abs(lastStart - start) > 1f || + Math.abs(lastLeft - left) > 1f || + Math.abs(lastRight - right) > 1f || + eqCount(lastWaveformCounts, waveforms) || + eqLoadedCounts(lastWaveformLoaded, waveforms) + ) { + if (lastWaveformCounts == null) { + lastWaveformCounts = new ArrayList<>(); + } else lastWaveformCounts.clear(); + for (int i = 0; i < waveforms.size(); ++i) { + lastWaveformCounts.add(waveforms.get(i) == null ? 0 : waveforms.get(i).getCount()); + } + if (lastWaveformLoaded == null) { + lastWaveformLoaded = new ArrayList<>(); + } else lastWaveformLoaded.clear(); + for (int i = 0; i < waveforms.size(); ++i) { + lastWaveformLoaded.add(waveforms.get(i) == null ? 0 : waveforms.get(i).animatedLoaded.set(waveforms.get(i).getLoadedCount())); + } + layout( + lastStart = start, lastLeft = left, lastRight = right, + lastAudioSelected = audioSelected, + lastMaxBar = maxBar, + lastAudioHeight = audioHeight, + lastBottom = bottom, + lastWaveformLoaded, + waveforms ); } } @@ -1369,19 +1905,70 @@ public class TimelineView extends View { private void layout( float start, float left, float right, float audioSelected, - float animatedLoaded, - long scrollDuration, float maxBar, float audioHeight, - float bottom + float bottom, + ArrayList animatedLoaded, + ArrayList waveforms ) { - waveformPath.rewind(); + rewind(); final float barWidth = Math.round(dpf2(3.3333f)); + int maxCount = 0; + for (int i = 0; i < waveforms.size(); ++i) { + if (waveforms.get(i) != null) { + maxCount = Math.max(maxCount, waveforms.get(i).getCount()); + } + } int from = Math.max(0, (int) ((left - ph - start) / barWidth)); - int to = Math.min(waveform.getCount() - 1, (int) Math.ceil((right + ph - start) / barWidth)); + int to = Math.min(maxCount - 1, (int) Math.ceil((right + ph - start) / barWidth)); for (int i = from; i <= to; ++i) { float x = start + i * barWidth + dp(2); - float h = maxBar <= 0 ? 0 : waveform.getBar(i) / (float) maxBar * audioHeight * .6f; + int sum = 0; + for (int j = 0; j < waveforms.size(); ++j) { + short bar = waveforms.get(j) == null || i >= waveforms.get(j).getCount() ? 0 : waveforms.get(j).getBar(i); + if (i < animatedLoaded.get(j) && i + 1 > animatedLoaded.get(j)) { + bar *= (animatedLoaded.get(j) - i); + } else if (i > animatedLoaded.get(j)) { + bar = 0; + } + sum += bar; + } + float h = maxBar <= 0 ? 0 : sum / (float) maxBar * audioHeight * .6f; + if (x < left || x > right) { + h *= audioSelected; + if (h <= 0) { + continue; + } + } + h = Math.max(h, lerp(dpf2(0.66f), dpf2(1.5f), audioSelected)); + AndroidUtilities.rectTmp.set( + x, + lerp(bottom - h, bottom - (audioHeight + h) / 2f, audioSelected), + x + dpf2(1.66f), + lerp(bottom, bottom - (audioHeight - h) / 2f, audioSelected) + ); + addRoundRect(AndroidUtilities.rectTmp, waveformRadii, Path.Direction.CW); + } + } + + private void layout( + float start, float left, float right, + float audioSelected, + float maxBar, + float audioHeight, + float bottom, + float animatedLoaded, + AudioWaveformLoader waveform + ) { + rewind(); + final float barWidth = Math.round(dpf2(3.3333f)); + int maxCount = waveform.getCount(); + int from = Math.max(0, (int) ((left - ph - start) / barWidth)); + int to = Math.min(maxCount - 1, (int) Math.ceil((right + ph - start) / barWidth)); + for (int i = from; i <= to; ++i) { + float x = start + i * barWidth + dp(2); + int sum = waveform.getBar(i); + float h = maxBar <= 0 ? 0 : sum / (float) maxBar * audioHeight * .6f; if (i < animatedLoaded && i + 1 > animatedLoaded) { h *= (animatedLoaded - i); } else if (i > animatedLoaded) { @@ -1400,83 +1987,355 @@ public class TimelineView extends View { x + dpf2(1.66f), lerp(bottom, bottom - (audioHeight - h) / 2f, audioSelected) ); - waveformPath.addRoundRect(AndroidUtilities.rectTmp, waveformRadii, Path.Direction.CW); + addRoundRect(AndroidUtilities.rectTmp, waveformRadii, Path.Direction.CW); } } } final float[] selectedVideoRadii = new float[8]; - final float[] waveformRadii = new float[8]; @Override protected void dispatchDraw(Canvas canvas) { final Paint blurPaint = backgroundBlur.getPaint(1f); + final float open = this.openT.set(this.open); + final long scrollDuration = Math.min(getBaseDuration(), getMaxScrollDuration()); - final long scrollDuration = Math.min(getBaseDuration(), MAX_SCROLL_DURATION); - float videoHeight = 0; - float videoT = hasVideo ? 1 : 0; - float videoSelected = videoSelectedT.set(!audioSelected && !roundSelected); + if (open < 1) { + timelineBounds.set(px, h - py - dp(28), w - px, h - py); + timelineClipPath.rewind(); + timelineClipPath.addRoundRect(timelineBounds, dp(8), dp(8), Path.Direction.CW); + canvas.saveLayerAlpha(timelineBounds, (int) (0xFF * (1.0f - open)), Canvas.ALL_SAVE_FLAG); + canvas.clipPath(timelineClipPath); + if (blurManager.hasRenderNode()) { + backgroundBlur.drawRect(canvas); + canvas.drawColor(0x33000000); + } else if (blurPaint == null) { + canvas.drawColor(0x40000000); + } else { + canvas.drawRect(timelineBounds, blurPaint); + canvas.drawColor(0x33000000); + } + if (!collageWaveforms.isEmpty() && blurManager != null && blurManager.hasRenderNode()) { + final float maxBar = timelineWaveformMax.set(WaveformPath.getMaxBar(collageWaveforms)); + final float start = px + ph + (audioOffset - scroll) / (float) scrollDuration * sw; + timelineWaveformPath.check(start, timelineBounds.left, timelineBounds.right, 0.0f, dp(28), maxBar, timelineBounds.bottom, collageWaveforms); + canvas.saveLayerAlpha(timelineBounds, (int) (0xFF * 0.4f), Canvas.ALL_SAVE_FLAG); + canvas.clipPath(timelineWaveformPath); + audioWaveformBlur.drawRect(canvas); + canvas.restore(); + } else if (!collageWaveforms.isEmpty()) { + Paint paint = audioWaveformBlur.getPaint(.4f); + if (paint == null) { + paint = waveformPaint; + paint.setAlpha((int) (0x40)); + } + final float maxBar = timelineWaveformMax.set(WaveformPath.getMaxBar(collageWaveforms)); +// final float animatedLoaded = timelineWaveformLoaded.set(WaveformPath.getMaxLoadedCount(collageWaveforms)); + final float start = px + ph + (audioOffset - scroll) / (float) scrollDuration * sw; + timelineWaveformPath.check(start, timelineBounds.left, timelineBounds.right, 0.0f, dp(28), maxBar, timelineBounds.bottom, collageWaveforms); + canvas.drawPath(timelineWaveformPath, paint); + } + final float w = timelineText.getCurrentWidth() + dp(3.66f) + timelineIcon.getIntrinsicWidth(); + int x = (int) (timelineBounds.centerX() - w / 2.0f), cy = (int) timelineBounds.centerY(); + timelineIcon.setBounds(x, cy - timelineIcon.getIntrinsicHeight() / 2, x + timelineIcon.getIntrinsicWidth(), cy + timelineIcon.getIntrinsicHeight() / 2); + timelineIcon.setAlpha((int) (0xFF * 0.75f)); + timelineIcon.draw(canvas); + timelineText.draw(canvas, timelineBounds.centerX() - w / 2.0f + timelineIcon.getIntrinsicWidth() + dp(3.66f), cy, 0xFFFFFFFF, 0.75f); + canvas.restore(); + } - // draw video thumbs - if (hasVideo) { - canvas.save(); - videoHeight = getVideoHeight(); - final float videoStartX = (videoDuration <= 0 ? 0 : px + ph - scroll / (float) scrollDuration * sw) - ph; - final float videoEndX = (videoDuration <= 0 ? 0 : px + ph + (videoDuration - scroll) / (float) scrollDuration * sw) + ph; - videoBounds.set(videoStartX, h - py - videoHeight, videoEndX, h - py); - videoClipPath.rewind(); - videoClipPath.addRoundRect(videoBounds, dp(8), dp(8), Path.Direction.CW); - canvas.clipPath(videoClipPath); - if (thumbs != null) { - float x = videoStartX; - final int frameWidth = thumbs.getFrameWidth(); - final int fromFrame = (int) Math.max(0, Math.floor((videoStartX - px) / frameWidth)); - final int toFrame = (int) Math.min(thumbs.count, Math.ceil(((videoEndX - videoStartX) - px) / frameWidth) + 1); + if (open > 0) { + boolean restore = false; + if (open < 1) { + canvas.saveLayerAlpha(0, 0, getWidth(), getHeight(), (int) (0xFF * open), Canvas.ALL_SAVE_FLAG); + restore = true; + } - final int y = (int) videoBounds.top; + float videoHeight = 0; + float videoT = videoTrack != null ? 1 : 0; + float videoSelected = videoTrack != null ? videoTrack.selectedT.set(!audioSelected && !roundSelected) : 0; - boolean allLoaded = thumbs.frames.size() >= toFrame; - boolean fullyCovered = allLoaded && !isMainVideoRound; - if (fullyCovered) { - for (int i = fromFrame; i < Math.min(thumbs.frames.size(), toFrame); ++i) { - VideoThumbsLoader.BitmapFrame frame = thumbs.frames.get(i); - if (frame.bitmap == null) { - fullyCovered = false; - break; + float bottom = h - py; + float regionTop = 0, regionBottom = 0; + float left = 0, right = 0; + + final float p = dp(4); + final float video = videoSelected; + // draw video thumbs + if (videoTrack != null) { + canvas.save(); + videoHeight = getVideoHeight(); + left += (videoTrack.left * videoTrack.duration) * video; + right += (videoTrack.right * videoTrack.duration) * video; + final float videoStartX = (videoTrack.duration <= 0 ? 0 : px + ph - scroll / (float) scrollDuration * sw) - ph; + final float videoEndX = (videoTrack.duration <= 0 ? 0 : px + ph + (videoTrack.duration - scroll) / (float) scrollDuration * sw) + ph; + videoBounds.set(videoStartX, bottom - videoHeight, videoEndX, bottom); + bottom -= videoHeight + p * videoT; + regionTop += videoBounds.top * video; + regionBottom += videoBounds.bottom * video; + videoClipPath.rewind(); + videoClipPath.addRoundRect(videoBounds, dp(8), dp(8), Path.Direction.CW); + canvas.clipPath(videoClipPath); + if (videoTrack.thumbs != null) { + float x = videoStartX; + final int frameWidth = videoTrack.thumbs.getFrameWidth(); + final int fromFrame = (int) Math.max(0, Math.floor((videoStartX - px) / frameWidth)); + final int toFrame = (int) Math.min(videoTrack.thumbs.count, Math.ceil(((videoEndX - videoStartX) - px) / frameWidth) + 1); + + final int y = (int) videoBounds.top; + + boolean allLoaded = videoTrack.thumbs.frames.size() >= toFrame; + boolean fullyCovered = frameWidth != 0 && allLoaded && !videoTrack.isRound; + if (fullyCovered) { + for (int i = fromFrame; i < Math.min(videoTrack.thumbs.frames.size(), toFrame); ++i) { + VideoThumbsLoader.BitmapFrame frame = videoTrack.thumbs.frames.get(i); + if (frame.bitmap == null) { + fullyCovered = false; + break; + } } } - } - if (!fullyCovered) { - if (blurPaint == null) { - canvas.drawColor(0x40000000); + if (!fullyCovered) { + if (blurManager.hasRenderNode()) { + backgroundBlur.drawRect(canvas); + canvas.drawColor(0x33000000); + } else if (blurPaint == null) { + canvas.drawColor(0x40000000); + } else { + canvas.drawRect(videoBounds, blurPaint); + canvas.drawColor(0x33000000); + } + } + + if (frameWidth != 0) { + for (int i = fromFrame; i < Math.min(videoTrack.thumbs.frames.size(), toFrame); ++i) { + VideoThumbsLoader.BitmapFrame frame = videoTrack.thumbs.frames.get(i); + if (frame.bitmap != null) { + videoFramePaint.setAlpha((int) (0xFF * frame.getAlpha())); + canvas.drawBitmap(frame.bitmap, x, y - (int) ((frame.bitmap.getHeight() - videoHeight) / 2f), videoFramePaint); + } + x += frameWidth; + } + } + + if (!allLoaded) { + videoTrack.thumbs.load(); + } + } + selectedVideoClipPath.rewind(); + + if (!isCover) { + AndroidUtilities.rectTmp.set( + px + ph + (videoTrack.left * videoTrack.duration - scroll) / (float) scrollDuration * sw - (videoTrack.left <= 0 ? ph : 0), + h - py - videoHeight, + px + ph + (videoTrack.right * videoTrack.duration - scroll) / (float) scrollDuration * sw + (videoTrack.right >= 1 ? ph : 0), + h - py + ); + selectedVideoClipPath.addRoundRect( + AndroidUtilities.rectTmp, + selectedVideoRadii, + Path.Direction.CW + ); + canvas.clipPath(selectedVideoClipPath, Region.Op.DIFFERENCE); + canvas.drawColor(0x50000000); + } + canvas.restore(); + } + + float collageHeight = 0; + float collageT = 0; + if (!collageTracks.isEmpty()) { + collageT = 1; + collageHeight = getCollageHeight(); + + for (int i = 0; i < collageTracks.size(); ++i) { + final Track track = collageTracks.get(i); + + final float selected = track.selectedT.set(!this.audioSelected && !this.roundSelected && collageSelected == i); + float _left, _right; + if (track != collageMain) { + _left = px + ph + (track.offset - scroll + lerp(track.left, 0, selected) * track.duration) / (float) scrollDuration * sw; + _right = px + ph + (track.offset - scroll + lerp(track.right, 1, selected) * track.duration) / (float) scrollDuration * sw; } else { - canvas.drawRect(videoBounds, blurPaint); - canvas.drawColor(0x33000000); + _left = px + ph + (track.offset - scroll) / (float) scrollDuration * sw; + _right = px + ph + (track.offset - scroll + track.duration) / (float) scrollDuration * sw; } - } - for (int i = fromFrame; i < Math.min(thumbs.frames.size(), toFrame); ++i) { - VideoThumbsLoader.BitmapFrame frame = thumbs.frames.get(i); - if (frame.bitmap != null) { - videoFramePaint.setAlpha((int) (0xFF * frame.getAlpha())); - canvas.drawBitmap(frame.bitmap, x, y - (int) ((frame.bitmap.getHeight() - videoHeight) / 2f), videoFramePaint); + canvas.save(); + final float height = lerp(dp(28), dp(38), selected); + track.bounds.set(_left - ph, bottom - height, _right + ph, bottom); + regionTop += track.bounds.top * selected; + regionBottom += track.bounds.bottom * selected; + left += (track.offset + track.left * track.duration) * selected; + right += (track.offset + track.right * track.duration) * selected; + + collageClipPath.rewind(); + collageClipPath.addRoundRect(track.bounds, dp(8), dp(8), Path.Direction.CW); + canvas.clipPath(collageClipPath); + if (track.thumbs != null) { + final float trackStartX = (track.duration <= 0 ? 0 : px + ph + (track.offset - scroll) / (float) scrollDuration * sw) - ph; + final float trackEndX = (track.duration <= 0 ? 0 : px + ph + (track.offset + track.duration - scroll) / (float) scrollDuration * sw) + ph; + + float x = trackStartX; + final int frameWidth = track.thumbs.getFrameWidth(); + final float L; +// if (videoTrack != null) { + L = px + ph + (track.offset - scroll) / (float) scrollDuration * sw; +// } else { +// L = px; +// } + final int fromFrame = (int) Math.max(0, Math.floor((trackStartX - L) / frameWidth)); + final int toFrame = (int) Math.min(track.thumbs.count, Math.ceil((trackEndX - trackStartX) / frameWidth) + 1); + + final int y = (int) track.bounds.top; + + boolean allLoaded = track.thumbs.frames.size() >= toFrame; + boolean fullyCovered = allLoaded; + if (fullyCovered) { + for (int j = fromFrame; j < Math.min(track.thumbs.frames.size(), toFrame); ++j) { + final VideoThumbsLoader.BitmapFrame frame = track.thumbs.frames.get(j); + if (frame.bitmap == null) { + fullyCovered = false; + break; + } + } + } + + if (!fullyCovered) { + if (blurManager.hasRenderNode()) { + backgroundBlur.drawRect(canvas); + canvas.drawColor(0x33000000); + } else if (blurPaint == null) { + canvas.drawColor(0x40000000); + } else { + canvas.drawRect(track.bounds, blurPaint); + canvas.drawColor(0x33000000); + } + } + + if (frameWidth != 0) { + for (int j = fromFrame; j < Math.min(track.thumbs.frames.size(), toFrame); ++j) { + VideoThumbsLoader.BitmapFrame frame = track.thumbs.frames.get(j); + if (frame.bitmap != null) { + collageFramePaint.setAlpha((int) (0xFF * frame.getAlpha())); + canvas.drawBitmap(frame.bitmap, x, y - (int) ((frame.bitmap.getHeight() - height) / 2f), collageFramePaint); + } + x += frameWidth; + } + } + + if (!allLoaded) { + track.thumbs.load(); + } } - x += frameWidth; - } + selectedCollageClipPath.rewind(); - if (!allLoaded) { - thumbs.load(); + if (!isCover) { + AndroidUtilities.rectTmp.set( + px + ph + (track.left * track.duration - scroll + track.offset) / (float) scrollDuration * sw - (track.left <= 0 ? ph : 0), + track.bounds.top, + px + ph + (track.right * track.duration - scroll + track.offset) / (float) scrollDuration * sw + (track.right >= 1 ? ph : 0), + track.bounds.bottom + ); + selectedCollageClipPath.addRoundRect(AndroidUtilities.rectTmp, selectedVideoRadii, Path.Direction.CW); + canvas.clipPath(selectedCollageClipPath, Region.Op.DIFFERENCE); + canvas.drawColor(0x50000000); + } + canvas.restore(); + + bottom -= height + p * collageT; } } - selectedVideoClipPath.rewind(); - if (!isCover) { + float roundT = this.roundT.set(hasRound); + float roundSelected = this.roundSelectedT.set(hasRound && this.roundSelected); + final float roundHeight = getRoundHeight() * roundT; + final float round = roundT * (videoTrack != null || hasAudio || !collageTracks.isEmpty() ? roundSelected : 1); + if (roundT > 0) { + left += (roundOffset + roundLeft * roundDuration) * round; + right += (roundOffset + roundRight * roundDuration) * round; + + float _left, _right; + if (videoTrack != null) { + _left = px + ph + (roundOffset - scroll + lerp(roundLeft, 0, roundSelected) * roundDuration) / (float) scrollDuration * sw; + _right = px + ph + (roundOffset - scroll + lerp(roundRight, 1, roundSelected) * roundDuration) / (float) scrollDuration * sw; + } else { + _left = px + ph + (roundOffset - scroll) / (float) scrollDuration * sw; + _right = px + ph + (roundOffset - scroll + roundDuration) / (float) scrollDuration * sw; + } + + roundBounds.set(_left - ph, bottom - roundHeight, _right + ph, bottom); + bottom -= roundHeight + p * roundT; + regionTop += roundBounds.top * round; + regionBottom += roundBounds.bottom * round; + + roundClipPath.rewind(); + roundClipPath.addRoundRect(roundBounds, dp(8), dp(8), Path.Direction.CW); + canvas.save(); + canvas.clipPath(roundClipPath); + if (roundThumbs != null) { + final float roundStartX = (roundDuration <= 0 ? 0 : px + ph + (roundOffset - scroll) / (float) scrollDuration * sw) - ph; + final float roundEndX = (roundDuration <= 0 ? 0 : px + ph + (roundOffset + roundDuration - scroll) / (float) scrollDuration * sw) + ph; + + float x = roundStartX; + final int frameWidth = roundThumbs.getFrameWidth(); + final float L; + if (videoTrack != null) { + L = px + ph + (roundOffset - scroll) / (float) scrollDuration * sw; + } else { + L = px; + } + final int fromFrame = (int) Math.max(0, Math.floor((roundStartX - L) / frameWidth)); + final int toFrame = (int) Math.min(roundThumbs.count, Math.ceil((roundEndX - roundStartX) / frameWidth) + 1); + + final int y = (int) roundBounds.top; + + boolean allLoaded = roundThumbs.frames.size() >= toFrame; + boolean fullyCovered = allLoaded; + if (fullyCovered) { + for (int i = fromFrame; i < Math.min(roundThumbs.frames.size(), toFrame); ++i) { + VideoThumbsLoader.BitmapFrame frame = roundThumbs.frames.get(i); + if (frame.bitmap == null) { + fullyCovered = false; + break; + } + } + } + + if (!fullyCovered) { + if (blurManager.hasRenderNode()) { + backgroundBlur.drawRect(canvas); + canvas.drawColor(0x33000000); + } else if (blurPaint == null) { + canvas.drawColor(0x40000000); + } else { + canvas.drawRect(roundBounds, blurPaint); + canvas.drawColor(0x33000000); + } + } + + if (frameWidth != 0) { + for (int i = fromFrame; i < Math.min(roundThumbs.frames.size(), toFrame); ++i) { + VideoThumbsLoader.BitmapFrame frame = roundThumbs.frames.get(i); + if (frame.bitmap != null) { + videoFramePaint.setAlpha((int) (0xFF * frame.getAlpha())); + canvas.drawBitmap(frame.bitmap, x, y - (int) ((frame.bitmap.getHeight() - roundHeight) / 2f), videoFramePaint); + } + x += frameWidth; + } + } + + if (!allLoaded) { + roundThumbs.load(); + } + } + selectedVideoClipPath.rewind(); AndroidUtilities.rectTmp.set( - px + ph + (videoLeft * videoDuration - scroll) / (float) scrollDuration * sw - (videoLeft <= 0 ? ph : 0), - h - py - videoHeight, - px + ph + (videoRight * videoDuration - scroll) / (float) scrollDuration * sw + (videoRight >= 1 ? ph : 0), - h - py + px + ph + (roundLeft * roundDuration - scroll + roundOffset) / (float) scrollDuration * sw - (roundLeft <= 0 ? ph : 0) - ph * (1f - roundSelected), + roundBounds.top, + px + ph + (roundRight * roundDuration - scroll + roundOffset) / (float) scrollDuration * sw + (roundRight >= 1 ? ph : 0) + ph * (1f - roundSelected), + roundBounds.bottom ); selectedVideoClipPath.addRoundRect( AndroidUtilities.rectTmp, @@ -1485,275 +2344,201 @@ public class TimelineView extends View { ); canvas.clipPath(selectedVideoClipPath, Region.Op.DIFFERENCE); canvas.drawColor(0x50000000); - } - canvas.restore(); - } - - final float p = dp(4); - float roundT = this.roundT.set(hasRound); - float roundSelected = this.roundSelectedT.set(hasRound && this.roundSelected); - final float roundHeight = getRoundHeight() * roundT; - if (roundT > 0) { - float left, right; - if (hasVideo) { - left = px + ph + (roundOffset - scroll + lerp(roundLeft, 0, roundSelected) * roundDuration) / (float) scrollDuration * sw; - right = px + ph + (roundOffset - scroll + lerp(roundRight, 1, roundSelected) * roundDuration) / (float) scrollDuration * sw; - } else { - left = px + ph + (roundOffset - scroll) / (float) scrollDuration * sw; - right = px + ph + (roundOffset - scroll + roundDuration) / (float) scrollDuration * sw; + canvas.restore(); } - final float bottom = h - py - videoHeight - p * videoT; - roundBounds.set(left - ph, bottom - roundHeight, right + ph, bottom); - roundClipPath.rewind(); - roundClipPath.addRoundRect(roundBounds, dp(8), dp(8), Path.Direction.CW); - canvas.save(); - canvas.clipPath(roundClipPath); - if (roundThumbs != null) { - final float roundStartX = (roundDuration <= 0 ? 0 : px + ph + (roundOffset - scroll) / (float) scrollDuration * sw) - ph; - final float roundEndX = (roundDuration <= 0 ? 0 : px + ph + (roundOffset + roundDuration - scroll) / (float) scrollDuration * sw) + ph; + // draw audio + float audioT = this.audioT.set(hasAudio); + float audioSelected = this.audioSelectedT.set(hasAudio && this.audioSelected); + final float audioHeight = getAudioHeight() * audioT; + final float audio = audioT * (videoTrack != null || hasRound || !collageTracks.isEmpty() ? audioSelected : 1); + if (audioT > 0) { + left += (audioOffset + audioLeft * audioDuration) * audio; + right += (audioOffset + audioRight * audioDuration) * audio; - float x = roundStartX; - final int frameWidth = roundThumbs.getFrameWidth(); - final float L; - if (hasVideo) { - L = px + ph + (roundOffset - scroll) / (float) scrollDuration * sw; + final Paint audioBlurPaint = audioBlur.getPaint(audioT); + canvas.save(); + float _left, _right; + if (videoTrack != null || hasRound || !collageTracks.isEmpty()) { + _left = px + ph + (audioOffset - scroll + lerp(audioLeft, 0, audioSelected) * audioDuration) / (float) scrollDuration * sw; + _right = px + ph + (audioOffset - scroll + lerp(audioRight, 1, audioSelected) * audioDuration) / (float) scrollDuration * sw; } else { - L = px; - } - final int fromFrame = (int) Math.max(0, Math.floor((roundStartX - L) / frameWidth)); - final int toFrame = (int) Math.min(roundThumbs.count, Math.ceil((roundEndX - roundStartX) / frameWidth) + 1); - - final int y = (int) roundBounds.top; - - boolean allLoaded = roundThumbs.frames.size() >= toFrame; - boolean fullyCovered = allLoaded; - if (fullyCovered) { - for (int i = fromFrame; i < Math.min(roundThumbs.frames.size(), toFrame); ++i) { - VideoThumbsLoader.BitmapFrame frame = roundThumbs.frames.get(i); - if (frame.bitmap == null) { - fullyCovered = false; - break; - } - } + _left = px + ph + (audioOffset - scroll) / (float) scrollDuration * sw; + _right = px + ph + (audioOffset - scroll + audioDuration) / (float) scrollDuration * sw; } - if (!fullyCovered) { - if (blurPaint == null) { - canvas.drawColor(0x40000000); - } else { - canvas.drawRect(roundBounds, blurPaint); - canvas.drawColor(0x33000000); - } + audioBounds.set(_left - ph, bottom - audioHeight, _right + ph, bottom); + bottom -= audioHeight + p * audioT; + regionTop += audioBounds.top * audio; + regionBottom += audioBounds.bottom * audio; + audioClipPath.rewind(); + audioClipPath.addRoundRect(audioBounds, dp(8), dp(8), Path.Direction.CW); + canvas.clipPath(audioClipPath); + + if (blurManager != null && blurManager.hasRenderNode()) { + backgroundBlur.drawRect(canvas); + canvas.drawColor(Theme.multAlpha(0x33000000, audioT)); + } else if (audioBlurPaint == null) { + canvas.drawColor(Theme.multAlpha(0x40000000, audioT)); + } else { + canvas.drawRect(audioBounds, audioBlurPaint); + canvas.drawColor(Theme.multAlpha(0x33000000, audioT)); } - for (int i = fromFrame; i < Math.min(roundThumbs.frames.size(), toFrame); ++i) { - VideoThumbsLoader.BitmapFrame frame = roundThumbs.frames.get(i); - if (frame.bitmap != null) { - videoFramePaint.setAlpha((int) (0xFF * frame.getAlpha())); - canvas.drawBitmap(frame.bitmap, x, y - (int) ((frame.bitmap.getHeight() - roundHeight) / 2f), videoFramePaint); - } - x += frameWidth; - } - - if (!allLoaded) { - roundThumbs.load(); - } - } - selectedVideoClipPath.rewind(); - AndroidUtilities.rectTmp.set( - px + ph + (roundLeft * roundDuration - scroll + roundOffset) / (float) scrollDuration * sw - (roundLeft <= 0 ? ph : 0) - ph * (1f - roundSelected), - roundBounds.top, - px + ph + (roundRight * roundDuration - scroll + roundOffset) / (float) scrollDuration * sw + (roundRight >= 1 ? ph : 0) + ph * (1f - roundSelected), - roundBounds.bottom - ); - selectedVideoClipPath.addRoundRect( - AndroidUtilities.rectTmp, - selectedVideoRadii, - Path.Direction.CW - ); - canvas.clipPath(selectedVideoClipPath, Region.Op.DIFFERENCE); - canvas.drawColor(0x50000000); - canvas.restore(); - } - - // draw audio - float audioT = this.audioT.set(hasAudio); - float audioSelected = this.audioSelectedT.set(hasAudio && this.audioSelected); - final float audioHeight = getAudioHeight() * audioT; - if (audioT > 0) { - final Paint audioBlurPaint = audioBlur.getPaint(audioT); - canvas.save(); - float left, right; - if (hasVideo || hasRound) { - left = px + ph + (audioOffset - scroll + lerp(audioLeft, 0, audioSelected) * audioDuration) / (float) scrollDuration * sw; - right = px + ph + (audioOffset - scroll + lerp(audioRight, 1, audioSelected) * audioDuration) / (float) scrollDuration * sw; - } else { - left = px + ph + (audioOffset - scroll) / (float) scrollDuration * sw; - right = px + ph + (audioOffset - scroll + audioDuration) / (float) scrollDuration * sw; - } - - final float bottom = h - py - videoHeight - p * videoT - roundHeight - p * roundT; - audioBounds.set(left - ph, bottom - audioHeight, right + ph, bottom); - audioClipPath.rewind(); - audioClipPath.addRoundRect(audioBounds, dp(8), dp(8), Path.Direction.CW); - canvas.clipPath(audioClipPath); - - if (audioBlurPaint == null) { - canvas.drawColor(Theme.multAlpha(0x40000000, audioT)); - } else { - canvas.drawRect(audioBounds, audioBlurPaint); - canvas.drawColor(Theme.multAlpha(0x33000000, audioT)); - } - - if (waveform != null && audioBlurPaint != null) { - Paint paint = audioWaveformBlur.getPaint(.4f * audioT); - if (paint == null) { - paint = waveformPaint; - paint.setAlpha((int) (0x40 * audioT)); - } - final float maxBar = waveformMax.set(waveform.getMaxBar(), !waveformIsLoaded); - waveformIsLoaded = waveform.getLoadedCount() > 0; - final float animatedLoaded = waveformLoaded.set(waveform.getLoadedCount()); - final float start = px + ph + (audioOffset - scroll) / (float) scrollDuration * sw; - waveformPath.check(start, left, right, audioSelected, animatedLoaded, scrollDuration, audioHeight, maxBar, bottom); - canvas.drawPath(waveformPath, paint); - } - - if (audioSelected < 1) { - final float tleft = px + ph + (audioOffset - scroll + audioLeft * audioDuration) / (float) scrollDuration * sw; - final float tright = px + ph + (audioOffset - scroll + audioRight * audioDuration) / (float) scrollDuration * sw; - - final float textCx = (Math.max(px, tleft) + Math.min(w - px, tright)) / 2f; - final float textCy = bottom - audioHeight + dp(28 / 2); - final float textMaxWidth = Math.max(0, Math.min(w - px, tright) - Math.max(px, tleft) - dp(24)); - float textWidth = dpf2(13) + (audioAuthor == null && audioTitle == null ? 0 : dpf2(3.11f) + audioAuthorWidth + dpf2(3.66f + 2 + 4) + audioTitleWidth); - final boolean fit = textWidth < textMaxWidth; - - float x = textCx - Math.min(textWidth, textMaxWidth) / 2f; - audioIcon.setBounds((int) x, (int) (textCy - dp(13) / 2f), (int) (x + dp(13)), (int) (textCy + dp(13) / 2f)); - audioIcon.setAlpha((int) (0xFF * (1f - audioSelected))); - audioIcon.draw(canvas); - x += dpf2(13 + 3.11f); - canvas.saveLayerAlpha(0, 0, w, h, 0xFF, Canvas.ALL_SAVE_FLAG); - final float x2 = Math.min(tright, w) - dp(12); - canvas.clipRect(x, 0, x2, h); - if (audioAuthor != null) { - canvas.save(); - canvas.translate(x - audioAuthorLeft, textCy - audioAuthor.getHeight() / 2f); - audioAuthorPaint.setAlpha((int) (0xFF * (1f - audioSelected) * audioT)); - audioAuthor.draw(canvas); + if (waveform != null && blurManager != null && blurManager.hasRenderNode()) { + final float maxBar = waveformMax.set(waveform.getMaxBar(), !waveformIsLoaded); + waveformIsLoaded = waveform.getLoadedCount() > 0; + final float start = px + ph + (audioOffset - scroll) / (float) scrollDuration * sw; + waveformPath.check(start, _left, _right, audioSelected, scrollDuration, audioHeight, maxBar, audioBounds.bottom, waveform); + canvas.saveLayerAlpha(audioBounds, (int) (0xFF * 0.4f), Canvas.ALL_SAVE_FLAG); + canvas.clipPath(waveformPath); + audioWaveformBlur.drawRect(canvas); canvas.restore(); - x += audioAuthorWidth; + } else if (waveform != null && audioBlurPaint != null) { + Paint paint = audioWaveformBlur.getPaint(.4f * audioT); + if (paint == null) { + paint = waveformPaint; + paint.setAlpha((int) (0x40 * audioT)); + } + final float maxBar = waveformMax.set(waveform.getMaxBar(), !waveformIsLoaded); + waveformIsLoaded = waveform.getLoadedCount() > 0; + final float start = px + ph + (audioOffset - scroll) / (float) scrollDuration * sw; + waveformPath.check(start, _left, _right, audioSelected, scrollDuration, audioHeight, maxBar, audioBounds.bottom, waveform); + canvas.drawPath(waveformPath, paint); } - if (audioAuthor != null && audioTitle != null) { - x += dpf2(3.66f); - int wasAlpha = audioDotPaint.getAlpha(); - audioDotPaint.setAlpha((int) (wasAlpha * (1f - audioSelected))); - canvas.drawCircle(x + dp(1), textCy, dp(1), audioDotPaint); - audioDotPaint.setAlpha(wasAlpha); - x += dpf2(2); - x += dpf2(4); - } - if (audioTitle != null) { - canvas.save(); - canvas.translate(x - audioTitleLeft, textCy - audioTitle.getHeight() / 2f); - audioTitlePaint.setAlpha((int) (0xFF * (1f - audioSelected) * audioT)); - audioTitle.draw(canvas); + + if (audioSelected < 1) { + final float tleft = px + ph + (audioOffset - scroll + audioLeft * audioDuration) / (float) scrollDuration * sw; + final float tright = px + ph + (audioOffset - scroll + audioRight * audioDuration) / (float) scrollDuration * sw; + + final float textCx = (Math.max(px, tleft) + Math.min(w - px, tright)) / 2f; + final float textCy = audioBounds.centerY(); + final float textMaxWidth = Math.max(0, Math.min(w - px, tright) - Math.max(px, tleft) - dp(24)); + float textWidth = dpf2(13) + (audioAuthor == null && audioTitle == null ? 0 : dpf2(3.11f) + audioAuthorWidth + dpf2(3.66f + 2 + 4) + audioTitleWidth); + final boolean fit = textWidth < textMaxWidth; + + float x = textCx - Math.min(textWidth, textMaxWidth) / 2f; + audioIcon.setBounds((int) x, (int) (textCy - dp(13) / 2f), (int) (x + dp(13)), (int) (textCy + dp(13) / 2f)); + audioIcon.setAlpha((int) (0xFF * (1f - audioSelected))); + audioIcon.draw(canvas); + x += dpf2(13 + 3.11f); + canvas.saveLayerAlpha(0, 0, w, h, 0xFF, Canvas.ALL_SAVE_FLAG); + final float x2 = Math.min(tright, w) - dp(12); + canvas.clipRect(x, 0, x2, h); + if (audioAuthor != null) { + canvas.save(); + canvas.translate(x - audioAuthorLeft, textCy - audioAuthor.getHeight() / 2f); + audioAuthorPaint.setAlpha((int) (0xFF * (1f - audioSelected) * audioT)); + audioAuthor.draw(canvas); + canvas.restore(); + x += audioAuthorWidth; + } + if (audioAuthor != null && audioTitle != null) { + x += dpf2(3.66f); + int wasAlpha = audioDotPaint.getAlpha(); + audioDotPaint.setAlpha((int) (wasAlpha * (1f - audioSelected))); + canvas.drawCircle(x + dp(1), textCy, dp(1), audioDotPaint); + audioDotPaint.setAlpha(wasAlpha); + x += dpf2(2); + x += dpf2(4); + } + if (audioTitle != null) { + canvas.save(); + canvas.translate(x - audioTitleLeft, textCy - audioTitle.getHeight() / 2f); + audioTitlePaint.setAlpha((int) (0xFF * (1f - audioSelected) * audioT)); + audioTitle.draw(canvas); + canvas.restore(); + } + if (!fit) { + ellipsizeMatrix.reset(); + ellipsizeMatrix.postScale(dpf2(8) / 16, 1); + ellipsizeMatrix.postTranslate(x2 - dp(8), 0); + ellipsizeGradient.setLocalMatrix(ellipsizeMatrix); + canvas.drawRect(x2 - dp(8), audioBounds.top, x2, audioBounds.bottom, ellipsizePaint); + } canvas.restore(); } - if (!fit) { - ellipsizeMatrix.reset(); - ellipsizeMatrix.postScale(dpf2(8) / 16, 1); - ellipsizeMatrix.postTranslate(x2 - dp(8), 0); - ellipsizeGradient.setLocalMatrix(ellipsizeMatrix); - canvas.drawRect(x2 - dp(8), bottom - audioHeight, x2, bottom, ellipsizePaint); - } canvas.restore(); } - canvas.restore(); - } - // draw region - final float audio = audioT * (hasVideo || hasRound ? audioSelected : 1); - final float round = roundT * (hasVideo || hasAudio ? roundSelected : 1); - final float video = videoSelected; - float regionTop = 0; - regionTop += (h - py - videoHeight - p * videoT - roundHeight - p * roundT - audioHeight) * audio; - regionTop += (h - py - videoHeight - p * videoT - roundHeight) * round; - regionTop += (h - py - videoHeight) * video; - float regionBottom = 0; - regionBottom += (h - py - videoHeight - p * videoT - roundHeight - p * roundT) * audio; - regionBottom += (h - py - videoHeight - p * videoT) * round; - regionBottom += (h - py) * video; - float left = 0; - left += (audioOffset + audioLeft * audioDuration) * audio; - left += (roundOffset + roundLeft * roundDuration) * round; - left += (videoLeft * videoDuration) * video; - float right = 0; - right += (audioOffset + audioRight * audioDuration) * audio; - right += (roundOffset + roundRight * roundDuration) * round; - right += (videoRight * videoDuration) * video; - float leftX = px + ph + (left - scroll) / (float) scrollDuration * sw; - float rightX = px + ph + (right - scroll) / (float) scrollDuration * sw; - float progressAlpha = (hasAudio && !hasVideo ? audioT : Math.max(videoT, roundT)); - if (audioT > 0. || roundT > 0. || videoT > 0.) { - drawRegion(canvas, blurPaint, regionTop, regionBottom, leftX, rightX, (hasVideo || hasRound ? 1 : lerp(.6f, 1f, audioSelected) * audioT) * progressAlpha); - if (hasVideo && (hasAudio || hasRound) && (audioSelected > 0 || roundSelected > 0)) { - drawRegion( - canvas, - blurPaint, - h - py - videoHeight, - h - py, - ph + px + (videoLeft * videoDuration - scroll) / (float) scrollDuration * sw, - ph + px + (videoRight * videoDuration - scroll) / (float) scrollDuration * sw, - .8f - ); - } - - // draw progress - float loopT = loopProgress.set(0); - final float y1 = h - py - videoHeight - (audioHeight + p * Math.max(roundT, videoT)) * audioT - (roundHeight + p * videoT) * roundT - dpf2(4.3f); - final float y2 = h - py + dpf2(4.3f); - if (loopT > 0) { - final long end; - if (loopProgressFrom != -1) { - end = loopProgressFrom; - } else if (hasVideo) { - end = (long) (videoDuration * videoRight); - } else if (hasRound) { - end = (long) (roundDuration * roundRight); - } else { - end = (long) (audioDuration * audioRight); + // draw region + float leftX = px + ph + (left - scroll) / (float) scrollDuration * sw; + float rightX = px + ph + (right - scroll) / (float) scrollDuration * sw; + float progressAlpha = !collageTracks.isEmpty() ? collageT : (hasAudio && videoTrack == null ? audioT : Math.max(videoT, roundT)); + if (audioT > 0. || roundT > 0. || videoT > 0. || collageT > 0.) { + drawRegion(canvas, blurPaint, regionTop, regionBottom, leftX, rightX, (videoTrack != null || hasRound || !collageTracks.isEmpty() ? 1 : lerp(.6f, 1f, audioSelected) * audioT) * progressAlpha); + if (videoTrack != null && (hasAudio || hasRound) && (audioSelected > 0 || roundSelected > 0)) { + drawRegion( + canvas, + blurPaint, + h - py - videoHeight, + h - py, + ph + px + (videoTrack.left * videoTrack.duration - scroll) / (float) scrollDuration * sw, + ph + px + (videoTrack.right * videoTrack.duration - scroll) / (float) scrollDuration * sw, + .8f + ); + } else if (collageMain != null && collageTracks.size() > 1) { + drawRegion( + canvas, + blurPaint, + collageMain.bounds.top, + collageMain.bounds.bottom, + ph + px + (collageMain.offset + collageMain.left * collageMain.duration - scroll) / (float) scrollDuration * sw, + ph + px + (collageMain.offset + collageMain.right * collageMain.duration - scroll) / (float) scrollDuration * sw, + .8f + ); } - drawProgress(canvas, y1, y2, end, loopT * progressAlpha); + + // draw progress + float loopT = loopProgress.set(0); + final float y1 = h - getContentHeight() + py - dpf2(2.3f); + final float y2 = h - py + dpf2(4.3f); + if (loopT > 0) { + final long end; + if (loopProgressFrom != -1) { + end = loopProgressFrom; + } else if (videoTrack != null) { + end = (long) (videoTrack.duration * videoTrack.right); + } else if (collageMain != null) { + end = (long) (collageMain.duration * (collageMain.right - collageMain.left)); + } else if (hasRound) { + end = (long) (roundDuration * roundRight); + } else { + end = (long) (audioDuration * audioRight); + } + drawProgress(canvas, y1, y2, end, loopT * progressAlpha); + } + drawProgress(canvas, y1, y2, progress, (1f - loopT) * progressAlpha); + } + + if (restore) { + canvas.restore(); } - drawProgress(canvas, y1, y2, progress, (1f - loopT) * progressAlpha); } if (dragged) { long Δd = (long) (dp(32) / (float) sw * scrollDuration * (1f / (1000f / AndroidUtilities.screenRefreshRate))); - if (pressHandle == HANDLE_VIDEO_REGION) { + if (pressHandle == HANDLE_VIDEO_REGION && videoTrack != null) { int direction = 0; - if (videoLeft < (scroll / (float) videoDuration)) { + if (videoTrack.left < (scroll / (float) videoTrack.duration)) { direction = -1; - } else if (videoRight > ((scroll + scrollDuration) / (float) videoDuration)) { + } else if (videoTrack.right > ((scroll + scrollDuration) / (float) videoTrack.duration)) { direction = +1; } long wasScroll = scroll; - scroll = Utilities.clamp(scroll + direction * Δd, videoDuration - scrollDuration, 0); + scroll = Utilities.clamp(scroll + direction * Δd, videoTrack.duration - scrollDuration, 0); progress += direction * Δd; - float d = (scroll - wasScroll) / (float) videoDuration; + float d = (scroll - wasScroll) / (float) videoTrack.duration; if (d > 0) { - d = Math.min(1 - videoRight, d); + d = Math.min(1 - videoTrack.right, d); } else { - d = Math.max(0 - videoLeft, d); + d = Math.max(0 - videoTrack.left, d); } - videoLeft = Utilities.clamp(videoLeft + d, 1, 0); - videoRight = Utilities.clamp(videoRight + d, 1, 0); + videoTrack.left = Utilities.clamp(videoTrack.left + d, 1, 0); + videoTrack.right = Utilities.clamp(videoTrack.right + d, 1, 0); if (delegate != null) { - delegate.onVideoLeftChange(videoLeft); - delegate.onVideoRightChange(videoRight); + delegate.onVideoLeftChange(videoTrack.left); + delegate.onVideoRightChange(videoTrack.right); } invalidate(); } else if (pressHandle == HANDLE_AUDIO_REGION) { @@ -1765,12 +2550,12 @@ public class TimelineView extends View { } if (direction != 0) { long wasOffset = audioOffset; - if (this.audioSelected && hasVideo) { - audioOffset = Utilities.clamp(audioOffset - direction * Δd, (long) ((videoRight * videoDuration) - (audioLeft * audioDuration)), (long) ((videoLeft * videoDuration) - (audioRight * audioDuration))); + if (this.audioSelected && videoTrack != null) { + audioOffset = Utilities.clamp(audioOffset - direction * Δd, (long) ((videoTrack.right * videoTrack.duration) - (audioLeft * audioDuration)), (long) ((videoTrack.left * videoTrack.duration) - (audioRight * audioDuration))); } else if (this.roundSelected && hasRound) { audioOffset = Utilities.clamp(audioOffset - direction * Δd, (long) ((roundRight * roundDuration) - (audioLeft * audioDuration)), (long) ((roundLeft * roundDuration) - (audioRight * audioDuration))); } else{ - audioOffset = Utilities.clamp(audioOffset - direction * Δd, 0, (long) -(audioDuration - Math.min(getBaseDuration(), MAX_SCROLL_DURATION))); + audioOffset = Utilities.clamp(audioOffset - direction * Δd, 0, (long) -(audioDuration - Math.min(getBaseDuration(), getMaxScrollDuration()))); } float d = -(audioOffset - wasOffset) / (float) audioDuration; if (d > 0) { @@ -1778,7 +2563,7 @@ public class TimelineView extends View { } else { d = Math.max(0 - audioLeft, d); } - if (!hasVideo) { + if (videoTrack == null) { progress = (long) Utilities.clamp(progress + d * audioDuration, audioDuration, 0); } audioLeft = Utilities.clamp(audioLeft + d, 1, 0); @@ -1792,6 +2577,7 @@ public class TimelineView extends View { } } } + } private void drawRegion(Canvas canvas, Paint blurPaint, float top, float bottom, float left, float right, float alpha) { @@ -1847,12 +2633,12 @@ public class TimelineView extends View { private void drawProgress(Canvas canvas, float y1, float y2, long progress, float scale) { if (isCover) return; - final long scrollDuration = Math.min(getBaseDuration(), MAX_SCROLL_DURATION); + final long scrollDuration = Math.min(getBaseDuration(), getMaxScrollDuration()); - final float progressT = (Utilities.clamp(progress, getBaseDuration(), 0) + (!hasVideo ? audioOffset : 0) - scroll) / (float) scrollDuration; + final float progressT = (Utilities.clamp(progress, getBaseDuration(), 0) + (collageMain != null ? collageMain.offset + collageMain.left * collageMain.duration : (videoTrack == null ? audioOffset : 0)) - scroll) / (float) scrollDuration; final float progressX = px + ph + sw * progressT; - float yd = (y1 + y2) / 2; + final float yd = (y2 - y1) / 2; y1 += yd / 2f * (1f - scale); y2 -= yd / 2f * (1f - scale); progressShadowPaint.setAlpha((int) (0x26 * scale)); @@ -1869,21 +2655,28 @@ public class TimelineView extends View { private int w, h, ph, px, py; public static int heightDp() { - return 5 + 38 + 4 + 28 + 4 + 28 + 5; + final int maxCollageCount = 9; + return 5 + 38 + 4 + 28 + 4 + 28 + 5 + (maxCollageCount - 1) * 28 + (maxCollageCount > 0 ? 32 : 0) + 5 * 4; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { audioAuthorPaint.setTextSize(dp(12)); audioTitlePaint.setTextSize(dp(12)); - waveformRadii[0] = waveformRadii[1] = waveformRadii[2] = waveformRadii[3] = dp(2); - waveformRadii[4] = waveformRadii[5] = waveformRadii[6] = waveformRadii[7] = 0; setPadding(px = dp(12), py = dp(5), dp(12), dp(5)); setMeasuredDimension(w = MeasureSpec.getSize(widthMeasureSpec), h = dp(heightDp())); ph = dp(10); sw = w - 2 * ph - 2 * px; - if (videoPath != null && this.thumbs == null) { - setupVideoThumbs(false); + if (videoTrack != null && videoTrack.path != null && videoTrack.thumbs == null) { + videoTrack.setupThumbs(false); + } + if (!collageTracks.isEmpty()) { + for (Track track : collageTracks) { + if (track.path != null && track.thumbs == null) { + track.setupThumbs(false); + track.setupWaveform(false); + } + } } if (audioPath != null && this.waveform == null) { setupAudioWaveform(); @@ -1893,81 +2686,86 @@ public class TimelineView extends View { private class VideoThumbsLoader { private long duration; - private final long frameIterator; - private final int count; + private volatile long frameIterator; + private int count; private final ArrayList frames = new ArrayList<>(); private MediaMetadataRetriever metadataRetriever; - private final int frameWidth; - private final int frameHeight; + private volatile int frameWidth; + private volatile int frameHeight; private final boolean isRound; private boolean destroyed; public VideoThumbsLoader(boolean isRound, String path, int uiWidth, int uiHeight, Long overrideDuration) { - this(isRound, path, uiWidth, uiHeight, overrideDuration, MAX_SCROLL_DURATION); + this(isRound, path, uiWidth, uiHeight, overrideDuration, getMaxScrollDuration(), -1, -1, null); } public VideoThumbsLoader(boolean isRound, String path, int uiWidth, int uiHeight, Long overrideDuration, long maxDuration) { - this(isRound, path, uiWidth, uiHeight, overrideDuration, maxDuration, -1, -1); + this(isRound, path, uiWidth, uiHeight, overrideDuration, maxDuration, -1, -1, null); } - public VideoThumbsLoader(boolean isRound, String path, int uiWidth, int uiHeight, Long overrideDuration, long maxDuration, long startFrom, long endTo) { + public VideoThumbsLoader(boolean isRound, String path, int uiWidth, int uiHeight, Long overrideDuration, long maxDuration, long startFrom, long endTo, Runnable inited) { this.isRound = isRound; metadataRetriever = new MediaMetadataRetriever(); - long duration = MAX_SCROLL_DURATION; - int width = 0; - int height = 0; - try { - metadataRetriever.setDataSource(path); - String value; - value = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); - if (value != null) { - this.duration = duration = Long.parseLong(value); - } - value = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); - if (value != null) { - width = Integer.parseInt(value); - } - value = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT); - if (value != null) { - height = Integer.parseInt(value); - } - value = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); - if (value != null) { - int orientation = Integer.parseInt(value); - if (orientation == 90 || orientation == 270) { - int temp = width; - width = height; - height = temp; + Utilities.themeQueue.postRunnable(() -> { + long duration = getMaxScrollDuration(); + int width = 0; + int height = 0; + try { + metadataRetriever.setDataSource(path); + String value; + value = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); + if (value != null) { + this.duration = duration = Long.parseLong(value); } + value = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); + if (value != null) { + width = Integer.parseInt(value); + } + value = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT); + if (value != null) { + height = Integer.parseInt(value); + } + value = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); + if (value != null) { + int orientation = Integer.parseInt(value); + if (orientation == 90 || orientation == 270) { + int temp = width; + width = height; + height = temp; + } + } + } catch (Exception e) { + metadataRetriever = null; + FileLog.e(e); } - } catch (Exception e) { - metadataRetriever = null; - FileLog.e(e); - } - if (overrideDuration != null) { - this.duration = duration = overrideDuration; - } - if (startFrom != -1 && endTo != -1) { - duration = endTo - startFrom; - } - float aspectRatio = 1; - if (width != 0 && height != 0) { - aspectRatio = width / (float) height; - } - aspectRatio = Utilities.clamp(aspectRatio, 4 / 3f, 9f / 16f); - frameHeight = Math.max(1, uiHeight); - frameWidth = Math.max(1, (int) Math.ceil(uiHeight * aspectRatio)); - final float uiScrollWidth = Math.max(duration, maxDuration) / (float) maxDuration * uiWidth; - count = (int) Math.ceil(uiScrollWidth / frameWidth); - frameIterator = (long) (duration / (float) count); - nextFrame = -frameIterator; - if (startFrom != -1) { - nextFrame = startFrom - frameIterator; - } - load(); + if (overrideDuration != null) { + this.duration = duration = overrideDuration; + } + if (startFrom != -1 && endTo != -1) { + duration = endTo - startFrom; + } + float aspectRatio = 1; + if (width != 0 && height != 0) { + aspectRatio = width / (float) height; + } + aspectRatio = Utilities.clamp(aspectRatio, 4 / 3f, 9f / 16f); + frameHeight = Math.max(1, uiHeight); + frameWidth = Math.max(1, (int) Math.ceil(uiHeight * aspectRatio)); + final float uiScrollWidth = Math.max(duration, maxDuration) / (float) maxDuration * uiWidth; + count = (int) Math.ceil(uiScrollWidth / frameWidth); + frameIterator = (long) (duration / (float) count); + nextFrame = -frameIterator; + if (startFrom != -1) { + nextFrame = startFrom - frameIterator; + } + load(); + if (inited != null) { + AndroidUtilities.runOnUIThread(inited); + } + }); } public int getFrameWidth() { @@ -2092,6 +2890,7 @@ public class TimelineView extends View { } private class AudioWaveformLoader { + private final AnimatedFloat animatedLoaded = new AnimatedFloat(TimelineView.this, 0, 600, CubicBezierInterpolator.EASE_OUT_QUINT); private final int count; private int loaded = 0; @@ -2132,14 +2931,14 @@ public class TimelineView extends View { FileLog.e(e); } - final float videoScrollWidth = Math.min(hasVideo ? videoDuration : (hasRound ? roundDuration : duration * 1000), MAX_SCROLL_DURATION); + final float videoScrollWidth = Math.min(videoTrack != null ? videoTrack.duration : (!collageTracks.isEmpty() ? getBaseDuration() : (hasRound ? roundDuration : duration * 1000)), getMaxScrollDuration()); final float uiScrollWidth = (duration * 1000) / videoScrollWidth * uiWidth; final int sampleWidth = Math.round(dpf2(3.3333f)); - count = Math.round(uiScrollWidth / sampleWidth); + count = Math.min(Math.round(uiScrollWidth / sampleWidth), 4000); data = new short[count]; if (duration > 0 && inputFormat != null) { - if ("audio/mpeg".equals(mime) || "audio/mp3".equals(mime)) { + if ("audio/mpeg".equals(mime) || "audio/mp3".equals(mime) || "audio/mp4a".equals(mime) || "audio/mp4a-latm".equals(mime)) { waveformLoader = new FfmpegAudioWaveformLoader(path, count, this::receiveData); } else { Utilities.phoneBookQueue.postRunnable(this::run); @@ -2314,7 +3113,11 @@ public class TimelineView extends View { } } + public int getTimelineHeight() { + return lerp(py + dp(28) + py, getContentHeight(), openT.get()); + } + public int getContentHeight() { - return (int) (py + (hasVideo ? getVideoHeight() + dp(4) : 0) + (hasRound ? getRoundHeight() + dp(4) : 0) + (hasAudio ? getAudioHeight() + dp(4) : 0) + py); + return (int) (py + (videoTrack != null ? getVideoHeight() + dp(4) : 0) + (collageTracks.isEmpty() ? 0 : getCollageHeight() + dp(4)) + (hasRound ? getRoundHeight() + dp(4) : 0) + (hasAudio ? getAudioHeight() + dp(4) : 0) + py); } } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/ToggleButton2.java b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/ToggleButton2.java index 8046c11a5..1c92979c6 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/ToggleButton2.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/Stories/recorder/ToggleButton2.java @@ -37,7 +37,7 @@ public class ToggleButton2 extends View implements FlashViews.Invertable { } private boolean selected; - private AnimatedFloat animatedSelected = new AnimatedFloat(this, 0, 380, CubicBezierInterpolator.EASE_OUT_QUINT); + private final AnimatedFloat animatedSelected = new AnimatedFloat(this, 0, 380, CubicBezierInterpolator.EASE_OUT_QUINT); private Drawable drawable; private Bitmap activeBitmap; @@ -50,6 +50,7 @@ public class ToggleButton2 extends View implements FlashViews.Invertable { if (currentIcon == iconRes) { return; } + currentIcon = iconRes; if (animator != null) { animator.cancel(); @@ -66,6 +67,7 @@ public class ToggleButton2 extends View implements FlashViews.Invertable { changed.set(true); setDrawable(iconRes); } + invalidate(); }); animator.start(); } else { @@ -74,11 +76,47 @@ public class ToggleButton2 extends View implements FlashViews.Invertable { } } + public void setIcon(Drawable iconDrawable, boolean animated) { + if (drawable == iconDrawable) { + return; + } + + if (animator != null) { + animator.cancel(); + animator = null; + } + + if (animated) { + animator = ValueAnimator.ofFloat(0, 1).setDuration(150); + AtomicBoolean changed = new AtomicBoolean(); + animator.addUpdateListener(animation -> { + float val = (float) animation.getAnimatedValue(); + this.scale = 0.5f + Math.abs(val - 0.5f); + if (val >= 0.5f && !changed.get()) { + changed.set(true); + setDrawable(iconDrawable); + } + }); + animator.start(); + } else { + scale = 1f; + setDrawable(iconDrawable); + } + } + public void setSelected(boolean selected) { this.selected = selected; invalidate(); } + public void setSelected(boolean selected, boolean animated) { + this.selected = selected; + if (!animated) { + animatedSelected.set(selected ? 1.0f : 0.0f, true); + } + invalidate(); + } + @Override public void setInvert(float invert) { if (drawable != null) { @@ -88,7 +126,7 @@ public class ToggleButton2 extends View implements FlashViews.Invertable { invalidate(); } - private void setDrawable(int iconRes) { + public void setDrawable(int iconRes) { drawable = getContext().getResources().getDrawable(iconRes).mutate(); if (activeBitmap != null) { activeBitmap.recycle(); @@ -97,6 +135,21 @@ public class ToggleButton2 extends View implements FlashViews.Invertable { if (activeBitmap == null && iconRes != 0) { activeBitmap = BitmapFactory.decodeResource(getResources(), iconRes); } + invalidate(); + } + + public void setDrawable(Drawable drawable) { + this.drawable = drawable; + if (activeBitmap != null) { + activeBitmap.recycle(); + activeBitmap = null; + } + if (activeBitmap == null && drawable != null && drawable.getIntrinsicWidth() > 0 && drawable.getIntrinsicHeight() > 0) { + activeBitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + drawable.setBounds(0, 0, activeBitmap.getWidth(), activeBitmap.getHeight()); + drawable.draw(new Canvas(activeBitmap)); + } + invalidate(); } @Override @@ -106,6 +159,8 @@ public class ToggleButton2 extends View implements FlashViews.Invertable { } float t = animatedSelected.set(selected); +// canvas.save(); +// canvas.scale(scale, scale, getWidth() / 2.0f, getHeight() / 2.0f); final int w = drawable.getIntrinsicWidth(), h = drawable.getIntrinsicHeight(); @@ -133,6 +188,8 @@ public class ToggleButton2 extends View implements FlashViews.Invertable { canvas.restore(); canvas.restore(); } + +// canvas.restore(); } @Override diff --git a/TMessagesProj/src/main/java/org/telegram/ui/TopicsFragment.java b/TMessagesProj/src/main/java/org/telegram/ui/TopicsFragment.java index f7157d87b..9db4fb59f 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/TopicsFragment.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/TopicsFragment.java @@ -2579,7 +2579,7 @@ public class TopicsFragment extends BaseFragment implements NotificationCenter.N private void updateSubtitle() { TLRPC.ChatFull chatFull = getMessagesController().getChatFull(chatId); - if (this.chatFull != null && this.chatFull.participants != null) { + if (chatFull != null && this.chatFull != null && this.chatFull.participants != null) { chatFull.participants = this.chatFull.participants; } this.chatFull = chatFull; diff --git a/TMessagesProj/src/main/java/org/telegram/ui/bots/AffiliateProgramFragment.java b/TMessagesProj/src/main/java/org/telegram/ui/bots/AffiliateProgramFragment.java new file mode 100644 index 000000000..df2453ad6 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/bots/AffiliateProgramFragment.java @@ -0,0 +1,767 @@ +package org.telegram.ui.bots; + +import static org.telegram.messenger.AndroidUtilities.dp; +import static org.telegram.messenger.LocaleController.formatPluralString; +import static org.telegram.messenger.LocaleController.getString; +import static org.telegram.ui.Components.Premium.boosts.cells.selector.SelectorUserCell.buildCountDownTime; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.text.TextUtils; +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.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.core.graphics.ColorUtils; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.gms.vision.Frame; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.messenger.Utilities; +import org.telegram.messenger.browser.Browser; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.TLRPC; +import org.telegram.tgnet.tl.TL_bots; +import org.telegram.tgnet.tl.TL_payments; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.INavigationLayout; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Adapters.MessagesSearchAdapter; +import org.telegram.ui.Cells.HeaderCell; +import org.telegram.ui.Components.Bulletin; +import org.telegram.ui.Components.BulletinFactory; +import org.telegram.ui.Components.CubicBezierInterpolator; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.LinkSpanDrawable; +import org.telegram.ui.Components.Premium.GLIcon.GLIconRenderer; +import org.telegram.ui.Components.Premium.GLIcon.GLIconTextureView; +import org.telegram.ui.Components.Premium.GLIcon.Icon3D; +import org.telegram.ui.Components.Premium.StarParticlesView; +import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.Components.SizeNotifierFrameLayout; +import org.telegram.ui.Components.TableView; +import org.telegram.ui.Components.UItem; +import org.telegram.ui.Components.UniversalAdapter; +import org.telegram.ui.GradientHeaderActivity; +import org.telegram.ui.PremiumFeatureCell; +import org.telegram.ui.ProfileActivity; +import org.telegram.ui.Stars.ExplainStarsSheet; +import org.telegram.ui.Stars.StarsIntroActivity; +import org.telegram.ui.Stories.recorder.ButtonWithCounterView; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +public class AffiliateProgramFragment extends GradientHeaderActivity implements NotificationCenter.NotificationCenterDelegate { + + private final long bot_id; + + private FrameLayout aboveTitleView; + private GLIconTextureView iconTextureView; + private View emptyLayout; + + private LinearLayout buttonLayout; + private ButtonWithCounterView button; + private LinkSpanDrawable.LinksTextView buttonSubtext; + + public AffiliateProgramFragment(long bot_id) { + this.bot_id = bot_id; + + setWhiteBackground(true); + setMinusHeaderHeight(dp(60)); + } + + @Override + public View createView(Context context) { + useFillLastLayoutManager = false; + particlesViewHeight = dp(32 + 190 + 16); +// transactionsLayout = new StarsIntroActivity.StarsTransactionsLayout(context, currentAccount, 0, getClassGuid(), getResourceProvider()); + emptyLayout = new View(context) { + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int firstViewHeight; + if (AffiliateProgramFragment.this.isLandscapeMode) { + firstViewHeight = AffiliateProgramFragment.this.statusBarHeight + actionBar.getMeasuredHeight() - AndroidUtilities.dp(16); + } else { + int h = AndroidUtilities.dp(140) + statusBarHeight; + if (backgroundView.getMeasuredHeight() + AndroidUtilities.dp(24) > h) { + h = backgroundView.getMeasuredHeight() + AndroidUtilities.dp(24); + } + firstViewHeight = h; + } + firstViewHeight -= 2.5f * yOffset; + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(firstViewHeight, MeasureSpec.EXACTLY)); + } + }; + emptyLayout.setBackgroundColor(Theme.getColor(Theme.key_dialogBackgroundGray)); + + super.createView(context); + + aboveTitleView = new FrameLayout(context); + aboveTitleView.setClickable(true); + iconTextureView = new GLIconTextureView(context, GLIconRenderer.DIALOG_STYLE, Icon3D.TYPE_DEAL); + iconTextureView.mRenderer.colorKey1 = Theme.key_starsGradient1; + iconTextureView.mRenderer.colorKey2 = Theme.key_starsGradient2; + iconTextureView.mRenderer.updateColors(); + iconTextureView.setStarParticlesView(particlesView); + aboveTitleView.addView(iconTextureView, LayoutHelper.createFrame(190, 190, Gravity.CENTER, 0, 32, 0, 24)); + configureHeader(getString(R.string.BotAffiliateProgramTitle), getString(R.string.BotAffiliateProgramText), aboveTitleView, null); + + buttonLayout = new LinearLayout(context); + buttonLayout.setOrientation(LinearLayout.VERTICAL); + buttonLayout.setBackgroundColor(getThemedColor(Theme.key_windowBackgroundWhite)); + + View buttonShadow = new View(context); + buttonShadow.setBackgroundColor(getThemedColor(Theme.key_divider)); + buttonLayout.addView(buttonShadow, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 1.0f / AndroidUtilities.density)); + + button = new ButtonWithCounterView(context, resourceProvider) { + @Override + protected boolean subTextSplitToWords() { + return false; + } + }; + button.setText(getString(R.string.AffiliateProgramStart), false); + button.setOnClickListener(v -> { + if (!button.isEnabled()) return; + final FrameLayout layout = new FrameLayout(context); + final TableView tableView = new TableView(context, resourceProvider); + final Runnable send = () -> { + TL_bots.updateStarRefProgram req = new TL_bots.updateStarRefProgram(); + req.bot = getMessagesController().getInputUser(bot_id); + req.commission_permille = program.commission_permille; + req.duration_months = program.duration_months; + if (program.duration_months > 0) { + req.flags |= 1; + program.duration_months |= 1; + } else { + req.flags &=~ 1; + program.duration_months &=~ 1; + } + final AlertDialog progressDialog = new AlertDialog(getContext(), AlertDialog.ALERT_TYPE_SPINNER); + progressDialog.showDelayed(150); + getConnectionsManager().sendRequest(req, (res, err) -> AndroidUtilities.runOnUIThread(() -> { + progressDialog.dismiss(); + if (res instanceof TL_payments.starRefProgram) { + TL_payments.starRefProgram newProgram = (TL_payments.starRefProgram) res; + TLRPC.UserFull userFull = getMessagesController().getUserFull(bot_id); + if (userFull != null) { + userFull.starref_program = newProgram; + getMessagesStorage().updateUserInfo(userFull, false); + NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.userInfoDidLoad, bot_id, userFull); + } + closeToProfile(false); + } else if (err != null) { + BulletinFactory.showError(err); + } + })); + }; + tableView.addRow(getString(R.string.AffiliateProgramCommission), percents(program.commission_permille)); + tableView.addRow(getString(R.string.AffiliateProgramDuration), program.duration_months <= 0 ? getString(R.string.Infinity) : program.duration_months < 12 || program.duration_months % 12 != 0 ? LocaleController.formatPluralString("Months", program.duration_months) : LocaleController.formatPluralString("Years", program.duration_months / 12)); + layout.addView(tableView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.FILL, 24, 0, 24, 0)); + new AlertDialog.Builder(context, resourceProvider) + .setTitle(getString(R.string.AffiliateProgramAlert)) + .setMessage(getString(new_program ? R.string.AffiliateProgramStartAlertText : R.string.AffiliateProgramUpdateAlertText)) + .setView(layout) + .setPositiveButton(getString(new_program ? R.string.AffiliateProgramStartAlertButton : R.string.AffiliateProgramUpdateAlertButton), (d, w) -> send.run()) + .setNegativeButton(getString(R.string.Cancel), null) + .show(); + }); + buttonLayout.addView(button, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48, 10, 10, 10, 7)); + + buttonSubtext = new LinkSpanDrawable.LinksTextView(context, resourceProvider); + buttonSubtext.setTextColor(getThemedColor(Theme.key_windowBackgroundWhiteGrayText2)); + buttonSubtext.setLinkTextColor(getThemedColor(Theme.key_chat_messageLinkIn)); + buttonSubtext.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); + buttonSubtext.setGravity(Gravity.CENTER); + buttonLayout.addView(buttonSubtext, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 32, 1, 32, 8)); + update(false); + + ((FrameLayout) fragmentView).addView(buttonLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM | Gravity.FILL_HORIZONTAL)); + listView.setPadding(0, 0, 0, dp(10 + 48 + 10 + 16)); + listView.setOnItemClickListener((view, position) -> { + if (adapter == null) return; + UItem item = adapter.getItem(position); + if (item.id == BUTTON_END) { + end(); + } else if (item.id == BUTTON_EXAMPLES) { + presentFragment(new SuggestedAffiliateProgramsFragment(bot_id)); + } + }); + DefaultItemAnimator itemAnimator = new DefaultItemAnimator(); + itemAnimator.setSupportsChangeAnimations(false); + itemAnimator.setDelayAnimations(false); + itemAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT); + itemAnimator.setDurations(350); + listView.setItemAnimator(itemAnimator); + + return fragmentView; + } + + private void closeToProfile(boolean ended) { + BaseFragment bulletinFragment = null; + if (getParentLayout() != null && getParentLayout().getFragmentStack() != null) { + INavigationLayout parentLayout = getParentLayout(); + List fragments = parentLayout.getFragmentStack(); + BaseFragment profileActivity = null; + int index = -1; + for (int i = fragments.size() - 1; i > 0; --i) { + BaseFragment f = fragments.get(i); + if (f instanceof ProfileActivity && ((ProfileActivity) f).getDialogId() == bot_id) { + profileActivity = f; + index = i; + break; + } + } + if (profileActivity != null) { + for (int i = fragments.size() - 1; i > index; --i) { + parentLayout.removeFragmentFromStack(fragments.get(i)); + } + finishFragment(); + bulletinFragment = profileActivity; + } else { + finishFragment(); + bulletinFragment = parentLayout.getBackgroundFragment(); + } + } else { + finishFragment(); + } + + if (bulletinFragment != null) { + if (ended) { + BulletinFactory.of(bulletinFragment) + .createSimpleBulletin(R.raw.linkbroken, getString(R.string.AffiliateProgramEndedTitle), AndroidUtilities.replaceTags(getString(R.string.AffiliateProgramEndedText))) + .show(); + } else { + BulletinFactory.of(bulletinFragment) + .createSimpleBulletin(R.raw.contact_check, getString(R.string.AffiliateProgramStartedTitle), getString(R.string.AffiliateProgramStartedText)) + .show(); + } + } + } + + private void update(boolean animated) { + button.setText(getString(new_program || program.end_date != 0 ? R.string.AffiliateProgramStart : R.string.AffiliateProgramUpdate), animated); + AndroidUtilities.cancelRunOnUIThread(updateTimerRunnable); + updateTimerRunnable.run(); + buttonSubtext.setText(AndroidUtilities.replaceSingleTag(getString(new_program || program.end_date != 0 ? R.string.AffiliateProgramStartInfo : R.string.AffiliateProgramUpdateInfo), () -> { + Browser.openUrl(getContext(), getString(new_program || program.end_date != 0 ? R.string.AffiliateProgramUpdateInfoLink : R.string.AffiliateProgramStartInfoLink)); + })); + updateEnabled(); + if (adapter != null) { + adapter.update(animated); + } + } + + private final Runnable updateTimerRunnable = () -> { + button.setSubText(this.program.end_date == 0 ? null : buildCountDownTime((this.program.end_date - getConnectionsManager().getCurrentTime()) * 1000L), true); + if (this.program.end_date != 0 && this.attached) { + AndroidUtilities.runOnUIThread(this.updateTimerRunnable, 1000); + } + }; + + private void updateEnabled() { + button.setEnabled(program.end_date == 0 && (initialProgram == null || initialProgram.commission_permille != program.commission_permille || initialProgram.duration_months != program.duration_months)); + } + + private class BulletinTextView extends TextView { + public BulletinTextView(Context context) { + super(context); + } + @Override + protected void dispatchDraw(@NonNull Canvas canvas) { + super.dispatchDraw(canvas); + canvas.drawCircle(dp(1 + 2.5f), dp(9 + 2.5f), dp(2.5f), getPaint()); + } + } + + private void end() { + LinearLayout layout = new LinearLayout(getContext()); + layout.setOrientation(LinearLayout.VERTICAL); + layout.setPadding(dp(24), 0, dp(24), 0); + + TextView textView = new TextView(getContext()); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourceProvider)); + textView.setText(AndroidUtilities.replaceTags(getString(R.string.AffiliateProgramStopText))); + layout.addView(textView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 0, 0, 17)); + + textView = new BulletinTextView(getContext()); + textView.setPadding(dp(15), 0, 0, 0); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourceProvider)); + textView.setText(AndroidUtilities.replaceTags(getString(R.string.AffiliateProgramStopText1))); + layout.addView(textView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 0, 0, 17)); + + textView = new BulletinTextView(getContext()); + textView.setPadding(dp(15), 0, 0, 0); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourceProvider)); + textView.setText(AndroidUtilities.replaceTags(getString(R.string.AffiliateProgramStopText2))); + layout.addView(textView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 0, 0, 17)); + + textView = new BulletinTextView(getContext()); + textView.setPadding(dp(15), 0, 0, 0); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourceProvider)); + textView.setText(AndroidUtilities.replaceTags(getString(R.string.AffiliateProgramStopText3))); + layout.addView(textView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 0, 0, 10)); + + new AlertDialog.Builder(getContext(), resourceProvider) + .setTitle(getString(R.string.AffiliateProgramAlert)) +// .setMessage(AndroidUtilities.replaceTags(getString(R.string.AffiliateProgramStopText))) + .setView(layout) + .setPositiveButton(getString(R.string.AffiliateProgramStopButton), (d, w) -> { + TL_bots.updateStarRefProgram req = new TL_bots.updateStarRefProgram(); + req.bot = getMessagesController().getInputUser(bot_id); + req.commission_permille = 0; + final AlertDialog progressDialog = new AlertDialog(getContext(), AlertDialog.ALERT_TYPE_SPINNER); + progressDialog.showDelayed(150); + getConnectionsManager().sendRequest(req, (res, err) -> AndroidUtilities.runOnUIThread(() -> { + progressDialog.dismiss(); + if (res instanceof TL_payments.starRefProgram) { + TL_payments.starRefProgram newProgram = (TL_payments.starRefProgram) res; + TLRPC.UserFull userFull = getMessagesController().getUserFull(bot_id); + if (userFull != null) { + program.flags |= 2; + program.end_date = getConnectionsManager().getCurrentTime() + (getConnectionsManager().isTestBackend() ? 300 : 86400); + userFull.starref_program = newProgram; + getMessagesStorage().updateUserInfo(userFull, false); + NotificationCenter.getInstance(currentAccount).postNotificationName(NotificationCenter.userInfoDidLoad, bot_id, userFull); + } + closeToProfile(true); + } else if (err != null) { + BulletinFactory.showError(err); + } + })); + }) + .setNegativeButton(getString(R.string.Cancel), null) + .makeRed(AlertDialog.BUTTON_POSITIVE) + .show(); + } + + private boolean new_program; + private TL_payments.starRefProgram initialProgram; + private TL_payments.starRefProgram program; + + private String[] durationTexts = null; + private final List durationValues = Arrays.asList(1, 3, 6, 12, 24, 36, 0); + + public TL_payments.starRefProgram getDefaultProgram() { + final TL_payments.starRefProgram program = new TL_payments.starRefProgram(); + program.commission_permille = Utilities.clamp(50, getMessagesController().starrefMaxCommissionPermille, getMessagesController().starrefMinCommissionPermille); + program.duration_months = 1; + return program; + } + + private static final int BUTTON_EXAMPLES = 2; + private static final int BUTTON_END = 4; + + public void fillItems(ArrayList items, UniversalAdapter adapter) { + if (getContext() == null) { + return; + } + + items.add(UItem.asFullyCustom(getHeader(getContext()))); + items.add(FeatureCell.Factory.as(R.drawable.menu_feature_premium, getString(R.string.BotAffiliateProgramFeature1Title), getString(R.string.BotAffiliateProgramFeature1))); + items.add(FeatureCell.Factory.as(R.drawable.msg_channel, getString(R.string.BotAffiliateProgramFeature2Title), getString(R.string.BotAffiliateProgramFeature2))); + items.add(FeatureCell.Factory.as(R.drawable.menu_feature_links2, getString(R.string.BotAffiliateProgramFeature3Title), getString(R.string.BotAffiliateProgramFeature3))); + items.add(UItem.asShadow(1, null)); + + items.add(UItem.asHeader(getString(R.string.AffiliateProgramCommission))); + items.add(UItem.asIntSlideView( + 1, + getMessagesController().starrefMinCommissionPermille, program.commission_permille, getMessagesController().starrefMaxCommissionPermille, + val -> String.format(Locale.US, "%.1f%%", val / 10.0f), + val -> { program.commission_permille = val; updateEnabled(); } + ).setMinSliderValue(initialProgram == null ? -1 : initialProgram.commission_permille)); + items.add(UItem.asShadow(getString(R.string.AffiliateProgramCommissionInfo))); + + items.add(UItem.asHeader(getString(R.string.AffiliateProgramDuration))); + if (durationTexts == null) { + durationTexts = new String[durationValues.size()]; + for (int i = 0; i < durationValues.size(); ++i) { + final int d = durationValues.get(i); + if (d == 0) { + durationTexts[i] = getString(R.string.Infinity); + } else if (d < 12 || d % 12 != 0) { + durationTexts[i] = formatPluralString("MonthsShort", d); + } else { + durationTexts[i] = formatPluralString("YearsShort", d / 12); + } + } + } + final UItem slideView = UItem.asSlideView(durationTexts, durationValues.indexOf(program.duration_months), v -> { program.duration_months = durationValues.get(v); updateEnabled(); }); + if (initialProgram != null) { + if (initialProgram.duration_months <= 0) { + slideView.setMinSliderValue(durationValues.size() - 1); + } else { + for (int i = durationValues.size() - 1; i >= 0; --i) { + if (durationValues.get(i) > 0 && durationValues.get(i) <= initialProgram.duration_months) { + slideView.setMinSliderValue(i); + break; + } + } + } + } + items.add(slideView); + items.add(UItem.asShadow(getString(R.string.AffiliateProgramDurationInfo))); + + items.add(ColorfulTextCell.Factory.as(BUTTON_EXAMPLES, getThemedColor(Theme.key_color_green), R.drawable.filled_earn_stars, getString(R.string.AffiliateProgramExistingProgramsTitle), getString(R.string.AffiliateProgramExistingProgramsText))); + items.add(UItem.asShadow(3, null)); + + if (!new_program && program.end_date == 0) { + items.add(UItem.asButton(BUTTON_END, getString(R.string.AffiliateProgramStop)).red()); + items.add(UItem.asShadow(5, null)); + } + + items.add(UItem.asShadow(6, null)); + items.add(UItem.asShadow(7, null)); + + } + + @Override + public StarParticlesView createParticlesView() { + return makeParticlesView(getContext(), 75, 1); + } + + public static StarParticlesView makeParticlesView(Context context, int particlesCount, int type) { + return new StarParticlesView(context) { + @Override + protected void configure() { + super.configure(); + drawable.useGradient = true; + drawable.useBlur = false; + drawable.forceMaxAlpha = true; + drawable.checkBounds = true; + drawable.init(); + } + + @Override + protected int getStarsRectWidth() { + return getMeasuredWidth(); + } + + { setClipWithGradient(); } + }; + } + + private boolean attached; + + @Override + public boolean onFragmentCreate() { + attached = true; + new_program = true; + program = getDefaultProgram(); + initialProgram = null; + TLRPC.UserFull userFull = getMessagesController().getUserFull(bot_id); + if (userFull != null) { + new_program = false; + program = userFull.starref_program; + if (program == null) { + new_program = true; + program = getDefaultProgram(); + initialProgram = null; + } else { + initialProgram = new TL_payments.starRefProgram(); + initialProgram.commission_permille = program.commission_permille; + initialProgram.duration_months = program.duration_months; + } + } else { + TLRPC.User user = getMessagesController().getUser(bot_id); + if (user != null) { + getMessagesController().loadFullUser(user, getClassGuid(), true, info -> AndroidUtilities.runOnUIThread(() -> { + if (info != null) { + new_program = false; + program = info.starref_program; + if (program == null) { + new_program = true; + program = getDefaultProgram(); + initialProgram = null; + } else { + initialProgram = new TL_payments.starRefProgram(); + initialProgram.commission_permille = program.commission_permille; + initialProgram.duration_months = program.duration_months; + } + } + update(true); + })); + } + } + return super.onFragmentCreate(); + } + + @Override + public void onFragmentDestroy() { + attached = false; + AndroidUtilities.cancelRunOnUIThread(updateTimerRunnable); + super.onFragmentDestroy(); + } + + @Override + public void didReceivedNotification(int id, int account, Object... args) { + + } + + @Override + protected View getHeader(Context context) { + return super.getHeader(context); + } + + @Override + public void onResume() { + super.onResume(); + if (iconTextureView != null) { + iconTextureView.setPaused(false); + iconTextureView.setDialogVisible(false); + } + } + + @Override + public void onPause() { + super.onPause(); + if (iconTextureView != null) { + iconTextureView.setPaused(true); + iconTextureView.setDialogVisible(true); + } + } + + private UniversalAdapter adapter; + @Override + protected RecyclerView.Adapter createAdapter() { + return adapter = new UniversalAdapter(listView, getContext(), currentAccount, classGuid, true, this::fillItems, getResourceProvider()) { + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + if (viewType == UniversalAdapter.VIEW_TYPE_ANIMATED_HEADER) { + HeaderCell headerCell = new HeaderCell(getContext(), Theme.key_windowBackgroundWhiteBlueHeader, 21, 0, false, resourceProvider); + headerCell.setHeight(40 - 15); + return new RecyclerListView.Holder(headerCell); + } + return super.onCreateViewHolder(parent, viewType); + } + }; + } + + public static class FeatureCell extends FrameLayout { + + private final Theme.ResourcesProvider resourcesProvider; + private ImageView imageView; + private LinearLayout textLayout; + private TextView titleView; + private TextView textView; + + public FeatureCell(Context context, Theme.ResourcesProvider resourcesProvider) { + super(context); + + this.resourcesProvider = resourcesProvider; + + imageView = new ImageView(context); + imageView.setScaleType(ImageView.ScaleType.CENTER); + imageView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourcesProvider), PorterDuff.Mode.SRC_IN)); + addView(imageView, LayoutHelper.createFrame(24, 24, Gravity.TOP | Gravity.LEFT, 20, 12.66f, 0, 0)); + + textLayout = new LinearLayout(context); + textLayout.setOrientation(LinearLayout.VERTICAL); + addView(textLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL | Gravity.FILL_HORIZONTAL, 64, 3, 24, 5 + 8.33f)); + + titleView = new TextView(context); + titleView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourcesProvider)); + titleView.setTypeface(AndroidUtilities.bold()); + titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textLayout.addView(titleView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.FILL_HORIZONTAL | Gravity.TOP, 0, 0, 0, 4)); + + textView = new TextView(context); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2, resourcesProvider)); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textLayout.addView(textView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.FILL_HORIZONTAL | Gravity.TOP, 0, 0, 0, 0)); + } + + public void set(int iconResId, CharSequence title, CharSequence text) { + imageView.setImageResource(iconResId); + titleView.setText(title); + textView.setText(text); + } + + public static class Factory extends UItem.UItemFactory { + static { setup(new Factory()); } + + @Override + public FeatureCell createView(Context context, int currentAccount, int classGuid, Theme.ResourcesProvider resourcesProvider) { + return new FeatureCell(context, resourcesProvider); + } + + @Override + public void bindView(View view, UItem item, boolean divider) { + ((FeatureCell) view).set(item.iconResId, item.text, item.subtext); + } + + public static UItem as(int iconResId, CharSequence title, CharSequence text) { + UItem item = UItem.ofFactory(Factory.class); + item.iconResId = iconResId; + item.text = title; + item.subtext = text; + return item; + } + + @Override + public boolean isClickable() { + return false; + } + } + } + + @Override + public int getNavigationBarColor() { + return getThemedColor(Theme.key_windowBackgroundWhite); + } + + public static class ColorfulTextCell extends FrameLayout { + private final Theme.ResourcesProvider resourcesProvider; + + private final ImageView imageView; + private final FrameLayout.LayoutParams imageViewLayoutParams; + private final LinearLayout textLayout; + private final FrameLayout.LayoutParams textLayoutLayoutParams; + private final TextView titleView; + private final TextView textView; + private final ImageView arrowView; + private final TextView percentView; + + public ColorfulTextCell(Context context, Theme.ResourcesProvider resourcesProvider) { + super(context); + this.resourcesProvider = resourcesProvider; + + imageView = new ImageView(context); + imageView.setColorFilter(new PorterDuffColorFilter(0xFFFFFFFF, PorterDuff.Mode.SRC_IN)); + imageView.setScaleType(ImageView.ScaleType.CENTER); + addView(imageView, imageViewLayoutParams = LayoutHelper.createFrame(28, 28, Gravity.LEFT | Gravity.TOP, 17, 14.33f, 0, 0)); + + textLayout = new LinearLayout(context); + textLayout.setOrientation(LinearLayout.VERTICAL); + addView(textLayout, textLayoutLayoutParams = LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.FILL_HORIZONTAL | Gravity.TOP, 62, 10, 40, 8.66f)); + + titleView = new TextView(context); + titleView.setTypeface(AndroidUtilities.bold()); + titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + titleView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourcesProvider)); + textLayout.addView(titleView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.FILL_HORIZONTAL | Gravity.TOP, 0, 0, 0, 0)); + + textView = new TextView(context); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2, resourcesProvider)); + textLayout.addView(textView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.FILL_HORIZONTAL | Gravity.TOP, 0, 3, 0, 0)); + + arrowView = new ImageView(context); + arrowView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2, resourcesProvider), PorterDuff.Mode.SRC_IN)); + arrowView.setImageResource(R.drawable.msg_arrowright); + arrowView.setScaleType(ImageView.ScaleType.CENTER); + addView(arrowView, LayoutHelper.createFrame(24, 24, Gravity.RIGHT | Gravity.CENTER_VERTICAL, 0, 0, 10, 0)); + + percentView = new TextView(context); + percentView.setTextColor(0xFFFFFFFF); + percentView.setBackground(Theme.createRoundRectDrawable(dp(4), Theme.getColor(Theme.key_color_green, resourcesProvider))); + percentView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); + percentView.setTypeface(AndroidUtilities.bold()); + percentView.setPadding(dp(5), 0, dp(4), dp(2)); + percentView.setGravity(Gravity.CENTER); + percentView.setVisibility(View.GONE); + addView(percentView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, 17, Gravity.RIGHT | Gravity.CENTER_VERTICAL, 0, 0, 35.33f, 0)); + } + + public void set(int color, int iconResId, CharSequence title, CharSequence text) { + imageView.setImageResource(iconResId); + imageView.setBackground(Theme.createRoundRectDrawable(dp(9), color)); + + titleView.setText(title); + if (TextUtils.isEmpty(text)) { + imageViewLayoutParams.topMargin = dp(10); + imageViewLayoutParams.bottomMargin = dp(10); + titleView.setTypeface(null); + titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + textLayoutLayoutParams.topMargin = 0; + textLayoutLayoutParams.bottomMargin = 0; + textLayoutLayoutParams.gravity = Gravity.FILL_HORIZONTAL | Gravity.CENTER_VERTICAL; + textView.setVisibility(View.GONE); + } else { + imageViewLayoutParams.topMargin = dp(14.33f); + imageViewLayoutParams.bottomMargin = dp(10); + titleView.setTypeface(AndroidUtilities.bold()); + titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15); + textLayoutLayoutParams.topMargin = dp(10); + textLayoutLayoutParams.bottomMargin = dp(8.66f); + textLayoutLayoutParams.gravity = Gravity.FILL_HORIZONTAL | Gravity.TOP; + textView.setText(text); + textView.setVisibility(View.VISIBLE); + } + } + + public void setPercent(CharSequence percent) { + if (TextUtils.isEmpty(percent)) { + percentView.setVisibility(View.GONE); + } else { + percentView.setVisibility(View.VISIBLE); + percentView.setText(percent); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), heightMeasureSpec); + } + + public static class Factory extends UItem.UItemFactory { + static { setup(new Factory()); } + + @Override + public ColorfulTextCell createView(Context context, int currentAccount, int classGuid, Theme.ResourcesProvider resourcesProvider) { + return new ColorfulTextCell(context, resourcesProvider); + } + + @Override + public void bindView(View view, UItem item, boolean divider) { + ((ColorfulTextCell) view).set( + item.intValue, item.iconResId, + item.text, item.subtext + ); + } + + public static UItem as(int id, int color, int iconResId, CharSequence title, CharSequence text) { + UItem item = UItem.ofFactory(Factory.class); + item.id = id; + item.intValue = color; + item.iconResId = iconResId; + item.text = title; + item.subtext = text; + return item; + } + } + + } + + public static CharSequence percents(int commission) { + float f = commission / 10.0f; + if ((int) f == f) { + return String.format(Locale.US, "%d%%", commission / 10); + } else { + return String.format(Locale.US, "%.1f%%", f); + } + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/bots/BotWebViewSheet.java b/TMessagesProj/src/main/java/org/telegram/ui/bots/BotWebViewSheet.java index 981beb128..8ec046f66 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/bots/BotWebViewSheet.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/bots/BotWebViewSheet.java @@ -50,7 +50,9 @@ import androidx.core.content.ContextCompat; import androidx.core.graphics.ColorUtils; import androidx.core.math.MathUtils; import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsAnimationCompat; import androidx.core.view.WindowInsetsCompat; +import androidx.core.view.WindowInsetsControllerCompat; import androidx.dynamicanimation.animation.SpringAnimation; import androidx.dynamicanimation.animation.SpringForce; @@ -1115,13 +1117,16 @@ public class BotWebViewSheet extends Dialog implements NotificationCenter.Notifi final WindowInsetsCompat insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets, v); final androidx.core.graphics.Insets navInsets = insetsCompat.getInsets(WindowInsetsCompat.Type.navigationBars()); this.navInsets.set(navInsets.left, navInsets.top, navInsets.right, navInsets.bottom); - final androidx.core.graphics.Insets cutoutInsets = insetsCompat.getInsets(WindowInsetsCompat.Type.displayCutout() | WindowInsetsCompat.Type.statusBars() | WindowInsetsCompat.Type.navigationBars()); + final androidx.core.graphics.Insets cutoutInsets = insetsCompat.getInsets(WindowInsetsCompat.Type.displayCutout() | WindowInsetsCompat.Type.systemBars()); this.insets.set( Math.max(cutoutInsets.left, insets.getStableInsetLeft()), Math.max(cutoutInsets.top, insets.getStableInsetTop()), Math.max(cutoutInsets.right, insets.getStableInsetRight()), Math.max(cutoutInsets.bottom, insets.getStableInsetBottom()) ); + if (Build.VERSION.SDK_INT <= 28) { + this.insets.top = Math.max(this.insets.top, AndroidUtilities.getStatusBarHeight(getContext())); + } final androidx.core.graphics.Insets keyboardInsets = insetsCompat.getInsets(WindowInsetsCompat.Type.ime()); final int keyboardHeight = keyboardInsets.bottom; if (keyboardHeight > this.insets.bottom && keyboardHeight > dp(20)) { @@ -1192,10 +1197,16 @@ public class BotWebViewSheet extends Dialog implements NotificationCenter.Notifi Window window = getWindow(); if (window == null) return; WindowManager.LayoutParams params = window.getAttributes(); - if (fullscreen) { - params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; + final int flags; + if (Build.VERSION.SDK_INT <= 28) { + flags = WindowManager.LayoutParams.FLAG_FULLSCREEN; } else { - params.flags &= ~WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; + flags = WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; + } + if (fullscreen) { + params.flags |= flags; + } else { + params.flags &= ~flags; } if (fullscreen && !(botButtons != null && botButtons.getTotalHeight() > 0) && !windowView.drawingFromOverlay) { windowView.setSystemUiVisibility(windowView.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); diff --git a/TMessagesProj/src/main/java/org/telegram/ui/bots/ChannelAffiliateProgramsFragment.java b/TMessagesProj/src/main/java/org/telegram/ui/bots/ChannelAffiliateProgramsFragment.java new file mode 100644 index 000000000..4b892cb84 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/bots/ChannelAffiliateProgramsFragment.java @@ -0,0 +1,1107 @@ +package org.telegram.ui.bots; + +import static org.telegram.messenger.AndroidUtilities.dp; +import static org.telegram.messenger.LocaleController.formatPluralString; +import static org.telegram.messenger.LocaleController.formatString; +import static org.telegram.messenger.LocaleController.getString; +import static org.telegram.ui.Stars.StarsIntroActivity.formatStarsAmount; +import static org.telegram.ui.Stars.StarsIntroActivity.formatStarsAmountShort; +import static org.telegram.ui.Stars.StarsIntroActivity.replaceStarsWithPlain; +import static org.telegram.ui.bots.AffiliateProgramFragment.percents; + +import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.drawable.ShapeDrawable; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextPaint; +import android.text.TextUtils; +import android.text.style.ClickableSpan; +import android.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.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DefaultItemAnimator; +import androidx.recyclerview.widget.RecyclerView; + +import org.telegram.messenger.AndroidUtilities; +import org.telegram.messenger.BuildVars; +import org.telegram.messenger.ChatObject; +import org.telegram.messenger.Emoji; +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.MessagesController; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.messenger.UserConfig; +import org.telegram.messenger.UserObject; +import org.telegram.messenger.browser.Browser; +import org.telegram.tgnet.ConnectionsManager; +import org.telegram.tgnet.TLObject; +import org.telegram.tgnet.TLRPC; +import org.telegram.tgnet.tl.TL_payments; +import org.telegram.ui.ActionBar.AlertDialog; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.BottomSheet; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Cells.HeaderCell; +import org.telegram.ui.ChatActivity; +import org.telegram.ui.Components.AvatarDrawable; +import org.telegram.ui.Components.BackupImageView; +import org.telegram.ui.Components.BulletinFactory; +import org.telegram.ui.Components.ColoredImageSpan; +import org.telegram.ui.Components.CubicBezierInterpolator; +import org.telegram.ui.Components.FlickerLoadingView; +import org.telegram.ui.Components.ItemOptions; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.LinkSpanDrawable; +import org.telegram.ui.Components.Paint.ShapeDetector; +import org.telegram.ui.Components.Premium.GLIcon.GLIconRenderer; +import org.telegram.ui.Components.Premium.GLIcon.GLIconTextureView; +import org.telegram.ui.Components.Premium.GLIcon.Icon3D; +import org.telegram.ui.Components.Premium.PremiumGradient; +import org.telegram.ui.Components.Premium.StarParticlesView; +import org.telegram.ui.Components.RecyclerListView; +import org.telegram.ui.Components.TableView; +import org.telegram.ui.Components.UItem; +import org.telegram.ui.Components.UniversalAdapter; +import org.telegram.ui.FilterCreateActivity; +import org.telegram.ui.GradientHeaderActivity; +import org.telegram.ui.LaunchActivity; +import org.telegram.ui.Stars.BotStarsController; +import org.telegram.ui.Stars.StarsIntroActivity; +import org.telegram.ui.Stories.recorder.ButtonWithCounterView; + +import java.util.ArrayList; + +public class ChannelAffiliateProgramsFragment extends GradientHeaderActivity implements NotificationCenter.NotificationCenterDelegate { + + public final long dialogId; + + private FrameLayout aboveTitleView; + private GLIconTextureView iconTextureView; + private View emptyLayout; + + public ChannelAffiliateProgramsFragment(long dialogId) { + this.dialogId = dialogId; + + setWhiteBackground(true); + setMinusHeaderHeight(dp(60)); + } + + @Override + public boolean onFragmentCreate() { + NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.channelConnectedBotsUpdate); + NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.channelSuggestedBotsUpdate); + return super.onFragmentCreate(); + } + + @Override + public void onFragmentDestroy() { + super.onFragmentDestroy(); + NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.channelConnectedBotsUpdate); + NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.channelSuggestedBotsUpdate); + } + + @Override + public View createView(Context context) { + useFillLastLayoutManager = false; + particlesViewHeight = dp(32 + 190 + 16); + emptyLayout = new View(context) { + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(dp(60), MeasureSpec.EXACTLY)); + } + }; + emptyLayout.setBackgroundColor(Theme.getColor(Theme.key_dialogBackgroundGray)); + + super.createView(context); + + aboveTitleView = new FrameLayout(context); + aboveTitleView.setClickable(true); + iconTextureView = new GLIconTextureView(context, GLIconRenderer.DIALOG_STYLE, Icon3D.TYPE_DEAL); + iconTextureView.mRenderer.colorKey1 = Theme.key_starsGradient1; + iconTextureView.mRenderer.colorKey2 = Theme.key_starsGradient2; + iconTextureView.mRenderer.updateColors(); + iconTextureView.setStarParticlesView(particlesView); + aboveTitleView.addView(iconTextureView, LayoutHelper.createFrame(190, 190, Gravity.CENTER, 0, 32, 0, 24)); + configureHeader(getString(R.string.ChannelAffiliateProgramTitle), AndroidUtilities.replaceTags(getString(R.string.ChannelAffiliateProgramText)), aboveTitleView, null); + + listView.setOnItemClickListener((view, position) -> { + if (adapter == null) return; + UItem item = adapter.getItem(position); + if (item.object instanceof TL_payments.starRefProgram) { + showConnectAffiliateAlert(context, currentAccount, ((TL_payments.starRefProgram) item.object), dialogId, resourceProvider, false); + } else if (item.object instanceof TL_payments.connectedBotStarRef) { + showShareAffiliateAlert(context, currentAccount, ((TL_payments.connectedBotStarRef) item.object), dialogId, resourceProvider); + } + }); + listView.setOnItemLongClickListener((view, position) -> { + if (adapter == null) return false; + UItem item = adapter.getItem(position); + if (item.object instanceof TL_payments.connectedBotStarRef) { + TL_payments.connectedBotStarRef bot = (TL_payments.connectedBotStarRef) item.object; + TLRPC.User botUser = MessagesController.getInstance(currentAccount).getUser(bot.bot_id); + ItemOptions.makeOptions(this, view) + .addIf(botUser.bot_has_main_app, R.drawable.msg_bot, getString(R.string.ProfileBotOpenApp), () -> { + getMessagesController().openApp(botUser, getClassGuid()); + }) + .addIf(!botUser.bot_has_main_app, R.drawable.msg_bot, getString(R.string.BotWebViewOpenBot), () -> { + presentFragment(ChatActivity.of(bot.bot_id)); + }) + .add(R.drawable.msg_link2, getString(R.string.CopyLink), () -> { + AndroidUtilities.addToClipboard(bot.url); + BulletinFactory.of(this) + .createSimpleBulletin(R.raw.copy, getString(R.string.AffiliateProgramLinkCopiedTitle), AndroidUtilities.replaceTags(formatString(R.string.AffiliateProgramLinkCopiedText, percents(bot.commission_permille), UserObject.getUserName(botUser)))) + .show(); + }) +// .addIf(bot.revoked, R.drawable.msg_leave, getString(R.string.Restore), () -> { +// final AlertDialog progressDialog = new AlertDialog(getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER); +// progressDialog.showDelayed(200); +// TL_payments.editConnectedStarRefBot req = new TL_payments.editConnectedStarRefBot(); +// req.link = bot.url; +// req.peer = MessagesController.getInstance(currentAccount).getInputPeer(dialogId); +// req.revoked = false; +// getConnectionsManager().sendRequest(req, (res, err) -> AndroidUtilities.runOnUIThread(() -> { +// if (res instanceof TL_payments.connectedStarRefBots) { +// TL_payments.connectedStarRefBots r = (TL_payments.connectedStarRefBots) res; +// BotStarsController.getInstance(currentAccount).getChannelConnectedBots(dialogId).applyEdit(r); +// adapter.update(true); +// } +// progressDialog.dismiss(); +// })); +// }) + .addIf(!bot.revoked, R.drawable.msg_leave, getString(R.string.LeaveAffiliateLinkButton), true, () -> { + new AlertDialog.Builder(context, resourceProvider) + .setTitle(getString(R.string.LeaveAffiliateLink)) + .setMessage(AndroidUtilities.replaceTags(formatString(R.string.LeaveAffiliateLinkAlert, UserObject.getUserName(botUser)))) + .setPositiveButton(getString(R.string.LeaveAffiliateLinkButton), (d, w) -> { + final AlertDialog progressDialog = new AlertDialog(getParentActivity(), AlertDialog.ALERT_TYPE_SPINNER); + progressDialog.showDelayed(200); + TL_payments.editConnectedStarRefBot req = new TL_payments.editConnectedStarRefBot(); + req.link = bot.url; + req.peer = MessagesController.getInstance(currentAccount).getInputPeer(dialogId); + req.revoked = true; + getConnectionsManager().sendRequest(req, (res, err) -> AndroidUtilities.runOnUIThread(() -> { + if (res instanceof TL_payments.connectedStarRefBots) { + TL_payments.connectedStarRefBots r = (TL_payments.connectedStarRefBots) res; + BotStarsController.getInstance(currentAccount).getChannelConnectedBots(dialogId).applyEdit(r); + BotStarsController.getInstance(currentAccount).getChannelSuggestedBots(dialogId).reload(); + adapter.update(true); + } + progressDialog.dismiss(); + })); + }) + .setNegativeButton(getString(R.string.Cancel), null) + .makeRed(AlertDialog.BUTTON_POSITIVE) + .show(); + }) + .setGravity(Gravity.RIGHT) + .show(); + return true; + } + return false; + }); + DefaultItemAnimator itemAnimator = new DefaultItemAnimator(); + itemAnimator.setSupportsChangeAnimations(false); + itemAnimator.setDelayAnimations(false); + itemAnimator.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT); + itemAnimator.setDurations(350); + listView.setItemAnimator(itemAnimator); + listView.setOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + if (isLoadingVisible() || !recyclerView.canScrollVertically(1)) { + BotStarsController.getInstance(currentAccount).getChannelConnectedBots(dialogId).load(); + BotStarsController.getInstance(currentAccount).getChannelSuggestedBots(dialogId).load(); + } + } + }); + + return fragmentView; + } + + private boolean isLoadingVisible() { + for (int i = 0; i < listView.getChildCount(); ++i) { + if (listView.getChildAt(i) instanceof FlickerLoadingView) + return true; + } + return false; + } + + + private UniversalAdapter adapter; + @Override + protected RecyclerView.Adapter createAdapter() { + return adapter = new UniversalAdapter(listView, getContext(), currentAccount, classGuid, true, this::fillItems, getResourceProvider()) { + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + if (viewType == UniversalAdapter.VIEW_TYPE_ANIMATED_HEADER) { + HeaderCell headerCell = new HeaderCell(getContext(), Theme.key_windowBackgroundWhiteBlueHeader, 21, 0, false, resourceProvider); + headerCell.setHeight(40 - 15); + return new RecyclerListView.Holder(headerCell); + } + return super.onCreateViewHolder(parent, viewType); + } + }; + } + + public void fillItems(ArrayList items, UniversalAdapter adapter) { + if (getContext() == null) { + return; + } + + items.add(UItem.asFullyCustom(getHeader(getContext()))); + items.add(AffiliateProgramFragment.FeatureCell.Factory.as(R.drawable.menu_feature_reliable, getString(R.string.ChannelAffiliateProgramFeature1Title), getString(R.string.ChannelAffiliateProgramFeature1))); + items.add(AffiliateProgramFragment.FeatureCell.Factory.as(R.drawable.menu_feature_transparent, getString(R.string.ChannelAffiliateProgramFeature2Title), getString(R.string.ChannelAffiliateProgramFeature2))); + items.add(AffiliateProgramFragment.FeatureCell.Factory.as(R.drawable.menu_feature_simple, getString(R.string.ChannelAffiliateProgramFeature3Title), getString(R.string.ChannelAffiliateProgramFeature3))); + items.add(UItem.asShadow(1, null)); + + final BotStarsController.ChannelConnectedBots connectedBots = BotStarsController.getInstance(currentAccount).getChannelConnectedBots(dialogId); + if (!connectedBots.bots.isEmpty() || connectedBots.count > 0) { + items.add(UItem.asHeader(getString(R.string.ChannelAffiliateProgramMyPrograms))); + for (int i = 0; i < connectedBots.bots.size(); ++i) { + TL_payments.connectedBotStarRef bot = connectedBots.bots.get(i); + items.add(BotCell.Factory.as(bot)); + } + if (connectedBots.isLoading()) { + items.add(UItem.asFlicker(FlickerLoadingView.PROFILE_SEARCH_CELL)); + items.add(UItem.asFlicker(FlickerLoadingView.PROFILE_SEARCH_CELL)); + items.add(UItem.asFlicker(FlickerLoadingView.PROFILE_SEARCH_CELL)); + } + items.add(UItem.asShadow(2, null)); + } + + final BotStarsController.ChannelSuggestedBots suggestedBots = BotStarsController.getInstance(currentAccount).getChannelSuggestedBots(dialogId); + if (!suggestedBots.bots.isEmpty() || suggestedBots.count > 0) { + items.add(HeaderSortCell.Factory.as(getString(R.string.ChannelAffiliateProgramPrograms), sortText(suggestedBots.getSort()))); + for (int i = 0; i < suggestedBots.bots.size(); ++i) { + items.add(BotCell.Factory.as(suggestedBots.bots.get(i))); + } + if (suggestedBots.isLoading()) { + items.add(UItem.asFlicker(FlickerLoadingView.PROFILE_SEARCH_CELL)); + items.add(UItem.asFlicker(FlickerLoadingView.PROFILE_SEARCH_CELL)); + items.add(UItem.asFlicker(FlickerLoadingView.PROFILE_SEARCH_CELL)); + } + items.add(UItem.asShadow(3, null)); + } + + items.add(UItem.asCustom(emptyLayout)); + + } + + private CharSequence sortText(BotStarsController.ChannelSuggestedBots.Sort sort) { + SpannableStringBuilder ssb = new SpannableStringBuilder(); + ssb.append(getString(R.string.ChannelAffiliateProgramProgramsSort)).append(" "); + SpannableString type; + if (sort == BotStarsController.ChannelSuggestedBots.Sort.BY_PROFITABILITY) { + type = new SpannableString(getString(R.string.ChannelAffiliateProgramProgramsSortProfitability) + "v"); + } else if (sort == BotStarsController.ChannelSuggestedBots.Sort.BY_REVENUE) { + type = new SpannableString(getString(R.string.ChannelAffiliateProgramProgramsSortRevenue) + "v"); + } else if (sort == BotStarsController.ChannelSuggestedBots.Sort.BY_DATE) { + type = new SpannableString(getString(R.string.ChannelAffiliateProgramProgramsSortDate) + "v"); + } else return ssb; + ColoredImageSpan arrowSpan = new ColoredImageSpan(R.drawable.arrow_more); + arrowSpan.useLinkPaintColor = true; + arrowSpan.setScale(.6f, .6f); + type.setSpan(arrowSpan, type.length() - 1, type.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + final BotStarsController.ChannelSuggestedBots suggestedBots = BotStarsController.getInstance(currentAccount).getChannelSuggestedBots(dialogId); + type.setSpan(new ClickableSpan() { + @Override + public void onClick(@NonNull View widget) { + ItemOptions.makeOptions(ChannelAffiliateProgramsFragment.this, widget) + .addChecked( + sort == BotStarsController.ChannelSuggestedBots.Sort.BY_DATE, + getString(R.string.ChannelAffiliateProgramProgramsSortDate), + () -> suggestedBots.setSort(BotStarsController.ChannelSuggestedBots.Sort.BY_DATE) + ) + .addChecked( + sort == BotStarsController.ChannelSuggestedBots.Sort.BY_REVENUE, + getString(R.string.ChannelAffiliateProgramProgramsSortRevenue), + () -> suggestedBots.setSort(BotStarsController.ChannelSuggestedBots.Sort.BY_REVENUE) + ) + .addChecked( + sort == BotStarsController.ChannelSuggestedBots.Sort.BY_PROFITABILITY, + getString(R.string.ChannelAffiliateProgramProgramsSortProfitability), + () -> suggestedBots.setSort(BotStarsController.ChannelSuggestedBots.Sort.BY_PROFITABILITY) + ) + .setGravity(Gravity.RIGHT) + .setDrawScrim(false) + .setDimAlpha(0) + .translate(dp(24), -dp(24)) + .show(); + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + ds.setUnderlineText(false); + ds.setColor(ds.linkColor); + } + }, 0, type.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append(type); + return ssb; + } + + @Override + public StarParticlesView createParticlesView() { + return makeParticlesView(getContext(), 75, 1); + } + + public static StarParticlesView makeParticlesView(Context context, int particlesCount, int type) { + return new StarParticlesView(context) { + @Override + protected void configure() { + super.configure(); + drawable.useGradient = true; + drawable.useBlur = false; + drawable.forceMaxAlpha = true; + drawable.checkBounds = true; + drawable.init(); + } + + @Override + protected int getStarsRectWidth() { + return getMeasuredWidth(); + } + + { setClipWithGradient(); } + }; + } + + @Override + public void onResume() { + super.onResume(); + if (iconTextureView != null) { + iconTextureView.setPaused(false); + iconTextureView.setDialogVisible(false); + } + } + + @Override + public void onPause() { + super.onPause(); + if (iconTextureView != null) { + iconTextureView.setPaused(true); + iconTextureView.setDialogVisible(true); + } + } + + @Override + public void didReceivedNotification(int id, int account, Object... args) { + if (id == NotificationCenter.channelConnectedBotsUpdate) { + Long did = (Long) args[0]; + if (did == dialogId) { + if (adapter != null) { + adapter.update(true); + } + BotStarsController.getInstance(currentAccount).getChannelConnectedBots(dialogId).load(); + } + } else if (id == NotificationCenter.channelSuggestedBotsUpdate) { + Long did = (Long) args[0]; + if (did == dialogId) { + if (adapter != null) { + adapter.update(true); + } + } + } + } + + public static class BotCell extends FrameLayout { + + private final int currentAccount; + private final Theme.ResourcesProvider resourcesProvider; + + private final BackupImageView imageView; + private final View linkBgView, linkFg2View; + private final ImageView linkFgView; + private final LinearLayout textLayout; + private final TextView titleView; + private final TextView textView; + private final ImageView arrowView; + + public BotCell(Context context, int currentAccount, Theme.ResourcesProvider resourcesProvider) { + super(context); + + this.currentAccount = currentAccount; + this.resourcesProvider = resourcesProvider; + + imageView = new BackupImageView(context); + imageView.setRoundRadius(dp(46)); + addView(imageView, LayoutHelper.createFrame(46, 46, Gravity.CENTER_VERTICAL | Gravity.LEFT, 13, 0, 13, 0)); + + linkBgView = new View(context); + linkBgView.setBackground(Theme.createCircleDrawable(dp(11), Theme.getColor(Theme.key_windowBackgroundWhite, resourcesProvider))); + addView(linkBgView, LayoutHelper.createFrame(22, 22, Gravity.LEFT | Gravity.CENTER_VERTICAL, 40, 15, 0, 0)); + + linkFg2View = new View(context); + linkFg2View.setBackground(Theme.createCircleDrawable(dp(19.33f / 2), Theme.getColor(Theme.key_color_green, resourcesProvider))); + addView(linkFg2View, LayoutHelper.createFrame(19.33f, 19.33f, Gravity.LEFT | Gravity.CENTER_VERTICAL, 41.33f, 15, 0, 0)); + + linkFgView = new ImageView(context); + linkFgView.setScaleX(0.6f); + linkFgView.setScaleY(0.6f); + addView(linkFgView, LayoutHelper.createFrame(19.33f, 19.33f, Gravity.LEFT | Gravity.CENTER_VERTICAL, 41.33f, 15, 0, 0)); + + textLayout = new LinearLayout(context); + textLayout.setOrientation(LinearLayout.VERTICAL); + addView(textLayout, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.FILL_HORIZONTAL | Gravity.TOP, 72 - 6, 8.66f, 16 - 6, 0)); + + titleView = new TextView(context); + titleView.setMaxLines(1); + titleView.setSingleLine(true); + titleView.setEllipsize(TextUtils.TruncateAt.END); + titleView.setTypeface(AndroidUtilities.bold()); + titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + titleView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourcesProvider)); + NotificationCenter.listenEmojiLoading(titleView); + textLayout.addView(titleView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.FILL_HORIZONTAL | Gravity.TOP, 6, 0, 24, 0)); + + textView = new TextView(context); + textView.setMaxLines(1); + textView.setSingleLine(true); + textView.setEllipsize(TextUtils.TruncateAt.END); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2, resourcesProvider)); + textLayout.addView(textView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.FILL_HORIZONTAL | Gravity.TOP, 6, 1, 24, 0)); + + arrowView = new ImageView(context); + arrowView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2, resourcesProvider), PorterDuff.Mode.SRC_IN)); + arrowView.setImageResource(R.drawable.photos_arrow); + arrowView.setScaleType(ImageView.ScaleType.CENTER); + addView(arrowView, LayoutHelper.createFrame(24, 24, Gravity.RIGHT | Gravity.CENTER_VERTICAL, 0, 0, 10, 0)); + } + + public void set(TL_payments.connectedBotStarRef bot, boolean showArrow) { + final TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(bot.bot_id); + + AvatarDrawable avatarDrawable = new AvatarDrawable(); + avatarDrawable.setInfo(user); + imageView.setForUserOrChat(user, avatarDrawable); + + titleView.setText(Emoji.replaceEmoji(UserObject.getUserName(user), titleView.getPaint().getFontMetricsInt(), false)); + SpannableStringBuilder ssb = new SpannableStringBuilder(); + if (bot.commission_permille > 0) { + ssb.append(" d"); + FilterCreateActivity.NewSpan span = new FilterCreateActivity.NewSpan(10); + span.setColor(Theme.getColor(Theme.key_color_green)); + span.setText(percents(bot.commission_permille)); + ssb.setSpan(span, 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (bot.duration_months == 0) { + ssb.append(getString(R.string.Lifetime)); + } else if (bot.duration_months < 12 || bot.duration_months % 12 != 0) { + ssb.append(formatPluralString("Months", bot.duration_months)); + } else { + ssb.append(formatPluralString("Years", bot.duration_months / 12)); + } + textView.setText(ssb); + + arrowView.setVisibility(showArrow ? View.VISIBLE : View.GONE); + linkBgView.setVisibility(View.VISIBLE); + linkFgView.setVisibility(View.VISIBLE); + linkFg2View.setVisibility(View.VISIBLE); + linkFg2View.setBackground(Theme.createCircleDrawable(dp(19.33f / 2), Theme.getColor(bot.revoked ? Theme.key_color_red : Theme.key_color_green, resourcesProvider))); + linkFgView.setImageResource(bot.revoked ? R.drawable.msg_link_2 : R.drawable.msg_limit_links); + linkFgView.setScaleX(bot.revoked ? 0.8f : 0.6f); + linkFgView.setScaleY(bot.revoked ? 0.8f : 0.6f); + } + + public void set(TL_payments.starRefProgram bot, boolean showArrow) { + final TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(bot.bot_id); + + AvatarDrawable avatarDrawable = new AvatarDrawable(); + avatarDrawable.setInfo(user); + imageView.setForUserOrChat(user, avatarDrawable); + + titleView.setText(UserObject.getUserName(user)); + SpannableStringBuilder ssb = new SpannableStringBuilder(); + if (bot.commission_permille > 0) { + ssb.append(" d"); + FilterCreateActivity.NewSpan span = new FilterCreateActivity.NewSpan(10); + span.setColor(Theme.getColor(Theme.key_color_green)); + span.setText(percents(bot.commission_permille)); + ssb.setSpan(span, 1, 2, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + if (bot.duration_months == 0) { + ssb.append(getString(R.string.Lifetime)); + } else if (bot.duration_months < 12 || bot.duration_months % 12 != 0) { + ssb.append(formatPluralString("Months", bot.duration_months)); + } else { + ssb.append(formatPluralString("Years", bot.duration_months / 12)); + } + textView.setText(ssb); + + arrowView.setVisibility(showArrow ? View.VISIBLE : View.GONE); + linkBgView.setVisibility(View.GONE); + linkFgView.setVisibility(View.GONE); + linkFg2View.setVisibility(View.GONE); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure( + MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(dp(58), MeasureSpec.EXACTLY) + ); + } + + public static class Factory extends UItem.UItemFactory { + static { setup(new Factory()); } + + @Override + public BotCell createView(Context context, int currentAccount, int classGuid, Theme.ResourcesProvider resourcesProvider) { + return new BotCell(context, currentAccount, resourcesProvider); + } + + @Override + public void bindView(View view, UItem item, boolean divider) { + if (item.object instanceof TL_payments.connectedBotStarRef) { + ((BotCell) view).set((TL_payments.connectedBotStarRef) item.object, item.red); + } else if (item.object instanceof TL_payments.starRefProgram) { + ((BotCell) view).set((TL_payments.starRefProgram) item.object, item.red); + } + } + + public static UItem as(Object obj) { + return as(obj, true); + } + + public static UItem as(Object obj, boolean showArrow) { + UItem item = UItem.ofFactory(Factory.class); + item.object = obj; + item.red = showArrow; + return item; + } + } + } + + private static class HeaderSortCell extends HeaderCell { + + private final LinkSpanDrawable.LinksTextView subtextView; + + public HeaderSortCell(Context context, Theme.ResourcesProvider resourcesProvider) { + super(context, resourcesProvider); + + subtextView = new LinkSpanDrawable.LinksTextView(context, resourcesProvider); + subtextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + subtextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2, resourcesProvider)); + subtextView.setLinkTextColor(Theme.getColor(Theme.key_chat_messageLinkIn, resourcesProvider)); + subtextView.setPadding(dp(4), 0, dp(4), 0); + addView(subtextView, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, (LocaleController.isRTL ? Gravity.LEFT : Gravity.RIGHT) | Gravity.TOP, 14 - 4, 20, 14 - 4, 0)); + } + + public void set(CharSequence text, CharSequence subtext) { + setText(text); + subtextView.setText(subtext); + } + + public static class Factory extends UItem.UItemFactory { + static { setup(new Factory()); } + + @Override + public HeaderSortCell createView(Context context, int currentAccount, int classGuid, Theme.ResourcesProvider resourcesProvider) { + return new HeaderSortCell(context, resourcesProvider); + } + + @Override + public void bindView(View view, UItem item, boolean divider) { + ((HeaderSortCell) view).set(item.text, item.subtext); + } + + public static UItem as(CharSequence text, CharSequence sortText) { + UItem item = UItem.ofFactory(Factory.class); + item.text = text; + item.subtext = sortText; + return item; + } + + @Override + public boolean isClickable() { + return false; + } + } + } + + public static void showConnectAffiliateAlert(Context context, int currentAccount, TL_payments.starRefProgram bot, long dialogId, Theme.ResourcesProvider resourcesProvider, boolean forceOpenStats) { + if (bot == null || context == null) + return; + + BottomSheet.Builder b = new BottomSheet.Builder(context, false, resourcesProvider); + final long[] selectedDialogId = new long[1]; + selectedDialogId[0] = dialogId; + final TLRPC.User botUser = MessagesController.getInstance(currentAccount).getUser(bot.bot_id); + + LinearLayout linearLayout = new LinearLayout(context); + linearLayout.setOrientation(LinearLayout.VERTICAL); + linearLayout.setPadding(dp(16), dp(20), dp(16), dp(8)); + linearLayout.setClipChildren(false); + linearLayout.setClipToPadding(false); + + FrameLayout topView = new FrameLayout(context); + + BackupImageView imageView1 = new BackupImageView(context); + imageView1.setRoundRadius(dp(30)); + AvatarDrawable avatarDrawable2 = new AvatarDrawable(); + avatarDrawable2.setInfo(botUser); + imageView1.setForUserOrChat(botUser, avatarDrawable2); + topView.addView(imageView1, LayoutHelper.createFrame(60, 60, Gravity.CENTER_VERTICAL | Gravity.LEFT, 0, 0, 0, 0)); + + ImageView arrowView = new ImageView(context); + arrowView.setImageResource(R.drawable.msg_arrow_avatar); + arrowView.setScaleType(ImageView.ScaleType.CENTER); + arrowView.setTranslationX(-dp(8.33f / 4)); + arrowView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText2, resourcesProvider), PorterDuff.Mode.SRC_IN)); + topView.addView(arrowView, LayoutHelper.createFrame(36, 60, Gravity.CENTER, 60, 0, 60, 0)); + + BackupImageView imageView2 = new BackupImageView(context); + imageView2.setRoundRadius(dp(30)); + topView.addView(imageView2, LayoutHelper.createFrame(60, 60, Gravity.CENTER_VERTICAL | Gravity.RIGHT, 0, 0, 5.66f, 0)); + + View starBgView = new View(context); + starBgView.setBackground(Theme.createCircleDrawable(dp(13.66f), Theme.getColor(Theme.key_dialogBackground, resourcesProvider))); + topView.addView(starBgView, LayoutHelper.createFrame(27.33f, 27.33f, Gravity.CENTER_VERTICAL | Gravity.RIGHT, 0, 18, 0, 0)); + + View starFg1View = new View(context); + PremiumGradient.PremiumGradientTools gradientTools = new PremiumGradient.PremiumGradientTools(Theme.key_premiumGradient1, Theme.key_premiumGradient2, -1, -1, -1, resourcesProvider); + ShapeDrawable circleDrawable = Theme.createCircleDrawable(dp(12), Theme.getColor(Theme.key_premiumGradient1, resourcesProvider)); + gradientTools.gradientMatrix(0, 0, dp(24), dp(24), 0, 0); + circleDrawable.getPaint().setShader(gradientTools.paint.getShader()); + starFg1View.setBackground(circleDrawable); + topView.addView(starFg1View, LayoutHelper.createFrame(24, 24, Gravity.CENTER_VERTICAL | Gravity.RIGHT, 0, 18, 1.66f, 0)); + + ImageView starFg2View = new ImageView(context); + starFg2View.setImageResource(R.drawable.msg_premium_badge); + starFg2View.setScaleX(0.77f); + starFg2View.setScaleY(0.77f); + topView.addView(starFg2View, LayoutHelper.createFrame(24, 24, Gravity.CENTER_VERTICAL | Gravity.RIGHT, 0, 18, 1.66f, 0)); + + linearLayout.addView(topView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL, 0, 0, 0, 0)); + + TextView titleView = new TextView(context); + titleView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourcesProvider)); + titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); + titleView.setGravity(Gravity.CENTER); + titleView.setText(getString(R.string.ChannelAffiliateProgramJoinTitle)); + titleView.setTypeface(AndroidUtilities.bold()); + linearLayout.addView(titleView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 21, 0, 8.33f)); + + TextView textView = new TextView(context); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourcesProvider)); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setGravity(Gravity.CENTER); + NotificationCenter.listenEmojiLoading(textView); + textView.setText(Emoji.replaceEmoji(AndroidUtilities.replaceTags(formatString(R.string.ChannelAffiliateProgramJoinText, UserObject.getUserName(botUser), percents(bot.commission_permille), bot.duration_months <= 0 ? getString(R.string.ChannelAffiliateProgramJoinText_Lifetime) : bot.duration_months < 12 || bot.duration_months % 12 != 0 ? formatPluralString("ChannelAffiliateProgramJoinText_Months", bot.duration_months) : formatPluralString("ChannelAffiliateProgramJoinText_Years", bot.duration_months / 12))), textView.getPaint().getFontMetricsInt(), false)); + linearLayout.addView(textView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, 0, 0, 22)); + + if (((botUser.flags & 4096) != 0 || BuildVars.DEBUG_PRIVATE_VERSION) && (bot.flags & 4) != 0) { + TableView tableView = new TableView(context, resourcesProvider); + tableView.addRow(getString(R.string.ChannelAffiliateProgramJoinMonthlyUsers), LocaleController.formatNumber(botUser.bot_active_users, ',')); + tableView.addRow(getString(R.string.ChannelAffiliateProgramJoinDailyRevenue), replaceStarsWithPlain("⭐️ " + formatStarsAmountShort(bot.daily_revenue_per_user, 0.95f, ','), .75f)); + linearLayout.addView(tableView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 0, -4, 0, 12)); + } + + BackupImageView chipImageView; + TextView chipTextView; + LinearLayout chipLayout; + if (dialogId >= 0) { + TextView sendToTextView = new TextView(context); + sendToTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourcesProvider)); + sendToTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + sendToTextView.setGravity(Gravity.CENTER); + sendToTextView.setText(getString(R.string.ChannelAffiliateProgramLinkSendTo)); + linearLayout.addView(sendToTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 20, 0, 20, 0)); + + chipLayout = new LinearLayout(context); + chipLayout.setOrientation(LinearLayout.HORIZONTAL); + chipLayout.setBackground(Theme.createRoundRectDrawable(dp(28), Theme.getColor(Theme.key_windowBackgroundGray, resourcesProvider))); + chipLayout.setBackground(Theme.createSimpleSelectorRoundRectDrawable(dp(28), Theme.getColor(Theme.key_windowBackgroundGray, resourcesProvider), Theme.blendOver(Theme.getColor(Theme.key_windowBackgroundGray, resourcesProvider), Theme.getColor(Theme.key_listSelector, resourcesProvider)))); + chipImageView = new BackupImageView(context); + chipImageView.setRoundRadius(dp(14)); + chipLayout.addView(chipImageView, LayoutHelper.createLinear(28, 28)); + chipTextView = new TextView(context); + chipTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); + chipTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourcesProvider)); + chipLayout.addView(chipTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 6, 0, 0, 0)); + ImageView selectView = new ImageView(context); + selectView.setScaleType(ImageView.ScaleType.CENTER); + selectView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogTextGray3, resourcesProvider), PorterDuff.Mode.SRC_IN)); + selectView.setImageResource(R.drawable.arrows_select); + chipLayout.addView(selectView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 2, 0, 5, 0)); + linearLayout.addView(chipLayout, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, 28, Gravity.CENTER_HORIZONTAL, 0, 11, 0, 20)); + } else { + chipLayout = null; + chipTextView = null; + chipImageView = null; + } + + ButtonWithCounterView button = new ButtonWithCounterView(context, resourcesProvider); + button.setText(getString(R.string.ChannelAffiliateProgramJoinButton), false); + linearLayout.addView(button, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); + + LinkSpanDrawable.LinksTextView infoTextView = new LinkSpanDrawable.LinksTextView(context, resourcesProvider); + infoTextView.setText(AndroidUtilities.replaceSingleTag(getString(R.string.ChannelAffiliateProgramJoinButtonInfo), () -> { + Browser.openUrl(context, getString(R.string.ChannelAffiliateProgramJoinButtonInfoLink)); + })); + infoTextView.setGravity(Gravity.CENTER); + infoTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); + infoTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText4, resourcesProvider)); + infoTextView.setLinkTextColor(Theme.getColor(Theme.key_chat_messageLinkIn, resourcesProvider)); + linearLayout.addView(infoTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 14, 14, 14, 6)); + + b.setCustomView(linearLayout); + + BottomSheet sheet = b.create(); + + button.setOnClickListener(v -> { + if (button.isLoading()) return; + button.setLoading(true); + final long finalDialogId = selectedDialogId[0]; + TL_payments.connectStarRefBot req = new TL_payments.connectStarRefBot(); + req.bot = MessagesController.getInstance(currentAccount).getInputUser(bot.bot_id); + req.peer = MessagesController.getInstance(currentAccount).getInputPeer(finalDialogId); + ConnectionsManager.getInstance(currentAccount).sendRequest(req, (res, err) -> AndroidUtilities.runOnUIThread(() -> { + button.setLoading(false); + if (res instanceof TL_payments.connectedStarRefBots) { + TL_payments.connectedStarRefBots r = (TL_payments.connectedStarRefBots) res; + BotStarsController.getInstance(currentAccount).getChannelConnectedBots(finalDialogId).apply(r); + sheet.dismiss(); + TL_payments.connectedBotStarRef connectedBot = null; + for (int i = 0; i < r.connected_bots.size(); ++i) { + TL_payments.connectedBotStarRef c = r.connected_bots.get(i); + if (c.bot_id == bot.bot_id) { + connectedBot = c; + break; + } + } + if (dialogId != finalDialogId || forceOpenStats) { + BaseFragment lastFragment = LaunchActivity.getSafeLastFragment(); + if (lastFragment != null && (!(lastFragment instanceof ChannelAffiliateProgramsFragment) || ((ChannelAffiliateProgramsFragment) lastFragment).dialogId != finalDialogId)) { + lastFragment.presentFragment(new ChannelAffiliateProgramsFragment(finalDialogId)); + } + } + if (connectedBot != null) { + BotStarsController.getInstance(currentAccount).getChannelSuggestedBots(finalDialogId).remove(connectedBot.bot_id); + BottomSheet shareSheet = showShareAffiliateAlert(context, currentAccount, connectedBot, finalDialogId, resourcesProvider); + BulletinFactory.of(shareSheet.topBulletinContainer, resourcesProvider) + .createUsersBulletin(botUser, getString(R.string.AffiliateProgramJoinedTitle), getString(R.string.AffiliateProgramJoinedText)) + .show(); + } + } else if (err != null) { + BulletinFactory.of(sheet.topBulletinContainer, resourcesProvider).showForError(err); + } + })); + }); + sheet.setOnDismissListener(d -> {}); + Runnable updateDialog = () -> { + if (selectedDialogId[0] >= 0) { + TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(selectedDialogId[0]); + AvatarDrawable avatarDrawable = new AvatarDrawable(); + avatarDrawable.setInfo(user); + imageView2.setForUserOrChat(user, avatarDrawable); + } else { + TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-selectedDialogId[0]); + AvatarDrawable avatarDrawable = new AvatarDrawable(); + avatarDrawable.setInfo(chat); + imageView2.setForUserOrChat(chat, avatarDrawable); + } + if (selectedDialogId[0] >= 0) { + TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(selectedDialogId[0]); + if (chipImageView != null) { + AvatarDrawable avatarDrawable1 = new AvatarDrawable(); + avatarDrawable1.setInfo(user); + chipImageView.setForUserOrChat(user, avatarDrawable1); + } + if (chipTextView != null) { + chipTextView.setText(UserObject.getUserName(user)); + } + } else { + TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-selectedDialogId[0]); + if (chipImageView != null) { + AvatarDrawable avatarDrawable1 = new AvatarDrawable(); + avatarDrawable1.setInfo(chat); + chipImageView.setForUserOrChat(chat, avatarDrawable1); + } + if (chipTextView != null) { + chipTextView.setText(chat == null ? "" : chat.title); + } + } + }; + updateDialog.run(); + if (chipLayout != null) { + BotStarsController.getInstance(currentAccount).loadAdmined(); + final View chip = chipLayout; + chipLayout.setOnClickListener(v -> { + ArrayList chats = BotStarsController.getInstance(currentAccount).getAdmined(); + chats.add(0, UserConfig.getInstance(currentAccount).getCurrentUser()); + + ItemOptions i = ItemOptions.makeOptions(sheet.getContainerView(), resourcesProvider, chip); + for (TLObject obj : chats) { + long did; + if (obj instanceof TLRPC.User) { + did = ((TLRPC.User) obj).id; + } else if (obj instanceof TLRPC.Chat) { + TLRPC.Chat chat = (TLRPC.Chat) obj; + if (!ChatObject.isChannelAndNotMegaGroup(chat)) + continue; + did = -chat.id; + } else continue; + i.addChat(obj, did == selectedDialogId[0], () -> { + selectedDialogId[0] = did; + updateDialog.run(); + }); + } + i.setDrawScrim(false) + .setDimAlpha(0) + .setGravity(Gravity.RIGHT) + .translate(dp(24), 0) + .show(); + }); + } + + sheet.fixNavigationBar(Theme.getColor(Theme.key_dialogBackground, resourcesProvider)); + BaseFragment lastFragment = LaunchActivity.getSafeLastFragment(); + if (!AndroidUtilities.isTablet() && lastFragment != null && !AndroidUtilities.hasDialogOnTop(lastFragment)) { + sheet.makeAttached(lastFragment); + } + + sheet.show(); + } + + public static BottomSheet showShareAffiliateAlert(Context context, int currentAccount, TL_payments.connectedBotStarRef bot, long dialogId, Theme.ResourcesProvider resourcesProvider) { + if (bot == null || context == null) + return null; + + BottomSheet.Builder b = new BottomSheet.Builder(context, false, resourcesProvider); + final TLRPC.User botUser = MessagesController.getInstance(currentAccount).getUser(bot.bot_id); + + LinearLayout linearLayout = new LinearLayout(context); + linearLayout.setOrientation(LinearLayout.VERTICAL); + linearLayout.setPadding(dp(16), dp(20), dp(16), dp(8)); + linearLayout.setClipChildren(false); + linearLayout.setClipToPadding(false); + + FrameLayout topView = new FrameLayout(context); + + View linkBgView = new View(context); + linkBgView.setBackground(Theme.createCircleDrawable(dp(40), Theme.getColor(bot.revoked ? Theme.key_color_red : Theme.key_featuredStickers_addButton, resourcesProvider))); + topView.addView(linkBgView, LayoutHelper.createFrame(80, 80, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 0, 0, 0, 0)); + + ImageView linkFgView = new ImageView(context); + linkFgView.setScaleType(ImageView.ScaleType.CENTER); + linkFgView.setImageResource(bot.revoked ? R.drawable.msg_link_2 : R.drawable.msg_limit_links); + linkFgView.setScaleX(bot.revoked ? 2 : 1.8f); + linkFgView.setScaleY(bot.revoked ? 2 : 1.8f); + topView.addView(linkFgView, LayoutHelper.createFrame(80, 80, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 0, 0, 0, 0)); + + if (bot.participants > 0) { + FrameLayout countLayout = new FrameLayout(context); + countLayout.setBackground(Theme.createRoundRectDrawable(dp(50), Theme.getColor(Theme.key_dialogBackground, resourcesProvider))); + topView.addView(countLayout, LayoutHelper.createFrame(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 0, 66, 0, 0)); + + TextView textView = new TextView(context); + textView.setTypeface(AndroidUtilities.bold()); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); + textView.setBackground(Theme.createRoundRectDrawable(dp(19 / 2.0f), Theme.getColor(bot.revoked ? Theme.key_color_red : Theme.key_color_green, resourcesProvider))); + textView.setTextColor(0xFFFFFFFF); + textView.setPadding(dp(6.66f), 0, dp(6.66f), 0); + SpannableStringBuilder ssb = new SpannableStringBuilder(); + ssb.append("s "); + ColoredImageSpan groupSpan = new ColoredImageSpan(R.drawable.mini_reply_user); + groupSpan.setScale(0.937f, 0.937f); + groupSpan.translate(-dp(1.33f), dp(1)); + groupSpan.spaceScaleX = .8f; + ssb.setSpan(groupSpan, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append(String.valueOf(bot.participants)); + textView.setText(ssb); + textView.setGravity(Gravity.CENTER); + countLayout.addView(textView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, 19, Gravity.FILL, 1.33f, 1.33f, 1.33f, 1.33f)); + } + + linearLayout.addView(topView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL, 0, 0, 0, 0)); + + TextView titleView = new TextView(context); + titleView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourcesProvider)); + titleView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 20); + titleView.setGravity(Gravity.CENTER); + titleView.setText(getString(R.string.ChannelAffiliateProgramLinkTitle)); + titleView.setTypeface(AndroidUtilities.bold()); + linearLayout.addView(titleView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 20, 18, 20, 8.33f)); + + TextView textView = new TextView(context); + textView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourcesProvider)); + textView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + textView.setGravity(Gravity.CENTER); + if (bot.revoked) { + textView.setText(AndroidUtilities.replaceTags(getString(R.string.ChannelAffiliateProgramLinkTextRevoked))); + } else if (dialogId < 0) { + textView.setText(AndroidUtilities.replaceTags(formatString(R.string.ChannelAffiliateProgramLinkTextChannel, percents(bot.commission_permille), UserObject.getUserName(botUser), bot.duration_months <= 0 ? getString(R.string.ChannelAffiliateProgramJoinText_Lifetime) : bot.duration_months < 12 || bot.duration_months % 12 != 0 ? formatPluralString("ChannelAffiliateProgramJoinText_Months", bot.duration_months) : formatPluralString("ChannelAffiliateProgramJoinText_Years", bot.duration_months / 12)))); + } else { + textView.setText(AndroidUtilities.replaceTags(formatString(R.string.ChannelAffiliateProgramLinkTextUser, percents(bot.commission_permille), UserObject.getUserName(botUser), bot.duration_months <= 0 ? getString(R.string.ChannelAffiliateProgramJoinText_Lifetime) : bot.duration_months < 12 || bot.duration_months % 12 != 0 ? formatPluralString("ChannelAffiliateProgramJoinText_Months", bot.duration_months) : formatPluralString("ChannelAffiliateProgramJoinText_Years", bot.duration_months / 12)))); + } + linearLayout.addView(textView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 20, 0, 20, 18)); + + LinearLayout chipLayout = null; + if (!bot.revoked) { + TextView sendToTextView = new TextView(context); + sendToTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourcesProvider)); + sendToTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14); + sendToTextView.setGravity(Gravity.CENTER); + sendToTextView.setText(getString(R.string.ChannelAffiliateProgramLinkSendTo)); + linearLayout.addView(sendToTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, 20, 0, 20, 0)); + + chipLayout = new LinearLayout(context); + chipLayout.setOrientation(LinearLayout.HORIZONTAL); + chipLayout.setBackground(Theme.createRoundRectDrawable(dp(28), Theme.getColor(Theme.key_windowBackgroundGray, resourcesProvider))); + BackupImageView imageView = new BackupImageView(context); + imageView.setRoundRadius(dp(14)); + AvatarDrawable avatarDrawable = new AvatarDrawable(); + chipLayout.addView(imageView, LayoutHelper.createLinear(28, 28)); + TextView chipTextView = new TextView(context); + chipTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 13); + chipTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourcesProvider)); + if (dialogId >= 0) { + TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(dialogId); + avatarDrawable.setInfo(user); + imageView.setForUserOrChat(user, avatarDrawable); + chipTextView.setText(UserObject.getUserName(user)); + } else { + TLRPC.Chat chat = MessagesController.getInstance(currentAccount).getChat(-dialogId); + avatarDrawable.setInfo(chat); + imageView.setForUserOrChat(chat, avatarDrawable); + chipTextView.setText(chat == null ? "" : chat.title); + } + chipLayout.addView(chipTextView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 6, 0, 0, 0)); + ImageView selectView = new ImageView(context); + selectView.setScaleType(ImageView.ScaleType.CENTER); + selectView.setColorFilter(new PorterDuffColorFilter(Theme.getColor(Theme.key_dialogTextGray3, resourcesProvider), PorterDuff.Mode.SRC_IN)); + selectView.setImageResource(R.drawable.arrows_select); + chipLayout.addView(selectView, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_VERTICAL, 2, 0, 5, 0)); + linearLayout.addView(chipLayout, LayoutHelper.createLinear(LayoutHelper.WRAP_CONTENT, 28, Gravity.CENTER_HORIZONTAL, 0, 11, 0, 22)); + } + + TextView linkView = new TextView(context); + linkView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 16); + linkView.setGravity(Gravity.CENTER); + linkView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText, resourcesProvider)); + linkView.setBackground(Theme.createSimpleSelectorRoundRectDrawable(dp(8), Theme.getColor(Theme.key_windowBackgroundGray, resourcesProvider), Theme.blendOver(Theme.getColor(Theme.key_windowBackgroundGray, resourcesProvider), Theme.getColor(Theme.key_listSelector, resourcesProvider)))); + linkView.setPadding(dp(16), dp(14.66f), dp(16), dp(14.66f)); + linkView.setText(bot.url != null && bot.url.startsWith("https://") ? bot.url.substring(8) : bot.url); + linearLayout.addView(linkView, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.FILL_HORIZONTAL, 0, 0, 0, 12)); + + ButtonWithCounterView button = new ButtonWithCounterView(context, resourcesProvider); + if (!bot.revoked) { + SpannableStringBuilder ssb = new SpannableStringBuilder(); + ssb.append("c "); + ColoredImageSpan copySpan = new ColoredImageSpan(R.drawable.msg_copy_filled); + ssb.setSpan(copySpan, 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + ssb.append(getString(R.string.ChannelAffiliateProgramLinkCopy)); + button.setText(ssb, false); + } else { + button.setText(getString(R.string.ChannelAffiliateProgramLinkRejoin), false); + } + linearLayout.addView(button, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, 48)); + + LinkSpanDrawable.LinksTextView infoTextView = new LinkSpanDrawable.LinksTextView(context, resourcesProvider); + infoTextView.setText(bot.participants <= 0 ? formatString(R.string.ChannelAffiliateProgramLinkOpenedNone, UserObject.getUserName(botUser)) : formatPluralString("ChannelAffiliateProgramLinkOpened", (int) bot.participants, UserObject.getUserName(botUser))); + infoTextView.setGravity(Gravity.CENTER); + infoTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 12); + infoTextView.setTextColor(Theme.getColor(Theme.key_windowBackgroundWhiteGrayText4, resourcesProvider)); + infoTextView.setLinkTextColor(Theme.getColor(Theme.key_chat_messageLinkIn, resourcesProvider)); + linearLayout.addView(infoTextView, LayoutHelper.createLinear(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.CENTER_HORIZONTAL | Gravity.TOP, 14, 12, 14, 2)); + + b.setCustomView(linearLayout); + + BottomSheet sheet = b.create(); + + Runnable copy = () -> { + AndroidUtilities.addToClipboard(bot.url); + BulletinFactory.of(sheet.topBulletinContainer, resourcesProvider) + .createSimpleBulletin(R.raw.copy, getString(R.string.AffiliateProgramLinkCopiedTitle), AndroidUtilities.replaceTags(formatString(R.string.AffiliateProgramLinkCopiedText, percents(bot.commission_permille), UserObject.getUserName(botUser)))) + .show(); + }; + if (!bot.revoked) { + linkView.setOnClickListener(v -> copy.run()); + } + button.setOnClickListener(v -> { + if (bot.revoked) { + TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(bot.bot_id); + if (user != null) { + MessagesController.getInstance(currentAccount).loadFullUser(user, 0, true, info -> AndroidUtilities.runOnUIThread(() -> { + if (info != null && info.starref_program != null) { + sheet.dismiss(); + ChannelAffiliateProgramsFragment.showConnectAffiliateAlert(context, currentAccount, info.starref_program, dialogId, resourcesProvider, true); + } + })); + } + } else { + copy.run(); + } + }); + sheet.setOnDismissListener(d -> { + + }); + + if (chipLayout != null) { + BotStarsController.getInstance(currentAccount).loadAdmined(); + final View chip = chipLayout; + chipLayout.setOnClickListener(v -> { + ArrayList chats = BotStarsController.getInstance(currentAccount).getAdmined(); + chats.add(0, UserConfig.getInstance(currentAccount).getCurrentUser()); + + ItemOptions i = ItemOptions.makeOptions(sheet.getContainerView(), resourcesProvider, chip); + for (TLObject obj : chats) { + long did; + if (obj instanceof TLRPC.User) { + did = ((TLRPC.User) obj).id; + } else if (obj instanceof TLRPC.Chat) { + TLRPC.Chat chat = (TLRPC.Chat) obj; + if (!ChatObject.isChannelAndNotMegaGroup(chat)) + continue; + did = -chat.id; + } else continue; + i.addChat(obj, did == dialogId, () -> { + BotStarsController.getInstance(currentAccount).getConnectedBot(context, did, bot.bot_id, connectedBot -> { + if (connectedBot == null) { + TLRPC.User user = MessagesController.getInstance(currentAccount).getUser(bot.bot_id); + if (user != null) { + MessagesController.getInstance(currentAccount).loadFullUser(user, 0, true, info -> AndroidUtilities.runOnUIThread(() -> { + if (info != null && info.starref_program != null) { + sheet.dismiss(); + ChannelAffiliateProgramsFragment.showConnectAffiliateAlert(context, currentAccount, info.starref_program, did, resourcesProvider, true); + } + })); + } + } else { + sheet.dismiss(); + ChannelAffiliateProgramsFragment.showShareAffiliateAlert(context, currentAccount, connectedBot, did, resourcesProvider); + } + }); + }); + } + i.setDrawScrim(false) + .setDimAlpha(0) + .setGravity(Gravity.RIGHT) + .translate(dp(24), 0) + .show(); + }); + } + + + sheet.fixNavigationBar(Theme.getColor(Theme.key_dialogBackground, resourcesProvider)); + BaseFragment lastFragment = LaunchActivity.getSafeLastFragment(); + if (!AndroidUtilities.isTablet() && lastFragment != null && !AndroidUtilities.hasDialogOnTop(lastFragment)) { + sheet.makeAttached(lastFragment); + } + + sheet.show(); + return sheet; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/bots/ChatAttachAlertBotWebViewLayout.java b/TMessagesProj/src/main/java/org/telegram/ui/bots/ChatAttachAlertBotWebViewLayout.java index f432c92df..969b364bb 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/bots/ChatAttachAlertBotWebViewLayout.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/bots/ChatAttachAlertBotWebViewLayout.java @@ -1,5 +1,6 @@ package org.telegram.ui.bots; +import static org.telegram.messenger.AndroidUtilities.dp; import static org.telegram.ui.Components.Bulletin.DURATION_PROLONG; import android.animation.Animator; @@ -211,7 +212,7 @@ public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert swipeContainer = new WebViewSwipeContainer(context) { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec) - ActionBar.getCurrentActionBarHeight() - AndroidUtilities.dp(84) + measureOffsetY, MeasureSpec.EXACTLY)); + super.onMeasure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec) - ActionBar.getCurrentActionBarHeight() - dp(84) + measureOffsetY, MeasureSpec.EXACTLY)); } }; swipeContainer.addView(webViewContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); @@ -226,7 +227,7 @@ public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert swipeContainer.stickTo(0); } }); - swipeContainer.setIsKeyboardVisible(obj -> parentAlert.sizeNotifierFrameLayout.getKeyboardHeight() >= AndroidUtilities.dp(20)); + swipeContainer.setIsKeyboardVisible(obj -> parentAlert.sizeNotifierFrameLayout.getKeyboardHeight() >= dp(20)); addView(swipeContainer, LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT)); addView(progressView = new WebProgressView(context, resourcesProvider), LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.WRAP_CONTENT, Gravity.BOTTOM, 0, 0, 0, 84)); @@ -407,7 +408,7 @@ public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert CharSequence title = UserObject.getUserName(MessagesController.getInstance(currentAccount).getUser(botId)); try { TextPaint tp = new TextPaint(); - tp.setTextSize(AndroidUtilities.dp(20)); + tp.setTextSize(dp(20)); title = Emoji.replaceEmoji(title, tp.getFontMetricsInt(), false); } catch (Exception ignore) {} parentAlert.actionBar.setTitle(title); @@ -437,7 +438,7 @@ public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert private void requestEnableKeyboard() { BaseFragment fragment = parentAlert.getBaseFragment(); - if (fragment instanceof ChatActivity && ((ChatActivity) fragment).contentView.measureKeyboardHeight() > AndroidUtilities.dp(20)) { + if (fragment instanceof ChatActivity && ((ChatActivity) fragment).contentView.measureKeyboardHeight() > dp(20)) { AndroidUtilities.hideKeyboard(parentAlert.baseFragment.getFragmentView()); AndroidUtilities.runOnUIThread(this::requestEnableKeyboard, 250); return; @@ -585,7 +586,7 @@ public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert @Override public int getFirstOffset() { - return getListTopPadding() + AndroidUtilities.dp(56); + return getListTopPadding() + dp(56); } @Override @@ -610,7 +611,7 @@ public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert @Override public int getButtonsHideOffset() { - return (int) swipeContainer.getTopActionBarOffsetY() + AndroidUtilities.dp(12); + return (int) swipeContainer.getTopActionBarOffsetY() + dp(12); } @Override @@ -818,11 +819,11 @@ public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert if (isSwipeDisallowed || !allowSwipes || fullsize && !allowFullSizeSwipe || (shouldWaitWebViewScroll && !allowingScroll(false))) { return false; } - if (velocityY >= 700 && (webView == null || webView.getScrollY() == 0)) { + if (velocityY >= dp(650) && (AndroidUtilities.distance(e1.getX(), e1.getY(), e2.getX(), e2.getY()) > dp(200) || (e2.getEventTime() - e1.getEventTime()) > 250) && (webView == null || webView.getScrollY() == 0)) { flingInProgress = true; if (swipeOffsetY >= swipeStickyRange || fullsize) { - if (fullsize && allowFullSizeSwipe && drawnSwipeOffsetY == -offsetY + topActionBarOffsetY) { + if (fullsize && allowFullSizeSwipe && (drawnSwipeOffsetY == -offsetY + topActionBarOffsetY || swipeOffsetY <= -swipeStickyRange && velocityY < dp(1200))) { stickTo(-offsetY + topActionBarOffsetY); } else if (delegate != null) { delegate.onDismiss(false); @@ -836,11 +837,12 @@ public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert stickTo(-offsetY + topActionBarOffsetY); return true; } - return true; + return false; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + distanceY = cap(distanceY); if (!isScrolling && !isSwipeDisallowed && allowSwipes && (!shouldWaitWebViewScroll || swipeOffsetY != -offsetY + topActionBarOffsetY || allowingScroll(false))) { if (isKeyboardVisible.provide(null) && swipeOffsetY == -offsetY + topActionBarOffsetY) { isSwipeDisallowed = true; @@ -872,7 +874,7 @@ public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert } else { swipeOffsetY -= distanceY; } - } else { + } else if (distanceY > 0) { swipeOffsetY -= distanceY; if (webView != null && swipeOffsetY < -offsetY + topActionBarOffsetY) { @@ -964,7 +966,7 @@ public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert float wasOffsetY = this.offsetY; float deltaOffsetY = offsetY - wasOffsetY; - boolean wasOnTop = Math.abs(swipeOffsetY + wasOffsetY - topActionBarOffsetY) <= AndroidUtilities.dp(1); + boolean wasOnTop = Math.abs(swipeOffsetY + wasOffsetY - topActionBarOffsetY) <= dp(1); if (!isSwipeOffsetAnimationDisallowed) { if (offsetYAnimator != null) { offsetYAnimator.cancel(); @@ -1054,14 +1056,42 @@ public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert this.delegate = delegate; } + private float sy = 0; + private boolean scrolledOut = false; + private final float minscroll = dp(60); + private float cap(float dy) { + if (scrolledOut) { + return dy; + } + sy += dy; + if (Math.abs(sy) > minscroll) { + scrolledOut = true; + if (sy > 0) { + dy = sy - minscroll; + } else { + dy = sy + minscroll; + } + } else { + dy = 0; + } + return dy; + } + public boolean stickToEdges = true; + private long pressDownTime; + private float pressDownX, pressDownY; @Override public boolean dispatchTouchEvent(MotionEvent ev) { if (isScrolling && ev.getActionIndex() != 0) { return false; } if (ev.getAction() == MotionEvent.ACTION_DOWN) { + pressDownTime = ev.getEventTime(); + pressDownX = ev.getX(); + pressDownY = ev.getY(); + scrolledOut = false; + sy = 0; if (shouldWaitWebViewScroll) { allowedScrollX = false; allowedScrollY = false; @@ -1098,8 +1128,10 @@ public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert stickTo(0); } } else { - if (delegate != null) { + if (delegate != null && ((ev.getEventTime() - pressDownTime) > 250 || AndroidUtilities.distance(ev.getX(), ev.getY(), pressDownX, pressDownY) > dp(200))) { delegate.onDismiss(!wasScrolling); + } else if (stickToEdges) { + stickTo(-offsetY + topActionBarOffsetY); } } } @@ -1205,7 +1237,7 @@ public class ChatAttachAlertBotWebViewLayout extends ChatAttachAlert.AttachAlert bluePaint.setColor(getThemedColor(Theme.key_featuredStickers_addButton)); bluePaint.setStyle(Paint.Style.STROKE); - bluePaint.setStrokeWidth(AndroidUtilities.dp(2)); + bluePaint.setStrokeWidth(dp(2)); bluePaint.setStrokeCap(Paint.Cap.ROUND); } diff --git a/TMessagesProj/src/main/java/org/telegram/ui/bots/SuggestedAffiliateProgramsFragment.java b/TMessagesProj/src/main/java/org/telegram/ui/bots/SuggestedAffiliateProgramsFragment.java new file mode 100644 index 000000000..fde8be866 --- /dev/null +++ b/TMessagesProj/src/main/java/org/telegram/ui/bots/SuggestedAffiliateProgramsFragment.java @@ -0,0 +1,121 @@ +package org.telegram.ui.bots; + +import android.content.Context; +import android.view.Gravity; +import android.view.View; + +import androidx.core.graphics.ColorUtils; + +import org.telegram.messenger.LocaleController; +import org.telegram.messenger.NotificationCenter; +import org.telegram.messenger.R; +import org.telegram.tgnet.tl.TL_payments; +import org.telegram.ui.ActionBar.ActionBar; +import org.telegram.ui.ActionBar.BackDrawable; +import org.telegram.ui.ActionBar.BaseFragment; +import org.telegram.ui.ActionBar.Theme; +import org.telegram.ui.Components.FlickerLoadingView; +import org.telegram.ui.Components.LayoutHelper; +import org.telegram.ui.Components.SizeNotifierFrameLayout; +import org.telegram.ui.Components.UItem; +import org.telegram.ui.Components.UniversalAdapter; +import org.telegram.ui.Components.UniversalRecyclerView; +import org.telegram.ui.Stars.BotStarsController; + +import java.util.ArrayList; + +public class SuggestedAffiliateProgramsFragment extends BaseFragment implements NotificationCenter.NotificationCenterDelegate { + + private final long dialogId; + + public SuggestedAffiliateProgramsFragment(long dialogId) { + this.dialogId = dialogId; + } + + @Override + public boolean onFragmentCreate() { + NotificationCenter.getInstance(currentAccount).addObserver(this, NotificationCenter.channelSuggestedBotsUpdate); + return super.onFragmentCreate(); + } + + @Override + public void onFragmentDestroy() { + NotificationCenter.getInstance(currentAccount).removeObserver(this, NotificationCenter.channelSuggestedBotsUpdate); + super.onFragmentDestroy(); + } + + private BackDrawable backDrawable; + private UniversalRecyclerView listView; + + @Override + public View createView(Context context) { + actionBar.setBackButtonDrawable(backDrawable = new BackDrawable(false)); + backDrawable.setAnimationTime(240); + actionBar.setActionBarMenuOnItemClick(new ActionBar.ActionBarMenuOnItemClick() { + @Override + public void onItemClick(int id) { + if (id == -1) { + finishFragment(); + } + } + }); + actionBar.setBackgroundColor(Theme.getColor(Theme.key_windowBackgroundWhite)); + actionBar.setItemsColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText), false); + actionBar.setItemsColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText), true); + actionBar.setItemsBackgroundColor(Theme.getColor(Theme.key_actionBarActionModeDefaultSelector), false); + actionBar.setTitleColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText)); + actionBar.setTitle(LocaleController.getString(R.string.ChannelAffiliatePrograms)); + + final SizeNotifierFrameLayout contentView = new SizeNotifierFrameLayout(context); + contentView.addView( + listView = new UniversalRecyclerView(this, this::fillItems, this::onItemClick, null), + LayoutHelper.createFrame(LayoutHelper.MATCH_PARENT, LayoutHelper.MATCH_PARENT, Gravity.FILL) + ); + fragmentView = contentView; + + return fragmentView; + } + + public void fillItems(ArrayList items, UniversalAdapter adapter) { + BotStarsController.ChannelSuggestedBots suggestedBots = BotStarsController.getInstance(currentAccount).getChannelSuggestedBots(dialogId); + for (int i = 0; i < suggestedBots.bots.size(); ++i) { + items.add(ChannelAffiliateProgramsFragment.BotCell.Factory.as(suggestedBots.bots.get(i), false)); + } + if (suggestedBots.isLoading()) { + items.add(UItem.asFlicker(FlickerLoadingView.PROFILE_SEARCH_CELL)); + items.add(UItem.asFlicker(FlickerLoadingView.PROFILE_SEARCH_CELL)); + items.add(UItem.asFlicker(FlickerLoadingView.PROFILE_SEARCH_CELL)); + } + } + + public void onItemClick(UItem item, View view, int position, float x, float y) { + if (item.object instanceof TL_payments.starRefProgram) { + TL_payments.starRefProgram bot = (TL_payments.starRefProgram) item.object; + ChannelAffiliateProgramsFragment.showConnectAffiliateAlert(getContext(), currentAccount, bot, dialogId, resourceProvider, false); + } + } + + @Override + public void didReceivedNotification(int id, int account, Object... args) { + if (id == NotificationCenter.channelSuggestedBotsUpdate) { + Long did = (Long) args[0]; + if (did == dialogId) { + if (listView != null && listView.getAdapter() instanceof UniversalAdapter) { + ((UniversalAdapter) listView.getAdapter()).update(true); + } + } + } + } + + @Override + public boolean isLightStatusBar() { + if (getLastStoryViewer() != null && getLastStoryViewer().isShown()) { + return false; + } + int color = Theme.getColor(Theme.key_windowBackgroundWhite); + if (actionBar.isActionModeShowed()) { + color = Theme.getColor(Theme.key_actionBarActionModeDefault); + } + return ColorUtils.calculateLuminance(color) > 0.7f; + } +} diff --git a/TMessagesProj/src/main/java/org/telegram/ui/web/BotWebViewContainer.java b/TMessagesProj/src/main/java/org/telegram/ui/web/BotWebViewContainer.java index b73d5c053..9d42d6e22 100644 --- a/TMessagesProj/src/main/java/org/telegram/ui/web/BotWebViewContainer.java +++ b/TMessagesProj/src/main/java/org/telegram/ui/web/BotWebViewContainer.java @@ -775,9 +775,9 @@ public abstract class BotWebViewContainer extends FrameLayout implements Notific public int getMinHeight() { if (getParent() instanceof ChatAttachAlertBotWebViewLayout.WebViewSwipeContainer) { ChatAttachAlertBotWebViewLayout.WebViewSwipeContainer swipeContainer = (ChatAttachAlertBotWebViewLayout.WebViewSwipeContainer) getParent(); -// if (swipeContainer.isFullSize()) { -// return (int) (swipeContainer.getMeasuredHeight() - swipeContainer.getOffsetY() - swipeContainer.getTopActionBarOffsetY() + viewPortHeightOffset); -// } + if (swipeContainer.isFullSize()) { + return (int) (swipeContainer.getMeasuredHeight() - swipeContainer.getOffsetY() /*- swipeContainer.getTopActionBarOffsetY()*/ + viewPortHeightOffset); + } } return 0; } @@ -1203,7 +1203,7 @@ public abstract class BotWebViewContainer extends FrameLayout implements Notific x = jsonArray.optBoolean(0, true); y = jsonArray.optBoolean(1, true); } catch (Exception e) {} - d("allowScroll " + x + " " + y); +// d("allowScroll " + x + " " + y); if (getParent() instanceof ChatAttachAlertBotWebViewLayout.WebViewSwipeContainer) { ChatAttachAlertBotWebViewLayout.WebViewSwipeContainer swipeContainer = (ChatAttachAlertBotWebViewLayout.WebViewSwipeContainer) getParent(); swipeContainer.allowThisScroll(x, y); diff --git a/TMessagesProj/src/main/res/drawable-hdpi/filled_affiliate.png b/TMessagesProj/src/main/res/drawable-hdpi/filled_affiliate.png new file mode 100644 index 0000000000000000000000000000000000000000..1292935c254b1b486849c00e7663d4757d0b3182 GIT binary patch literal 771 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB7>k44ofy`glX(f`xTHpSruq6Z zXaU(A42G$TlC0TWzSWdSpS4N|DZ_xKM315>W2i(^Ox z=i6z%-l2gaZSSv|IDXV#*cqXq;+4V7&SrUXrm*_iKips0S=j$_*E={1E#B7w*h&Wkzm0a%>%QJ zNUp33REgX+Gx*EX3r$UCrk&QF8$@S3VVnIYJ#7B2HAgzijVI_=P4Lff{QX=*WN?SXeSSJl^99 zx7gz|^TLw%hOTX4;a#BW=d@qWhS57gzvE` za`o=)M^i*f_t=Mb?8=sFyqzF==2pFsjOY%wH=mP)Z^!ozzjk1u7bwXxc)I$ztaD0e0sx4HNHhQd literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-hdpi/filled_earn_stars.png b/TMessagesProj/src/main/res/drawable-hdpi/filled_earn_stars.png new file mode 100644 index 0000000000000000000000000000000000000000..e796559a00ebaaf136b8d46d213c3e6ef8e11948 GIT binary patch literal 773 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k1|%Oc%$NbB7>k44ofy`glX(f`xTHpSruq6Z zXaU(A42G$TlC0TWzSWdSpS4N|DZ_xKM315>`Ii(^Ox z=i6!fy{86B9N#-bn|9Ym7Bkl;YKy!D7Me`;*4^s%g?$H$tKUNf-bYu$I4mta zCVO|!2o*_SjZIid+Ohpaxmj^k{{ywPlgFESyUaRY;|BB1*ryr1+ zVC|(WJxgWsWET!2bIqFEuBpFf7_FZsw!8h*Ub(5VU0(xT{cK%*JMK=ryC-n{6=_fF zDW6WUJ$PVcZvSCFPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91B%lKT1ONa40RR91Bme*a04fgS`~Uz0xk*GpR9Fe^m(5ESQ543<@}tN$ z5tS7o64?i|kkCa`v@j4tXlFuYi4iXP4_XK<`V(xWi#Ba4F=$!t453BP2YskWY6dN$ z45RYx_so0GcpGQNdDXX72cGwwd(LytJ#+8enR$cYzhtjKMRqtlS|}7Yz}N|PJLU{} zuA-u1(T&TmXrdZi0QbN=h>LuRiBnzHj+meWjDx%)ZbvCi<=Qfe)==~|xa1Z8g#8da z1==c!wyp)71v`MrAjUPe@`pKAtWE!UQP& z^pVsBOuB6n9JQbioHJisE9N-07GvBEOuEdUTa!8DvS-oJ4UFCE{0r!1H>rF;F-fmY zh^-r4wIoC4k83??mk9}#I9pv{nB)4}BiYuenN6PGO!=%);|8wAup_m)IV>mAQ9}^? zXHp%E&5ku1v5Uq7)ErQwUt%#$ET1M8M_ga@%#x_K<_IM=cM#y{2XmlF40JOvX)C5J z{Sxq-UkZ3u5(i(K6-76Tj&1^)yI*5lp)1&WvbM{K94xBv_poP&CaX}0`{Lsi&JmAi zb7)S@tsbI=tTDTJXBBjEmF}a{V$M=EHMWw|sTk0+6H z_!{6`275iOKS$(FYbYW(Lxpa{o#hzYR%O*zBV%AKZ))YL#)=n}XN4YfgWhDz^d8eu zUGap$@kLuROO2n@IPM3M>^u#Hc`Ar8l~kkz-$##YH?`*qg4_-Lm9@$N7xkW1(idbU z_KT!4*3dhGjX=7rLL+x$6WE-gwua6?Itm`e$m>32g>;Y#G-k*C4&%?@dFo^&od71w zULC24D+DN51zv(Nka#%Kj{OI(cKCkt#%nPfNInFP0sWuLFI<8B-O-J>C4Xi*lRAyS zWLFY1ZH2_`a|ybt0jjL|edg+As>FS?44Tk)js9avCcq$g2EKtbndF9@RFM2_sQaKE zNbKP%dm7k>-&^c<^1dsODiqYmxqZrF=8{ENr&JW(k}IP~3O`3OSI9bre-T^nq5bB| z&^peix!MtN$UTXwmyPJvb1Srh;5ypAIN!h@l|NTQu7HZ~!`=gG%$J~}EsMv<*TeU< z^ViXY|0)UH_F#xlpW literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-hdpi/menu_camera_retake.png b/TMessagesProj/src/main/res/drawable-hdpi/menu_camera_retake.png new file mode 100644 index 0000000000000000000000000000000000000000..cbcc31f98c38039c48da6820f0e230f76f7e99e4 GIT binary patch literal 1099 zcmV-R1ho5!P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91B%lKT1ONa40RR91Bme*a04fgS`~Uz0wMj%lR9Fe^m(NR8K@`V*+7GiI zDya0sAleuOS{VgJs0(XnM2JEVB;jAsu0^ZTbqGpPf%-=pkP&^XCM{r4)-oDF0RL03pNH= z)AZk@QmNU1ePy}~qznv#Sr8`jiBq;RqcB-1Pz~MzR}r^5$)KG7G7=Y)cnlme1qRS} z!Hnr!RYIc;oHK?O=;Qo_zf6B=iUAaYtzb8(1tY*pPh3`rJ(i4Cta2!qau&wLXZ(;W zz%B3tsS#^JmU67X%ub_8q!nw17{3P`?#eZ)C`xwcyXY#v~b# zeMR^3i(PhNakNE9zNS_DGs%FSm~=Y2o`_HFam1BqdvKB@1G+~>ePkuQ%+i*0GxuqH z+9D*18TMKZ59a}~kTvaHCg)D{7=`x-YZ#kOmf}ddOl-Or^Gx#ED^)kpF`@}=(mLF@YfPgkm?AK+AJRsNS5_zP>W63EI0 R=5GK1002ovPDHLkV1ko;)E58% literal 0 HcmV?d00001 diff --git a/TMessagesProj_AppStandalone/src/main/res/drawable-hdpi/menu_feature_premium.png b/TMessagesProj/src/main/res/drawable-hdpi/menu_feature_premium.png similarity index 100% rename from TMessagesProj_AppStandalone/src/main/res/drawable-hdpi/menu_feature_premium.png rename to TMessagesProj/src/main/res/drawable-hdpi/menu_feature_premium.png diff --git a/TMessagesProj/src/main/res/drawable-hdpi/menu_feature_reliable.png b/TMessagesProj/src/main/res/drawable-hdpi/menu_feature_reliable.png new file mode 100644 index 0000000000000000000000000000000000000000..e7c0f74d31d0d41ceee2559a472a5d4a5928dec2 GIT binary patch literal 1121 zcmV-n1fKheP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91B%lKT1ONa40RR91Bme*a04fgS`~Uz0%Sl8*R9Fekm`zAkK@`V*rWTo% zm6e5wmAH@!49Q$cWf*2|qD@5~U{sqHfmG0TT7*GG5EUVT)G`-E3$sN;S(HU+ zrV-eOpLzS8_hw}7bnmkO{~{O#4IQ z5$uN9!K3*o2rhxs&;_QA(~tr7K(1Ff{S_cfCrfDBAdMENfpGf`;X=3vw!NU^I23wK z!mAH$7Bs+Ju$TB9ecQa^Tz!Z-LEC34=U|OXltP0X`EVGXg0@jA{<(2hE2S(;BV?v9 zc3=p3%Rq01^JaiOV7YR3$6~Qom@viZs+0E(xgM*0t}S3ss6dJO(=~XKJfq5&V`}UP zEmF>DGokhHGuP2Rsxui`npT(zub_^}xCW(9EYuU~O=vTURp}}iY#sQX*!b2G z&<45i0cx4d0Hv4dG5Wlm@jenB0kJMyL|;8XAL~^eYlS9w7v1zY{A0XBT^5(>idJ%Mk*Z`DkpI3>0l?auUcxiOv6yNza-h_PQ_zgTWL0-gMf+mmSFmy`Md_gwTGzJc*WR# z=x&0T?6>5G=xki-IM4l*_?LruRh1>)?x#2V(PhJT5Nk4ho^?dkacSa@{c}GVgIg|K z4?s5Z&Phi=1?>S4le_lf0C~cV@QFlV&mb+wh-4;L9`u6PwNR5$fmMl^_}_z}0|SI) zyR7oc+gcbPce@F=rGjPSZgqR3x5#ly<&&a0at4CtL8nAx^8HmEIf)7&_NXLLCf{+X zZ36ZmsGqIZ7`*STQ;J6FzVkLs_}2E{Gf@UD+YNfwB4uTdkzNW zbNNX$2X5%hlyY<&13A%MMxQ*D6ClehCZAL%A+=?4Ww#n6-3R(Sqw0KuCXoM*kpr_H z^b%8bdSHu{CcRAtkbNcJ?Ze<_qd4S?SKoQkI|;9B3?>KhRTu-)9?~fDiV4#<<#lCn n*{DSzZ%<#63t5=?Oh3(ER|tw2>84NU00000NkvXXu0mjfixBcM literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-hdpi/menu_feature_simple.png b/TMessagesProj/src/main/res/drawable-hdpi/menu_feature_simple.png new file mode 100644 index 0000000000000000000000000000000000000000..a93580a7c3fd59e43a97ea58a287640672712f3c GIT binary patch literal 904 zcmV;319$w1P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91B%lKT1ONa40RR91Bme*a04fgS`~Uy~@<~KNR9Fe^mpy1yK@i99#rPG2 z1cQZzSSXT+V4)G=3PJD#J3$Z?B!yUtSZL!%Ay@~3f**}#3c)Hwu@E7J5UX&A9}rMb zA_N2_phnN%yqh`ZHuqj~@7~>ky6}H9JG(RU-`Rcp?h1u}p_R+!9`g1yV+}m5GlkAcv{Ea-|6VdO}F%f(HSW9%0>;pd}8|_w@1Y0p4sXW$) zW$}SBgu75B;rK9 zz}SVf9q6<5<|Nq?FDwGC!D$;R1d`vh7D?*CDtxC;S+wJT|ZsL zc_WZp_a{hdPZ(I46p69(Nsell`{@cuEJz$%CV(lB)P&?6kQ#{b0brLRYGRW+Y*o_2 zS9zUJ0(qO{KG_1S`SPd5lEsN{h(T;OfbGD|v>+o)l=jF^qTd@^E05iNEUQka z%NkQYJJpJJ1p)iC|0S^ha(z4G&Xh^Lt6p-4?XUPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91B%lKT1ONa40RR91Bme*a04fgS`~Uz0zDYzuR9Fe^m(5F5Q5c6EHT)tJ zwGWLlGLwqTP$*l35VnYf7H#?i42tLzS_y92NMN)Hi`HSS3Mz^gDG{_Nf{YSLGYc`L zB+0TbZ_n>~&Uoj}%=M~Mi|~bqbI$wz-gC~JbMHGdX=#i0Kb(N$I7guAZ+#Z#u7tYK z^CNyK!X%(ehhiuSqn8HlRp<_-M_2~d%KS+L%HcTNgdz9^)+Xt^fm?7G)+ET^j{ghD z4*KKKF~J%*13h3(efer7q%DOBn2TLExhqL}njoGa&IhQA5j@ti+hF=Ce6{wI~}JIweiuNfAE@G8Na2(WMx)%Zm*_Ry2^HLtIxP)54ql5`3FYjK*@(AxM56h0QnKM>8tX@M%vG`n*TVEGjK7w}46>?OU&9SLMX9Q&vPvt|M&3v!T+B4+_hiZaeJ=vjIHk8WO z{_OOU&Trb8*IpaIam56`(gt1~@l+JX66VoYh%B}+nhoUyXmcXAldMIoAM9;V83di46k5Z;)44?KV{7APx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91B%lKT1ONa40RR91Bme*a04fgS`~Uz02}wjjR9Fe^mrqDlK@`S)nrPY| z0vBa1BsB5xV)b(6+&#HN94Ce+H^r+Bd}kt8F25wRmccg<~3nHYW-(9!<{wgxpL#AJ(pJhx9_ zd%672p8>y+&`B@%5WfRxGA}H?M7txX8L*t1c0ZTPr>NT#=nsp?UG(x#@LyVAlQ|aQ z@ACND!hFp>#r_H%h!wy&2bR5oG?}9*+(D05?4%*^#zZc~22qUsd2gVz8N92;n@i)= zj3aQvMBdSM#0rS(NIUCE;cS864#>3_Csz!S?ZCa&qgOxq%n6a#OZh&KeFj~*22Ud) zFe)Xp3n7nqtqD!IV%p-Dq8k_y{X%#3YXhHfIkogV!54!*+A2ou3UD@2N&KyY;(DM9 zPxcM$_ciJF3PH@@>{I0H{!OHdZw|;7fctFq=TY{_9R}_?c!0mIid2sP!u>$61=-D* zCh=zAKF*%uM^9(0RSV!Rc{ z-(owh{lMxto`o9J@XH_DD(Ezw273&rXy<`!szJk7%QDzL;QvFY3HYWBU-b=DtyIT| z1_XL(egw|s-vcXd?K%#dPl^d_67sRvSpV4y{07@$C|LLH0RjL3002ovPDHLkV1gOc BkhlN< literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-mdpi/filled_affiliate.png b/TMessagesProj/src/main/res/drawable-mdpi/filled_affiliate.png new file mode 100644 index 0000000000000000000000000000000000000000..4299ab4b8b6219d265bece211d42775e6d88d739 GIT binary patch literal 549 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgftl!hcF~o!S z?UapP%#H$W#ceK~VMdj*=LTgSiep(dH)xs8GS}QTTQrhAJ{U-JTsJF_Yt$`-@a$wOq+vOo+_x% zle@U5{cUW6`R^kMFCr5%s%&%_J9A$hSe>`a)N8HqjCa!wBc|~k6N@>t+40j8`-3}P zl^2GntvSr~^%t9aY~-6;zIfkh zy0h=)f(nII!q#Qtx3)gGa&1LzqpbU_(d$1t?OSo| ziK&ECR#@${TecY|*53@R&2qWXW^B>At?NnY4rbnyPhZbssTB5mAoFcYTt!Nd_?;JG zh8xZwxIX`$%4*HkX(#u4RDNZ%iLiJ4|3x?tv=`n+xxUr UqBC1RodiXZr>mdKI;Vst06=}+5dZ)H literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-mdpi/filled_earn_stars.png b/TMessagesProj/src/main/res/drawable-mdpi/filled_earn_stars.png new file mode 100644 index 0000000000000000000000000000000000000000..c7c1024c6f4ecc7d7280ef6ee0da1c26a92561d2 GIT binary patch literal 578 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDlgfY>lUjV~7Xu z-Kl%MSque^=JN~wIpnlO=!(-57HMzqwXD+I8$~w;eu&>7sN-_s+O2EX>>qF))ZY*k zAjs;?FUWE*x<3E9hu-WrZ#kPRK9s2$=f_Pt`)pmvFK@BqAD4W{&UX3}m66(1)X@{L zd-*kAzl;qG<_{~>7bLd6GOpX;c+&XCqmI?{dbXw-WiD!Z&EovP&{y^RD$&^iGKE(s zZ@j=FxkxBb|uzRz3tWgn90U*t4~UJC9527vUApxZ z`y=HI%&WrgwaBw%w;ti`Y%`Kzu|4(B{h(b1`?kGCGfZoBt7mPU&AcG=SB|N_$@fJI z&3rx^zBn&fd3xp4n)aN}(`R>dua@7nz;t%Psh2kCHBX;d%_%&*;U>#2AJYxoYCAlB z?Y?sUlZ*x9rrB5iJ~$~^k-ux=qI;>2{=c?ooY!E+%k}JUIw<};UHx3vIVCg!0Ci&K Ae*gdg literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-mdpi/menu_bots_add.png b/TMessagesProj/src/main/res/drawable-mdpi/menu_bots_add.png new file mode 100644 index 0000000000000000000000000000000000000000..4b6c0f8e77b60a2413aedd869d500bf9bf259225 GIT binary patch literal 747 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDq2jfhp9}#WBQ# zck2|}?BGO^Tq8(_M_HHwS%E4PclS#|+)b@z?3n@V0S>9*MZ!aU>F zLg&&VLH0tQD`6a0SOj^c>i>V4qi(KmzO%LJ!Sv#JpTF6df44E8{PVof(zo|>%=#x- zXa2k$C}Mi)n%c9ACQZKsn%H)nnC8E+vE7F!sAA&W_`|$EIA4DdR0%E+?mHO0$ReqpKc@zSbkHH>S| zKH9nIX|PW6PSrD(W`X_N9$YwG#rOQ7k;gO}=HDV2SynD~NAGm*WfA%sp#OEwh4=~V zCu3*iM(#1t_gTI2^T8P#Y>6#;kt(C4L9b_*y1?(Ia80LSem2%e{Qprobo=`au??w zV{$~PDf39`;tnydfJGhgE?08qm6xY|J6Ey3D`B#@T>3f7_m;))@9xglDn0LYKdo+M z$f~2)Sxb-AAKW$1Z@Ip2&G*)aiwjs5PLbZ5xoPjBi20HemPa&5UYP3rO1jNvo=CaT z!jw6C+*@`^UgoY~yZeF9Y{7g*_BDPL(Hvrm%vK4a*B)5ka}J9);W?=15~@(~%6j$0 zECv6NS%x8{8`;*^MJ>a+V>1U?K-^aiDBH=72(DvtB+Te98mGtt0$SW z;;OU!pO)YX{zCB!-f^=^lsUu_FRLm^f8zbXdCtLVozBz!!Gf!A2i;_;wG$Ow!y$cp z>YQ~|oJ-b!V7w->Ape2f)VCMC?#(Y(O}z8OzR4tyeZ&0r8^?t|-ucy_+opSXYI<(_ z(X~JBGIsXPu*#_8Dc*CvUYKp~!SI%4J9ZUyvIkhR^7`d0`zH3qa*9G&Cwsxw<0T@B zr$t{yzdre7lS=oo*H?2VR5Td&Y_@C4WZQOIZS((}tWPoeQg_;gtX4jCuxg2H{uZS7 zMO*FtdF71^w+quQ8+gPX{?2oYKko7UW&0kyV)>@U9`mVBI*qp>WUqA-&zu8%Jr3$y zoc{VgKasr0KjP}@_~5grD)y?x9n9w_^?Srna(gw~+o{32Q_^0%)zbdce%bAt>6e4f u6#^GLbY5B)2`#w(i@oOI*1PF{{lD;^w{78J_|0emN>84yelF{r5}E*e5hlO@ literal 0 HcmV?d00001 diff --git a/TMessagesProj_AppStandalone/src/main/res/drawable-mdpi/menu_feature_premium.png b/TMessagesProj/src/main/res/drawable-mdpi/menu_feature_premium.png similarity index 100% rename from TMessagesProj_AppStandalone/src/main/res/drawable-mdpi/menu_feature_premium.png rename to TMessagesProj/src/main/res/drawable-mdpi/menu_feature_premium.png diff --git a/TMessagesProj/src/main/res/drawable-mdpi/menu_feature_reliable.png b/TMessagesProj/src/main/res/drawable-mdpi/menu_feature_reliable.png new file mode 100644 index 0000000000000000000000000000000000000000..4454e31686bff448b382dc40636846c7e5b47a97 GIT binary patch literal 747 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjjKx9jP7LeL$-D$|Tv8)E(|mmy zw18|52FCVG1{RPKAeI7R1_tH@j10^`nh_+nfC(-uv49!D1}S`GTDq2jfhp9}#WBQ# zckNVPk5EU6He01AkBj|XvjxO+7cDSj70Qe0;0|<(Zk1l>z{%t}K_I46_m*Urv@(Z? z3P*1EyG>CZvz69lOjcNw`h@?-^_P|Fze_)xSIu|*_Sfe2nU(wJ&onMRH)p|%31V#z z4V_*;P}#%Vl<;%H%9DvAx@~L~Os^BR`9Bveai}b6W#RV}f2gR_l*4;$f!sM~`x@4p zY&NXAODq;XY4kbZ`-5SVYz1R(fY|hoIQGR0SWna)s68={W20co=L@`|g|3fHl1?v> zjcZzCuFY2{v1QUFopd>-pYIc|1^sbf=@j$m?G3}{_1pHJu+H20kLA`R(PEJcS&i?` z{NeO__+zer;x?!1|1xZQ_SpVXHT$N1!~M%~G2z=mA+Bl*MeV8_f9aX!zDaXBG21sMSQvb-~QgeqTV*id|O3Ud91%O{i=Lh^ab~if}Hb0FI{&`mui(e zylTBsTtj)mwUS@W3erYrqqYRyXlA?|(_O)za#ih8ew*$Op63h1Q!a)s54xbge9s+@ zu)|v|^p?(@>$jx2O!f=ox@CXQXZ&4T`tSw+1N|MYR?Dp{_8orB^U>k@lluuwf5Z>B zE_(g!FH8O$iMJ2-aBo_+LjU@VIo2!B80?uS;qAbgvA8GF z5nsP2a)NL3yN!Q(UFLf7uW5+07oFP}JE2UvBv&L&#FFtB|LfKB+T9CwZCK?#(Z3g z-Y3P1lipwdEd70!b@R-RM%>Hgp5G}x|95Vs|8-uLO*seWzp9=WerBd&MicW49=2xl zANQpnEfr{FztNmN4wezY%()E3|N{SZDy{h-Q z1m7Rp6#M%V$CE>1cbxW$D(W@4&)8rYFW0C!fp-_vn%zl}=Usv34I{EcW+maAb#&MqG7k6OYL@3iFZcC=yHBsKf=k>LFOYn;AX zo>R1yp0~Nq?Bnu}7N+s7*VR_4t1fgb+IXK$mLvAyM3%ey7jBvE=iMa``B-hQmrf{i ztaaqO-QNGF|1=L0Z4!;1yQ*=%{tL51t8ZpqX#QaL*z)X>gZ*wtDpm5Vxx2}q$rK?i%ZMdpEEAs?U!`j%i>+Y)V{#2YO>x##(kTG%%mO&vPqoZ z75(F)gXiUx85iuzyp|jPO@7-fcclO3a_cYk-|jQ$8+!ZJ{?ktcMY^Y}pUXO@geCyP CZ31rq literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-mdpi/menu_feature_transparent.png b/TMessagesProj/src/main/res/drawable-mdpi/menu_feature_transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..31ffb39111bf59a19da6308ef50fd9109d11b42e GIT binary patch literal 731 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 literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/menu_camera_retake.png b/TMessagesProj/src/main/res/drawable-xhdpi/menu_camera_retake.png new file mode 100644 index 0000000000000000000000000000000000000000..b6af8d5204fc8a5b0f29eb22d294f0b0ae18ae9f GIT binary patch literal 1477 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 literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xhdpi/menu_lightbulb.png b/TMessagesProj/src/main/res/drawable-xhdpi/menu_lightbulb.png new file mode 100644 index 0000000000000000000000000000000000000000..a647ac5b847740b41d4844529bb873b58fac9b64 GIT binary patch literal 1303 zcmV+y1?c*TP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91FrWhf1ONa40RR91FaQ7m0NXcg3;+NGfk{L`RA>e5m|KiZR~X07FhOQA zG9>E$K-CjEMU6CZO~fVDj-p~b)swnB5q;#rL;K)icn}ZnA!rlYG(}CAkctNplaXSi z4ABy8+)ZN4IDX&BS|?xkK5Or7?|p{E`I7(nzV+SrI&1H}*3M)~RGNYJdj`tVzWi)9 zTTRXyFc=Im`Tv0nn#;<{9vR)c!)Uq^tOAXo1Gq?RCxQ4@zV$1hiE{8QxC`EtBxVID zPjVr#mk@0g8qQFFxdqd*n7Y%9X0 z=(JeL2qJbm(38=^xGHYG&^VHB0hZbkCWZ!uIFRF0Ah|3xgoMO1lYJD>kI1F*GWny( zTW`BJH9quvCDf33pDNOrFr6=)+^1CjJwAsd3KGjr_f!=~ymY+%kXx3Lr6&lzygsqV zB%5b5!~b+8!dBt3S$&WXwkyEWTpJU_y)PA)?@VPls^q*#)$I&q=Jta=d4mzq$vxD< z@IJiGYd7Pcm41ejonq7r7^X5bD>*09ki5|sgPx!s97op|o+IRs5SqXkAbX(CK_Sw) zbF+A>te~v;eFHj`zZIqVsUfj* ztTX&rkgv5xT|&2Q0DO{?eImq%681XF!tt#fruo$sN{Rd9q``je_7%oqoFlti{`X5y4|A{LY$cAvl?n zLVhA$0DrxlV;fYit<01hXi_0^jU&+yD%mD#%F`-6qDVE6gNLOv2+lJAwvE;izQNTA zwn5;A@|Ep+FPFbpAL$>EAoDNC4z$Z8nF8NSvC#X$`1=^K{ZPvC9AS));c~o`o4za&b{H-3Jl{Ua|=M8RSipFU` zHfuLcBf_=>6~itc1@ysaXOMpsTkI!@O-@5{@c7M)Z^E05T!_gIbG9*DhmYln^P-Sr zx9PZmp}>-C*rL+GG@6Y39tbFAw;1A*=sRaX4W}uMg8HSCrP6X|wM<4nAFeib=UY4= zzBneD9u17^BRbq^+N@Mi!J~F N002ovPDHLkV1lK?R3iWY literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/filled_affiliate.png b/TMessagesProj/src/main/res/drawable-xxhdpi/filled_affiliate.png new file mode 100644 index 0000000000000000000000000000000000000000..a7bd398f58d4e0e9b092cf33033bb8b5d2d39ca5 GIT binary patch literal 1392 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 QRR91007*qoM6N<$f*IX-w*UYD literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_bots_add.png b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_bots_add.png new file mode 100644 index 0000000000000000000000000000000000000000..69ae51f5a1df69be87335c9bdf74e48241f2685f GIT binary patch literal 2051 zcmV+e2>kbnP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS?ZAnByRCodHoL#6@MHI)+wXzaS zi_Fg`%8y9Nx?(|=;A=u3FCz3(7NmmMgAfHhNS4r34+%zy1|cCt8Kn|M9}h{TX&G8% zSWuc~YWRIqZ@>R_?{nwOUVERjzs^19+`HiK%&eKU)_>O7GkfpZ7b}$^8Op#=28J>) zl!2DbK-WMr>+S8mp7_(iWH1_x1d@~B1lSLL?&|6~PTBcFLb||gunPPL8WoL|jcf<> zM=j-7Vp8M5Yv2!%SL8#!#+O32nNFF+NbnLk4T=`|l<#m^HgjtEV?lf=I0DKR#aNo3 zvF)Mo^$gi-3AF(%utU~pFVXrH9CRQ~T0Rp3+)goBK z2WMlG9QLErUKtmNzPaF7VADgnIBsm5UN%6s68V~&Fm`3%sM>$Txsg|v zIJb@I2{NZPZeL5AW9(ZseS18c^R#t9){tl{kc}&Q;*1%jQv_?no)c#ir_YknM+|4% zcuCYLU_>CQ7}LFu;k0#<*r?pWK-su5J@D=7`sx9fZIO;j#o}%Z>sMt-kad@Ozu#q~ zrWn@4+7*$J@d}Hld|LX@%jU4+loofVJ6cT&5E;!crcHQ?*KvXN7Ek9kb)=y&zV!Ht zgtj$SKIJ=3KBB{qJ0+MyoJ~ZZDnVH5Quk$F(xEzin#6X`=s4}Yo-V1Wv9fVxd*IvE zRqFw#eN{z&CC$=~UZP}J2VQZauMfWHu&bUt&4BNNL5C<8GJ!MojKcn4>_XbImB^QT z))R}HzXGa066a3mRIm$3^c7)7)iS7{TcJ)**8Ah)+SoOG=PJSn@z#4>)m-8GMmGa! z&JyKdYB?V^}dOGVR^j2?7D>W@{HFF5&*zJ+NFKoLB@{^%Hw_i5bgoaMpTF09XF{&0f ze-73>tGVzKQ0qS0Iucb?w0AvZ=m%64aH{Sl4jF3>PIqlJZGytPi0efUY#SkD{aWt0 zrqu&38wqGVpjvZ?<(4)kNGb;nIu8zrjJc z{2O|QOIXcZ7Lp)0dO%Plt)m8aXj{~aSYoX_QR^|oKSWF9-{}e3Y0UmRLl>*+=mfFc zKV#;v0$oWu+0-${d!S+I=maSXB%eI0OF*if+ufv3<77ApoqiNwUVSH z9q($6n(NgLZ{?y-+$%_50bJRNa}<6a zY{Y50Dpi64cCSqw>^K1&mLi2rghANZe?6<4?PJ5_N+`{*gB9ag1#2hMMW3YD1> zBtyvQl|;u0am%WQzAwRkpySZ2?{#Of{S@LmC~E``&!Er|Mv*d~SX&Bl2*Cy594K2Z z65w&4G`hD-2>gm0C-olW8k6ZwwC4f6=#?qrsdF7)KKB&`v}=4;Uskg^CP~}*6=!0< z5NoaeI&+J;cqGZ)jM1W4H%>><%upPNJp({Q<)PP^6ektR8s?JZzA- zr=FeLpLvsZomh2<>FzNZOaQv|XMO9B-K6oEC`u3mHhY7*x$1QXZb(X74v^6ElF-(Y zYm;v4nKwv&C_!}+H5e0=>a$O$CrD56uILlb@2+Cd*SVAFe~8ghtDg9m4Elsv2_P~0 h&kx)WgVr!p_djb0#Ijxly literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_camera_retake.png b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_camera_retake.png new file mode 100644 index 0000000000000000000000000000000000000000..333703102a014e13512eb9a16084732bd056b07d GIT binary patch literal 2167 zcmV--2#EKIP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS?;Ymb6RCodHn_GxgWf+EMO6@RH zYHFmV=A@3KB@{_{z_PFlgF!1HyX?-$yvY*AvZRyRMczaRk{2bU9V`q)P16XWqYIL=J_gO|1b@BfbPUWb3JSyNL}l_~>O2C58H8R)bO z)OK`bZEbDWkaH3k4EljeHMC)K1ndXfYHMqcvh98XF=N15a0Vn5`6|YkZp9D{HG?$7 z;2sdgYVH;!(a4fOYuOH#bZL^{Vs5s4n9=6uNm8W?Wa8Ykaxc#7!KFr!U(sy?yNo$Y z)re9T_UnzwbLj5p;=Re3>zpdc83qoUzjs^Po9KJRtz14pzLqH0u(&ZViamw)2j5#= zpnE-SQHl`~TRHoujqmy=I>jjDN&*c61HolpP@PJ*fH^>G99KR?p;MvNorp6gcMt4! z>P|8I_M_8}@B`4PC;12b1`d$>loYuLg$)Gr!FF&S#F4!iYf+eqC>Bu6IHH*l5#W!cnekf>ZgS&hJtoI+FBh9cq8>+t)n#6!ap;Z1b}%Z;5&d4E^^7ip z5-raUj#z}4jB%JJp-Hr?}7nYg(lVzpxV`gB`YH3!c$M(D$o_(HAY7YlER1_3w?ql)AmTGCGIHjGwhC{6Nh~ZyDBYKd14LoVh zr_d>G8nJo{aNrV@CYk9AIePN{08BLvN|kyFuhjSny$s698YV|Wj<&f=-bc^^ItW|neZ<@|G^%S-yFjW)vqVSDX!H$ZE=(m-M2<-6P|1xTx_A}1r?4JqXVR#yNl`?E zbof!@FDw^=IC}0P$uxf=Z+~F~a`YD9Ja`XWl%vM0^}kIn1QGcWe-dy1XSS6tz%>x4 z#alhw(?*OASGlJ^Rs}Q}fnK2LvxdGz)mg|IOlN<8ffI&XXsL?Amug!tOA7}c@?@n{1D%y3hX!+ zf;;L|L%k1nMenM!7Ol?x^=p4h)=B&cvWnee1nO(((yE3U0Wv?0^C0MeQTPppF0IOn zY2{9pCl7-3ryy4*a(&3`t)v%gqML-VmD@0juF8X;9i(?ZwCsa!O7yZSS}K-e`b9aF zJ5$M=Anqc17+q&n*=*>Ns;pS+yxd7+ntquR-4g6hJ}f& z&x@dsso-0qisdvo*3d^%dA`dHW93rL%q16sIMnr8W1dlM9l8xPY8?TFc~Ry0Y(S$J zuFUgty86`PTnJKC6ugm*R=TbXK(UF2p9NO40ejiVRz5(!uB`HMx%y1Txe?@5_ZZu+ zjY@l=dmike`Nd!YxT;{MeNVSoHlD3~Tv2T0$W8Ki7Hcn-U-mSOHlf%G8rmm{JJ+%M zln4EJsIU^QFPenz?PaD(IYKQmn8^DG(y6A!+c>Z}LcJm;3OR~3&Wjx(Y;qHqAnPv* zu1)N0G;#!|so)jxckBQ~2l*<-R4-<6CfT0VTYv-C;INUWzoCn*qM=qWAICA;n(VlK> zxzuHbap14KW?LBP18Y~#qL^ShbU~$h0qV(>MHSn@qaQf#dNS5GeGgO&(8&$dgVzh2 zfT-ta|1N>yf`DsraGqVQe6Q%_Jd~8Dn!Vc9gC)%+PAFAJdbt|tj@Q?>9nAjiKr8y*z>+N}@1y3x8c2pFL7E_;9s@oD zBQ=gG`rEol^pkdq@fo1Mt`@e|KGQXN?$Rq-!P=KjA7=T1fRU%R`AdQne0aze>bvt^GU85D%0 zN&na~2h0Lt=W@Zkvu<_TzXO&*%+4r9Mkc5b45D-g8xO_;eW;+9SpI8)_S@g>u-}n# zf@Hex2`%1MN63$02lxVFV!a-TO%O>c3MA?-=#q9hI$d1DF1!W3;+MOK7cT62vJa3! t^Cfp71*=Mxfhq%42C58H8TfxP@Gt*HA_Uc}&+Y&K002ovPDHLkV1lfH=9>Tj literal 0 HcmV?d00001 diff --git a/TMessagesProj_AppStandalone/src/main/res/drawable-xxhdpi/menu_feature_premium.png b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_feature_premium.png similarity index 100% rename from TMessagesProj_AppStandalone/src/main/res/drawable-xxhdpi/menu_feature_premium.png rename to TMessagesProj/src/main/res/drawable-xxhdpi/menu_feature_premium.png diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_feature_reliable.png b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_feature_reliable.png new file mode 100644 index 0000000000000000000000000000000000000000..04434f06c808b2a321ff6edcd2eb57cc9ed2302a GIT binary patch literal 2286 zcmVPx#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS@SV=@dRCodHoLP(&MHGe|Ko&s| z!37Z&Tp}1jArjQYC@4l=6pa~;;zW%hCV~+(2GNK?Q1n5gk{|>DaZ89Iin59DL|hR? zB0+G83L&C^vTx(}kJHdop6agKx9`kcoRj?1U8l}g=T=u$R~H;}OcNz$pdzuh9xJP> ztJ~tP7w7?g0Bb5LDt2-G{}M@Q4Q>YSfqfuOD!&Vcf|mcAEYa8sa0QqKwt*1(1EI;F z|52ML8h1XJ0Dc0+&{~uq0cVtbvS`FfU?lh)xI}mymTn(Bvx?~ZQ#+56=bZ6_yUB|-w4hE zMLz=AXbCj+&jnfvgplM)yJWyYKcMwWA>+zIS*XeR)PxeK_tg|E}Ie*>t`p#?xWj3qnk3m?lWRP*5K8!k{&T@IxqJb zOpQKdB*U8rZv{r05=0UTP0%(Y`wh-w#vKe6gLgo$lz^-r+mp&|Gr;u`$E38LgEj5C3`g{w-)UAl<3 zUwBB0(gl57B=J z+!gRei3`6ezkRg&Ei{xm{qN)5c!|Di< zGuz;&u{4gwNxjhf5}x#kS({^B#(oI>yZ+3XjH>R5k@YOyqSKLZ7NFn&eRnVq?(#WUhff8)3{n zl<;oB&!iE>?{LTx2FGabFCsLx2= z%_*rOus_fzpUTIWPG-1td8#4;D76R6UFF)cPGi(LvqSkFkL)t| z*85XP7ez^iI&C%*SO=lLg@0m*j}RGbbpYzv3x&>H|6zpmpXzH2CaDoyx zPXOxqaax|BQdkvM-GNrwKc_G&N2Jl!R|xm?myQqjJ0g|e9d<_nm+s2V1D0=cty6xb z7zgP01JPPZ1M(%s*z@UPw++|?;gqKI_+BYSb+__v6qan|4=;W z+WVJscZlKPhNA_!JIc`@@tzHKI&o_(Iz2}>|C++67JXW`xKx$9vm6W(q;>?Xm3;oa zCyEz`TJgHH6-MR(JF=bkm6#^Ffl}TGvcFn{1c`%rhU2v*cN`L2tqif;Zp0|o4F#CV zCW@1;K)uGLB{1BtoY|Zdp+yYW0(Z@$SZ5VswlGzkT?gFv8U?Wsq;7{4<}F+r--^ph zj$V3m*%1hi28Y(-3wHG~VD9$>8_yt!Pp#Fguuo1*H~b^ zxUac|?XCeU8%JwY$RrtA@VjulxNz57))pATZ4$ z))GLGt2fOAnFmaC`+!e@kLFX6+ZJu{88Ivn;JIL>uLbb+ZuB?^e(aFdSo18{4=fU2 z19efKg^cEuhDD&qf^opNF-BGOX7DiZ{^Hc>ak?95s`k;3ROmQf`4NF%3|4_8`jg5h zz*#{5*@=>(7vmv7%Y`JGL1l;X%SEFn%=h{R~8EDGD(V2mN0MNf0Fb3$=4gdfE07*qo IM6N<$g3AmC-2eap literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_feature_simple.png b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_feature_simple.png new file mode 100644 index 0000000000000000000000000000000000000000..49f20d963c54d485d3e4ed8e2e78bb95df9842d9 GIT binary patch literal 1738 zcmV;*1~vJKP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS>G)Y83RCodHn^}llMHGg6G$xw3 zM5A${ViFiNi7%oe0po%wiHXUJf<_?@F5r`(sEi^A`VjQN3i#TN~NxJ8R#;Q zBLh7-&fT!7%jNQ9ChG-fgCk&bPfyPt{&u!N%rfvE_!qcxn8sQ#zatqU@EPD;kR}IE zc(|iTBH+`(#x!Qk_==7oiJ&Kgbtd>M&21>nnD8VK|EV?$DKA1gDf;a<3_&kAJD_^0Zzw%lBnsoxfZcUZCK|Nk00A!b1euyziWR%dft8i* z$b;8sB0CrCv=P#N%4=Xc(Oc!Qy~L=sXOqAtfiFD)uBY#WEK#BNtg+Yq(@9xu3$H^U zPn+P>{2N@5)p>M>W#q{BS#1lgM?fopEv||)bNxW~M4+d5S6(V~;8~m_nz>-N8&&1{ z#Vqz!9c*;pWTn44-j;ufMxXkKjf(aLFeN4^^aniiTo^M);dBY;WlvDrO><+N#b(gV zwLNx@Lg^FCT_zwkRfnPvxvc?q}EdsXy`@#pe&P8pzvnfaSOC@l zyT$3sEPtXo(-UN-E^O@+eCCBw)ML!2;C5g?v`p(KDlwssI}d_-WEaMDJq}>MwH^l? zTYaNmoEUJ-02B2)6W4nABZhU6BcGY$janV>*y+f2Nemaq=rX}wc-bhe7 zEIT#hB-7BF2UdaQ#zqHrs;l#vd(@1Dn;7Y-`!C>%jwtl)@l3X!8%BL3-Erkh0dh)OA0G#=fsDt0qwLj z_L$_?MJj#5?Dpw@7JXOvRt*v4JG@HEQ~fl>N}&jeVw%*} zp+cn?_2(jdoFhja`N+8*cbNx4HqDB*gY51RAs>%p#c6aRLAL(vg^zU(C*v;jAgHh@ zMtV&l`fJ89P$~^lf9$URUnLKM>|#Yqv8#z(M;xvs)*mT#8bLO_#6e>H$%~-ErWgTR zg7l{2HxZE}9kYX|Kj%Th0z*VFy`qR z@1#jD`Eb1<;&bYcJrgo2os(hx<#|)oZt4{l&odi(0zNuf@)s`026@HQ_r<_<%F1ekt({{rYqi zxczm9RPFu7bo8&)Ku)48I0NW|TMCkP`Fh6g15x6AVPBnrz|P`&6VPuC+gVg|ZsJ4V z85=~X29+uk>K?)t-q>|ossA>MUsnq`nVn`Q=|@X@Ks(7#IJ+!!kO_*x6mxA@OzHM0 zxDCv5#|m`)2fhq}x4>G}(RzK$Oi*BX3`8^VA4z6hvMJ-N3jhEB07*qoM6N<$f|mFvq5uE@ literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_feature_transparent.png b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_feature_transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..1d43b114e56595319a5d4ff8c1df85f994cad6f9 GIT binary patch literal 2333 zcmV+&3F7vNP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS@hex7^emPQs6gnqc{aD0^7iO z5GT@+u52b+fhvI)Lw>XT&MCs_S0+bT&YPqmolnQ2u+#IZdFjpo^Wm7ov;AIRyT1U( zZu+)@gWxbY6kN}7?E!j%Az%a;1#SZ297r)whTyjcel@vE8o;mM2H+!l5v++-#K~-s zR+QwNKvV_Z0>{B#@F92x3<`C8uh{5up9b_e90aCpaPgpU2dD$KzHBw}KLU?|t}c6L z8_E-b9v3|}NguH(PST7UiK{)(!u3_~0?-m6Qt0ms{syMZh&1r+fGyP|?d#`a=y5L^ zd7=D8p?zk4AuIYdpIHzoiMR(n8KHcTk}lsIR*fX{aZ@?+^`38C9yQ|a2o3{D_FE-L zH7A9#tvqf!Sri0+BN&&&D(bcA;%G1$&9c#4-bHOKfOEYQ6?u{bnXl4yQG}OTdsUJf zVtDGX>?D7MWl8Mfbm7$-gJ(}Qn*Wp-6)8SoVK}lyfw#+VU72gY? z5?^l<7f}~Ssw83G8|(+R)D^rFv3q{NP&MiCM=O3fH0QWv9gUGk66yoeaY^bq3DVac#Ba2^ z0X3kg?SwLD$^j}mNH1Ej*Q4c{khOV?U57~rm>W&1IT2^tN9`&&v~e}%G!>mi@zeP0y6`oktlv+GT5T z>5rKDTnCO-7H@cT+j7RMWxhSvY;weyv`M_(ETi>SJxSHJ)k&g!q?K#v;T`{?QBn@|u!(fow!UVZCt@>57+{6`UNn;EXq@P@D@%tgow#u&` z$gk#VX?SygRg2T*!%~T@4s~ekS6i5%M#`rwr3bBgF;)HEb<#4NY}J!gZCi1>{s3yf!AK_4Z6oz4d}A&nmtGN4dY|wOBl3-TUhB&cYF-`i8JExazGo(`qZG zYA+W$Lb8N_Oy3201KlMk$<`E`+wAi#&qA@YrM$wb=e8Ps2*h46HPV)9v50*+Z#8-n zpKGh({>296Pk=42waR9~1_;~IW_5Ao?QtW+fxVp9k-(TpBhL;v%JI14JaL(#m3iTX zCkn13k3FOoqmhal}PUXRv3Mh`Z1dd=T#>co~Yr~@w1y2dZl1-!ta(Nqn@r)S?5wW&`!iARlWKscXj9&O>E zd@Be~8JZ~1E)c4_3nP8}^eJ%3Mj8u#ljCM}x5B04mg~Ui04k>%so2*5Q)*1_bPNQu zz+Gm0qB6#vK|OHEW*T!x0*Od3k}|QawSrz-64yBC1~fstp;_ z_#Q-w67?ClrIa~=UkRxJnvf5HsKn5fC(@I?>}o*v34>Ix=7k_?G|`m{cs17v!25W^ zC#mo~4)lk}LbP7WTTJ@I;H3lcD6rKdN`1_&$y^_9yN?sRJYGXL1Nm zlv>n12^IpM?`)dT+W~$CyTD#>4CwbL?f3c`(C%7sF6S6@BiEeZ^AQR34%s7 zR%%_o!ZxhS<1H2j<1zy1 zx2~u+Tr+)*K=ag6<_NEk74W0i_8DN_pSZffah9vMm;~NNSt@cf`Yuotv3?v-0u|GV z0FB(xY08NOUhTciuf_Ba!tzrQWq<@}0yd?n-F(#0z7(?jxpfnE-Ri!~stg^y(ZZL4 zR?69LC)#>WNy=2v04BBdd#S`<$v`Col?+re;K;!La?$5hlDdLC00000NkvXXu0mjf D`|?PX literal 0 HcmV?d00001 diff --git a/TMessagesProj/src/main/res/drawable-xxhdpi/menu_lightbulb.png b/TMessagesProj/src/main/res/drawable-xxhdpi/menu_lightbulb.png new file mode 100644 index 0000000000000000000000000000000000000000..11b3efa952587a60aeb9e4c2abc6758e65f3b6e2 GIT binary patch literal 1921 zcmV-{2Y&d8P)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91NT34%1ONa40RR91NB{r;0FW_8?*IS>>q$gGRCodHn`?+wRTRf(O3g=^ zSZbDvmRV6j5GsC&nGXfo%N|5j^Z)*^IqntBXLFpqL1Q?0bhVx zvWt!jA^9EPO*BQ(qpQY)*d{-;>V!M@{U=AoECv3;^KbAmhrz;M2gCAxd^ z%;Sp{lY73;zHl7`M}a-S%HpEPka)38PFtKt6~&=u9!M0PQhLh@xUo9KK&bJ(=Ck^d17pJmPBBSPB@ zq3yJXHH(mP195OgZ|SKe^*jLsy$Me=nY(!1KWKD?HKW{GcDOPz;_B*x&r=tO40AkL z-t*{=Q9)KuJ?#Mn3--goR1m_~$HB)nIDaZb$gTsfbk_@n@txpM^-1{MF7mh39l3?Es{sgY9r=i_Muh4frIP5xbUiTq@%?jhQ%bOrT z8FI;1g(3MMly+^ky~^-dg_axI265#Q8f%F+*;VQs-3=Vx2G(b&`s;}0qP3eiCzezN zBFh=T`d0RGyd;=F`{-Ec={y&migaP6kMkQj_Z82P#*a68d; zBq$E%ZR7Nh5#64NCC>s)PLiDgzlHs^pf|=(#p%R$V~mZj>l~x(r|tWr8nYam+$LM1 zd%JS@Hcfb(X93?9s=ioEOBwTr3aRM0DNUSAJ|uq8kobqTU3vuN!0&DoOU>JZoQ)BF z7kOeWHD!YIdZEbA>0)Pw0Z4u)ch$uOKGQzX`a|1l1Gh4G5iN^bK#4Qy&^%^(0yVSKn)i z$24aZqy_`KFhteAJV`3-GhIRW>yy)0z{;8=JQ`vPBVE>-psmaeO^9Ra0^zIuXkE2d z{zCEp@+Fi@L+l{(jAci=4_pV52pd zm6-v4Pg$p^y`-LR;r$RB2YmBU5Bjs?2+^;-WOsjC=fmpwJcNx;-bI=EsTG$qb05T} zf!!X~8_}H>mw^5$;JFB~Te8^P1$^_~r|1iRg2s)jstS%V7&UbTB>t5uKCb{Pn(xkQ`ashXfV^JfR@+h0elST)>?Z&_DQh0;ND?QF zt=3Kr6V^Z)l(sFl0{17ox1hJXiE%viE~VW9R~{z*=dPk<&ndM3XR-X>RzZ@wwyHZ9~bWz_nT z>WT*E`?%-Yxaal<4C=F2rE8ztc~J%OeYMapJ3_@SC3?B~4uv{ru`8-T>pdiI_2_D; zI!(uB({BAH^N4;O=8E33@2EF2Y#wl(qVq%_1?uI9>n~)Y69EP`mqV9_%vyB%2)PMV zCUfoUOa)hi%Yc@Ira(zRwE z0~`z91a|uZRZWrY>!7uE4YT}-80-wN9Bcr!M4SDxN%ix&Ec`0Ig-z0xguWvM&~2mt z08{x-FuOV5%|u+8w0&-+uB<>=fwBT+1 + + + + \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/menu_link_above.xml b/TMessagesProj/src/main/res/drawable/menu_link_above.xml new file mode 100644 index 000000000..11e270ed5 --- /dev/null +++ b/TMessagesProj/src/main/res/drawable/menu_link_above.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/menu_link_below.xml b/TMessagesProj/src/main/res/drawable/menu_link_below.xml new file mode 100644 index 000000000..b20cdf3da --- /dev/null +++ b/TMessagesProj/src/main/res/drawable/menu_link_below.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/TMessagesProj/src/main/res/drawable/timeline.xml b/TMessagesProj/src/main/res/drawable/timeline.xml new file mode 100644 index 000000000..bad30ca91 --- /dev/null +++ b/TMessagesProj/src/main/res/drawable/timeline.xml @@ -0,0 +1,11 @@ + + + + \ No newline at end of file diff --git a/TMessagesProj/src/main/res/raw/camera_blur_frag.glsl b/TMessagesProj/src/main/res/raw/camera_blur_frag.glsl new file mode 100644 index 000000000..243415f0e --- /dev/null +++ b/TMessagesProj/src/main/res/raw/camera_blur_frag.glsl @@ -0,0 +1,22 @@ +#extension GL_OES_EGL_image_external : require + +precision lowp float; + +varying vec2 uv; +uniform samplerExternalOES sTexture; +uniform vec2 pixelWH; + +void main() { + float r = 2.; + vec2 d = 1.0 / (pixelWH / 8.0); //step == 0 ? 1. / (texSz / sz) : 1. / sz * r; + vec2 st = d / r; + vec4 col = vec4(0.); + float count = 0.; + for (float x = -d.x; x < d.x; x += st.x) + for (float y = -d.y; y < d.y; y += st.y) + { + col += texture2D(sTexture, uv + vec2(x, y)); + count++; + } + gl_FragColor = vec4((col / count).rgb, 1.); +} \ No newline at end of file diff --git a/TMessagesProj/src/main/res/raw/camera_blur_frag2.glsl b/TMessagesProj/src/main/res/raw/camera_blur_frag2.glsl new file mode 100644 index 000000000..a6e50ad55 --- /dev/null +++ b/TMessagesProj/src/main/res/raw/camera_blur_frag2.glsl @@ -0,0 +1,26 @@ +precision lowp float; + +varying vec2 uv; +uniform sampler2D sTexture; +uniform vec2 pixelWH; + +const float kernel = 10.0; +const float weight = 1.0; + +void main() { + + vec3 sum = vec3(0); + float pixelSize = 1.0 / pixelWH.x; + + // Horizontal Blur + vec3 accumulation = vec3(0); + vec3 weightsum = vec3(0); + for (float i = -kernel; i <= kernel; i++) { + accumulation += texture2D(sTexture, uv + vec2(i * pixelSize, 0.0)).rgb * weight; + weightsum += weight; + } + + sum = accumulation / weightsum; + + gl_FragColor = vec4(sum, 1.0); +} \ No newline at end of file diff --git a/TMessagesProj/src/main/res/raw/camera_blur_vert.glsl b/TMessagesProj/src/main/res/raw/camera_blur_vert.glsl new file mode 100644 index 000000000..620ffa6ad --- /dev/null +++ b/TMessagesProj/src/main/res/raw/camera_blur_vert.glsl @@ -0,0 +1,11 @@ +uniform mat4 uMVPMatrix; +uniform mat4 uSTMatrix; +attribute vec4 aPosition; +attribute vec4 aTextureCoord; +varying vec2 uv; +uniform float crossfade; +uniform mat4 cameraMatrix; +void main() { + gl_Position = uMVPMatrix * cameraMatrix * aPosition; + uv = (uSTMatrix * aTextureCoord).xy; +} \ No newline at end of file diff --git a/TMessagesProj/src/main/res/raw/caption_down.json b/TMessagesProj/src/main/res/raw/caption_down.json new file mode 100644 index 000000000..cdc96a818 --- /dev/null +++ b/TMessagesProj/src/main/res/raw/caption_down.json @@ -0,0 +1 @@ +{"tgs":1,"v":"5.5.2","fr":60,"ip":0,"op":35,"w":512,"h":512,"nm":"Caption DOWN 5","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Line 1","sr":1,"ks":{"p":{"a":0,"k":[148.25,283.817,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":13,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[41.75,0.317],[48.25,0.634]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":24,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-88.25,0],[88.25,0]],"c":false}]},{"t":34,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-75.25,0],[75.25,0]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":37},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Path-Copy","bm":0,"hd":false}],"ip":13,"op":185,"st":5,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Line 2","sr":1,"ks":{"p":{"a":0,"k":[178.417,185.817,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":9,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[15.583,0.317],[22.417,0.634]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":20,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-118.417,0],[119.417,0]],"c":false}]},{"t":32,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-105.417,0],[105.417,0]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":37},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Path","bm":0,"hd":false}],"ip":10,"op":173,"st":-7,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Line 3","sr":1,"ks":{"p":{"a":0,"k":[234,87.817,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":3,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-40,0.317],[-36,0.634]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":15,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-174,0],[174,0]],"c":false}]},{"t":28,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-161,0],[161,0]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":37},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Path","bm":0,"hd":false}],"ip":4,"op":161,"st":-19,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Arrow","sr":1,"ks":{"p":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[360,186.281,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":16,"s":[360,350.281,0],"to":[0,0,0],"ti":[0,0,0]},{"t":31,"s":[360,320.281,0]}]},"a":{"a":0,"k":[104,64.281,0]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[103.7,-44.077],[103.816,-14.221]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[103.371,-122.08],[103.904,66.441]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":7,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[103.344,-111.137],[103.937,96.766]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":16,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[104,-46.183],[104,155.159]],"c":false}]},{"t":31,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[104,-46.183],[104,155.159]],"c":false}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[0,0],[-0.54,0.534],[0,0],[0,0]],"o":[[0,0],[0.54,0.534],[0,0],[0,0],[0,0]],"v":[[88.428,-25.29],[102.842,-11.034],[104.79,-11.034],[119.204,-25.29],[119.204,-25.29]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":16,"s":[{"i":[[0,0],[0,0],[-2.735,2.913],[0,0],[0,0]],"o":[[0,0],[2.735,2.913],[0,0],[0,0],[0,0]],"v":[[26,94.738],[99.064,172.559],[108.936,172.559],[182,94.738],[182,94.738]],"c":false}]},{"t":31,"s":[{"i":[[0,0],[0,0],[-2.945,2.913],[0,0],[0,0]],"o":[[0,0],[2.945,2.913],[0,0],[0,0],[0,0]],"v":[[20,94.738],[98.684,172.559],[109.316,172.559],[188,94.738],[188,94.738]],"c":false}]}]},"nm":"Path 2","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":37},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"CombinedShape--","bm":0,"hd":false}],"ip":1,"op":168,"st":-12,"bm":0}]} \ No newline at end of file diff --git a/TMessagesProj/src/main/res/raw/caption_up.json b/TMessagesProj/src/main/res/raw/caption_up.json new file mode 100644 index 000000000..efc171ebc --- /dev/null +++ b/TMessagesProj/src/main/res/raw/caption_up.json @@ -0,0 +1 @@ +{"tgs":1,"v":"5.5.2","fr":60,"ip":0,"op":35,"w":512,"h":512,"nm":"Caption UP 5","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Line 1","sr":1,"ks":{"r":{"a":0,"k":-180},"p":{"a":0,"k":[148.25,224.683,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":13,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[41.75,0.317],[48.25,0.634]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":24,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-88.25,0],[88.25,0]],"c":false}]},{"t":34,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-75.25,0],[75.25,0]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":37},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Path-Copy","bm":0,"hd":false}],"ip":13,"op":185,"st":5,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Line 2","sr":1,"ks":{"r":{"a":0,"k":-180},"p":{"a":0,"k":[178.417,322.683,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":9,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[15.583,0.317],[22.417,0.634]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":20,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-118.417,0],[119.417,0]],"c":false}]},{"t":32,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-105.417,0],[105.417,0]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":37},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Path","bm":0,"hd":false}],"ip":10,"op":173,"st":-7,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Line 3","sr":1,"ks":{"r":{"a":0,"k":-180},"p":{"a":0,"k":[234,420.683,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":3,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-40,0.317],[-36,0.634]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":15,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-174,0],[174,0]],"c":false}]},{"t":28,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-161,0],[161,0]],"c":false}]}]},"nm":"Path 1","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":37},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"Path","bm":0,"hd":false}],"ip":4,"op":161,"st":-19,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Arrow","sr":1,"ks":{"r":{"a":0,"k":-180},"p":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[360,322.219,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":16,"s":[360,158.219,0],"to":[0,0,0],"ti":[0,0,0]},{"t":31,"s":[360,194.719,0]}]},"a":{"a":0,"k":[104,64.281,0]},"s":{"a":0,"k":[-100,100,100]}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[103.7,-44.077],[103.816,-14.221]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[103.371,-122.08],[103.904,66.441]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":7,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[103.344,-111.137],[103.937,96.766]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":16,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[104,-46.183],[104,155.159]],"c":false}]},{"t":31,"s":[{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[104,-46.183],[104,155.159]],"c":false}]}]},"nm":"Path 1","hd":false},{"ind":1,"ty":"sh","ks":{"a":1,"k":[{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[0,0],[-0.54,0.534],[0,0],[0,0]],"o":[[0,0],[0.54,0.534],[0,0],[0,0],[0,0]],"v":[[88.428,-25.29],[102.842,-11.034],[104.79,-11.034],[119.204,-25.29],[119.204,-25.29]],"c":false}]},{"i":{"x":0.3,"y":1},"o":{"x":0.167,"y":0},"t":16,"s":[{"i":[[0,0],[0,0],[-2.735,2.913],[0,0],[0,0]],"o":[[0,0],[2.735,2.913],[0,0],[0,0],[0,0]],"v":[[26,94.738],[99.064,172.559],[108.936,172.559],[182,94.738],[182,94.738]],"c":false}]},{"t":31,"s":[{"i":[[0,0],[0,0],[-2.945,2.913],[0,0],[0,0]],"o":[[0,0],[2.945,2.913],[0,0],[0,0],[0,0]],"v":[[20,94.738],[98.684,172.559],[109.316,172.559],[188,94.738],[188,94.738]],"c":false}]}]},"nm":"Path 2","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1]},"o":{"a":0,"k":100},"w":{"a":0,"k":37},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0]},"a":{"a":0,"k":[0,0]},"s":{"a":0,"k":[100,100]},"r":{"a":0,"k":0},"o":{"a":0,"k":100},"sk":{"a":0,"k":0},"sa":{"a":0,"k":0},"nm":"Transform"}],"nm":"CombinedShape--","bm":0,"hd":false}],"ip":1,"op":168,"st":-12,"bm":0}]} \ No newline at end of file diff --git a/TMessagesProj/src/main/res/values/strings.xml b/TMessagesProj/src/main/res/values/strings.xml index 040b0b92b..962001c7b 100644 --- a/TMessagesProj/src/main/res/values/strings.xml +++ b/TMessagesProj/src/main/res/values/strings.xml @@ -80,9 +80,13 @@ Profile info You have just successfully transferred %1$s to %2$s for %3$s + You have just successfully transferred %1$s to %2$s for %3$s until %4$s You have just successfully transferred %1$s to %2$s + You have just successfully transferred %1$s to %2$s until %3$s You successfully transferred %1$s to %2$s for %3$s and allowed future recurrent payments You successfully transferred %1$s to %2$s and allowed future recurrent payments + %1$s have transferred %2$s for %3$s subscription until %4$s + %1$s have transferred %2$s for subscription until %3$s Checkout Shipping methods Sorry, it is not possible to deliver to your address. @@ -539,6 +543,8 @@ Channel created un1 sent you a gift for **un2** You have sent a gift for **un2** + %s sent you a gift + Gift %d Month Premium %d Months Premium Subscription for exclusive Telegram features. @@ -1748,6 +1754,9 @@ Revoke Link Are you sure you want to revoke the link **%1$s**?\n\nThe group \"**%2$s**\" will become private. Are you sure you want to revoke the link **%1$s**?\n\nThe channel \"**%2$s**\" will become private. + Leave + Revoke Link + Are you sure you want to revoke the link of **%1$s**? Copy Link Copy E-Mail Copy Hashtag @@ -4432,6 +4441,12 @@ %1$d weeks %1$d month %1$d months + %1$dm + %1$dm + %1$dy + %1$dy + + Lifetime %1$d-month %1$d-months %1$d-month @@ -6272,6 +6287,9 @@ Tap on %s to post a story for your contacts. Take photos or videos to share with all your contacts or close friends at once. Turn on both cameras at once + Tap to remove grid + Hold and drag tiles to reorder them. + You can drag a tile to reorder **%s** mentioned you in a story. View Story The story you were mentioned in is no longer available @@ -7843,6 +7861,7 @@ Edit price %d Star %d Stars + Stars %d Star %d Stars Media @@ -7918,6 +7937,9 @@ https://telegram.org/tos/mini-apps By publishing this mini app, you agree to the **Telegram Terms of Service for Developers.** https://telegram.org/tos/mini-apps + Affiliate Program + Share a link to %1$s with your friends and earn %2$s of their spending there. + Share a link to %1$s with other users and earn %2$s of their spending there. Preview No Preview Upload up to %d screenshot and video demos for your mini app. @@ -8122,6 +8144,13 @@ Gift Reason Giveaway + %s Commission + Affiliate + Commission + Mini App + Referred User + Reason + Affiliate Program Monthly subscription fee My subscriptions Show More @@ -8386,4 +8415,100 @@ File saved to Downloads Open Cancel + Retake + %d% + Affiliate Program + Off + Affiliate Program + Reward those who help grow your user base. + Share revenue with affiliates + Set the commission for revenue generated by users referred to you. + Launch your affiliate program + Telegram will feature your program for millions of potential affiliates. + Let affiliates promote you + Affiliates will share your referral link with their audience. + Commission + Define the percentage of star revenue your affiliates earn for referring users to your bot. + Duration + Set the duration for which affiliates will earn commissions from referred users. + View existing programs + Explore what other mini apps offer. + Start Affiliate Program + By creating an affiliate program, you agree to the **Terms and Conditions** of Affiliate Programs. + https://telegram.org/privacy + Available in %s + Once you start the affiliated program, you won\'t be able to decrease its commission or duration. You can only increase these parameters or end the program, which will disable all previously distributed referral links. + Start + Update Affiliate Program + By updating an affiliate program, you agree to the **Terms and Conditions** of Affiliate Programs. + https://telegram.org/privacy + This change is irreversible. You won\'t be able to reduce commission or duration. You can only increase these parameters or end the program, which will disable all previously shared referral links. + Update + Warning + End Affiliate Program + If you end your affiliate program: + Any referral links already shared will be disabled in **24** hours. + All participants affiliates will be notified. + You will be able to start a new affiliate program only in **24** hours. + End Anyway + Affiliate Programs + Affiliate Programs + Earn a commission each time a user who first accessed a mini app through your referral link spends **Stars** within it. + Reliable + Receive guaranteed commissions for spending by users you refer. + Transparent + Track your commissions from referred users in real time. + Simple + Choose a mini app below, get your referral link, and start earning Stars. + My Programs + Programs + Sort by + Date + Revenue + Profitability + Affiliate Program + **%1$s** will share **%2$s** of the revenue from each user you refer to it for **%3$s**. + %d month + %d months + %d year + %d years + lifetime + Monthly users + Their average daily\nrevenue per user + Join Program + By joining this program, you agree to the **Terms and Services** of Affiliate Programs. + https://telegram.org/privacy + Earn Stars + Distribute links to mini apps and earn a share of their revenue in Stars. + Earn Stars + Distribute links to mini apps and earn a share of their revenue in Stars. + Earn Stars + Distribute links to mini apps and earn a share of their revenue in Stars. + Referral Link + Share this link with your friends to earn a **%1$s** commission on their spending in **%2$s** for **%3$s**. + Share this link with your subscribers to earn a **%1$s** commission on their spending in **%2$s** for **%3$s**. + You revoked this referral link. + Commissions will be sent to: + Copy Link + No one has opened %s through this link. + %1$d user has opened %2$s through this link. + %1$d users have opened %2$s through this link. + Rejoin Program + Link copied to clipboard + Share this link and earn **%1$s** of what people who use it spend in **%2$s**! + Joined Program + You can now copy the referral link. + Affiliate program ended + Participating affiliates have been notified. All referral links will be disabled in **24** hours. + Affiliate program started + Any Telegram user, channel owner or mini app developer can now join your program. + Link expired + This link was part of an affiliate program, that has been terminated by the app owner. + Timeline + Move caption up + Caption moved up + Caption will be shown above the media + Move caption down + Caption moved down + Caption will be shown below the media \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 2655cb26f..205cd4217 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,8 +13,8 @@ # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true #Sat Mar 12 05:53:50 MSK 2016 -APP_VERSION_CODE=5469 -APP_VERSION_NAME=11.4.2 +APP_VERSION_CODE=5511 +APP_VERSION_NAME=11.5.3 APP_PACKAGE=org.telegram.messenger IS_PRIVATE=false RELEASE_KEY_PASSWORD=android