diff --git a/Telegram.Msix/Package.appxmanifest b/Telegram.Msix/Package.appxmanifest index 6763f06e0a..20740462ae 100644 --- a/Telegram.Msix/Package.appxmanifest +++ b/Telegram.Msix/Package.appxmanifest @@ -1,6 +1,6 @@  - + Unigram—Telegram for Windows diff --git a/Telegram.Native/CachedVideoAnimation.cpp b/Telegram.Native/CachedVideoAnimation.cpp index cdc5c1090d..88db780206 100644 --- a/Telegram.Native/CachedVideoAnimation.cpp +++ b/Telegram.Native/CachedVideoAnimation.cpp @@ -175,14 +175,14 @@ namespace winrt::Telegram::Native::implementation m_frameIndex = 0; } - void CachedVideoAnimation::RenderSync(IBuffer bitmap, int32_t& seconds, bool& completed) + void CachedVideoAnimation::RenderSync(IBuffer bitmap, double& seconds, bool& completed) { uint8_t* pixels = bitmap.data(); bool rendered; RenderSync(pixels, seconds, completed, &rendered); } - void CachedVideoAnimation::RenderSync(uint8_t* pixels, int32_t& seconds, bool& completed, bool* rendered) + void CachedVideoAnimation::RenderSync(uint8_t* pixels, double& seconds, bool& completed, bool* rendered) { bool loadedFromCache = false; if (rendered) @@ -337,7 +337,7 @@ namespace winrt::Telegram::Native::implementation pixels = new uint8_t[w * h * 4]; } - int32_t seconds = 0; + double seconds = 0; bool completed = false; std::vector offsets; diff --git a/Telegram.Native/CachedVideoAnimation.h b/Telegram.Native/CachedVideoAnimation.h index d21cd5f72e..89c32f3568 100644 --- a/Telegram.Native/CachedVideoAnimation.h +++ b/Telegram.Native/CachedVideoAnimation.h @@ -46,7 +46,7 @@ namespace winrt::Telegram::Native::implementation static winrt::Telegram::Native::CachedVideoAnimation LoadFromFile(IVideoAnimationSource file, int32_t width, int32_t height, bool precache); - void RenderSync(IBuffer bitmap, int32_t& seconds, bool& completed); + void RenderSync(IBuffer bitmap, double& seconds, bool& completed); void Stop(); void Cache(); @@ -80,7 +80,7 @@ namespace winrt::Telegram::Native::implementation private: bool Load(IVideoAnimationSource file, int32_t width, int32_t height); - void RenderSync(uint8_t* pixels, int32_t& seconds, bool& completed, bool* rendered); + void RenderSync(uint8_t* pixels, double& seconds, bool& completed, bool* rendered); bool ReadHeader(HANDLE precacheFile); diff --git a/Telegram.Native/CachedVideoAnimation.idl b/Telegram.Native/CachedVideoAnimation.idl index d2884502ab..c8cf779436 100644 --- a/Telegram.Native/CachedVideoAnimation.idl +++ b/Telegram.Native/CachedVideoAnimation.idl @@ -7,7 +7,7 @@ namespace Telegram.Native { static CachedVideoAnimation LoadFromFile(IVideoAnimationSource file, Int32 width, Int32 height, Boolean precache); - void RenderSync(Windows.Storage.Streams.IBuffer bitmap, out Int32 seconds, out Boolean completed); + void RenderSync(Windows.Storage.Streams.IBuffer bitmap, out Double seconds, out Boolean completed); void Stop(); void Cache(); diff --git a/Telegram.Native/ParticlesAnimation.cpp b/Telegram.Native/ParticlesAnimation.cpp index 13e2dc39e7..d89c969294 100644 --- a/Telegram.Native/ParticlesAnimation.cpp +++ b/Telegram.Native/ParticlesAnimation.cpp @@ -581,6 +581,356 @@ namespace winrt::Telegram::Native::implementation set_pixel_alpha(pixels, cx + 1, cy + 4, width, height, sa, sr, sg, sb, 64); } + // 4px diameter star-like plus shapes (thick center, thin arms) + inline void draw_plus_4px_100(int32_t* pixels, int width, int height, + int cx, int cy, int sa, int sr, int sg, int sb) + { + // Star-like 4px plus - thick center, thin arms + // Thick center (3x3) + for (int i = -1; i <= 1; i++) + { + set_pixel(pixels, cx + i, cy, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx, cy + i, width, height, sa, sr, sg, sb); + } + + // Thin arm extensions + set_pixel(pixels, cx - 2, cy, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx + 2, cy, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx, cy - 2, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx, cy + 2, width, height, sa, sr, sg, sb); + + // Thin arm extensions + set_pixel_alpha(pixels, cx - 1, cy - 1, width, height, sa, sr, sg, sb, 64); + set_pixel_alpha(pixels, cx - 1, cy + 1, width, height, sa, sr, sg, sb, 64); + set_pixel_alpha(pixels, cx + 1, cy - 1, width, height, sa, sr, sg, sb, 64); + set_pixel_alpha(pixels, cx + 1, cy + 1, width, height, sa, sr, sg, sb, 64); + } + + inline void draw_plus_4px_125(int32_t* pixels, int width, int height, + int cx, int cy, int sa, int sr, int sg, int sb) + { + // 5px effective size with tapered arms + draw_plus_4px_100(pixels, width, height, cx, cy, sa, sr, sg, sb); + + // Tapered anti-aliasing on arm tips + set_pixel_alpha(pixels, cx - 3, cy, width, height, sa, sr, sg, sb, 64); + set_pixel_alpha(pixels, cx + 3, cy, width, height, sa, sr, sg, sb, 64); + set_pixel_alpha(pixels, cx, cy - 3, width, height, sa, sr, sg, sb, 64); + set_pixel_alpha(pixels, cx, cy + 3, width, height, sa, sr, sg, sb, 64); + } + + inline void draw_plus_4px_150(int32_t* pixels, int width, int height, + int cx, int cy, int sa, int sr, int sg, int sb) + { + // 6px effective size with gradual taper + draw_plus_4px_100(pixels, width, height, cx, cy, sa, sr, sg, sb); + + // Medium taper on arm tips + set_pixel_alpha(pixels, cx - 3, cy, width, height, sa, sr, sg, sb, 128); + set_pixel_alpha(pixels, cx + 3, cy, width, height, sa, sr, sg, sb, 128); + set_pixel_alpha(pixels, cx, cy - 3, width, height, sa, sr, sg, sb, 128); + set_pixel_alpha(pixels, cx, cy + 3, width, height, sa, sr, sg, sb, 128); + } + + inline void draw_plus_4px_200(int32_t* pixels, int width, int height, + int cx, int cy, int sa, int sr, int sg, int sb) + { + // 8px effective size - star-like with longer tapered arms + // Thick center (3x3) + for (int i = -1; i <= 1; i++) + { + set_pixel(pixels, cx + i, cy, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx, cy + i, width, height, sa, sr, sg, sb); + } + + // Medium thickness at arm base + set_pixel(pixels, cx - 2, cy, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx + 2, cy, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx, cy - 2, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx, cy + 2, width, height, sa, sr, sg, sb); + + // Gradual taper with alpha + set_pixel_alpha(pixels, cx - 3, cy, width, height, sa, sr, sg, sb, 160); + set_pixel_alpha(pixels, cx + 3, cy, width, height, sa, sr, sg, sb, 160); + set_pixel_alpha(pixels, cx, cy - 3, width, height, sa, sr, sg, sb, 160); + set_pixel_alpha(pixels, cx, cy + 3, width, height, sa, sr, sg, sb, 160); + + // Thin tips + set_pixel_alpha(pixels, cx - 4, cy, width, height, sa, sr, sg, sb, 96); + set_pixel_alpha(pixels, cx + 4, cy, width, height, sa, sr, sg, sb, 96); + set_pixel_alpha(pixels, cx, cy - 4, width, height, sa, sr, sg, sb, 96); + set_pixel_alpha(pixels, cx, cy + 4, width, height, sa, sr, sg, sb, 96); + } + + inline void draw_plus_4px_250(int32_t* pixels, int width, int height, + int cx, int cy, int sa, int sr, int sg, int sb) + { + // 10px effective size with smooth star taper + // Thick center (3x3) + for (int i = -1; i <= 1; i++) + { + set_pixel(pixels, cx + i, cy, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx, cy + i, width, height, sa, sr, sg, sb); + } + + // Medium thickness at arm base + set_pixel(pixels, cx - 2, cy, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx + 2, cy, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx, cy - 2, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx, cy + 2, width, height, sa, sr, sg, sb); + + // Smooth gradual taper + set_pixel_alpha(pixels, cx - 3, cy, width, height, sa, sr, sg, sb, 200); + set_pixel_alpha(pixels, cx + 3, cy, width, height, sa, sr, sg, sb, 200); + set_pixel_alpha(pixels, cx, cy - 3, width, height, sa, sr, sg, sb, 200); + set_pixel_alpha(pixels, cx, cy + 3, width, height, sa, sr, sg, sb, 200); + + set_pixel_alpha(pixels, cx - 4, cy, width, height, sa, sr, sg, sb, 140); + set_pixel_alpha(pixels, cx + 4, cy, width, height, sa, sr, sg, sb, 140); + set_pixel_alpha(pixels, cx, cy - 4, width, height, sa, sr, sg, sb, 140); + set_pixel_alpha(pixels, cx, cy + 4, width, height, sa, sr, sg, sb, 140); + + // Very thin tips + set_pixel_alpha(pixels, cx - 5, cy, width, height, sa, sr, sg, sb, 80); + set_pixel_alpha(pixels, cx + 5, cy, width, height, sa, sr, sg, sb, 80); + set_pixel_alpha(pixels, cx, cy - 5, width, height, sa, sr, sg, sb, 80); + set_pixel_alpha(pixels, cx, cy + 5, width, height, sa, sr, sg, sb, 80); + } + + inline void draw_plus_4px_400(int32_t* pixels, int width, int height, + int cx, int cy, int sa, int sr, int sg, int sb) + { + // 16px effective size with dramatic star taper + // Thick center (5x5) + for (int i = -2; i <= 2; i++) + { + set_pixel(pixels, cx + i, cy, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx, cy + i, width, height, sa, sr, sg, sb); + } + + // Add some thickness to center + set_pixel(pixels, cx - 1, cy - 1, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx + 1, cy - 1, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx - 1, cy + 1, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx + 1, cy + 1, width, height, sa, sr, sg, sb); + + // Gradual taper with multiple alpha levels + set_pixel_alpha(pixels, cx - 3, cy, width, height, sa, sr, sg, sb, 220); + set_pixel_alpha(pixels, cx + 3, cy, width, height, sa, sr, sg, sb, 220); + set_pixel_alpha(pixels, cx, cy - 3, width, height, sa, sr, sg, sb, 220); + set_pixel_alpha(pixels, cx, cy + 3, width, height, sa, sr, sg, sb, 220); + + set_pixel_alpha(pixels, cx - 4, cy, width, height, sa, sr, sg, sb, 180); + set_pixel_alpha(pixels, cx + 4, cy, width, height, sa, sr, sg, sb, 180); + set_pixel_alpha(pixels, cx, cy - 4, width, height, sa, sr, sg, sb, 180); + set_pixel_alpha(pixels, cx, cy + 4, width, height, sa, sr, sg, sb, 180); + + set_pixel_alpha(pixels, cx - 5, cy, width, height, sa, sr, sg, sb, 140); + set_pixel_alpha(pixels, cx + 5, cy, width, height, sa, sr, sg, sb, 140); + set_pixel_alpha(pixels, cx, cy - 5, width, height, sa, sr, sg, sb, 140); + set_pixel_alpha(pixels, cx, cy + 5, width, height, sa, sr, sg, sb, 140); + + set_pixel_alpha(pixels, cx - 6, cy, width, height, sa, sr, sg, sb, 100); + set_pixel_alpha(pixels, cx + 6, cy, width, height, sa, sr, sg, sb, 100); + set_pixel_alpha(pixels, cx, cy - 6, width, height, sa, sr, sg, sb, 100); + set_pixel_alpha(pixels, cx, cy + 6, width, height, sa, sr, sg, sb, 100); + + // Very thin tips + set_pixel_alpha(pixels, cx - 7, cy, width, height, sa, sr, sg, sb, 60); + set_pixel_alpha(pixels, cx + 7, cy, width, height, sa, sr, sg, sb, 60); + set_pixel_alpha(pixels, cx, cy - 7, width, height, sa, sr, sg, sb, 60); + set_pixel_alpha(pixels, cx, cy + 7, width, height, sa, sr, sg, sb, 60); + } + + // 6px diameter star-like plus shapes (thick center, thin arms) + inline void draw_plus_6px_100(int32_t* pixels, int width, int height, + int cx, int cy, int sa, int sr, int sg, int sb) + { + // Star-like 6px plus - thick center, thin arms + // Thick center (5x5) + for (int i = -2; i <= 2; i++) + { + set_pixel(pixels, cx + i, cy, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx, cy + i, width, height, sa, sr, sg, sb); + } + + // Thin arm extensions + set_pixel(pixels, cx - 3, cy, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx + 3, cy, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx, cy - 3, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx, cy + 3, width, height, sa, sr, sg, sb); + + // Thin arm extensions + set_pixel_alpha(pixels, cx - 1, cy - 1, width, height, sa, sr, sg, sb, 128); + set_pixel_alpha(pixels, cx - 1, cy + 1, width, height, sa, sr, sg, sb, 128); + set_pixel_alpha(pixels, cx + 1, cy - 1, width, height, sa, sr, sg, sb, 128); + set_pixel_alpha(pixels, cx + 1, cy + 1, width, height, sa, sr, sg, sb, 128); + } + + inline void draw_plus_6px_125(int32_t* pixels, int width, int height, + int cx, int cy, int sa, int sr, int sg, int sb) + { + // 7.5px effective size with tapered arms + draw_plus_6px_100(pixels, width, height, cx, cy, sa, sr, sg, sb); + + // Tapered anti-aliasing on arm tips + set_pixel_alpha(pixels, cx - 4, cy, width, height, sa, sr, sg, sb, 64); + set_pixel_alpha(pixels, cx + 4, cy, width, height, sa, sr, sg, sb, 64); + set_pixel_alpha(pixels, cx, cy - 4, width, height, sa, sr, sg, sb, 64); + set_pixel_alpha(pixels, cx, cy + 4, width, height, sa, sr, sg, sb, 64); + } + + inline void draw_plus_6px_150(int32_t* pixels, int width, int height, + int cx, int cy, int sa, int sr, int sg, int sb) + { + // 9px effective size with gradual taper + draw_plus_6px_100(pixels, width, height, cx, cy, sa, sr, sg, sb); + + // Medium taper on arm tips + set_pixel_alpha(pixels, cx - 4, cy, width, height, sa, sr, sg, sb, 128); + set_pixel_alpha(pixels, cx + 4, cy, width, height, sa, sr, sg, sb, 128); + set_pixel_alpha(pixels, cx, cy - 4, width, height, sa, sr, sg, sb, 128); + set_pixel_alpha(pixels, cx, cy + 4, width, height, sa, sr, sg, sb, 128); + } + + inline void draw_plus_6px_200(int32_t* pixels, int width, int height, + int cx, int cy, int sa, int sr, int sg, int sb) + { + // 12px effective size - star-like with longer tapered arms + // Thick center (5x5) + for (int i = -2; i <= 2; i++) + { + set_pixel(pixels, cx + i, cy, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx, cy + i, width, height, sa, sr, sg, sb); + } + + // Medium thickness at arm base + set_pixel(pixels, cx - 3, cy, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx + 3, cy, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx, cy - 3, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx, cy + 3, width, height, sa, sr, sg, sb); + + // Gradual taper with alpha + set_pixel_alpha(pixels, cx - 4, cy, width, height, sa, sr, sg, sb, 180); + set_pixel_alpha(pixels, cx + 4, cy, width, height, sa, sr, sg, sb, 180); + set_pixel_alpha(pixels, cx, cy - 4, width, height, sa, sr, sg, sb, 180); + set_pixel_alpha(pixels, cx, cy + 4, width, height, sa, sr, sg, sb, 180); + + // Thin tips + set_pixel_alpha(pixels, cx - 5, cy, width, height, sa, sr, sg, sb, 120); + set_pixel_alpha(pixels, cx + 5, cy, width, height, sa, sr, sg, sb, 120); + set_pixel_alpha(pixels, cx, cy - 5, width, height, sa, sr, sg, sb, 120); + set_pixel_alpha(pixels, cx, cy + 5, width, height, sa, sr, sg, sb, 120); + + set_pixel_alpha(pixels, cx - 6, cy, width, height, sa, sr, sg, sb, 80); + set_pixel_alpha(pixels, cx + 6, cy, width, height, sa, sr, sg, sb, 80); + set_pixel_alpha(pixels, cx, cy - 6, width, height, sa, sr, sg, sb, 80); + set_pixel_alpha(pixels, cx, cy + 6, width, height, sa, sr, sg, sb, 80); + } + + inline void draw_plus_6px_250(int32_t* pixels, int width, int height, + int cx, int cy, int sa, int sr, int sg, int sb) + { + // 15px effective size with smooth star taper + // Thick center (5x5) + for (int i = -2; i <= 2; i++) + { + set_pixel(pixels, cx + i, cy, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx, cy + i, width, height, sa, sr, sg, sb); + } + + // Medium thickness at arm base + set_pixel(pixels, cx - 3, cy, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx + 3, cy, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx, cy - 3, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx, cy + 3, width, height, sa, sr, sg, sb); + + // Smooth gradual taper + set_pixel_alpha(pixels, cx - 4, cy, width, height, sa, sr, sg, sb, 200); + set_pixel_alpha(pixels, cx + 4, cy, width, height, sa, sr, sg, sb, 200); + set_pixel_alpha(pixels, cx, cy - 4, width, height, sa, sr, sg, sb, 200); + set_pixel_alpha(pixels, cx, cy + 4, width, height, sa, sr, sg, sb, 200); + + set_pixel_alpha(pixels, cx - 5, cy, width, height, sa, sr, sg, sb, 160); + set_pixel_alpha(pixels, cx + 5, cy, width, height, sa, sr, sg, sb, 160); + set_pixel_alpha(pixels, cx, cy - 5, width, height, sa, sr, sg, sb, 160); + set_pixel_alpha(pixels, cx, cy + 5, width, height, sa, sr, sg, sb, 160); + + set_pixel_alpha(pixels, cx - 6, cy, width, height, sa, sr, sg, sb, 120); + set_pixel_alpha(pixels, cx + 6, cy, width, height, sa, sr, sg, sb, 120); + set_pixel_alpha(pixels, cx, cy - 6, width, height, sa, sr, sg, sb, 120); + set_pixel_alpha(pixels, cx, cy + 6, width, height, sa, sr, sg, sb, 120); + + // Very thin tips + set_pixel_alpha(pixels, cx - 7, cy, width, height, sa, sr, sg, sb, 80); + set_pixel_alpha(pixels, cx + 7, cy, width, height, sa, sr, sg, sb, 80); + set_pixel_alpha(pixels, cx, cy - 7, width, height, sa, sr, sg, sb, 80); + set_pixel_alpha(pixels, cx, cy + 7, width, height, sa, sr, sg, sb, 80); + } + + inline void draw_plus_6px_400(int32_t* pixels, int width, int height, + int cx, int cy, int sa, int sr, int sg, int sb) + { + // 24px effective size with dramatic star taper + // Thick center (7x7) + for (int i = -3; i <= 3; i++) + { + set_pixel(pixels, cx + i, cy, width, height, sa, sr, sg, sb); + set_pixel(pixels, cx, cy + i, width, height, sa, sr, sg, sb); + } + + // Add thickness to center + for (int i = -1; i <= 1; i++) + { + for (int j = -1; j <= 1; j++) + { + set_pixel(pixels, cx + i, cy + j, width, height, sa, sr, sg, sb); + } + } + + // Gradual taper with multiple alpha levels + set_pixel_alpha(pixels, cx - 4, cy, width, height, sa, sr, sg, sb, 230); + set_pixel_alpha(pixels, cx + 4, cy, width, height, sa, sr, sg, sb, 230); + set_pixel_alpha(pixels, cx, cy - 4, width, height, sa, sr, sg, sb, 230); + set_pixel_alpha(pixels, cx, cy + 4, width, height, sa, sr, sg, sb, 230); + + set_pixel_alpha(pixels, cx - 5, cy, width, height, sa, sr, sg, sb, 200); + set_pixel_alpha(pixels, cx + 5, cy, width, height, sa, sr, sg, sb, 200); + set_pixel_alpha(pixels, cx, cy - 5, width, height, sa, sr, sg, sb, 200); + set_pixel_alpha(pixels, cx, cy + 5, width, height, sa, sr, sg, sb, 200); + + set_pixel_alpha(pixels, cx - 6, cy, width, height, sa, sr, sg, sb, 170); + set_pixel_alpha(pixels, cx + 6, cy, width, height, sa, sr, sg, sb, 170); + set_pixel_alpha(pixels, cx, cy - 6, width, height, sa, sr, sg, sb, 170); + set_pixel_alpha(pixels, cx, cy + 6, width, height, sa, sr, sg, sb, 170); + + set_pixel_alpha(pixels, cx - 7, cy, width, height, sa, sr, sg, sb, 140); + set_pixel_alpha(pixels, cx + 7, cy, width, height, sa, sr, sg, sb, 140); + set_pixel_alpha(pixels, cx, cy - 7, width, height, sa, sr, sg, sb, 140); + set_pixel_alpha(pixels, cx, cy + 7, width, height, sa, sr, sg, sb, 140); + + set_pixel_alpha(pixels, cx - 8, cy, width, height, sa, sr, sg, sb, 110); + set_pixel_alpha(pixels, cx + 8, cy, width, height, sa, sr, sg, sb, 110); + set_pixel_alpha(pixels, cx, cy - 8, width, height, sa, sr, sg, sb, 110); + set_pixel_alpha(pixels, cx, cy + 8, width, height, sa, sr, sg, sb, 110); + + set_pixel_alpha(pixels, cx - 9, cy, width, height, sa, sr, sg, sb, 80); + set_pixel_alpha(pixels, cx + 9, cy, width, height, sa, sr, sg, sb, 80); + set_pixel_alpha(pixels, cx, cy - 9, width, height, sa, sr, sg, sb, 80); + set_pixel_alpha(pixels, cx, cy + 9, width, height, sa, sr, sg, sb, 80); + + // Very thin tips + set_pixel_alpha(pixels, cx - 10, cy, width, height, sa, sr, sg, sb, 50); + set_pixel_alpha(pixels, cx + 10, cy, width, height, sa, sr, sg, sb, 50); + set_pixel_alpha(pixels, cx, cy - 10, width, height, sa, sr, sg, sb, 50); + set_pixel_alpha(pixels, cx, cy + 10, width, height, sa, sr, sg, sb, 50); + + set_pixel_alpha(pixels, cx - 11, cy, width, height, sa, sr, sg, sb, 30); + set_pixel_alpha(pixels, cx + 11, cy, width, height, sa, sr, sg, sb, 30); + set_pixel_alpha(pixels, cx, cy - 11, width, height, sa, sr, sg, sb, 30); + set_pixel_alpha(pixels, cx, cy + 11, width, height, sa, sr, sg, sb, 30); + } + // Dispatch function for easy usage inline void draw_plus_scaled(int32_t* pixels, int width, int height, Particle* particle, Color color, int scale, double rasterizationScale) { @@ -612,6 +962,30 @@ namespace winrt::Telegram::Native::implementation case 400: draw_plus_2px_400(pixels, width, height, cx, cy, color.A, color.R, color.G, color.B); break; } } + else if (radius == 2) + { + switch (scale) + { + case 100: draw_plus_4px_100(pixels, width, height, cx, cy, color.A, color.R, color.G, color.B); break; + case 125: draw_plus_4px_125(pixels, width, height, cx, cy, color.A, color.R, color.G, color.B); break; + case 150: draw_plus_4px_150(pixels, width, height, cx, cy, color.A, color.R, color.G, color.B); break; + case 200: draw_plus_4px_200(pixels, width, height, cx, cy, color.A, color.R, color.G, color.B); break; + case 250: draw_plus_4px_250(pixels, width, height, cx, cy, color.A, color.R, color.G, color.B); break; + case 400: draw_plus_4px_400(pixels, width, height, cx, cy, color.A, color.R, color.G, color.B); break; + } + } + else if (radius == 3) + { + switch (scale) + { + case 100: draw_plus_6px_100(pixels, width, height, cx, cy, color.A, color.R, color.G, color.B); break; + case 125: draw_plus_6px_125(pixels, width, height, cx, cy, color.A, color.R, color.G, color.B); break; + case 150: draw_plus_6px_150(pixels, width, height, cx, cy, color.A, color.R, color.G, color.B); break; + case 200: draw_plus_6px_200(pixels, width, height, cx, cy, color.A, color.R, color.G, color.B); break; + case 250: draw_plus_6px_250(pixels, width, height, cx, cy, color.A, color.R, color.G, color.B); break; + case 400: draw_plus_6px_400(pixels, width, height, cx, cy, color.A, color.R, color.G, color.B); break; + } + } } #pragma endregion @@ -632,10 +1006,10 @@ namespace winrt::Telegram::Native::implementation // if(dot.mOpacity <= 0) dot.mOpacity = dot.opacity; // const easedOpacity = easing(dot.mOpacity); - auto easedOpacity = (byte)(std::clamp(dot->Opacity, 0., 1.) * 255); + auto easedOpacity = (byte)(std::clamp(dot->Opacity, 0., 1.) * m_foreground.A); auto color = premultiply_color(m_foreground.R, m_foreground.G, m_foreground.B, easedOpacity); - if (m_type == ParticlesType::Status) + if (m_type == ParticlesType::Status || m_type == ParticlesType::Premium) { draw_plus_scaled(pixels, m_width, m_height, dot, color, m_scalePercent, m_rasterizationScale); } @@ -789,7 +1163,7 @@ namespace winrt::Telegram::Native::implementation auto h = m_height * (1 / m_rasterizationScale); auto count = round(w * h / (35 * (IS_MOBILE ? 2 : 1))); - count *= m_type == ParticlesType::Text ? 4 : 1; + count *= m_type == ParticlesType::Text ? 4 : m_type == ParticlesType::Premium ? 0.5 : 1; count = min(/*!liteMode.isAvailable('chat_spoilers') ? 400 :*/ IS_MOBILE ? 1000 : 2200, count); auto particles = NextPoints(count, m_width, m_height); @@ -803,10 +1177,12 @@ namespace winrt::Telegram::Native::implementation Particle ParticlesAnimation::GenerateParticle(int32_t type, const Point& position) { - const auto threshold = m_type == ParticlesType::Status ? .2f : .8f; + const auto threshold = m_type == ParticlesType::Status || m_type == ParticlesType::Premium ? .2f : .8f; + const auto small = m_type == ParticlesType::Premium ? 2 : 0.5f; + const auto large = m_type == ParticlesType::Premium ? 3 : 1.0f; auto opacity = type == 1 ? 0 : NextDouble(); - auto radius = (NextDouble() >= threshold ? 1.f : 0.5f); + auto radius = (NextDouble() >= threshold ? large : small); auto adding = type == -1 ? NextDouble() >= .5 : type; diff --git a/Telegram.Native/ParticlesAnimation.idl b/Telegram.Native/ParticlesAnimation.idl index e4519d2f1f..ff1b45d596 100644 --- a/Telegram.Native/ParticlesAnimation.idl +++ b/Telegram.Native/ParticlesAnimation.idl @@ -4,7 +4,8 @@ namespace Telegram.Native { Media, Text, - Status + Status, + Premium }; [default_interface] diff --git a/Telegram.Native/PlaceholderImageHelper.cpp b/Telegram.Native/PlaceholderImageHelper.cpp index 6af95cd07a..f6f87a378a 100644 --- a/Telegram.Native/PlaceholderImageHelper.cpp +++ b/Telegram.Native/PlaceholderImageHelper.cpp @@ -1263,7 +1263,7 @@ namespace winrt::Telegram::Native::implementation { (float)controlPoint1.X(), (float)controlPoint1.Y() }, { (float)controlPoint2.X(), (float)controlPoint2.Y() }, { (float)endPoint.X(), (float)endPoint.Y() } - }); + }); } } } @@ -1290,6 +1290,231 @@ namespace winrt::Telegram::Native::implementation return CompositionPath(geometry.as()); } + CompositionPath PlaceholderImageHelper::GetEllipticalClip(float width, float height, float radius, float x, float y) + { + std::lock_guard const guard(m_criticalSection); + HRESULT result; + + winrt::com_ptr d2dGeometrySink; + winrt::com_ptr d2dPathGeometry; + + ReturnNullIfFailed(result, m_d2dFactory->CreatePathGeometry(d2dPathGeometry.put())); + ReturnNullIfFailed(result, d2dPathGeometry->Open(d2dGeometrySink.put())); + + d2dGeometrySink->SetFillMode(D2D1_FILL_MODE_ALTERNATE); + d2dGeometrySink->BeginFigure({ 0, 0 }, D2D1_FIGURE_BEGIN_FILLED); + d2dGeometrySink->AddLine({ width, 0 }); + d2dGeometrySink->AddLine({ width, height }); + d2dGeometrySink->AddLine({ 0, height }); + d2dGeometrySink->EndFigure(D2D1_FIGURE_END_CLOSED); + + D2D1_POINT_2F startPoint = D2D1::Point2F(x + radius, y); + D2D1_SIZE_F radii = D2D1::SizeF(radius, radius); + + d2dGeometrySink->BeginFigure(startPoint, D2D1_FIGURE_BEGIN_FILLED); + d2dGeometrySink->AddArc(D2D1::ArcSegment( + D2D1::Point2F(x - radius, y), + radii, + 0.0f, + D2D1_SWEEP_DIRECTION_CLOCKWISE, + D2D1_ARC_SIZE_SMALL + )); + d2dGeometrySink->AddArc(D2D1::ArcSegment( + startPoint, + radii, + 0.0f, + D2D1_SWEEP_DIRECTION_CLOCKWISE, + D2D1_ARC_SIZE_SMALL + )); + d2dGeometrySink->EndFigure(D2D1_FIGURE_END_CLOSED); + + ReturnNullIfFailed(result, d2dGeometrySink->Close()); + + auto geometry = winrt::make_self(d2dPathGeometry); + return CompositionPath(geometry.as()); + } + + inline void AppendButton(winrt::com_ptr d2dGeometrySink, float x, float y, float width, float height, float topLeftRadius, float topRightRadius, float bottomRightRadius, float bottomLeftRadius) + { + d2dGeometrySink->BeginFigure({ x + topLeftRadius, y }, D2D1_FIGURE_BEGIN_FILLED); + + // Top edge + d2dGeometrySink->AddLine({ x + width - topRightRadius, y }); + + // Top-right corner + if (topRightRadius > 0) + d2dGeometrySink->AddArc({ { x + width, y + topRightRadius }, { topRightRadius, topRightRadius }, 0, D2D1_SWEEP_DIRECTION_CLOCKWISE, D2D1_ARC_SIZE_SMALL }); + + // Right edge + d2dGeometrySink->AddLine({ x + width, y + height - bottomRightRadius }); + + // Bottom-right corner + if (bottomRightRadius > 0) + d2dGeometrySink->AddArc({ { x + width - bottomRightRadius, y + height }, { bottomRightRadius, bottomRightRadius }, 0, D2D1_SWEEP_DIRECTION_CLOCKWISE, D2D1_ARC_SIZE_SMALL }); + + // Bottom edge + d2dGeometrySink->AddLine({ x + bottomLeftRadius, y + height }); + + // Bottom-left corner + if (bottomLeftRadius > 0) + d2dGeometrySink->AddArc({ { x, y + height - bottomLeftRadius }, { bottomLeftRadius, bottomLeftRadius }, 0, D2D1_SWEEP_DIRECTION_CLOCKWISE, D2D1_ARC_SIZE_SMALL }); + + // Left edge + d2dGeometrySink->AddLine({ x, y + topLeftRadius }); + + // Top-left corner + if (topLeftRadius > 0) + d2dGeometrySink->AddArc({ { x + topLeftRadius, y }, { topLeftRadius, topLeftRadius }, 0, D2D1_SWEEP_DIRECTION_CLOCKWISE, D2D1_ARC_SIZE_SMALL }); + + d2dGeometrySink->EndFigure(D2D1_FIGURE_END_CLOSED); + } + + CompositionPath PlaceholderImageHelper::GetReplyMarkupClip(IVector> rows, float bottomRightRadius, float bottomLeftRadius) + { + std::lock_guard const guard(m_criticalSection); + HRESULT result; + + winrt::com_ptr d2dGeometrySink; + winrt::com_ptr d2dPathGeometry; + + ReturnNullIfFailed(result, m_d2dFactory->CreatePathGeometry(d2dPathGeometry.put())); + ReturnNullIfFailed(result, d2dPathGeometry->Open(d2dGeometrySink.put())); + + auto padding = 2; + auto x = 0.f; + auto y = 0.f; + + auto j = 0; + + for (const IVector& row : rows) + { + auto i = 0; + + for (const Windows::Foundation::Rect& button : row) + { + auto bottomRight = 4.f; + auto bottomLeft = 4.f; + + if (j == rows.Size() - 1) + { + if (i == 0) + { + bottomLeft = bottomLeftRadius; + } + + if (i == row.Size() - 1) + { + bottomRight = bottomRightRadius; + } + } + + AppendButton(d2dGeometrySink, button.X, button.Y, button.Width, button.Height, 4, 4, bottomRight, bottomLeft); + + i++; + } + + j++; + } + + ReturnNullIfFailed(result, d2dGeometrySink->Close()); + + auto geometry = winrt::make_self(d2dPathGeometry); + return CompositionPath(geometry.as()); + } + + CompositionPath PlaceholderImageHelper::GetVoiceNoteClip(IVector waveform, double waveformWidth) + { + std::lock_guard const guard(m_criticalSection); + HRESULT result; + + winrt::com_ptr d2dGeometrySink; + winrt::com_ptr d2dPathGeometry; + + ReturnNullIfFailed(result, m_d2dFactory->CreatePathGeometry(d2dPathGeometry.put())); + ReturnNullIfFailed(result, d2dPathGeometry->Open(d2dGeometrySink.put())); + + auto lines = waveform.Size() * 8 / 5; + auto bytes = new double[lines]; + + for (int i = 0; i < lines; i++) + { + int j = (i * 5) / 8, shift = (i * 5) % 8; + bytes[i] = ((waveform.GetAt(j) | ((j + 1 < waveform.Size() ? waveform.GetAt(j + 1) : 0) << 8)) >> shift & 0x1F) / 31.0; + } + + auto imageWidth = waveformWidth; // 142d; // double.IsNaN(ActualWidth) ? 142 : ActualWidth; + auto imageHeight = 20; + + auto space = 1.0; + auto lineWidth = 2.0; + auto maxLines = (imageWidth - space) / (lineWidth + space); + auto maxWidth = lines / maxLines; + + for (int index = 0; index < maxLines; index++) + { + auto lineIndex = (int)(index * maxWidth); + auto lineHeight = bytes[lineIndex] * (double)(imageHeight - 2.0) + 2.0; + + float x1 = (int)(index * (lineWidth + space)); + float y1 = (imageHeight - (int)lineHeight) / 2; + float x2 = (int)(index * (lineWidth + space) + lineWidth); + float y2 = imageHeight - y1; + + //d2dGeometrySink->BeginFigure({ x1, y1 }, D2D1_FIGURE_BEGIN_FILLED); + //d2dGeometrySink->AddLine({ x2, y1 }); + //d2dGeometrySink->AddLine({ x2, y2 }); + //d2dGeometrySink->AddLine({ x1, y2 }); + //d2dGeometrySink->EndFigure(D2D1_FIGURE_END_CLOSED); + + if (lineHeight > 2) + { + d2dGeometrySink->BeginFigure({ x1, y1 + 1 }, D2D1_FIGURE_BEGIN_FILLED); + d2dGeometrySink->AddArc(D2D1::ArcSegment( + D2D1::Point2F(x2, y1 + 1), + D2D1::SizeF(1, 1), + 0.0f, + D2D1_SWEEP_DIRECTION_CLOCKWISE, + D2D1_ARC_SIZE_SMALL + )); + d2dGeometrySink->AddLine({ x2, y2 - 1 }); + d2dGeometrySink->AddArc(D2D1::ArcSegment( + { x1, y2 - 1 }, + D2D1::SizeF(1, 1), + 0.0f, + D2D1_SWEEP_DIRECTION_CLOCKWISE, + D2D1_ARC_SIZE_SMALL + )); + } + else + { + d2dGeometrySink->BeginFigure({ x1, 10 }, D2D1_FIGURE_BEGIN_FILLED); + d2dGeometrySink->AddArc(D2D1::ArcSegment( + D2D1::Point2F(x2, 10), + D2D1::SizeF(1, 1), + 0.0f, + D2D1_SWEEP_DIRECTION_CLOCKWISE, + D2D1_ARC_SIZE_SMALL + )); + d2dGeometrySink->AddArc(D2D1::ArcSegment( + { x1, 10 }, + D2D1::SizeF(1, 1), + 0.0f, + D2D1_SWEEP_DIRECTION_CLOCKWISE, + D2D1_ARC_SIZE_SMALL + )); + } + + d2dGeometrySink->EndFigure(D2D1_FIGURE_END_CLOSED); + } + + delete[] bytes; + + ReturnNullIfFailed(result, d2dGeometrySink->Close()); + + auto geometry = winrt::make_self(d2dPathGeometry); + return CompositionPath(geometry.as()); + } + HRESULT PlaceholderImageHelper::Encode(IBuffer source, IRandomAccessStream destination, int32_t width, int32_t height, int32_t rotation) { HRESULT result; diff --git a/Telegram.Native/PlaceholderImageHelper.h b/Telegram.Native/PlaceholderImageHelper.h index 5866f8e269..002c0fffed 100644 --- a/Telegram.Native/PlaceholderImageHelper.h +++ b/Telegram.Native/PlaceholderImageHelper.h @@ -112,6 +112,9 @@ namespace winrt::Telegram::Native::implementation CompositionPath GetTail(float width, float height, float topLeftRadius, float topRightRadius, float bottomRightRadius, float bottomLeftRadius); CompositionPath GetOutline(IVector contours); + CompositionPath GetEllipticalClip(float width, float height, float radius, float x, float y); + CompositionPath GetReplyMarkupClip(IVector> rows, float bottomRightRadius, float bottomLeftRadius); + CompositionPath GetVoiceNoteClip(IVector waveform, double waveformWidth); HRESULT Encode(IBuffer source, IRandomAccessStream destination, int32_t width, int32_t height, int32_t rotation); diff --git a/Telegram.Native/PlaceholderImageHelper.idl b/Telegram.Native/PlaceholderImageHelper.idl index 324f54e188..5f636d46ac 100644 --- a/Telegram.Native/PlaceholderImageHelper.idl +++ b/Telegram.Native/PlaceholderImageHelper.idl @@ -15,6 +15,9 @@ namespace Telegram.Native Windows.UI.Composition.CompositionPath GetTail(Single width, Single height, Single topLeftRadius, Single topRightRadius, Single bottomRightRadius, Single bottomLeftRadius); Windows.UI.Composition.CompositionPath GetOutline(Windows.Foundation.Collections.IVector contours); + Windows.UI.Composition.CompositionPath GetEllipticalClip(Single width, Single height, Single radius, Single x, Single y); + Windows.UI.Composition.CompositionPath GetReplyMarkupClip(Windows.Foundation.Collections.IVector > buttons, Single bottomRightRadius, Single bottomLeftRadius); + Windows.UI.Composition.CompositionPath GetVoiceNoteClip(Windows.Foundation.Collections.IVector waveform, Double waveformWidth); void Encode(Windows.Storage.Streams.IBuffer source, Windows.Storage.Streams.IRandomAccessStream destination, Int32 width, Int32 height, Int32 rotation); diff --git a/Telegram.Native/QrBuffer.cpp b/Telegram.Native/QrBuffer.cpp index f06e67fed4..0d60fa346d 100644 --- a/Telegram.Native/QrBuffer.cpp +++ b/Telegram.Native/QrBuffer.cpp @@ -21,10 +21,11 @@ namespace winrt::Telegram::Native::implementation return ReplaceElements(data) * pixel; } - winrt::Telegram::Native::QrBuffer QrBuffer::FromString(hstring text) { + winrt::Telegram::Native::QrBuffer QrBuffer::FromString(hstring text, int minVersion, int maxVersion) { auto data = QrData(); const auto utf8 = winrt::to_string(text); - const auto qr = QrCode::encodeText(utf8.c_str(), QrCode::Ecc::MEDIUM); + const auto segs = QrSegment::makeSegments(utf8.c_str()); + const auto qr = QrCode::encodeSegments(segs, QrCode::Ecc::MEDIUM, minVersion, maxVersion); data.size = qr.getSize(); data.values.reserve(data.size * data.size); diff --git a/Telegram.Native/QrBuffer.h b/Telegram.Native/QrBuffer.h index 90f2e27c87..4cebf1920b 100644 --- a/Telegram.Native/QrBuffer.h +++ b/Telegram.Native/QrBuffer.h @@ -39,7 +39,7 @@ namespace winrt::Telegram::Native::implementation return m_replaceTill; } - static winrt::Telegram::Native::QrBuffer FromString(hstring text); + static winrt::Telegram::Native::QrBuffer FromString(hstring text, int minVersion = 1, int maxVersion = 40); private: int32_t m_size; diff --git a/Telegram.Native/QrBuffer.idl b/Telegram.Native/QrBuffer.idl index c741e14e7e..6dbcd5b7aa 100644 --- a/Telegram.Native/QrBuffer.idl +++ b/Telegram.Native/QrBuffer.idl @@ -3,7 +3,9 @@ namespace Telegram.Native [default_interface] runtimeclass QrBuffer { + [default_overload] static QrBuffer FromString(String text); + static QrBuffer FromString(String text, Int32 minVersion, Int32 maxVersion); Int32 Size{ get; }; Windows.Foundation.Collections.IVector Values{ get; }; diff --git a/Telegram.Native/VideoAnimation.cpp b/Telegram.Native/VideoAnimation.cpp index bb1f9a4bce..b1dd464019 100644 --- a/Telegram.Native/VideoAnimation.cpp +++ b/Telegram.Native/VideoAnimation.cpp @@ -472,7 +472,7 @@ namespace winrt::Telegram::Native::implementation return nullptr; } - int VideoAnimation::RenderSync(IBuffer bitmap, int32_t w, int32_t h, bool preview, int32_t& seconds) + int VideoAnimation::RenderSync(IBuffer bitmap, int32_t w, int32_t h, bool preview, double& seconds) { uint8_t* pixels = bitmap.data(); bool completed; @@ -481,7 +481,7 @@ namespace winrt::Telegram::Native::implementation return result; } - int VideoAnimation::RenderSync(uint8_t* pixels, int32_t width, int32_t height, bool preview, int32_t& seconds, bool& completed) + int VideoAnimation::RenderSync(uint8_t* pixels, int32_t width, int32_t height, bool preview, double& seconds, bool& completed) { slim_lock_guard const guard(m_lock); @@ -526,7 +526,7 @@ namespace winrt::Telegram::Native::implementation //if (nextFrame >= prevFrame + 1.0 / 30 || framerate < 60) { - seconds = static_cast(nextFrame); + seconds = nextFrame; prevFrame = nextFrame; decode_frame(pixels, width, height); diff --git a/Telegram.Native/VideoAnimation.h b/Telegram.Native/VideoAnimation.h index c068616faf..fa98415d46 100644 --- a/Telegram.Native/VideoAnimation.h +++ b/Telegram.Native/VideoAnimation.h @@ -132,8 +132,8 @@ namespace winrt::Telegram::Native::implementation IRandomAccessStream GetAlbumCover(); - int RenderSync(IBuffer buffer, int32_t width, int32_t height, bool preview, int32_t& seconds); - int RenderSync(uint8_t* pixels, int32_t width, int32_t height, bool preview, int32_t& seconds, bool& completed); + int RenderSync(IBuffer buffer, int32_t width, int32_t height, bool preview, double& seconds); + int RenderSync(uint8_t* pixels, int32_t width, int32_t height, bool preview, double& seconds, bool& completed); int PixelWidth() { diff --git a/Telegram.Native/VideoAnimation.idl b/Telegram.Native/VideoAnimation.idl index e0e83f5b12..d6b5cd2f79 100644 --- a/Telegram.Native/VideoAnimation.idl +++ b/Telegram.Native/VideoAnimation.idl @@ -23,7 +23,7 @@ namespace Telegram.Native Windows.Storage.Streams.IRandomAccessStream GetAlbumCover(); - Int32 RenderSync(Windows.Storage.Streams.IBuffer bitmap, Int32 width, Int32 height, Boolean preview, out Int32 seconds); + Int32 RenderSync(Windows.Storage.Streams.IBuffer bitmap, Int32 width, Int32 height, Boolean preview, out Double seconds); Int32 PixelWidth{ get; }; Int32 PixelHeight{ get; }; diff --git a/Telegram/App.xaml.cs b/Telegram/App.xaml.cs index 417f73222b..25d767e2d8 100644 --- a/Telegram/App.xaml.cs +++ b/Telegram/App.xaml.cs @@ -77,6 +77,7 @@ public App() } WatchDog.Initialize(); + MediaHttpServer.Start(); TypeResolver.Current.Configure(); RequestedTheme = SettingsService.Current.Appearance.GetCalculatedApplicationTheme(); diff --git a/Telegram/Assets/Animations/QrLogo.json b/Telegram/Assets/Animations/QrLogo.json new file mode 100644 index 0000000000..71c1521c8f --- /dev/null +++ b/Telegram/Assets/Animations/QrLogo.json @@ -0,0 +1 @@ +{"layers":[{"ddd":0,"ty":3,"nm":"Null 2","sr":1,"ks":{"p":{"a":1,"k":[{"t":20,"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"s":[253.375,252.125,0],"ti":[0,0,0],"to":[0,0,0]},{"t":24,"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"s":[260.5,258.625,0],"ti":[0,0,0],"to":[0,0,0]},{"t":28,"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"s":[253.375,252.125,0],"ti":[0,0,0],"to":[0,0,0]},{"t":33,"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"s":[260.5,258.625,0],"ti":[0,0,0],"to":[0,0,0]},{"t":38,"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"s":[253.375,252.125,0],"ti":[0,0,0],"to":[0,0,0]},{"t":42,"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"s":[260.5,258.625,0],"ti":[0,0,0],"to":[0,0,0]},{"t":47,"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"s":[253.375,252.125,0],"ti":[0,0,0],"to":[0,0,0]},{"t":51,"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"s":[260.5,258.625,0],"ti":[0,0,0],"to":[0,0,0]},{"t":55,"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"s":[253.375,252.125,0],"ti":[0,0,0],"to":[0,0,0]},{"t":60,"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"s":[260.5,258.625,0],"ti":[0,0,0],"to":[0,0,0]},{"t":64,"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"s":[253.375,252.125,0],"ti":[0,0,0],"to":[0,0,0]},{"t":69,"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"s":[260.5,258.625,0],"ti":[0,0,0],"to":[0,0,0]},{"t":73,"s":[253.375,252.125,0]}]},"o":{"k":0,"a":0}},"ao":0,"ip":0,"op":180,"st":0,"bm":0,"ind":1},{"ddd":0,"ty":3,"nm":"Null 1","parent":1,"sr":1,"ks":{"p":{"a":1,"k":[{"t":20,"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"s":[-1,-22.5,0],"ti":[8.25,-6.75,0],"to":[11,-5.75,0]},{"t":42.084,"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"s":[19,1.25,0],"ti":[5,7,0],"to":[-8.25,6.75,0]},{"t":52.684,"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"s":[0,0,0],"ti":[-11,5.75,0],"to":[-5,-7,0]},{"t":73,"s":[-1,-22.5,0]}]},"o":{"k":0,"a":0}},"ao":0,"ip":0,"op":180,"st":0,"bm":0,"ind":2},{"ddd":0,"ty":4,"nm":"Plane","parent":2,"sr":1,"ks":{"a":{"k":[12.637,41.19,0],"a":0},"p":{"a":1,"k":[{"t":20,"i":{"x":[0.69],"y":[0.452]},"o":{"x":[0.31],"y":[0]},"s":[13.035,0.078,0],"ti":[0,0,0],"to":[0,0,0]},{"t":42,"i":{"x":[0.69],"y":[1]},"o":{"x":[0.31],"y":[0.253]},"s":[15.045,-3.387,0],"ti":[0,0,0],"to":[0,0,0]},{"t":68,"i":{"x":[0.69],"y":[1]},"o":{"x":[0.31],"y":[0]},"s":[10.025,5.541,0],"ti":[0,0,0],"to":[0,0,0]},{"t":73,"s":[13.035,0.078,0]}]},"r":{"k":[{"t":22,"i":{"x":[0.69],"y":[1]},"o":{"x":[0.31],"y":[0]},"s":[-52]},{"t":47,"i":{"x":[0.69],"y":[1]},"o":{"x":[0.31],"y":[0]},"s":[-46]},{"t":73,"s":[-52]}],"a":1}},"ao":0,"ip":0,"op":180,"st":0,"bm":0,"ind":3,"shapes":[{"hd":false,"nm":"Group 1","ty":"gr","bm":0,"it":[{"hd":false,"nm":"Path 1","ty":"sh","ks":{"k":[{"t":20,"i":{"x":[0.5],"y":[1]},"o":{"x":[0.5],"y":[0]},"s":[{"c":true,"i":[[-9.785,-2.748],[0,0],[10.073,-3.525],[0,0],[2.014,8.701],[0,0],[-10.841,-0.565],[0,0],[4.226,0.677],[0,0],[4.908,4.225],[5.646,8.06]],"o":[[0,0],[10.58,2.999],[0,0],[-12.974,4.687],[0,0],[-0.722,-6.213],[0,0],[4.378,0.17],[0,0],[-8.58,-1.284],[-8.728,-7.514],[-4.055,-5.788]],"v":[[-86.852,-21.107],[121.04,37.828],[121.043,49.217],[-1.939,95.039],[-35.635,89.169],[-66.913,39.948],[-49.277,27.303],[43.295,34.291],[44.526,30.201],[-61.21,11.74],[-83.062,5.284],[-99.928,-14.021]]}]},{"t":44.732,"i":{"x":[0.5],"y":[1]},"o":{"x":[0.5],"y":[0]},"s":[{"c":true,"i":[[-9.785,-5.623],[0,0],[10.073,-7.212],[0,0],[2.014,17.803],[0,0],[-10.841,-1.157],[0,0],[4.225,1.386],[0,0],[4.908,8.645],[4.363,17.802]],"o":[[0,0],[10.58,6.137],[0,0],[-12.974,9.59],[0,0],[1.88,-12.095],[0,0],[4.378,0.348],[0,0],[-8.58,-2.628],[-8.728,-15.375],[-2.98,-12.157]],"v":[[-80.291,-79.452],[116.546,26.896],[117.812,56.207],[-25.252,167.906],[-57.833,157.377],[-63.507,81.884],[-38.439,58.15],[33.696,48.095],[32.959,37.461],[-54.013,17.63],[-75.012,0.191],[-96.665,-61.799]]}]},{"t":73,"s":[{"c":true,"i":[[-9.785,-2.748],[0,0],[10.073,-3.525],[0,0],[2.014,8.701],[0,0],[-10.841,-0.565],[0,0],[4.226,0.677],[0,0],[4.908,4.225],[5.646,8.06]],"o":[[0,0],[10.58,2.999],[0,0],[-12.974,4.687],[0,0],[-0.722,-6.213],[0,0],[4.378,0.17],[0,0],[-8.58,-1.284],[-8.728,-7.514],[-4.055,-5.788]],"v":[[-86.852,-21.107],[121.04,37.828],[121.043,49.217],[-1.939,95.039],[-35.635,89.169],[-66.913,39.948],[-49.277,27.303],[43.295,34.291],[44.526,30.201],[-61.21,11.74],[-83.062,5.284],[-99.928,-14.021]]}]}],"a":1},"ind":0},{"hd":false,"nm":"Fill 1","ty":"fl","bm":0,"o":{"k":100,"a":0},"c":{"k":[0.996,1,0.992,1],"a":0},"r":1},{"nm":"Transform","ty":"tr","a":{"k":[0,0],"a":0},"p":{"k":[0,0],"a":0},"s":{"k":[100,100],"a":0},"r":{"k":0,"a":0},"o":{"k":100,"a":0},"sk":{"k":0,"a":0},"sa":{"k":0,"a":0}}]}]}],"v":"5.5.2","fr":60,"ip":20,"op":74,"w":512,"h":512,"nm":"4","ddd":0,"assets":[]} \ No newline at end of file diff --git a/Telegram/Assets/Fonts/Nunito.ttf b/Telegram/Assets/Fonts/Nunito.ttf new file mode 100644 index 0000000000..bd7946ce76 Binary files /dev/null and b/Telegram/Assets/Fonts/Nunito.ttf differ diff --git a/Telegram/Assets/Fonts/Telegram.json b/Telegram/Assets/Fonts/Telegram.json index 069d368c2c..a289ab811e 100644 --- a/Telegram/Assets/Fonts/Telegram.json +++ b/Telegram/Assets/Fonts/Telegram.json @@ -1648,13 +1648,21 @@ }, { "selection": [ + { + "order": 965, + "id": 22, + "name": "ic_fluent_window_16_filled", + "prevSize": 32, + "code": 60101, + "tempChar": "" + }, { "order": 948, "id": 21, "name": "ic_fluent_mic_off_16_filled", "prevSize": 32, "code": 60087, - "tempChar": "" + "tempChar": "" }, { "order": 947, @@ -1662,7 +1670,7 @@ "name": "ic_fluent_mic_16_filled", "prevSize": 32, "code": 60088, - "tempChar": "" + "tempChar": "" }, { "order": 911, @@ -1670,7 +1678,7 @@ "name": "ic_fluent_copy_16_filled", "prevSize": 32, "code": 60055, - "tempChar": "" + "tempChar": "" }, { "order": 893, @@ -1678,7 +1686,7 @@ "name": "ic_fluent_pin_16_filled", "prevSize": 32, "code": 60042, - "tempChar": "" + "tempChar": "" }, { "order": 838, @@ -1686,7 +1694,7 @@ "name": "ic_fluent_code_16_filled", "prevSize": 32, "code": 59994, - "tempChar": "" + "tempChar": "" }, { "order": 824, @@ -1694,7 +1702,7 @@ "name": "ic_fluent_arrow_right_16_filled", "prevSize": 32, "code": 59981, - "tempChar": "" + "tempChar": "" }, { "order": 823, @@ -1702,7 +1710,7 @@ "name": "ic_fluent_chat_empty_16_filled", "prevSize": 32, "code": 59980, - "tempChar": "" + "tempChar": "" }, { "order": 813, @@ -1710,7 +1718,7 @@ "name": "ic_fluent_megaphone_16_filled", "prevSize": 32, "code": 59975, - "tempChar": "" + "tempChar": "" }, { "order": 814, @@ -1718,7 +1726,7 @@ "name": "ic_fluent_person_16_filled", "prevSize": 32, "code": 59976, - "tempChar": "" + "tempChar": "" }, { "order": 819, @@ -1726,7 +1734,7 @@ "name": "tl_fluent_quote_block_16_filled", "prevSize": 32, "code": 59905, - "tempChar": "" + "tempChar": "" }, { "order": 818, @@ -1734,7 +1742,7 @@ "name": "ic_fluent_code_block_16_filled", "prevSize": 32, "code": 59965, - "tempChar": "" + "tempChar": "" }, { "order": 817, @@ -1742,7 +1750,7 @@ "name": "ic_fluent_person_circle_16_filled", "prevSize": 32, "code": 59942, - "tempChar": "" + "tempChar": "" }, { "order": 816, @@ -1750,7 +1758,7 @@ "name": "ic_fluent_people_16_filled", "prevSize": 32, "code": 59943, - "tempChar": "" + "tempChar": "" }, { "order": 815, @@ -1758,7 +1766,7 @@ "name": "ic_fluent_star_16_filled", "prevSize": 32, "code": 59939, - "tempChar": "" + "tempChar": "" }, { "order": 756, @@ -1766,7 +1774,7 @@ "name": "ic_fluent_arrow_down_left_16_regular", "prevSize": 32, "code": 59688, - "tempChar": "" + "tempChar": "" }, { "order": 728, @@ -1774,7 +1782,7 @@ "name": "ic_fluent_chevron_down_16_regular", "prevSize": 32, "code": 59926, - "tempChar": "" + "tempChar": "" }, { "order": 699, @@ -1782,7 +1790,7 @@ "name": "ic_fluent_number_symbol_16_filled", "prevSize": 32, "code": 59899, - "tempChar": "" + "tempChar": "" }, { "order": 631, @@ -1790,7 +1798,7 @@ "name": "ic_fluent_lock_closed_16_filled1", "prevSize": 32, "code": 59838, - "tempChar": "" + "tempChar": "" }, { "order": 623, @@ -1798,7 +1806,7 @@ "name": "ic_fluent_arrow_up_right_16_regular", "prevSize": 32, "code": 59831, - "tempChar": "" + "tempChar": "" }, { "order": 610, @@ -1806,7 +1814,7 @@ "name": "ic_fluent_window_16_regular", "prevSize": 32, "code": 59818, - "tempChar": "" + "tempChar": "" }, { "order": 611, @@ -1814,7 +1822,7 @@ "name": "ic_fluent_payment_16_regular", "prevSize": 32, "code": 59819, - "tempChar": "" + "tempChar": "" }, { "order": 591, @@ -1822,7 +1830,7 @@ "name": "ic_fluent_compose_16_regular", "prevSize": 32, "code": 59798, - "tempChar": "" + "tempChar": "" } ], "id": 7, @@ -1836,6 +1844,21 @@ "height": 1024, "prevSize": 32, "icons": [ + { + "id": 22, + "paths": [ + "M128 288c0-88.365 71.635-160 160-160h448c88.365 0 160 71.635 160 160v448c0 88.365-71.635 160-160 160h-448c-88.365 0-160-71.635-160-160v-448zM192 384v352c0 53.018 42.98 96 96 96h448c53.018 0 96-42.982 96-96v-352h-640z" + ], + "attrs": [ + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": [ + "ic_fluent_window_16_filled" + ] + }, { "id": 21, "paths": [ @@ -2184,7 +2207,7 @@ "name": "device_android", "prevSize": 32, "code": 59756, - "tempChar": "" + "tempChar": "" }, { "order": 499, @@ -2192,7 +2215,7 @@ "name": "device_chrome", "prevSize": 32, "code": 59757, - "tempChar": "" + "tempChar": "" }, { "order": 500, @@ -2200,7 +2223,7 @@ "name": "device_edge", "prevSize": 32, "code": 59758, - "tempChar": "" + "tempChar": "" }, { "order": 501, @@ -2208,7 +2231,7 @@ "name": "device_firefox", "prevSize": 32, "code": 59759, - "tempChar": "" + "tempChar": "" }, { "order": 502, @@ -2216,7 +2239,7 @@ "name": "device_ipad", "prevSize": 32, "code": 59760, - "tempChar": "" + "tempChar": "" }, { "order": 503, @@ -2224,7 +2247,7 @@ "name": "device_iphone", "prevSize": 32, "code": 59761, - "tempChar": "" + "tempChar": "" }, { "order": 504, @@ -2232,7 +2255,7 @@ "name": "device_linux", "prevSize": 32, "code": 59762, - "tempChar": "" + "tempChar": "" }, { "order": 505, @@ -2240,7 +2263,7 @@ "name": "device_mac", "prevSize": 32, "code": 59763, - "tempChar": "" + "tempChar": "" }, { "order": 506, @@ -2248,7 +2271,7 @@ "name": "device_safari", "prevSize": 32, "code": 59764, - "tempChar": "" + "tempChar": "" }, { "order": 507, @@ -2256,7 +2279,7 @@ "name": "device_ubuntu", "prevSize": 32, "code": 59766, - "tempChar": "" + "tempChar": "" }, { "order": 508, @@ -2264,7 +2287,7 @@ "name": "device_windows", "prevSize": 32, "code": 59767, - "tempChar": "" + "tempChar": "" } ], "id": 6, @@ -2439,13 +2462,45 @@ }, { "selection": [ + { + "order": 971, + "id": 321, + "name": "ic_fluent_chevron_circle_down_20_regular", + "prevSize": 32, + "code": 60107, + "tempChar": "" + }, + { + "order": 970, + "id": 320, + "name": "ic_fluent_qr_code_20_regular", + "prevSize": 32, + "code": 60106, + "tempChar": "" + }, + { + "order": 967, + "id": 319, + "name": "ic_fluent_text_quote_20_regular", + "prevSize": 32, + "code": 60103, + "tempChar": "" + }, + { + "order": 966, + "id": 318, + "name": "tl_fluent_reply_another_chat_20_filled", + "prevSize": 32, + "code": 60102, + "tempChar": "" + }, { "order": 962, "id": 317, "name": "ic_fluent_calendar_20_regular", "prevSize": 32, "code": 60098, - "tempChar": "" + "tempChar": "" }, { "order": 959, @@ -2453,7 +2508,7 @@ "name": "ic_fluent_checkmark_square_20_filled", "prevSize": 32, "code": 60095, - "tempChar": "" + "tempChar": "" }, { "order": 958, @@ -2461,7 +2516,7 @@ "name": "ic_fluent_checkmark_square_20_regular", "prevSize": 32, "code": 60094, - "tempChar": "" + "tempChar": "" }, { "order": 956, @@ -2469,7 +2524,7 @@ "name": "ic_fluent_crop_20_regular", "prevSize": 32, "code": 60092, - "tempChar": "" + "tempChar": "" }, { "order": 950, @@ -2477,7 +2532,7 @@ "name": "ic_fluent_add_20_regular", "prevSize": 32, "code": 60086, - "tempChar": "" + "tempChar": "" }, { "order": 949, @@ -2485,7 +2540,7 @@ "name": "tl_fluent_navigation_menu_20_regular", "prevSize": 32, "code": 60085, - "tempChar": "" + "tempChar": "" }, { "order": 944, @@ -2493,7 +2548,7 @@ "name": "ic_fluent_tag_20_regular", "prevSize": 32, "code": 60084, - "tempChar": "" + "tempChar": "" }, { "order": 943, @@ -2501,7 +2556,7 @@ "name": "tl_fluent_crown_off_20_filled", "prevSize": 32, "code": 60081, - "tempChar": "" + "tempChar": "" }, { "order": 942, @@ -2509,7 +2564,7 @@ "name": "ic_fluent_tag_off_20_filled", "prevSize": 32, "code": 60082, - "tempChar": "" + "tempChar": "" }, { "order": 941, @@ -2517,7 +2572,7 @@ "name": "ic_fluent_crown_20_filled", "prevSize": 32, "code": 60083, - "tempChar": "" + "tempChar": "" }, { "order": 934, @@ -2525,7 +2580,7 @@ "name": "ic_fluent_call_add_20_regular", "prevSize": 32, "code": 60074, - "tempChar": "" + "tempChar": "" }, { "order": 933, @@ -2533,7 +2588,7 @@ "name": "ic_fluent_text_font_20_regular", "prevSize": 32, "code": 60073, - "tempChar": "" + "tempChar": "" }, { "order": 932, @@ -2541,7 +2596,7 @@ "name": "ic_fluent_hourglass_20_regular", "prevSize": 32, "code": 60072, - "tempChar": "" + "tempChar": "" }, { "order": 931, @@ -2549,7 +2604,7 @@ "name": "ic_fluent_thumb_like_20_regular", "prevSize": 32, "code": 60071, - "tempChar": "" + "tempChar": "" }, { "order": 930, @@ -2557,7 +2612,7 @@ "name": "ic_fluent_connected_20_regular", "prevSize": 32, "code": 60070, - "tempChar": "" + "tempChar": "" }, { "order": 928, @@ -2565,7 +2620,7 @@ "name": "ic_fluent_home_add_20_regular", "prevSize": 32, "code": 60068, - "tempChar": "" + "tempChar": "" }, { "order": 927, @@ -2573,7 +2628,7 @@ "name": "ic_fluent_lock_closed_20_regular", "prevSize": 32, "code": 60067, - "tempChar": "" + "tempChar": "" }, { "order": 916, @@ -2581,7 +2636,7 @@ "name": "ic_fluent_ticket_diagonal_20_regular", "prevSize": 32, "code": 60061, - "tempChar": "" + "tempChar": "" }, { "order": 915, @@ -2589,7 +2644,7 @@ "name": "ic_fluent_add_circle_20_filled", "prevSize": 32, "code": 60060, - "tempChar": "" + "tempChar": "" }, { "order": 914, @@ -2597,7 +2652,7 @@ "name": "ic_fluent_copy_20_filled", "prevSize": 32, "code": 60059, - "tempChar": "" + "tempChar": "" }, { "order": 913, @@ -2605,7 +2660,7 @@ "name": "ic_fluent_mic_off_20_filled", "prevSize": 32, "code": 60056, - "tempChar": "" + "tempChar": "" }, { "order": 912, @@ -2613,7 +2668,7 @@ "name": "ic_fluent_mic_20_filled", "prevSize": 32, "code": 60058, - "tempChar": "" + "tempChar": "" }, { "order": 900, @@ -2621,7 +2676,7 @@ "name": "ic_fluent_camera_add_20_filled", "prevSize": 32, "code": 60050, - "tempChar": "" + "tempChar": "" }, { "order": 898, @@ -2629,7 +2684,7 @@ "name": "ic_fluent_chat_sparkle_20_filled", "prevSize": 32, "code": 60048, - "tempChar": "" + "tempChar": "" }, { "order": 896, @@ -2637,7 +2692,7 @@ "name": "ic_fluent_info_20_regular", "prevSize": 32, "code": 60044, - "tempChar": "" + "tempChar": "" }, { "order": 894, @@ -2645,7 +2700,7 @@ "name": "ic_fluent_checkmark_starburst_20_regular", "prevSize": 32, "code": 60043, - "tempChar": "" + "tempChar": "" }, { "order": 892, @@ -2653,7 +2708,7 @@ "name": "ic_fluent_resize_20_regular", "prevSize": 32, "code": 60041, - "tempChar": "" + "tempChar": "" }, { "order": 891, @@ -2661,7 +2716,7 @@ "name": "ic_fluent_megaphone_off_20_regular", "prevSize": 32, "code": 60040, - "tempChar": "" + "tempChar": "" }, { "order": 889, @@ -2669,7 +2724,7 @@ "name": "ic_fluent_building_shop_20_filled", "prevSize": 32, "code": 60038, - "tempChar": "" + "tempChar": "" }, { "order": 888, @@ -2677,7 +2732,7 @@ "name": "ic_fluent_link_add_20_regular", "prevSize": 32, "code": 60037, - "tempChar": "" + "tempChar": "" }, { "order": 887, @@ -2685,7 +2740,7 @@ "name": "ic_fluent_food_cake_20_regular", "prevSize": 32, "code": 60036, - "tempChar": "" + "tempChar": "" }, { "order": 879, @@ -2693,7 +2748,7 @@ "name": "ic_fluent_hand_wave_20_filled", "prevSize": 32, "code": 60027, - "tempChar": "" + "tempChar": "" }, { "order": 878, @@ -2701,7 +2756,7 @@ "name": "ic_fluent_location_20_filled", "prevSize": 32, "code": 60026, - "tempChar": "" + "tempChar": "" }, { "order": 876, @@ -2709,7 +2764,7 @@ "name": "ic_fluent_bot_20_filled", "prevSize": 32, "code": 60023, - "tempChar": "" + "tempChar": "" }, { "order": 875, @@ -2717,7 +2772,7 @@ "name": "ic_fluent_megaphone_20_filled", "prevSize": 32, "code": 60024, - "tempChar": "" + "tempChar": "" }, { "order": 872, @@ -2725,7 +2780,7 @@ "name": "ic_fluent_hand_wave_20_regular", "prevSize": 32, "code": 60020, - "tempChar": "" + "tempChar": "" }, { "order": 873, @@ -2733,7 +2788,7 @@ "name": "ic_fluent_chat_arrow_back_20_regular", "prevSize": 32, "code": 60021, - "tempChar": "" + "tempChar": "" }, { "order": 870, @@ -2741,7 +2796,7 @@ "name": "ic_fluent_image_multiple_20_regular", "prevSize": 32, "code": 60018, - "tempChar": "" + "tempChar": "" }, { "order": 869, @@ -2749,7 +2804,7 @@ "name": "ic_fluent_image_sparkle_20_regular", "prevSize": 32, "code": 60017, - "tempChar": "" + "tempChar": "" }, { "order": 867, @@ -2757,7 +2812,7 @@ "name": "ic_fluent_emoji_sparkle_20_regular", "prevSize": 32, "code": 60015, - "tempChar": "" + "tempChar": "" }, { "order": 866, @@ -2765,7 +2820,7 @@ "name": "ic_fluent_hand_open_heart_20_regular", "prevSize": 32, "code": 60016, - "tempChar": "" + "tempChar": "" }, { "order": 865, @@ -2773,7 +2828,7 @@ "name": "ic_fluent_chat_settings_20_filled", "prevSize": 32, "code": 60014, - "tempChar": "" + "tempChar": "" }, { "order": 863, @@ -2781,7 +2836,7 @@ "name": "ic_fluent_chat_lock_20_filled", "prevSize": 32, "code": 60012, - "tempChar": "" + "tempChar": "" }, { "order": 860, @@ -2789,7 +2844,7 @@ "name": "ic_fluent_hand_open_heart_20_filled", "prevSize": 32, "code": 60009, - "tempChar": "" + "tempChar": "" }, { "order": 859, @@ -2797,7 +2852,7 @@ "name": "ic_fluent_tag_20_filled", "prevSize": 32, "code": 60008, - "tempChar": "" + "tempChar": "" }, { "order": 858, @@ -2805,7 +2860,7 @@ "name": "ic_fluent_apps_list_detail_20_regular", "prevSize": 32, "code": 60007, - "tempChar": "" + "tempChar": "" }, { "order": 857, @@ -2816,7 +2871,7 @@ "codes": [ 60003 ], - "tempChar": "" + "tempChar": "" }, { "order": 856, @@ -2824,7 +2879,7 @@ "name": "tl_fluent_tag_edit_20_regular", "prevSize": 32, "code": 60004, - "tempChar": "" + "tempChar": "" }, { "order": 855, @@ -2832,7 +2887,7 @@ "name": "ic_fluent_tag_search_20_regular", "prevSize": 32, "code": 60005, - "tempChar": "" + "tempChar": "" }, { "order": 854, @@ -2840,7 +2895,7 @@ "name": "ic_fluent_tag_off_20_regular", "prevSize": 32, "code": 60006, - "tempChar": "" + "tempChar": "" }, { "order": 841, @@ -2848,7 +2903,7 @@ "name": "ic_fluent_arrow_rotate_counterclockwise_20_regular", "prevSize": 32, "code": 59995, - "tempChar": "" + "tempChar": "" }, { "order": 837, @@ -2856,7 +2911,7 @@ "name": "ic_fluent_more_horizontal_20_filled", "prevSize": 32, "code": 59993, - "tempChar": "" + "tempChar": "" }, { "order": 835, @@ -2864,7 +2919,7 @@ "name": "ic_fluent_search_20_filled", "prevSize": 32, "code": 59992, - "tempChar": "" + "tempChar": "" }, { "order": 834, @@ -2872,7 +2927,7 @@ "name": "ic_fluent_arrow_exit_20_filled", "prevSize": 32, "code": 59990, - "tempChar": "" + "tempChar": "" }, { "order": 833, @@ -2880,7 +2935,7 @@ "name": "ic_fluent_arrow_enter_20_filled", "prevSize": 32, "code": 59991, - "tempChar": "" + "tempChar": "" }, { "order": 832, @@ -2888,7 +2943,7 @@ "name": "ic_fluent_edit_20_filled", "prevSize": 32, "code": 59989, - "tempChar": "" + "tempChar": "" }, { "order": 831, @@ -2896,7 +2951,7 @@ "name": "ic_fluent_call_20_filled", "prevSize": 32, "code": 59986, - "tempChar": "" + "tempChar": "" }, { "order": 830, @@ -2904,7 +2959,7 @@ "name": "ic_fluent_alert_off_20_filled", "prevSize": 32, "code": 59987, - "tempChar": "" + "tempChar": "" }, { "order": 829, @@ -2912,7 +2967,7 @@ "name": "ic_fluent_alert_20_filled", "prevSize": 32, "code": 59988, - "tempChar": "" + "tempChar": "" }, { "order": 828, @@ -2920,7 +2975,7 @@ "name": "ic_fluent_wallpaper_20_filled", "prevSize": 32, "code": 59984, - "tempChar": "" + "tempChar": "" }, { "order": 827, @@ -2928,7 +2983,7 @@ "name": "ic_fluent_paint_brush_20_filled", "prevSize": 32, "code": 59985, - "tempChar": "" + "tempChar": "" }, { "order": 825, @@ -2936,7 +2991,7 @@ "name": "ic_fluent_dismiss_circle_20_regular", "prevSize": 32, "code": 59982, - "tempChar": "" + "tempChar": "" }, { "order": 805, @@ -2944,7 +2999,7 @@ "name": "ic_fluent_person_delete_20_filled", "prevSize": 32, "code": 59968, - "tempChar": "" + "tempChar": "" }, { "order": 806, @@ -2952,7 +3007,7 @@ "name": "ic_fluent_person_question_mark_20_filled", "prevSize": 32, "code": 59969, - "tempChar": "" + "tempChar": "" }, { "order": 796, @@ -2960,7 +3015,7 @@ "name": "ic_fluent_wallet_20_regular", "prevSize": 32, "code": 59960, - "tempChar": "" + "tempChar": "" }, { "order": 792, @@ -2968,7 +3023,7 @@ "name": "ic_fluent_clock_20_filled", "prevSize": 32, "code": 59955, - "tempChar": "" + "tempChar": "" }, { "order": 793, @@ -2976,7 +3031,7 @@ "name": "ic_fluent_clock_20_regular1", "prevSize": 32, "code": 59956, - "tempChar": "" + "tempChar": "" }, { "order": 789, @@ -2984,7 +3039,7 @@ "name": "ic_fluent_speaker_2_20_filled", "prevSize": 32, "code": 59950, - "tempChar": "" + "tempChar": "" }, { "order": 788, @@ -2992,7 +3047,7 @@ "name": "ic_fluent_speaker_1_20_filled", "prevSize": 32, "code": 59951, - "tempChar": "" + "tempChar": "" }, { "order": 787, @@ -3000,7 +3055,7 @@ "name": "ic_fluent_speaker_mute_20_filled", "prevSize": 32, "code": 59952, - "tempChar": "" + "tempChar": "" }, { "order": 779, @@ -3008,7 +3063,7 @@ "name": "tl_fluent_stories_pinned_20_regular", "prevSize": 32, "code": 59820, - "tempChar": "" + "tempChar": "" }, { "order": 778, @@ -3016,7 +3071,7 @@ "name": "tl_fluent_stories_pinned_off_20_regular", "prevSize": 32, "code": 59858, - "tempChar": "" + "tempChar": "" }, { "order": 777, @@ -3024,7 +3079,7 @@ "name": "ic_fluent_multiplier_2x_20_filled", "prevSize": 32, "code": 59834, - "tempChar": "" + "tempChar": "" }, { "order": 776, @@ -3032,7 +3087,7 @@ "name": "ic_fluent_heart_20_filled", "prevSize": 32, "code": 59830, - "tempChar": "" + "tempChar": "" }, { "order": 775, @@ -3040,7 +3095,7 @@ "name": "ic_fluent_sticker_20_filled", "prevSize": 32, "code": 59821, - "tempChar": "" + "tempChar": "" }, { "order": 774, @@ -3048,7 +3103,7 @@ "name": "ic_fluent_play_circle_20_filled", "prevSize": 32, "code": 59822, - "tempChar": "" + "tempChar": "" }, { "order": 773, @@ -3056,7 +3111,7 @@ "name": "ic_fluent_megaphone_off_20_filled", "prevSize": 32, "code": 59823, - "tempChar": "" + "tempChar": "" }, { "order": 772, @@ -3064,7 +3119,7 @@ "name": "ic_fluent_chat_empty_20_filled", "prevSize": 32, "code": 59824, - "tempChar": "" + "tempChar": "" }, { "order": 771, @@ -3072,7 +3127,7 @@ "name": "ic_fluent_emoji_20_filled", "prevSize": 32, "code": 59825, - "tempChar": "" + "tempChar": "" }, { "order": 770, @@ -3080,7 +3135,7 @@ "name": "ic_fluent_translate_20_filled", "prevSize": 32, "code": 59826, - "tempChar": "" + "tempChar": "" }, { "order": 769, @@ -3088,7 +3143,7 @@ "name": "ic_fluent_top_speed_20_filled", "prevSize": 32, "code": 59827, - "tempChar": "" + "tempChar": "" }, { "order": 768, @@ -3096,7 +3151,7 @@ "name": "ic_fluent_document_20_filled", "prevSize": 32, "code": 59828, - "tempChar": "" + "tempChar": "" }, { "order": 765, @@ -3104,7 +3159,7 @@ "name": "ic_fluent_lock_closed_20_filled", "prevSize": 32, "code": 59941, - "tempChar": "" + "tempChar": "" }, { "order": 762, @@ -3112,7 +3167,7 @@ "name": "ic_fluent_error_circle_20_regular", "prevSize": 32, "code": 59938, - "tempChar": "" + "tempChar": "" }, { "order": 758, @@ -3120,7 +3175,7 @@ "name": "ic_fluent_more_horizontal_20_regular", "prevSize": 32, "code": 59154, - "tempChar": "" + "tempChar": "" }, { "order": 744, @@ -3128,7 +3183,7 @@ "name": "ic_fluent_picture_in_picture_exit_20_regular", "prevSize": 32, "code": 59693, - "tempChar": "" + "tempChar": "" }, { "order": 743, @@ -3136,7 +3191,7 @@ "name": "ic_fluent_picture_in_picture_enter_20_regular", "prevSize": 32, "code": 59692, - "tempChar": "" + "tempChar": "" }, { "order": 727, @@ -3144,7 +3199,7 @@ "name": "ic_fluent_arrow_repeat_all_20_regular", "prevSize": 32, "code": 59924, - "tempChar": "" + "tempChar": "" }, { "order": 726, @@ -3152,7 +3207,7 @@ "name": "ic_fluent_arrow_repeat_1_20_regular", "prevSize": 32, "code": 59925, - "tempChar": "" + "tempChar": "" }, { "order": 723, @@ -3160,7 +3215,7 @@ "name": "ic_fluent_speaker_0_20_regular", "prevSize": 32, "code": 59921, - "tempChar": "" + "tempChar": "" }, { "order": 724, @@ -3168,7 +3223,7 @@ "name": "ic_fluent_speaker_3_20_regular", "prevSize": 32, "code": 59922, - "tempChar": "" + "tempChar": "" }, { "order": 721, @@ -3176,7 +3231,7 @@ "name": "ic_fluent_chat_multiple_20_filled", "prevSize": 32, "code": 59917, - "tempChar": "" + "tempChar": "" }, { "order": 720, @@ -3184,7 +3239,7 @@ "name": "ic_fluent_archive_20_filled", "prevSize": 32, "code": 59918, - "tempChar": "" + "tempChar": "" }, { "order": 719, @@ -3192,7 +3247,7 @@ "name": "ic_fluent_bookmark_20_filled", "prevSize": 32, "code": 59919, - "tempChar": "" + "tempChar": "" }, { "order": 718, @@ -3200,7 +3255,7 @@ "name": "ic_fluent_history_20_regular", "prevSize": 32, "code": 59780, - "tempChar": "" + "tempChar": "" }, { "order": 716, @@ -3208,7 +3263,7 @@ "name": "PersonCircleOnline", "prevSize": 32, "code": 59778, - "tempChar": "" + "tempChar": "" }, { "order": 715, @@ -3216,7 +3271,7 @@ "name": "ic_fluent_arrow_sync_20_regular", "prevSize": 32, "code": 59777, - "tempChar": "" + "tempChar": "" }, { "order": 712, @@ -3224,7 +3279,7 @@ "name": "ic_fluent_slide_text_20_regular", "prevSize": 32, "code": 59914, - "tempChar": "" + "tempChar": "" }, { "order": 711, @@ -3232,7 +3287,7 @@ "name": "ic_fluent_arrow_reset_20_regular", "prevSize": 32, "code": 59913, - "tempChar": "" + "tempChar": "" }, { "order": 710, @@ -3240,7 +3295,7 @@ "name": "ic_fluent_chat_add_20_regular", "prevSize": 32, "code": 59912, - "tempChar": "" + "tempChar": "" }, { "order": 0, @@ -3256,7 +3311,7 @@ "name": "ic_fluent_arrow_upload_20_regular", "prevSize": 32, "code": 59904, - "tempChar": "" + "tempChar": "" }, { "order": 702, @@ -3264,7 +3319,7 @@ "name": "ic_fluent_camera_sparkles_20_regular", "prevSize": 32, "code": 59902, - "tempChar": "" + "tempChar": "" }, { "order": 701, @@ -3272,7 +3327,7 @@ "name": "ic_fluent_camera_add_20_regular", "prevSize": 32, "code": 59901, - "tempChar": "" + "tempChar": "" }, { "order": 700, @@ -3280,7 +3335,7 @@ "name": "ic_fluent_play_circle_20_regular", "prevSize": 32, "code": 59900, - "tempChar": "" + "tempChar": "" }, { "order": 698, @@ -3288,7 +3343,7 @@ "name": "ic_fluent_shield_task_20_regular", "prevSize": 32, "code": 59897, - "tempChar": "" + "tempChar": "" }, { "order": 693, @@ -3296,7 +3351,7 @@ "name": "ic_fluent_re_order_dots_vertical_20_regular", "prevSize": 32, "code": 59894, - "tempChar": "" + "tempChar": "" }, { "order": 0, @@ -3320,7 +3375,7 @@ "name": "ic_fluent_mention_20_regular", "prevSize": 32, "code": 59890, - "tempChar": "" + "tempChar": "" }, { "order": 683, @@ -3328,7 +3383,7 @@ "name": "ic_fluent_my_location_20_regular", "prevSize": 32, "code": 59887, - "tempChar": "" + "tempChar": "" }, { "order": 682, @@ -3336,7 +3391,7 @@ "name": "ic_fluent_weather_sunny_20_regular", "prevSize": 32, "code": 59886, - "tempChar": "" + "tempChar": "" }, { "order": 678, @@ -3344,7 +3399,7 @@ "name": "ic_fluent_emoji_edit_20_regular", "prevSize": 32, "code": 59884, - "tempChar": "" + "tempChar": "" }, { "order": 679, @@ -3352,7 +3407,7 @@ "name": "ic_fluent_emoji_add_20_regular", "prevSize": 32, "code": 59885, - "tempChar": "" + "tempChar": "" }, { "order": 677, @@ -3360,7 +3415,7 @@ "name": "ic_fluent_text_font_size_20_regular", "prevSize": 32, "code": 59883, - "tempChar": "" + "tempChar": "" }, { "order": 676, @@ -3368,7 +3423,7 @@ "name": "ic_fluent_shapes_20_regular", "prevSize": 32, "code": 59880, - "tempChar": "" + "tempChar": "" }, { "order": 675, @@ -3376,7 +3431,7 @@ "name": "ic_fluent_weather_moon_20_regular", "prevSize": 32, "code": 59882, - "tempChar": "" + "tempChar": "" }, { "order": 674, @@ -3384,7 +3439,7 @@ "name": "ic_fluent_wallpaper_20_regular", "prevSize": 32, "code": 59879, - "tempChar": "" + "tempChar": "" }, { "order": 673, @@ -3392,7 +3447,7 @@ "name": "ic_fluent_arrow_sort_20_regular", "prevSize": 32, "code": 59878, - "tempChar": "" + "tempChar": "" }, { "order": 0, @@ -3416,7 +3471,7 @@ "name": "ic_fluent_chat_20_regular", "prevSize": 32, "code": 59839, - "tempChar": "" + "tempChar": "" }, { "order": 624, @@ -3424,7 +3479,7 @@ "name": "ic_fluent_person_20_filled", "prevSize": 32, "code": 59832, - "tempChar": "" + "tempChar": "" }, { "order": 625, @@ -3432,7 +3487,7 @@ "name": "ic_fluent_people_20_filled", "prevSize": 32, "code": 59833, - "tempChar": "" + "tempChar": "" }, { "order": 0, @@ -3448,7 +3503,7 @@ "name": "ic_fluent_pin_20_filled", "prevSize": 32, "code": 59835, - "tempChar": "" + "tempChar": "" }, { "order": 628, @@ -3456,7 +3511,7 @@ "name": "ic_fluent_link_20_filled", "prevSize": 32, "code": 59836, - "tempChar": "" + "tempChar": "" }, { "order": 629, @@ -3464,7 +3519,7 @@ "name": "ic_fluent_folder_20_filled", "prevSize": 32, "code": 59837, - "tempChar": "" + "tempChar": "" }, { "order": 609, @@ -3472,7 +3527,7 @@ "name": "ic_fluent_person_available_20_regular", "prevSize": 32, "code": 59816, - "tempChar": "" + "tempChar": "" }, { "order": 608, @@ -3480,7 +3535,7 @@ "name": "ic_fluent_person_circle_20_regular", "prevSize": 32, "code": 59817, - "tempChar": "" + "tempChar": "" }, { "order": 607, @@ -3488,7 +3543,7 @@ "name": "ic_fluent_eye_off_20_regular", "prevSize": 32, "code": 59814, - "tempChar": "" + "tempChar": "" }, { "order": 606, @@ -3496,7 +3551,7 @@ "name": "ic_fluent_eye_20_regular", "prevSize": 32, "code": 59815, - "tempChar": "" + "tempChar": "" }, { "order": 605, @@ -3504,7 +3559,7 @@ "name": "ic_fluent_speaker_off_20_regular1", "prevSize": 32, "code": 59813, - "tempChar": "" + "tempChar": "" }, { "order": 604, @@ -3512,7 +3567,7 @@ "name": "ic_fluent_alert_on_20_regular", "prevSize": 32, "code": 59812, - "tempChar": "" + "tempChar": "" }, { "order": 598, @@ -3520,7 +3575,7 @@ "name": "ic_fluent_alert_snooze_20_regular", "prevSize": 32, "code": 59804, - "tempChar": "" + "tempChar": "" }, { "order": 597, @@ -3528,7 +3583,7 @@ "name": "ic_fluent_music_note_2_20_regular", "prevSize": 32, "code": 59805, - "tempChar": "" + "tempChar": "" }, { "order": 596, @@ -3536,7 +3591,7 @@ "name": "ic_fluent_music_note_off_2_20_regular", "prevSize": 32, "code": 59806, - "tempChar": "" + "tempChar": "" }, { "order": 594, @@ -3544,7 +3599,7 @@ "name": "ic_fluent_arrow_exit_20_regular", "prevSize": 32, "code": 59801, - "tempChar": "" + "tempChar": "" }, { "order": 595, @@ -3552,7 +3607,7 @@ "name": "ic_fluent_arrow_enter_20_regular", "prevSize": 32, "code": 59803, - "tempChar": "" + "tempChar": "" }, { "order": 593, @@ -3560,7 +3615,7 @@ "name": "ic_fluent_heart_12_filled", "prevSize": 32, "code": 59800, - "tempChar": "" + "tempChar": "" }, { "order": 588, @@ -3568,7 +3623,7 @@ "name": "ic_fluent_compose_20_regular", "prevSize": 32, "code": 59796, - "tempChar": "" + "tempChar": "" }, { "order": 587, @@ -3576,7 +3631,7 @@ "name": "ic_fluent_chat_multiple_20_regular", "prevSize": 32, "code": 59794, - "tempChar": "" + "tempChar": "" }, { "order": 534, @@ -3584,7 +3639,7 @@ "name": "ic_fluent_live_20_regular", "prevSize": 32, "code": 59789, - "tempChar": "" + "tempChar": "" }, { "order": 533, @@ -3592,7 +3647,7 @@ "name": "ic_fluent_dialpad_20_regular", "prevSize": 32, "code": 59788, - "tempChar": "" + "tempChar": "" }, { "order": 532, @@ -3600,7 +3655,7 @@ "name": "ic_fluent_hand_right_20_regular", "prevSize": 32, "code": 59787, - "tempChar": "" + "tempChar": "" }, { "order": 531, @@ -3608,7 +3663,7 @@ "name": "ic_fluent_folder_add_20_regular", "prevSize": 32, "code": 59786, - "tempChar": "" + "tempChar": "" }, { "order": 530, @@ -3616,7 +3671,7 @@ "name": "ic_fluent_arrow_trending_20_regular", "prevSize": 32, "code": 59785, - "tempChar": "" + "tempChar": "" }, { "order": 529, @@ -3624,7 +3679,7 @@ "name": "ic_fluent_subtract_circle_20_filled", "prevSize": 32, "code": 59784, - "tempChar": "" + "tempChar": "" }, { "order": 528, @@ -3632,7 +3687,7 @@ "name": "ic_fluent_heart_20_regular1", "prevSize": 32, "code": 59783, - "tempChar": "" + "tempChar": "" }, { "order": 526, @@ -3640,7 +3695,7 @@ "name": "ic_fluent_heart_16_filled", "prevSize": 32, "code": 59781, - "tempChar": "" + "tempChar": "" }, { "order": 527, @@ -3648,7 +3703,7 @@ "name": "ic_fluent_mention_16_regular", "prevSize": 32, "code": 59782, - "tempChar": "" + "tempChar": "" }, { "order": 520, @@ -3656,7 +3711,7 @@ "name": "ic_fluent_window_new_20_regular", "prevSize": 32, "code": 59775, - "tempChar": "" + "tempChar": "" }, { "order": 519, @@ -3664,7 +3719,7 @@ "name": "ic_fluent_checkmark_circle_20_regular", "prevSize": 32, "code": 59774, - "tempChar": "" + "tempChar": "" }, { "order": 0, @@ -3680,7 +3735,7 @@ "name": "ic_fluent_translate_20_regular", "prevSize": 32, "code": 59773, - "tempChar": "" + "tempChar": "" }, { "order": 516, @@ -3688,7 +3743,7 @@ "name": "Pin", "prevSize": 32, "code": 59771, - "tempChar": "" + "tempChar": "" }, { "order": 515, @@ -3696,7 +3751,7 @@ "name": "Verified", "prevSize": 32, "code": 59770, - "tempChar": "" + "tempChar": "" }, { "order": 514, @@ -3704,7 +3759,7 @@ "name": "Speaker-Mute", "prevSize": 32, "code": 59769, - "tempChar": "" + "tempChar": "" }, { "order": 0, @@ -3720,7 +3775,7 @@ "name": "ic_fluent_paint_brush_20_regular", "prevSize": 32, "code": 59662, - "tempChar": "" + "tempChar": "" }, { "order": 495, @@ -3728,7 +3783,7 @@ "name": "ic_fluent_top_speed_20_regular", "prevSize": 32, "code": 59661, - "tempChar": "" + "tempChar": "" }, { "order": 490, @@ -3736,7 +3791,7 @@ "name": "ic_fluent_checkmark_20_regular", "prevSize": 32, "code": 59643, - "tempChar": "" + "tempChar": "" }, { "order": 488, @@ -3744,7 +3799,7 @@ "name": "ic_fluent_zoom_out_20_regular", "prevSize": 32, "code": 59754, - "tempChar": "" + "tempChar": "" }, { "order": 487, @@ -3752,7 +3807,7 @@ "name": "ic_fluent_zoom_in_20_regular", "prevSize": 32, "code": 59753, - "tempChar": "" + "tempChar": "" }, { "order": 486, @@ -3760,7 +3815,7 @@ "name": "ic_fluent_stack_20_regular", "prevSize": 32, "code": 59660, - "tempChar": "" + "tempChar": "" }, { "order": 482, @@ -3768,7 +3823,7 @@ "name": "ic_fluent_dock_panel_right_20_filled", "prevSize": 32, "code": 59751, - "tempChar": "" + "tempChar": "" }, { "order": 483, @@ -3776,7 +3831,7 @@ "name": "ic_fluent_dock_panel_right_20_regular", "prevSize": 32, "code": 59752, - "tempChar": "" + "tempChar": "" }, { "order": 481, @@ -3784,7 +3839,7 @@ "name": "ic_fluent_arrow_clockwise_20_regular", "prevSize": 32, "code": 59180, - "tempChar": "" + "tempChar": "" }, { "order": 920, @@ -3792,7 +3847,7 @@ "name": "ic_fluent_arrow_minimize_20_regular", "prevSize": 32, "code": 59749, - "tempChar": "" + "tempChar": "" }, { "order": 921, @@ -3800,7 +3855,7 @@ "name": "ic_fluent_arrow_maximize_20_regular", "prevSize": 32, "code": 59750, - "tempChar": "" + "tempChar": "" }, { "order": 476, @@ -3808,7 +3863,7 @@ "name": "ic_fluent_video_off_20_filled", "prevSize": 32, "code": 59747, - "tempChar": "" + "tempChar": "" }, { "order": 475, @@ -3816,7 +3871,7 @@ "name": "ic_fluent_video_20_filled", "prevSize": 32, "code": 59748, - "tempChar": "" + "tempChar": "" }, { "order": 473, @@ -3824,7 +3879,7 @@ "name": "ic_fluent_share_screen_stop_20_filled", "prevSize": 32, "code": 59733, - "tempChar": "" + "tempChar": "" }, { "order": 474, @@ -3832,7 +3887,7 @@ "name": "ic_fluent_share_screen_start_20_filled", "prevSize": 32, "code": 59734, - "tempChar": "" + "tempChar": "" }, { "order": 417, @@ -3840,7 +3895,7 @@ "name": "uniE930", "prevSize": 32, "code": 59696, - "tempChar": "" + "tempChar": "" }, { "order": 416, @@ -3848,7 +3903,7 @@ "name": "uniE92E", "prevSize": 32, "code": 59694, - "tempChar": "" + "tempChar": "" }, { "order": 415, @@ -3856,7 +3911,7 @@ "name": "uniE92F", "prevSize": 32, "code": 59695, - "tempChar": "" + "tempChar": "" }, { "order": 412, @@ -3864,7 +3919,7 @@ "name": "uniE910", "prevSize": 32, "code": 59664, - "tempChar": "" + "tempChar": "" }, { "order": 411, @@ -3872,7 +3927,7 @@ "name": "uniE915", "prevSize": 32, "code": 59669, - "tempChar": "" + "tempChar": "" }, { "order": 408, @@ -3880,7 +3935,7 @@ "name": "uniE90F", "prevSize": 32, "code": 59663, - "tempChar": "" + "tempChar": "" }, { "order": 407, @@ -3888,7 +3943,7 @@ "name": "uniE905", "prevSize": 32, "code": 59653, - "tempChar": "" + "tempChar": "" }, { "order": 406, @@ -3896,7 +3951,7 @@ "name": "uniE904", "prevSize": 32, "code": 59652, - "tempChar": "" + "tempChar": "" }, { "order": 405, @@ -3904,7 +3959,7 @@ "name": "uniE903", "prevSize": 32, "code": 59651, - "tempChar": "" + "tempChar": "" }, { "order": 404, @@ -3912,7 +3967,7 @@ "name": "uniE901", "prevSize": 32, "code": 59649, - "tempChar": "" + "tempChar": "" }, { "order": 400, @@ -3920,7 +3975,7 @@ "name": "uniE99A", "prevSize": 32, "code": 59802, - "tempChar": "" + "tempChar": "" }, { "order": 401, @@ -3928,7 +3983,7 @@ "name": "uniE783", "prevSize": 32, "code": 59267, - "tempChar": "" + "tempChar": "" }, { "order": 399, @@ -3936,7 +3991,7 @@ "name": "uniE919", "prevSize": 32, "code": 59673, - "tempChar": "" + "tempChar": "" }, { "order": 398, @@ -3944,7 +3999,7 @@ "name": "uniF12E", "prevSize": 32, "code": 61742, - "tempChar": "" + "tempChar": "" }, { "order": 390, @@ -3952,7 +4007,7 @@ "name": "uniF122", "prevSize": 32, "code": 61730, - "tempChar": "" + "tempChar": "" }, { "order": 386, @@ -3960,7 +4015,7 @@ "name": "ic_fluent_speaker_1_20_regular", "prevSize": 32, "code": 59795, - "tempChar": "" + "tempChar": "" }, { "order": 387, @@ -3968,7 +4023,7 @@ "name": "ic_fluent_speaker_2_20_regular", "prevSize": 32, "code": 59797, - "tempChar": "" + "tempChar": "" }, { "order": 382, @@ -3976,7 +4031,7 @@ "name": "uniEC42", "prevSize": 32, "code": 60482, - "tempChar": "" + "tempChar": "" }, { "order": 383, @@ -3984,7 +4039,7 @@ "name": "ic_fluent_add_circle_20_regular", "prevSize": 32, "code": 61796, - "tempChar": "" + "tempChar": "" }, { "order": 384, @@ -3992,7 +4047,7 @@ "name": "ic_fluent_subtract_circle_20_regular", "prevSize": 32, "code": 61798, - "tempChar": "" + "tempChar": "" }, { "order": 378, @@ -4000,7 +4055,7 @@ "name": "uniE72A", "prevSize": 32, "code": 59178, - "tempChar": "" + "tempChar": "" }, { "order": 377, @@ -4008,7 +4063,7 @@ "name": "uniE897", "prevSize": 32, "code": 59543, - "tempChar": "" + "tempChar": "" }, { "order": 372, @@ -4016,7 +4071,7 @@ "name": "uniE7AC", "prevSize": 32, "code": 59308, - "tempChar": "" + "tempChar": "" }, { "order": 373, @@ -4024,7 +4079,7 @@ "name": "uniE91D", "prevSize": 32, "code": 59677, - "tempChar": "" + "tempChar": "" }, { "order": 374, @@ -4032,7 +4087,7 @@ "name": "uniE93D", "prevSize": 32, "code": 59709, - "tempChar": "" + "tempChar": "" }, { "order": 371, @@ -4040,7 +4095,7 @@ "name": "uniEA1A", "prevSize": 32, "code": 59930, - "tempChar": "" + "tempChar": "" }, { "order": 264, @@ -4048,7 +4103,7 @@ "name": "uniE0E2", "prevSize": 32, "code": 57570, - "tempChar": "" + "tempChar": "" }, { "order": 265, @@ -4056,7 +4111,7 @@ "name": "uniE0E3", "prevSize": 32, "code": 57571, - "tempChar": "" + "tempChar": "" }, { "order": 266, @@ -4064,7 +4119,7 @@ "name": "uniE0E4", "prevSize": 32, "code": 57572, - "tempChar": "" + "tempChar": "" }, { "order": 267, @@ -4072,7 +4127,7 @@ "name": "uniE0E5", "prevSize": 32, "code": 57573, - "tempChar": "" + "tempChar": "" }, { "order": 268, @@ -4080,7 +4135,7 @@ "name": "uniE001", "prevSize": 32, "code": 57345, - "tempChar": "" + "tempChar": "" }, { "order": 269, @@ -4088,7 +4143,7 @@ "name": "uniE1C4", "prevSize": 32, "code": 57796, - "tempChar": "" + "tempChar": "" }, { "order": 270, @@ -4096,7 +4151,7 @@ "name": "uniE2B1", "prevSize": 32, "code": 58033, - "tempChar": "" + "tempChar": "" }, { "order": 271, @@ -4104,7 +4159,7 @@ "name": "uniE7A6", "prevSize": 32, "code": 59302, - "tempChar": "" + "tempChar": "" }, { "order": 272, @@ -4112,7 +4167,7 @@ "name": "uniE7A7", "prevSize": 32, "code": 59303, - "tempChar": "" + "tempChar": "" }, { "order": 273, @@ -4120,7 +4175,7 @@ "name": "uniE7A8", "prevSize": 32, "code": 59304, - "tempChar": "" + "tempChar": "" }, { "order": 274, @@ -4128,7 +4183,7 @@ "name": "ic_fluent_archive_20_regular", "prevSize": 32, "code": 59320, - "tempChar": "" + "tempChar": "" }, { "order": 275, @@ -4136,7 +4191,7 @@ "name": "uniE7C3", "prevSize": 32, "code": 59331, - "tempChar": "" + "tempChar": "" }, { "order": 276, @@ -4144,7 +4199,7 @@ "name": "uniE7ED", "prevSize": 32, "code": 59373, - "tempChar": "" + "tempChar": "" }, { "order": 277, @@ -4152,7 +4207,7 @@ "name": "ic_fluent_chat_empty_20_regular", "prevSize": 32, "code": 59581, - "tempChar": "" + "tempChar": "" }, { "order": 278, @@ -4160,7 +4215,7 @@ "name": "uniE8C6", "prevSize": 32, "code": 59590, - "tempChar": "" + "tempChar": "" }, { "order": 279, @@ -4168,7 +4223,7 @@ "name": "uniE8C8", "prevSize": 32, "code": 59592, - "tempChar": "" + "tempChar": "" }, { "order": 280, @@ -4176,7 +4231,7 @@ "name": "uniE8CB", "prevSize": 32, "code": 59595, - "tempChar": "" + "tempChar": "" }, { "order": 281, @@ -4184,7 +4239,7 @@ "name": "uniE8D2", "prevSize": 32, "code": 59602, - "tempChar": "" + "tempChar": "" }, { "order": 282, @@ -4192,7 +4247,7 @@ "name": "uniE8D6", "prevSize": 32, "code": 59606, - "tempChar": "" + "tempChar": "" }, { "order": 283, @@ -4200,7 +4255,7 @@ "name": "uniE8D9", "prevSize": 32, "code": 59609, - "tempChar": "" + "tempChar": "" }, { "order": 284, @@ -4208,7 +4263,7 @@ "name": "uniE8DB", "prevSize": 32, "code": 59611, - "tempChar": "" + "tempChar": "" }, { "order": 285, @@ -4216,7 +4271,7 @@ "name": "uniE8DC", "prevSize": 32, "code": 59612, - "tempChar": "" + "tempChar": "" }, { "order": 286, @@ -4224,7 +4279,7 @@ "name": "uniE8DD", "prevSize": 32, "code": 59613, - "tempChar": "" + "tempChar": "" }, { "order": 287, @@ -4232,7 +4287,7 @@ "name": "uniE8DE", "prevSize": 32, "code": 59614, - "tempChar": "" + "tempChar": "" }, { "order": 288, @@ -4240,7 +4295,7 @@ "name": "uniE8FA", "prevSize": 32, "code": 59642, - "tempChar": "" + "tempChar": "" }, { "order": 289, @@ -4248,7 +4303,7 @@ "name": "uniE9CE", "prevSize": 32, "code": 59854, - "tempChar": "" + "tempChar": "" }, { "order": 290, @@ -4256,7 +4311,7 @@ "name": "uniE9D9", "prevSize": 32, "code": 59865, - "tempChar": "" + "tempChar": "" }, { "order": 291, @@ -4264,7 +4319,7 @@ "name": "uniE9E9", "prevSize": 32, "code": 59881, - "tempChar": "" + "tempChar": "" }, { "order": 292, @@ -4272,7 +4327,7 @@ "name": "uniE10B", "prevSize": 32, "code": 57611, - "tempChar": "" + "tempChar": "" }, { "order": 511, @@ -4280,7 +4335,7 @@ "name": "uniE10C", "prevSize": 32, "code": 57612, - "tempChar": "" + "tempChar": "" }, { "order": 294, @@ -4288,7 +4343,7 @@ "name": "uniE71B", "prevSize": 32, "code": 59163, - "tempChar": "" + "tempChar": "" }, { "order": 295, @@ -4296,7 +4351,7 @@ "name": "uniE71F", "prevSize": 32, "code": 59167, - "tempChar": "" + "tempChar": "" }, { "order": 296, @@ -4304,7 +4359,7 @@ "name": "uniE72B", "prevSize": 32, "code": 59179, - "tempChar": "" + "tempChar": "" }, { "order": 298, @@ -4312,7 +4367,7 @@ "name": "uniE72E", "prevSize": 32, "code": 59182, - "tempChar": "" + "tempChar": "" }, { "order": 299, @@ -4320,7 +4375,7 @@ "name": "uniE73E", "prevSize": 32, "code": 59198, - "tempChar": "" + "tempChar": "" }, { "order": 300, @@ -4328,7 +4383,7 @@ "name": "uniE74B", "prevSize": 32, "code": 59211, - "tempChar": "" + "tempChar": "" }, { "order": 301, @@ -4336,7 +4391,7 @@ "name": "uniE74D", "prevSize": 32, "code": 59213, - "tempChar": "" + "tempChar": "" }, { "order": 302, @@ -4344,7 +4399,7 @@ "name": "uniE75C", "prevSize": 32, "code": 59228, - "tempChar": "" + "tempChar": "" }, { "order": 303, @@ -4352,7 +4407,7 @@ "name": "uniE76E", "prevSize": 32, "code": 59246, - "tempChar": "" + "tempChar": "" }, { "order": 304, @@ -4360,7 +4415,7 @@ "name": "uniE77A", "prevSize": 32, "code": 59258, - "tempChar": "" + "tempChar": "" }, { "order": 305, @@ -4368,7 +4423,7 @@ "name": "uniE77B", "prevSize": 32, "code": 59259, - "tempChar": "" + "tempChar": "" }, { "order": 306, @@ -4376,7 +4431,7 @@ "name": "uniE77F", "prevSize": 32, "code": 59263, - "tempChar": "" + "tempChar": "" }, { "order": 307, @@ -4384,7 +4439,7 @@ "name": "uniE81C", "prevSize": 32, "code": 59420, - "tempChar": "" + "tempChar": "" }, { "order": 308, @@ -4392,7 +4447,7 @@ "name": "tl_fluent_chat_unread_20_regular", "prevSize": 32, "code": 59676, - "tempChar": "", + "tempChar": "", "codes": [ 59676 ] @@ -4403,7 +4458,7 @@ "name": "uniE91F", "prevSize": 32, "code": 59679, - "tempChar": "" + "tempChar": "" }, { "order": 310, @@ -4411,7 +4466,7 @@ "name": "uniE92B", "prevSize": 32, "code": 59691, - "tempChar": "" + "tempChar": "" }, { "order": 311, @@ -4419,7 +4474,7 @@ "name": "uniE104", "prevSize": 32, "code": 57604, - "tempChar": "" + "tempChar": "" }, { "order": 312, @@ -4427,7 +4482,7 @@ "name": "uniE118", "prevSize": 32, "code": 57624, - "tempChar": "" + "tempChar": "" }, { "order": 313, @@ -4435,7 +4490,7 @@ "name": "uniE168", "prevSize": 32, "code": 57704, - "tempChar": "" + "tempChar": "" }, { "order": 314, @@ -4443,7 +4498,7 @@ "name": "uniE192", "prevSize": 32, "code": 57746, - "tempChar": "" + "tempChar": "" }, { "order": 316, @@ -4451,7 +4506,7 @@ "name": "uniE610", "prevSize": 32, "code": 58896, - "tempChar": "" + "tempChar": "" }, { "order": 317, @@ -4459,7 +4514,7 @@ "name": "ic_fluent_navigation_20_regular", "prevSize": 32, "code": 59136, - "tempChar": "" + "tempChar": "" }, { "order": 318, @@ -4467,7 +4522,7 @@ "name": "uniE710", "prevSize": 32, "code": 59152, - "tempChar": "" + "tempChar": "" }, { "order": 319, @@ -4475,7 +4530,7 @@ "name": "uniE711", "prevSize": 32, "code": 59153, - "tempChar": "" + "tempChar": "" }, { "order": 320, @@ -4483,7 +4538,7 @@ "name": "uniE713", "prevSize": 32, "code": 59155, - "tempChar": "" + "tempChar": "" }, { "order": 321, @@ -4491,7 +4546,7 @@ "name": "uniE714", "prevSize": 32, "code": 59156, - "tempChar": "" + "tempChar": "" }, { "order": 322, @@ -4499,7 +4554,7 @@ "name": "uniE716", "prevSize": 32, "code": 59158, - "tempChar": "" + "tempChar": "" }, { "order": 323, @@ -4507,7 +4562,7 @@ "name": "uniE717", "prevSize": 32, "code": 59159, - "tempChar": "" + "tempChar": "" }, { "order": 324, @@ -4515,7 +4570,7 @@ "name": "uniE720", "prevSize": 32, "code": 59168, - "tempChar": "" + "tempChar": "" }, { "order": 325, @@ -4523,7 +4578,7 @@ "name": "uniE721", "prevSize": 32, "code": 59169, - "tempChar": "" + "tempChar": "" }, { "order": 326, @@ -4531,7 +4586,7 @@ "name": "uniE722", "prevSize": 32, "code": 59170, - "tempChar": "" + "tempChar": "" }, { "order": 327, @@ -4539,7 +4594,7 @@ "name": "uniE730", "prevSize": 32, "code": 59184, - "tempChar": "" + "tempChar": "" }, { "order": 328, @@ -4547,7 +4602,7 @@ "name": "uniE734", "prevSize": 32, "code": 59188, - "tempChar": "" + "tempChar": "" }, { "order": 329, @@ -4555,7 +4610,7 @@ "name": "uniE735", "prevSize": 32, "code": 59189, - "tempChar": "" + "tempChar": "" }, { "order": 330, @@ -4563,7 +4618,7 @@ "name": "uniE762", "prevSize": 32, "code": 59234, - "tempChar": "" + "tempChar": "" }, { "order": 0, @@ -4587,7 +4642,7 @@ "name": "uniE774", "prevSize": 32, "code": 59252, - "tempChar": "" + "tempChar": "" }, { "order": 334, @@ -4595,7 +4650,7 @@ "name": "uniE785", "prevSize": 32, "code": 59269, - "tempChar": "" + "tempChar": "" }, { "order": 335, @@ -4603,7 +4658,7 @@ "name": "uniE787", "prevSize": 32, "code": 59271, - "tempChar": "" + "tempChar": "" }, { "order": 336, @@ -4611,7 +4666,7 @@ "name": "uniE789", "prevSize": 32, "code": 59273, - "tempChar": "" + "tempChar": "" }, { "order": 337, @@ -4619,7 +4674,7 @@ "name": "uniE792", "prevSize": 32, "code": 59282, - "tempChar": "" + "tempChar": "" }, { "order": 338, @@ -4627,7 +4682,7 @@ "name": "uniE825", "prevSize": 32, "code": 59429, - "tempChar": "" + "tempChar": "" }, { "order": 339, @@ -4635,7 +4690,7 @@ "name": "uniE838", "prevSize": 32, "code": 59448, - "tempChar": "" + "tempChar": "" }, { "order": 340, @@ -4643,7 +4698,7 @@ "name": "uniE840", "prevSize": 32, "code": 59456, - "tempChar": "" + "tempChar": "" }, { "order": 341, @@ -4651,7 +4706,7 @@ "name": "uniE892", "prevSize": 32, "code": 59538, - "tempChar": "" + "tempChar": "" }, { "order": 342, @@ -4659,7 +4714,7 @@ "name": "uniE893", "prevSize": 32, "code": 59539, - "tempChar": "" + "tempChar": "" }, { "order": 343, @@ -4667,7 +4722,7 @@ "name": "uniE902", "prevSize": 32, "code": 59650, - "tempChar": "" + "tempChar": "" }, { "order": 344, @@ -4675,7 +4730,7 @@ "name": "uniE907", "prevSize": 32, "code": 59655, - "tempChar": "" + "tempChar": "" }, { "order": 345, @@ -4683,7 +4738,7 @@ "name": "uniE909", "prevSize": 32, "code": 59657, - "tempChar": "" + "tempChar": "" }, { "order": 346, @@ -4691,7 +4746,7 @@ "name": "uniEA19", "prevSize": 32, "code": 59929, - "tempChar": "" + "tempChar": "" }, { "order": 347, @@ -4699,7 +4754,7 @@ "name": "uniE916", "prevSize": 32, "code": 59670, - "tempChar": "" + "tempChar": "" }, { "order": 348, @@ -4707,7 +4762,7 @@ "name": "uniE917", "prevSize": 32, "code": 59671, - "tempChar": "" + "tempChar": "" }, { "order": 0, @@ -4723,7 +4778,7 @@ "name": "uniE929", "prevSize": 32, "code": 59689, - "tempChar": "" + "tempChar": "" }, { "order": 351, @@ -4731,7 +4786,7 @@ "name": "uniE932", "prevSize": 32, "code": 59698, - "tempChar": "" + "tempChar": "" }, { "order": 352, @@ -4739,7 +4794,7 @@ "name": "uniE943", "prevSize": 32, "code": 59715, - "tempChar": "" + "tempChar": "" }, { "order": 385, @@ -4747,7 +4802,7 @@ "name": "ic_fluent_speaker_off_20_regular", "prevSize": 32, "code": 59215, - "tempChar": "" + "tempChar": "" }, { "order": 353, @@ -4755,7 +4810,7 @@ "name": "uniE946", "prevSize": 32, "code": 59718, - "tempChar": "" + "tempChar": "" }, { "order": 354, @@ -4763,7 +4818,7 @@ "name": "uniE975", "prevSize": 32, "code": 59765, - "tempChar": "" + "tempChar": "" }, { "order": 355, @@ -4771,7 +4826,7 @@ "name": "uniEA8F", "prevSize": 32, "code": 60047, - "tempChar": "" + "tempChar": "" }, { "order": 356, @@ -4779,7 +4834,7 @@ "name": "uniEA37", "prevSize": 32, "code": 59959, - "tempChar": "" + "tempChar": "" }, { "order": 357, @@ -4787,7 +4842,7 @@ "name": "uniEA80", "prevSize": 32, "code": 60032, - "tempChar": "" + "tempChar": "" }, { "order": 358, @@ -4795,7 +4850,7 @@ "name": "uniEA99", "prevSize": 32, "code": 60057, - "tempChar": "" + "tempChar": "" }, { "order": 359, @@ -4803,7 +4858,7 @@ "name": "uniEB05", "prevSize": 32, "code": 60165, - "tempChar": "" + "tempChar": "" }, { "order": 360, @@ -4811,7 +4866,7 @@ "name": "uniEB9F", "prevSize": 32, "code": 60319, - "tempChar": "" + "tempChar": "" }, { "order": 361, @@ -4819,7 +4874,7 @@ "name": "uniEC61", "prevSize": 32, "code": 60513, - "tempChar": "" + "tempChar": "" }, { "order": 362, @@ -4827,7 +4882,7 @@ "name": "uniED15", "prevSize": 32, "code": 60693, - "tempChar": "" + "tempChar": "" }, { "order": 363, @@ -4835,7 +4890,7 @@ "name": "uniEE56", "prevSize": 32, "code": 61014, - "tempChar": "" + "tempChar": "" }, { "order": 364, @@ -4843,7 +4898,7 @@ "name": "uniF0E3", "prevSize": 32, "code": 61667, - "tempChar": "" + "tempChar": "" }, { "order": 365, @@ -4851,7 +4906,7 @@ "name": "uniF3B1", "prevSize": 32, "code": 62385, - "tempChar": "" + "tempChar": "" }, { "order": 366, @@ -4859,7 +4914,7 @@ "name": "uniF4A9", "prevSize": 32, "code": 62633, - "tempChar": "" + "tempChar": "" }, { "order": 367, @@ -4867,7 +4922,7 @@ "name": "uniF4AA", "prevSize": 32, "code": 62634, - "tempChar": "" + "tempChar": "" }, { "order": 368, @@ -4875,7 +4930,7 @@ "name": "uniF12B", "prevSize": 32, "code": 61739, - "tempChar": "" + "tempChar": "" }, { "order": 369, @@ -4883,7 +4938,7 @@ "name": "uniF61B", "prevSize": 32, "code": 63003, - "tempChar": "" + "tempChar": "" }, { "order": 0, @@ -4905,6 +4960,72 @@ "height": 1024, "prevSize": 32, "icons": [ + { + "id": 321, + "paths": [ + "M153.6 512c0-197.939 160.461-358.4 358.4-358.4s358.4 160.461 358.4 358.4c0 197.939-160.461 358.4-358.4 358.4s-358.4-160.461-358.4-358.4zM512 102.4c-226.216 0-409.6 183.384-409.6 409.6 0 226.217 183.384 409.6 409.6 409.6 226.217 0 409.6-183.383 409.6-409.6 0-226.216-183.383-409.6-409.6-409.6zM350.902 417.098c-9.997-9.998-26.206-9.998-36.204 0s-9.998 26.206 0 36.204l179.2 179.203c4.801 4.797 11.312 7.496 18.102 7.496s13.302-2.698 18.104-7.496l179.2-179.203c9.994-9.997 9.994-26.206 0-36.204-9.999-9.998-26.209-9.998-36.209 0l-161.096 161.098-161.098-161.098z" + ], + "attrs": [ + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": [ + "ic_fluent_chevron_circle_down_20_regular" + ] + }, + { + "id": 320, + "paths": [ + "M563.2 768h102.4v102.4h-102.4v-102.4zM768 768h102.4v102.4h-102.4v-102.4zM563.2 563.2h102.4v102.4h-102.4v-102.4zM665.6 665.6h102.4v102.4h-102.4v-102.4zM768 563.2h102.4v102.4h-102.4v-102.4zM153.6 256c0-56.554 45.846-102.4 102.4-102.4h102.4c56.554 0 102.4 45.846 102.4 102.4v102.4c0 56.554-45.846 102.4-102.4 102.4h-102.4c-56.554 0-102.4-45.846-102.4-102.4v-102.4zM256 204.8c-28.277 0-51.2 22.923-51.2 51.2v102.4c0 28.277 22.923 51.2 51.2 51.2h102.4c28.277 0 51.2-22.923 51.2-51.2v-102.4c0-28.277-22.923-51.2-51.2-51.2h-102.4zM256 256h102.4v102.4h-102.4v-102.4zM153.6 665.6c0-56.556 45.846-102.4 102.4-102.4h102.4c56.554 0 102.4 45.844 102.4 102.4v102.4c0 56.556-45.846 102.4-102.4 102.4h-102.4c-56.554 0-102.4-45.844-102.4-102.4v-102.4zM256 614.4c-28.277 0-51.2 22.922-51.2 51.2v102.4c0 28.278 22.923 51.2 51.2 51.2h102.4c28.277 0 51.2-22.922 51.2-51.2v-102.4c0-28.278-22.923-51.2-51.2-51.2h-102.4zM256 665.6h102.4v102.4h-102.4v-102.4zM563.2 256c0-56.554 45.844-102.4 102.4-102.4h102.4c56.556 0 102.4 45.846 102.4 102.4v102.4c0 56.554-45.844 102.4-102.4 102.4h-102.4c-56.556 0-102.4-45.846-102.4-102.4v-102.4zM665.6 204.8c-28.278 0-51.2 22.923-51.2 51.2v102.4c0 28.277 22.922 51.2 51.2 51.2h102.4c28.278 0 51.2-22.923 51.2-51.2v-102.4c0-28.277-22.922-51.2-51.2-51.2h-102.4zM665.6 256h102.4v102.4h-102.4v-102.4z" + ], + "attrs": [ + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": [ + "ic_fluent_qr_code_20_regular" + ] + }, + { + "id": 319, + "paths": [ + "M400.714 441.317c-10.011 57.193-27.667 103.22-48.745 141.543-33.246 60.452-75.595 102.963-114.071 141.435-9.998 9.999-9.998 26.209 0 36.209 9.997 9.994 26.206 9.994 36.204 0l0.609-0.609c38.215-38.216 85.163-85.161 122.12-152.356 37.387-67.978 63.969-155.455 63.969-274.739 0-70.692-57.308-128-128-128s-128 57.308-128 128c0 70.692 57.308 128 128 128 24.949 0 48.23-7.138 67.914-19.483zM409.6 332.8c0 42.416-34.384 76.8-76.8 76.8s-76.8-34.384-76.8-76.8c0-42.416 34.384-76.8 76.8-76.8s76.8 34.384 76.8 76.8zM759.112 441.317c-10.010 57.193-27.663 103.22-48.742 141.543-33.249 60.452-75.597 102.963-114.074 141.435-9.994 9.999-9.994 26.209 0 36.209 9.999 9.994 26.209 9.994 36.209 0l0.609-0.609c38.211-38.216 85.161-85.161 122.117-152.356 37.386-67.978 63.969-155.455 63.969-274.739 0-70.692-57.308-128-128-128s-128 57.308-128 128c0 70.692 57.308 128 128 128 24.95 0 48.23-7.138 67.912-19.483zM691.2 409.6c-42.414 0-76.8-34.384-76.8-76.8s34.386-76.8 76.8-76.8c42.414 0 76.8 34.384 76.8 76.8s-34.386 76.8-76.8 76.8z" + ], + "attrs": [ + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": [ + "ic_fluent_text_quote_20_regular" + ] + }, + { + "id": 318, + "paths": [ + "M230.4 552.596l120.502 120.504c9.998 9.994 9.998 26.204 0 36.204-9.997 9.994-26.206 9.994-36.204 0l-58.698-58.701v117.396c0 21.652 5.396 33.193 11.702 39.501 6.306 6.303 17.845 11.699 39.498 11.699h127.957l0.043 25.6-0.001 25.6h-127.999c-29.546 0-56.408-7.404-75.702-26.696-19.294-19.297-26.698-46.157-26.698-75.704v-117.396l-58.698 58.701c-9.997 9.994-26.206 9.994-36.204 0-9.998-9.999-9.998-26.209 0-36.204l120.502-120.504zM435.2 844.8l-0.001 25.6c14.139 0 25.601-11.459 25.601-25.6 0-14.136-11.504-25.6-25.643-25.6l0.043 25.6z", + "M793.6 471.403l-120.504-120.502c-9.994-9.997-9.994-26.207 0-36.204 9.999-9.997 26.209-9.997 36.209 0l58.696 58.698v-117.396c0-21.654-5.396-33.192-11.704-39.498-6.303-6.306-17.843-11.702-39.496-11.702h-127.959l-0.041-25.6v-25.6h128c29.548 0 56.407 7.404 75.704 26.698 19.292 19.294 26.696 46.156 26.696 75.702v117.396l58.696-58.698c9.999-9.997 26.209-9.997 36.209 0 9.994 9.997 9.994 26.207 0 36.204l-120.504 120.502zM588.8 179.199v-25.6c-14.136 0-25.6 11.461-25.6 25.6s11.505 25.6 25.641 25.6l-0.041-25.6z", + "M204.8 102.4c-56.554 0-102.4 45.846-102.4 102.4v153.6c0 56.554 45.846 102.4 102.4 102.4h153.6c56.554 0 102.4-45.846 102.4-102.4v-153.6c0-56.554-45.846-102.4-102.4-102.4h-153.6z", + "M665.6 563.2c-56.556 0-102.4 45.844-102.4 102.4v153.6c0 56.556 45.844 102.4 102.4 102.4h153.6c56.556 0 102.4-45.844 102.4-102.4v-153.6c0-56.556-45.844-102.4-102.4-102.4h-153.6z" + ], + "attrs": [ + {}, + {}, + {}, + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": [ + "tl_fluent_reply_another_chat_20_filled" + ] + }, { "id": 317, "paths": [ @@ -9346,7 +9467,7 @@ "name": "AirplaneFilled24", "prevSize": 32, "code": 59857, - "tempChar": "" + "tempChar": "" }, { "order": 647, @@ -9354,7 +9475,7 @@ "name": "ic_fluent_clipboard_text_ltr_24_filled", "prevSize": 32, "code": 59855, - "tempChar": "" + "tempChar": "" }, { "order": 648, @@ -9362,7 +9483,7 @@ "name": "ic_fluent_clipboard_text_ltr_24_regular", "prevSize": 32, "code": 59856, - "tempChar": "" + "tempChar": "" }, { "order": 644, @@ -9373,7 +9494,7 @@ "codes": [ 59840 ], - "tempChar": "" + "tempChar": "" }, { "order": 645, @@ -9384,7 +9505,7 @@ "codes": [ 59841 ], - "tempChar": "" + "tempChar": "" }, { "order": 642, @@ -9392,7 +9513,7 @@ "name": "ic_fluent_paint_brush_24_filled", "prevSize": 32, "code": 59842, - "tempChar": "" + "tempChar": "" }, { "order": 643, @@ -9400,7 +9521,7 @@ "name": "ic_fluent_paint_brush_24_regular", "prevSize": 32, "code": 59843, - "tempChar": "" + "tempChar": "" }, { "order": 640, @@ -9408,7 +9529,7 @@ "name": "ic_fluent_music_note_1_24_filled", "prevSize": 32, "code": 59844, - "tempChar": "" + "tempChar": "" }, { "order": 641, @@ -9416,7 +9537,7 @@ "name": "ic_fluent_music_note_1_24_regular", "prevSize": 32, "code": 59845, - "tempChar": "" + "tempChar": "" }, { "order": 638, @@ -9424,7 +9545,7 @@ "name": "ic_fluent_thumb_like_24_filled", "prevSize": 32, "code": 59846, - "tempChar": "" + "tempChar": "" }, { "order": 639, @@ -9432,7 +9553,7 @@ "name": "ic_fluent_thumb_like_24_regular", "prevSize": 32, "code": 59847, - "tempChar": "" + "tempChar": "" }, { "order": 636, @@ -9440,7 +9561,7 @@ "name": "ic_fluent_lightbulb_24_filled", "prevSize": 32, "code": 59848, - "tempChar": "" + "tempChar": "" }, { "order": 637, @@ -9448,7 +9569,7 @@ "name": "ic_fluent_lightbulb_24_regular", "prevSize": 32, "code": 59849, - "tempChar": "" + "tempChar": "" }, { "order": 634, @@ -9456,7 +9577,7 @@ "name": "ic_fluent_money_24_filled", "prevSize": 32, "code": 59850, - "tempChar": "" + "tempChar": "" }, { "order": 635, @@ -9464,7 +9585,7 @@ "name": "ic_fluent_money_24_regular", "prevSize": 32, "code": 59851, - "tempChar": "" + "tempChar": "" }, { "order": 632, @@ -9472,7 +9593,7 @@ "name": "ic_fluent_book_open_24_filled", "prevSize": 32, "code": 59852, - "tempChar": "" + "tempChar": "" }, { "order": 633, @@ -9480,7 +9601,7 @@ "name": "ic_fluent_book_open_24_regular", "prevSize": 32, "code": 59853, - "tempChar": "" + "tempChar": "" }, { "order": 589, @@ -9488,7 +9609,7 @@ "name": "mask_filled_1", "prevSize": 32, "code": 59716, - "tempChar": "" + "tempChar": "" }, { "order": 590, @@ -9496,7 +9617,7 @@ "name": "mask_outline_1", "prevSize": 32, "code": 59717, - "tempChar": "" + "tempChar": "" }, { "order": 583, @@ -9504,7 +9625,7 @@ "name": "ic_fluent_folder_24_filled", "prevSize": 32, "code": 59745, - "tempChar": "" + "tempChar": "" }, { "order": 584, @@ -9512,7 +9633,7 @@ "name": "ic_fluent_folder_24_regular", "prevSize": 32, "code": 59746, - "tempChar": "" + "tempChar": "" }, { "order": 581, @@ -9520,7 +9641,7 @@ "name": "ic_fluent_person_24_filled", "prevSize": 32, "code": 59743, - "tempChar": "" + "tempChar": "" }, { "order": 582, @@ -9528,7 +9649,7 @@ "name": "ic_fluent_person_24_regular", "prevSize": 32, "code": 59744, - "tempChar": "" + "tempChar": "" }, { "order": 579, @@ -9536,7 +9657,7 @@ "name": "ic_fluent_people_24_filled", "prevSize": 32, "code": 59741, - "tempChar": "" + "tempChar": "" }, { "order": 580, @@ -9544,7 +9665,7 @@ "name": "ic_fluent_people_24_regular", "prevSize": 32, "code": 59742, - "tempChar": "" + "tempChar": "" }, { "order": 577, @@ -9552,7 +9673,7 @@ "name": "ic_fluent_megaphone_24_filled", "prevSize": 32, "code": 59739, - "tempChar": "" + "tempChar": "" }, { "order": 578, @@ -9560,7 +9681,7 @@ "name": "ic_fluent_megaphone_24_regular", "prevSize": 32, "code": 59740, - "tempChar": "" + "tempChar": "" }, { "order": 575, @@ -9568,7 +9689,7 @@ "name": "ic_fluent_bot_24_filled", "prevSize": 32, "code": 59737, - "tempChar": "" + "tempChar": "" }, { "order": 576, @@ -9576,7 +9697,7 @@ "name": "ic_fluent_bot_24_regular", "prevSize": 32, "code": 59738, - "tempChar": "" + "tempChar": "" }, { "order": 573, @@ -9584,7 +9705,7 @@ "name": "ic_fluent_alert_24_filled", "prevSize": 32, "code": 59735, - "tempChar": "" + "tempChar": "" }, { "order": 574, @@ -9592,7 +9713,7 @@ "name": "ic_fluent_alert_24_regular", "prevSize": 32, "code": 59736, - "tempChar": "" + "tempChar": "" }, { "order": 571, @@ -9600,7 +9721,7 @@ "name": "ic_fluent_chat_multiple_24_filled", "prevSize": 32, "code": 59731, - "tempChar": "" + "tempChar": "" }, { "order": 572, @@ -9608,7 +9729,7 @@ "name": "ic_fluent_chat_multiple_24_regular", "prevSize": 32, "code": 59732, - "tempChar": "" + "tempChar": "" }, { "order": 569, @@ -9616,7 +9737,7 @@ "name": "ic_fluent_briefcase_24_filled", "prevSize": 32, "code": 59729, - "tempChar": "" + "tempChar": "" }, { "order": 570, @@ -9624,7 +9745,7 @@ "name": "ic_fluent_briefcase_24_regular", "prevSize": 32, "code": 59730, - "tempChar": "" + "tempChar": "" }, { "order": 567, @@ -9632,7 +9753,7 @@ "name": "ic_fluent_airplane_24_filled", "prevSize": 32, "code": 59727, - "tempChar": "" + "tempChar": "" }, { "order": 568, @@ -9640,7 +9761,7 @@ "name": "ic_fluent_airplane_24_regular", "prevSize": 32, "code": 59728, - "tempChar": "" + "tempChar": "" }, { "order": 565, @@ -9648,7 +9769,7 @@ "name": "ic_fluent_arrow_trending_lines_24_filled", "prevSize": 32, "code": 59725, - "tempChar": "" + "tempChar": "" }, { "order": 566, @@ -9656,7 +9777,7 @@ "name": "ic_fluent_arrow_trending_lines_24_regular", "prevSize": 32, "code": 59726, - "tempChar": "" + "tempChar": "" }, { "order": 563, @@ -9664,7 +9785,7 @@ "name": "ic_fluent_hat_graduation_24_filled", "prevSize": 32, "code": 59723, - "tempChar": "" + "tempChar": "" }, { "order": 564, @@ -9672,7 +9793,7 @@ "name": "ic_fluent_hat_graduation_24_regular", "prevSize": 32, "code": 59724, - "tempChar": "" + "tempChar": "" }, { "order": 561, @@ -9680,7 +9801,7 @@ "name": "ic_fluent_sport_soccer_24_filled", "prevSize": 32, "code": 59721, - "tempChar": "" + "tempChar": "" }, { "order": 562, @@ -9688,7 +9809,7 @@ "name": "ic_fluent_sport_soccer_24_regular", "prevSize": 32, "code": 59722, - "tempChar": "" + "tempChar": "" }, { "order": 559, @@ -9696,7 +9817,7 @@ "name": "ic_fluent_drink_wine_24_filled", "prevSize": 32, "code": 59719, - "tempChar": "" + "tempChar": "" }, { "order": 560, @@ -9704,7 +9825,7 @@ "name": "ic_fluent_drink_wine_24_regular", "prevSize": 32, "code": 59720, - "tempChar": "" + "tempChar": "" }, { "order": 555, @@ -9712,7 +9833,7 @@ "name": "ic_fluent_heart_24_filled", "prevSize": 32, "code": 59713, - "tempChar": "" + "tempChar": "" }, { "order": 556, @@ -9720,7 +9841,7 @@ "name": "ic_fluent_heart_24_regular", "prevSize": 32, "code": 59714, - "tempChar": "" + "tempChar": "" }, { "order": 553, @@ -9728,7 +9849,7 @@ "name": "ic_fluent_home_24_filled", "prevSize": 32, "code": 59708, - "tempChar": "" + "tempChar": "" }, { "order": 554, @@ -9736,7 +9857,7 @@ "name": "ic_fluent_home_24_regular", "prevSize": 32, "code": 59712, - "tempChar": "" + "tempChar": "" }, { "order": 551, @@ -9744,7 +9865,7 @@ "name": "ic_fluent_games_24_filled", "prevSize": 32, "code": 59706, - "tempChar": "" + "tempChar": "" }, { "order": 552, @@ -9752,7 +9873,7 @@ "name": "ic_fluent_games_24_regular", "prevSize": 32, "code": 59707, - "tempChar": "" + "tempChar": "" }, { "order": 549, @@ -9760,7 +9881,7 @@ "name": "ic_fluent_leaf_one_24_filled", "prevSize": 32, "code": 59704, - "tempChar": "" + "tempChar": "" }, { "order": 550, @@ -9768,7 +9889,7 @@ "name": "ic_fluent_leaf_one_24_regular", "prevSize": 32, "code": 59705, - "tempChar": "" + "tempChar": "" }, { "order": 547, @@ -9776,7 +9897,7 @@ "name": "ic_fluent_star_24_filled", "prevSize": 32, "code": 59702, - "tempChar": "" + "tempChar": "" }, { "order": 548, @@ -9784,7 +9905,7 @@ "name": "ic_fluent_star_24_regular", "prevSize": 32, "code": 59703, - "tempChar": "" + "tempChar": "" }, { "order": 545, @@ -9792,7 +9913,7 @@ "name": "ic_fluent_trophy_24_filled", "prevSize": 32, "code": 59700, - "tempChar": "" + "tempChar": "" }, { "order": 546, @@ -9800,7 +9921,7 @@ "name": "ic_fluent_trophy_24_regular", "prevSize": 32, "code": 59701, - "tempChar": "" + "tempChar": "" }, { "order": 543, @@ -9808,7 +9929,7 @@ "name": "ic_fluent_animal_cat_24_filled", "prevSize": 32, "code": 59697, - "tempChar": "" + "tempChar": "" }, { "order": 544, @@ -9816,7 +9937,7 @@ "name": "ic_fluent_animal_cat_24_regular", "prevSize": 32, "code": 59699, - "tempChar": "" + "tempChar": "" }, { "order": 541, @@ -9824,7 +9945,7 @@ "name": "ic_fluent_call_24_filled", "prevSize": 32, "code": 59792, - "tempChar": "" + "tempChar": "" }, { "order": 542, @@ -9832,7 +9953,7 @@ "name": "ic_fluent_call_24_regular", "prevSize": 32, "code": 59793, - "tempChar": "" + "tempChar": "" }, { "order": 539, @@ -9840,7 +9961,7 @@ "name": "ic_fluent_settings_24_filled", "prevSize": 32, "code": 59790, - "tempChar": "" + "tempChar": "" }, { "order": 540, @@ -9848,7 +9969,7 @@ "name": "ic_fluent_settings_24_regular", "prevSize": 32, "code": 59791, - "tempChar": "" + "tempChar": "" } ], "id": 5, @@ -10759,7 +10880,7 @@ "name": "ic_fluent_clock_20_regular", "prevSize": 32, "code": 59665, - "tempChar": "" + "tempChar": "" }, { "order": 423, @@ -10767,7 +10888,7 @@ "name": "ic_fluent_emoji_20_regular", "prevSize": 32, "code": 59680, - "tempChar": "" + "tempChar": "" }, { "order": 424, @@ -10775,7 +10896,7 @@ "name": "ic_fluent_animal_cat_20_regular", "prevSize": 32, "code": 59681, - "tempChar": "" + "tempChar": "" }, { "order": 421, @@ -10783,7 +10904,7 @@ "name": "ic_fluent_food_pizza_20_regular", "prevSize": 32, "code": 59682, - "tempChar": "" + "tempChar": "" }, { "order": 420, @@ -10791,7 +10912,7 @@ "name": "ic_fluent_sport_soccer_20_regular", "prevSize": 32, "code": 59683, - "tempChar": "" + "tempChar": "" }, { "order": 425, @@ -10799,7 +10920,7 @@ "name": "ic_fluent_vehicle_car_20_regular", "prevSize": 32, "code": 59684, - "tempChar": "" + "tempChar": "" }, { "order": 419, @@ -10807,7 +10928,7 @@ "name": "ic_fluent_lightbulb_filament_20_regular", "prevSize": 32, "code": 59685, - "tempChar": "" + "tempChar": "" }, { "order": 418, @@ -10815,7 +10936,7 @@ "name": "ic_fluent_symbols_20_regular", "prevSize": 32, "code": 59686, - "tempChar": "" + "tempChar": "" }, { "order": 422, @@ -10823,7 +10944,7 @@ "name": "ic_fluent_flag_20_regular", "prevSize": 32, "code": 59687, - "tempChar": "" + "tempChar": "" } ], "id": 3, @@ -10984,13 +11105,29 @@ }, { "selection": [ + { + "order": 969, + "id": 175, + "name": "tl_fluent_open_with_20_regular", + "prevSize": 32, + "code": 60104, + "tempChar": "" + }, + { + "order": 968, + "id": 174, + "name": "tl_fluent_copy_20_regular", + "prevSize": 32, + "code": 60105, + "tempChar": "" + }, { "order": 964, "id": 173, "name": "tl_fluent_poll_undo_20_regular", "prevSize": 32, "code": 60100, - "tempChar": "" + "tempChar": "" }, { "order": 963, @@ -10998,7 +11135,7 @@ "name": "tl_copy_as_path_20_regular", "prevSize": 32, "code": 60099, - "tempChar": "" + "tempChar": "" }, { "order": 957, @@ -11006,7 +11143,7 @@ "name": "tl_fluent_diamond_20_regular", "prevSize": 32, "code": 60093, - "tempChar": "" + "tempChar": "" }, { "order": 935, @@ -11014,7 +11151,7 @@ "name": "tl_fluent_number_symbol_arrow_up_16_regular", "prevSize": 32, "code": 60075, - "tempChar": "" + "tempChar": "" }, { "order": 936, @@ -11022,7 +11159,7 @@ "name": "tl_fluent_number_symbol_arrow_up_20_regular", "prevSize": 32, "code": 60076, - "tempChar": "" + "tempChar": "" }, { "order": 937, @@ -11030,7 +11167,7 @@ "name": "tl_fluent_dollar_arrow_up_16_regular", "prevSize": 32, "code": 60077, - "tempChar": "" + "tempChar": "" }, { "order": 938, @@ -11038,7 +11175,7 @@ "name": "tl_fluent_dollar_arrow_up_20_regular", "prevSize": 32, "code": 60078, - "tempChar": "" + "tempChar": "" }, { "order": 939, @@ -11046,7 +11183,7 @@ "name": "tl_fluent_calendar_arrow_up_16_regular", "prevSize": 32, "code": 60079, - "tempChar": "" + "tempChar": "" }, { "order": 940, @@ -11054,7 +11191,7 @@ "name": "tl_fluent_calendar_arrow_up_20_regular", "prevSize": 32, "code": 60080, - "tempChar": "" + "tempChar": "" }, { "order": 929, @@ -11062,7 +11199,7 @@ "name": "tl_fluent_home_dismiss_20_regular", "prevSize": 32, "code": 60069, - "tempChar": "" + "tempChar": "" }, { "order": 926, @@ -11070,7 +11207,7 @@ "name": "tl_fluent_chat_stars_20_filled", "prevSize": 32, "code": 59772, - "tempChar": "" + "tempChar": "" }, { "order": 925, @@ -11078,7 +11215,7 @@ "name": "tl_fluent_spoiler_20_regular", "prevSize": 32, "code": 60065, - "tempChar": "" + "tempChar": "" }, { "order": 924, @@ -11086,7 +11223,7 @@ "name": "tl_fluent_media_spoiler_20_regular", "prevSize": 32, "code": 60066, - "tempChar": "" + "tempChar": "" }, { "order": 918, @@ -11094,7 +11231,7 @@ "name": "tl_fluent_clock_arrow_forward_20_regular", "prevSize": 32, "code": 60063, - "tempChar": "" + "tempChar": "" }, { "order": 917, @@ -11102,7 +11239,7 @@ "name": "tl_fluent_clock_edit_20_regular", "prevSize": 32, "code": 60062, - "tempChar": "" + "tempChar": "" }, { "order": 899, @@ -11110,7 +11247,7 @@ "name": "ic_fluent_person_add_20_regular", "prevSize": 32, "code": 60049, - "tempChar": "" + "tempChar": "" }, { "order": 897, @@ -11118,7 +11255,7 @@ "name": "tl_fluent_fragment_20_filled", "prevSize": 32, "code": 60046, - "tempChar": "" + "tempChar": "" }, { "order": 895, @@ -11126,7 +11263,7 @@ "name": "tl_fluent_coin_20_regular", "prevSize": 32, "code": 60045, - "tempChar": "" + "tempChar": "" }, { "order": 890, @@ -11134,7 +11271,7 @@ "name": "Premium-1", "prevSize": 32, "code": 60039, - "tempChar": "" + "tempChar": "" }, { "order": 886, @@ -11142,7 +11279,7 @@ "name": "tl_fluent_chat_link_20_filled", "prevSize": 32, "code": 60035, - "tempChar": "" + "tempChar": "" }, { "order": 885, @@ -11150,7 +11287,7 @@ "name": "tl_fluent_chat_info_20_filled", "prevSize": 32, "code": 60034, - "tempChar": "" + "tempChar": "" }, { "order": 884, @@ -11158,7 +11295,7 @@ "name": "tl_fluent_fifty_fifty_20_regular", "prevSize": 32, "code": 60031, - "tempChar": "" + "tempChar": "" }, { "order": 883, @@ -11166,7 +11303,7 @@ "name": "tl_fluent_ton_coin_20_regular", "prevSize": 32, "code": 60033, - "tempChar": "" + "tempChar": "" }, { "order": 881, @@ -11174,7 +11311,7 @@ "name": "tl_fluent_arrow_reply_20_filled", "prevSize": 32, "code": 60028, - "tempChar": "" + "tempChar": "" }, { "order": 880, @@ -11182,7 +11319,7 @@ "name": "tl_fluent_chat_snooze_20_filled", "prevSize": 32, "code": 60029, - "tempChar": "" + "tempChar": "" }, { "order": 877, @@ -11190,7 +11327,7 @@ "name": "tl_fluent_chat_unread_20_filled", "prevSize": 32, "code": 60025, - "tempChar": "" + "tempChar": "" }, { "order": 874, @@ -11198,7 +11335,7 @@ "name": "tl_fluent_chat_snooze_20_regular", "prevSize": 32, "code": 60022, - "tempChar": "" + "tempChar": "" }, { "order": 871, @@ -11211,7 +11348,7 @@ 60020, 60021 ], - "tempChar": "" + "tempChar": "" }, { "order": 864, @@ -11219,7 +11356,7 @@ "name": "tl_fluent_last_seen_20_filled", "prevSize": 32, "code": 60013, - "tempChar": "" + "tempChar": "" }, { "order": 853, @@ -11227,7 +11364,7 @@ "name": "tl_fluent_my_notes_20_filled", "prevSize": 32, "code": 60001, - "tempChar": "" + "tempChar": "" }, { "order": 852, @@ -11235,7 +11372,7 @@ "name": "tl_fluent_author_hidden_20_filled", "prevSize": 32, "code": 60002, - "tempChar": "" + "tempChar": "" }, { "order": 847, @@ -11243,7 +11380,7 @@ "name": "tl_fluent_view_once_20_regular", "prevSize": 32, "code": 59672, - "tempChar": "" + "tempChar": "" }, { "order": 836, @@ -11251,7 +11388,7 @@ "name": "tl_fluent_videochat_20_filled", "prevSize": 32, "code": 59983, - "tempChar": "" + "tempChar": "" }, { "order": 810, @@ -11259,7 +11396,7 @@ "name": "tl_fluent_move_down_20_regular", "prevSize": 32, "code": 59971, - "tempChar": "" + "tempChar": "" }, { "order": 811, @@ -11267,7 +11404,7 @@ "name": "tl_fluent_move_up_20_regular", "prevSize": 32, "code": 59972, - "tempChar": "" + "tempChar": "" }, { "order": 809, @@ -11275,7 +11412,7 @@ "name": "tl_fluent_enlarge_20_regular", "prevSize": 32, "code": 59973, - "tempChar": "" + "tempChar": "" }, { "order": 808, @@ -11283,7 +11420,7 @@ "name": "tl_fluent_shrink_20_regular", "prevSize": 32, "code": 59974, - "tempChar": "" + "tempChar": "" }, { "order": 807, @@ -11291,7 +11428,7 @@ "name": "tl_fluent_id_rectangle_20_regular", "prevSize": 32, "code": 59970, - "tempChar": "" + "tempChar": "" }, { "order": 804, @@ -11299,7 +11436,7 @@ "name": "tl_fluent_reply_another_chat_20_regular", "prevSize": 32, "code": 59967, - "tempChar": "" + "tempChar": "" }, { "order": 803, @@ -11307,7 +11444,7 @@ "name": "tl_fluent_quote_block_20_regular", "prevSize": 32, "code": 59966, - "tempChar": "" + "tempChar": "" }, { "order": 800, @@ -11315,7 +11452,7 @@ "name": "tl_fluent_text_quote_20_regular", "prevSize": 32, "code": 59964, - "tempChar": "", + "tempChar": "", "codes": [ 59964 ] @@ -11326,7 +11463,7 @@ "name": "tl_fluent_boost_20_regular", "prevSize": 32, "code": 59961, - "tempChar": "" + "tempChar": "" }, { "order": 785, @@ -11334,7 +11471,7 @@ "name": "tl_fluent_stories_off_20_regular", "prevSize": 32, "code": 59949, - "tempChar": "" + "tempChar": "" }, { "order": 784, @@ -11342,7 +11479,7 @@ "name": "tl_fluent_stealth_20_filled", "prevSize": 32, "code": 59948, - "tempChar": "" + "tempChar": "" }, { "order": 783, @@ -11350,7 +11487,7 @@ "name": "tl_fluent_stealth_20_regular", "prevSize": 32, "code": 59944, - "tempChar": "" + "tempChar": "" }, { "order": 782, @@ -11358,7 +11495,7 @@ "name": "tl_fluent_stealth_locked_20_regular", "prevSize": 32, "code": 59945, - "tempChar": "" + "tempChar": "" }, { "order": 781, @@ -11366,7 +11503,7 @@ "name": "tl_fluent_eye_on_20_regular", "prevSize": 32, "code": 59946, - "tempChar": "" + "tempChar": "" }, { "order": 780, @@ -11374,7 +11511,7 @@ "name": "tl_fluent_save_locked_20_regular", "prevSize": 32, "code": 59947, - "tempChar": "" + "tempChar": "" }, { "order": 764, @@ -11382,7 +11519,7 @@ "name": "tl_fluent_unarchive_20_regular", "prevSize": 32, "code": 59940, - "tempChar": "" + "tempChar": "" }, { "order": 759, @@ -11393,7 +11530,7 @@ "codes": [ 59859 ], - "tempChar": "" + "tempChar": "" }, { "order": 757, @@ -11401,7 +11538,7 @@ "name": "Arrow", "prevSize": 32, "code": 59710, - "tempChar": "" + "tempChar": "" }, { "order": 753, @@ -11409,7 +11546,7 @@ "name": "tl_fluent_arrow_forward_20_filled", "prevSize": 32, "code": 59668, - "tempChar": "" + "tempChar": "" }, { "order": 752, @@ -11417,7 +11554,7 @@ "name": "tl_fluent_arrow_forward_20_regular", "prevSize": 32, "code": 59181, - "tempChar": "" + "tempChar": "" }, { "order": 751, @@ -11425,7 +11562,7 @@ "name": "tl_fluent_arrow_reply_20_regular", "prevSize": 32, "code": 57928, - "tempChar": "" + "tempChar": "" }, { "order": 749, @@ -11433,7 +11570,7 @@ "name": "VoiceRecognition1", "prevSize": 32, "code": 59667, - "tempChar": "" + "tempChar": "" }, { "order": 750, @@ -11441,7 +11578,7 @@ "name": "VoiceRecognition2", "prevSize": 32, "code": 59659, - "tempChar": "" + "tempChar": "" }, { "order": 725, @@ -11449,7 +11586,7 @@ "name": "ic_fluent_speed_template_20_regular", "prevSize": 32, "code": 59923, - "tempChar": "" + "tempChar": "" }, { "order": 722, @@ -11457,7 +11594,7 @@ "name": "ic_fluent_folder_arrow_download_20_regular", "prevSize": 32, "code": 59920, - "tempChar": "" + "tempChar": "" }, { "order": 717, @@ -11465,7 +11602,7 @@ "name": "ShieldStar", "prevSize": 32, "code": 59779, - "tempChar": "" + "tempChar": "" }, { "order": 713, @@ -11473,7 +11610,7 @@ "name": "bot_command_0", "prevSize": 32, "code": 59915, - "tempChar": "" + "tempChar": "" }, { "order": 696, @@ -11484,7 +11621,7 @@ "codes": [ 59895 ], - "tempChar": "" + "tempChar": "" }, { "order": 697, @@ -11492,7 +11629,7 @@ "name": "link_1", "prevSize": 32, "code": 59896, - "tempChar": "" + "tempChar": "" }, { "order": 692, @@ -11507,7 +11644,7 @@ 59896, 59897 ], - "tempChar": "" + "tempChar": "" }, { "order": 691, @@ -11522,7 +11659,7 @@ 59901, 59902 ], - "tempChar": "" + "tempChar": "" }, { "order": 690, @@ -11537,7 +11674,7 @@ 59906, 59907 ], - "tempChar": "" + "tempChar": "" }, { "order": 689, @@ -11552,7 +11689,7 @@ 59911, 59912 ], - "tempChar": "" + "tempChar": "" }, { "order": 652, @@ -11560,7 +11697,7 @@ "name": "gift_premium", "prevSize": 32, "code": 59860, - "tempChar": "" + "tempChar": "" }, { "order": 630, @@ -11568,7 +11705,7 @@ "name": "Circle", "prevSize": 32, "code": 59963, - "tempChar": "" + "tempChar": "" }, { "order": 621, @@ -11576,7 +11713,7 @@ "name": "Premium", "prevSize": 32, "code": 59829, - "tempChar": "" + "tempChar": "" }, { "order": 603, @@ -11584,7 +11721,7 @@ "name": "ic_fluent_clock_alarm_hour_20_regular", "prevSize": 32, "code": 59811, - "tempChar": "" + "tempChar": "" }, { "order": 601, @@ -11592,7 +11729,7 @@ "name": "never", "prevSize": 32, "code": 59807, - "tempChar": "" + "tempChar": "" }, { "order": 599, @@ -11600,7 +11737,7 @@ "name": "day", "prevSize": 32, "code": 59808, - "tempChar": "" + "tempChar": "" }, { "order": 602, @@ -11608,7 +11745,7 @@ "name": "week", "prevSize": 32, "code": 59809, - "tempChar": "" + "tempChar": "" }, { "order": 600, @@ -11616,7 +11753,7 @@ "name": "month", "prevSize": 32, "code": 59810, - "tempChar": "" + "tempChar": "" }, { "order": 592, @@ -11624,7 +11761,7 @@ "name": "mask_outline_2", "prevSize": 32, "code": 59799, - "tempChar": "" + "tempChar": "" }, { "order": 521, @@ -11632,7 +11769,7 @@ "name": "Reactions", "prevSize": 32, "code": 59776, - "tempChar": "" + "tempChar": "" }, { "order": 513, @@ -11640,7 +11777,7 @@ "name": "ic_fluent_dismiss_24_regular", "prevSize": 32, "code": 59768, - "tempChar": "" + "tempChar": "" }, { "order": 497, @@ -11648,7 +11785,7 @@ "name": "Seen", "prevSize": 32, "code": 59755, - "tempChar": "" + "tempChar": "" }, { "order": 494, @@ -11656,7 +11793,7 @@ "name": "ArrowDownSmall", "prevSize": 32, "code": 60892, - "tempChar": "" + "tempChar": "" }, { "order": 492, @@ -11664,7 +11801,7 @@ "name": "ArrowLeftSmall", "prevSize": 32, "code": 60889, - "tempChar": "" + "tempChar": "" }, { "order": 491, @@ -11672,7 +11809,7 @@ "name": "ArrowRightSmall", "prevSize": 32, "code": 60890, - "tempChar": "" + "tempChar": "" }, { "order": 493, @@ -11680,7 +11817,7 @@ "name": "ArrowUpSmall", "prevSize": 32, "code": 60891, - "tempChar": "" + "tempChar": "" }, { "order": 489, @@ -11688,7 +11825,7 @@ "name": "DismissSmall", "prevSize": 32, "code": 57610, - "tempChar": "" + "tempChar": "" }, { "order": 403, @@ -11696,7 +11833,7 @@ "name": "tl_fluent_videochat_20_regular", "prevSize": 32, "code": 59648, - "tempChar": "" + "tempChar": "" }, { "order": 397, @@ -11704,7 +11841,7 @@ "name": "ic_fluent_play_20_regular", "prevSize": 32, "code": 59240, - "tempChar": "" + "tempChar": "" }, { "order": 394, @@ -11712,7 +11849,7 @@ "name": "uniE769", "prevSize": 32, "code": 59241, - "tempChar": "" + "tempChar": "" }, { "order": 0, @@ -11784,7 +11921,7 @@ "name": "CancelSmall", "prevSize": 32, "code": 59711, - "tempChar": "" + "tempChar": "" }, { "order": 237, @@ -11792,7 +11929,7 @@ "name": "DownloadSmall", "prevSize": 32, "code": 59690, - "tempChar": "" + "tempChar": "" }, { "order": 0, @@ -11872,7 +12009,7 @@ "prevSize": 32, "code": 58880, "name": "uniE600", - "tempChar": "" + "tempChar": "" }, { "id": 9, @@ -11881,7 +12018,7 @@ "prevSize": 32, "code": 58881, "name": "uniE601", - "tempChar": "" + "tempChar": "" }, { "id": 10, @@ -11890,7 +12027,7 @@ "prevSize": 32, "code": 58882, "name": "uniE602", - "tempChar": "" + "tempChar": "" }, { "id": 11, @@ -11899,7 +12036,7 @@ "prevSize": 32, "code": 58883, "name": "uniE603", - "tempChar": "" + "tempChar": "" }, { "id": 12, @@ -11935,7 +12072,7 @@ "prevSize": 32, "code": 58885, "name": "uniE60B", - "tempChar": "" + "tempChar": "" }, { "id": 16, @@ -11944,7 +12081,7 @@ "prevSize": 32, "code": 58892, "name": "uniE60C", - "tempChar": "" + "tempChar": "" }, { "order": 235, @@ -11952,7 +12089,7 @@ "name": "Trending", "prevSize": 32, "code": 58893, - "tempChar": "" + "tempChar": "" }, { "id": 18, @@ -11996,7 +12133,7 @@ "prevSize": 32, "code": 58894, "name": "uniE611", - "tempChar": "" + "tempChar": "" }, { "id": 23, @@ -12005,7 +12142,7 @@ "prevSize": 32, "code": 58898, "name": "uniE612", - "tempChar": "" + "tempChar": "" }, { "order": 0, @@ -12103,7 +12240,7 @@ "prevSize": 32, "code": 59654, "name": "uniE906", - "tempChar": "" + "tempChar": "" }, { "id": 34, @@ -12121,7 +12258,7 @@ "prevSize": 32, "code": 59656, "name": "uniE908", - "tempChar": "" + "tempChar": "" }, { "id": 36, @@ -12139,7 +12276,7 @@ "prevSize": 32, "code": 59658, "name": "uniE90A", - "tempChar": "" + "tempChar": "" }, { "id": 38, @@ -12211,7 +12348,7 @@ "prevSize": 32, "code": 59666, "name": "uniE912", - "tempChar": "" + "tempChar": "" }, { "id": 46, @@ -12283,7 +12420,7 @@ "prevSize": 32, "code": 59674, "name": "uniE91A", - "tempChar": "" + "tempChar": "" }, { "id": 55, @@ -12319,7 +12456,7 @@ "prevSize": 32, "code": 942080, "name": "uE6000", - "tempChar": "" + "tempChar": "" } ], "id": 2, @@ -12333,6 +12470,48 @@ "height": 1024, "prevSize": 32, "icons": [ + { + "id": 175, + "paths": [ + "M230.4 153.6c42.416 0 76.8 34.384 76.8 76.8v51.2c0 42.416-34.384 76.8-76.8 76.8h-51.2c-42.416 0-76.8-34.384-76.8-76.8v-51.2c0-42.416 34.384-76.8 76.8-76.8h51.2zM179.2 204.8c-14.138 0-25.6 11.462-25.6 25.6v51.2c0 14.138 11.462 25.6 25.6 25.6h51.2c14.138 0 25.6-11.462 25.6-25.6v-51.2c0-14.138-11.462-25.6-25.6-25.6h-51.2z", + "M230.4 409.6c42.416 0 76.8 34.384 76.8 76.8v51.2c0 42.414-34.384 76.8-76.8 76.8h-51.2c-42.416 0-76.8-34.386-76.8-76.8v-51.2c0-42.416 34.384-76.8 76.8-76.8h51.2zM179.2 460.8c-14.138 0-25.6 11.462-25.6 25.6v51.2c0 14.136 11.462 25.6 25.6 25.6h51.2c14.138 0 25.6-11.464 25.6-25.6v-51.2c0-14.138-11.462-25.6-25.6-25.6h-51.2z", + "M230.4 665.6c42.416 0 76.8 34.386 76.8 76.8v51.2c0 42.414-34.384 76.8-76.8 76.8h-51.2c-42.416 0-76.8-34.386-76.8-76.8v-51.2c0-42.414 34.384-76.8 76.8-76.8h51.2zM179.2 716.8c-14.138 0-25.6 11.464-25.6 25.6v51.2c0 14.136 11.462 25.6 25.6 25.6h51.2c14.138 0 25.6-11.464 25.6-25.6v-51.2c0-14.136-11.462-25.6-25.6-25.6h-51.2z", + "M793.6 204.8c14.136 0 25.6 11.462 25.6 25.6s-11.464 25.6-25.6 25.6h-409.6c-14.138 0-25.6-11.462-25.6-25.6s11.462-25.6 25.6-25.6h409.6z", + "M802.918 528.282c-89.979-89.976-235.858-89.976-325.835 0-89.977 89.979-89.977 235.858 0 325.837s235.856 89.979 325.835 0c89.979-89.979 89.979-235.858 0-325.837zM738.012 763.607c-0.005 14.136-11.469 25.6-25.605 25.605-14.136-0.005-25.6-11.469-25.6-25.605v-83.011l-101.11 101.115c-9.999 9.994-26.209 9.994-36.209 0-9.994-9.999-9.994-26.209 0-36.204l101.115-101.115-83.011 0.005c-14.136-0.005-25.6-11.469-25.605-25.605 0.005-14.136 11.469-25.6 25.605-25.605h144.814c3.471 0.005 6.784 0.691 9.8 1.946 2.975 1.234 5.765 3.046 8.197 5.448l0.215 0.22c2.401 2.427 4.214 5.217 5.448 8.192s1.925 6.231 1.94 9.646l0.005 0.184v144.783z", + "M478.2 460.8c-13.080 9.21-25.601 19.601-37.3 31.3-6.401 6.401-12.394 13.063-18.050 19.9h-38.85c-14.138 0-25.6-11.464-25.6-25.6s11.462-25.6 25.6-25.6h94.2z" + ], + "attrs": [ + {}, + {}, + {}, + {}, + {}, + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": [ + "tl_fluent_open_with_20_regular" + ] + }, + { + "id": 174, + "paths": [ + "M768 665.6v51.2h-256v-51.2h256zM819.2 614.4v-358.4c0-28.277-22.922-51.2-51.2-51.2h-256c-28.277 0-51.2 22.923-51.2 51.2v358.4c0 28.278 22.923 51.2 51.2 51.2v51.2l-10.45-0.548c-48.207-4.89-86.513-43.192-91.4-91.402l-0.55-10.45v-358.4c0-56.554 45.846-102.4 102.4-102.4h256c56.556 0 102.4 45.846 102.4 102.4v358.4l-0.548 10.45c-4.89 48.21-43.192 86.513-91.402 91.402l-10.45 0.548v-51.2c28.278 0 51.2-22.922 51.2-51.2z", + "M358.4 358.4h-102.4c-28.277 0-51.2 22.923-51.2 51.2v358.4c0 28.278 22.923 51.2 51.2 51.2h256c28.278 0 51.2-22.922 51.2-51.2h51.2l-0.548 10.45c-4.89 48.21-43.192 86.513-91.402 91.402l-10.45 0.548h-256l-10.45-0.548c-48.207-4.89-86.513-43.192-91.4-91.402l-0.55-10.45v-358.4c0-56.554 45.846-102.4 102.4-102.4h102.4v51.2z" + ], + "attrs": [ + {}, + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 0, + "tags": [ + "tl_fluent_copy_20_regular" + ] + }, { "id": 173, "paths": [ @@ -14737,7 +14916,7 @@ "metadata": { "fontFamily": "Telegram", "majorVersion": 2, - "minorVersion": 151 + "minorVersion": 154 }, "metrics": { "emSize": 1024, @@ -14762,7 +14941,9 @@ "name": "icomoon" }, "historySize": 100, - "quickUsageToken": {}, + "quickUsageToken": { + "Telegram": "ZmNiYWJkZWMxYiMxNzUyNzU2NDgwIzFEZ2daSWNXa3MyV0sxSUhXeWhwUkJZakVISWhlZis0MTFXRGc3VFZ0N1cv" + }, "gridSize": 16 }, "uid": -1 diff --git a/Telegram/Assets/Fonts/Telegram.ttf b/Telegram/Assets/Fonts/Telegram.ttf index c5c887e931..acaf3c08c1 100644 Binary files a/Telegram/Assets/Fonts/Telegram.ttf and b/Telegram/Assets/Fonts/Telegram.ttf differ diff --git a/Telegram/Collections/Experimental/ItemCacheManager.cs b/Telegram/Collections/Experimental/ItemCacheManager.cs index f9b9bd5ed9..76902f2394 100644 --- a/Telegram/Collections/Experimental/ItemCacheManager.cs +++ b/Telegram/Collections/Experimental/ItemCacheManager.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -48,11 +54,12 @@ class ItemCacheManager // Used to be able to cancel outstanding requests private CancellationTokenSource _cancelTokenSource; // Callback that will be used to request data - private fetchDataCallbackHandler fetchDataCallback; + private fetchDataCallbackHandler _fetchDataCallback; // Maximum number of items that can be fetched in one batch private int _maxBatchFetchSize; // Timer to optimize the the fetching of data so we throttle requests if the list is still changing private DispatcherTimer _timer; + private bool _stopped; #if DEBUG // Name for trace messages, and when debugging so you know which instance of the cache manager you are dealing with @@ -63,7 +70,7 @@ public ItemCacheManager(fetchDataCallbackHandler callback, int batchsize = 50, s _cacheBlocks = new List>(); _requests = new ItemIndexRangeList(); _cachedResults = new ItemIndexRangeList(); - fetchDataCallback = callback; + _fetchDataCallback = callback; _maxBatchFetchSize = batchsize; //set up a timer that is used to delay fetching data so that we can catch up if the list is scrolling fast _timer = new Windows.UI.Xaml.DispatcherTimer(); @@ -83,6 +90,13 @@ public ItemCacheManager(fetchDataCallbackHandler callback, int batchsize = 50, s public event TypedEventHandler> CacheChanged; + public void Stop() + { + _stopped = true; + _cancelTokenSource.Cancel(); + _timer.Stop(); + } + /// /// Indexer for access to the item cache /// @@ -122,9 +136,9 @@ public T this[int index] return; } } + // No blocks exist, so creating a new block AddOrExtendBlock(index, value, _cacheBlocks.Count); - } } @@ -143,11 +157,11 @@ private void AddOrExtendBlock(int index, T value, int insertBeforeBlock) return; } } + CacheEntryBlock newBlock = new CacheEntryBlock(index, new T[] { value }); _cacheBlocks.Insert(insertBeforeBlock, newBlock); } - /// /// Updates the desired item range of the cache, discarding items that are not needed, and figuring out which items need to be requested. It will then kick off a fetch if required. /// @@ -231,6 +245,11 @@ public ItemIndexRange GetFirstRequestBlock(int maxsize = 50) // If another fetch is requested in that time, it will reset the timer, so we don't fetch data if the view is actively scrolling public void StartFetchData() { + if (_stopped) + { + return; + } + // Verify if an active request is still needed if (_requestInProgress != null) { @@ -260,6 +279,12 @@ public async void FetchData() { //Stop the timer so we don't get fired again unless data is requested _timer.Stop(); + + if (_stopped) + { + return; + } + if (_requestInProgress != null) { // Verify if an active request is still needed @@ -290,7 +315,7 @@ public async void FetchData() Debug.WriteLine(">" + debugName + " Fetching items " + nextRequest.FirstIndex + "->" + nextRequest.LastIndex); #endif // Use the callback to get the data, passing in a cancellation token - data = await fetchDataCallback(nextRequest, ct); + data = await _fetchDataCallback(nextRequest, ct); if (data != null && !ct.IsCancellationRequested) { diff --git a/Telegram/Collections/Experimental/ItemIndexRangeExtensions.cs b/Telegram/Collections/Experimental/ItemIndexRangeExtensions.cs index 26bfbbfe6a..6b194e147d 100644 --- a/Telegram/Collections/Experimental/ItemIndexRangeExtensions.cs +++ b/Telegram/Collections/Experimental/ItemIndexRangeExtensions.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using System.Collections.Generic; using Windows.UI.Xaml.Data; diff --git a/Telegram/Collections/Experimental/ItemIndexRangeList.cs b/Telegram/Collections/Experimental/ItemIndexRangeList.cs index 84947b1642..c35d81dab8 100644 --- a/Telegram/Collections/Experimental/ItemIndexRangeList.cs +++ b/Telegram/Collections/Experimental/ItemIndexRangeList.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using System.Collections; using System.Collections.Generic; using Windows.UI.Xaml.Data; diff --git a/Telegram/Collections/Experimental/MediaDataSource.cs b/Telegram/Collections/Experimental/MediaDataSource.cs index f422c94877..6c92e4ef19 100644 --- a/Telegram/Collections/Experimental/MediaDataSource.cs +++ b/Telegram/Collections/Experimental/MediaDataSource.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; @@ -59,16 +65,17 @@ public static async Task Create(IClientService clientService, l return ds; } - // Handles a change notification for the list of files from the OS - private void ResetCollection() + public async void SetFilter(SearchMessagesFilter filter) { - // Unhook the old change notification if (_itemCache != null) { + _itemCache.Stop(); _itemCache.CacheChanged -= ItemCache_CacheChanged; } - // Create a new instance of the cache manager + _filter = filter; + await UpdateCount(true); + _itemCache = new ItemCacheManager(FetchDataCallback, 50); _itemCache.CacheChanged += ItemCache_CacheChanged; @@ -78,16 +85,10 @@ private void ResetCollection() } } - public async void SetFilter(SearchMessagesFilter filter) - { - _filter = filter; - - await UpdateCount(true); - ResetCollection(); - } - private async Task UpdateCount(bool getSparseMessages) { + await _gettingPositions.WaitAsync(); + if (getSparseMessages) { var response = await _clientService.SendAsync(new GetChatSparseMessagePositions(_chatId, _filter, 0, 2000, _savedMessagesTopicId)); @@ -111,6 +112,8 @@ private async Task UpdateCount(bool getSparseMessages) } } + _gettingPositions.Release(); + if (CollectionChanged != null) { CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); diff --git a/Telegram/Collections/FlatteningCollection.cs b/Telegram/Collections/FlatteningCollection.cs index 319815d993..31b549aa56 100644 --- a/Telegram/Collections/FlatteningCollection.cs +++ b/Telegram/Collections/FlatteningCollection.cs @@ -86,7 +86,7 @@ private void Assert() foreach (var collection in _groups) { - if (collection.Count > 0) + if (collection.Count > 0 && collection.Key != null) { temp.Add(collection); } diff --git a/Telegram/Collections/ListWithTotalCount.cs b/Telegram/Collections/ListWithTotalCount.cs index 1ed0c180ef..243cb7d1d7 100644 --- a/Telegram/Collections/ListWithTotalCount.cs +++ b/Telegram/Collections/ListWithTotalCount.cs @@ -1,10 +1,9 @@ -// +// // Copyright (c) Fela Ameghino 2015-2025 // // Distributed under the GNU General Public License v3.0. (See accompanying // file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) // - using System.Collections.Generic; using System.ComponentModel; diff --git a/Telegram/Collections/MvxObservableCollection.cs b/Telegram/Collections/MvxObservableCollection.cs index 7fb48a9620..a50caf19b4 100644 --- a/Telegram/Collections/MvxObservableCollection.cs +++ b/Telegram/Collections/MvxObservableCollection.cs @@ -6,7 +6,7 @@ // // MvxObservableCollection.cs // https://github.com/MvvmCross/MvvmCross/blob/develop/MvvmCross/Core/Core/ViewModels/MvxObservableCollection.cs - +// // MvvmCross is licensed using Microsoft Public License (Ms-PL) // Contributions and inspirations noted in readme.md and license.txt // @@ -376,4 +376,4 @@ public virtual void RaisePropertyChanged([CallerMemberName] string propertyName catch { } } } -} \ No newline at end of file +} diff --git a/Telegram/Collections/SearchEmojiCollection.cs b/Telegram/Collections/SearchEmojiCollection.cs index 44ea82ea9d..bb448e30e7 100644 --- a/Telegram/Collections/SearchEmojiCollection.cs +++ b/Telegram/Collections/SearchEmojiCollection.cs @@ -1,4 +1,10 @@ -using System.Collections.ObjectModel; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System.Collections.ObjectModel; using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; using Telegram.Common; diff --git a/Telegram/Common/AsyncMediaPlayer.cs b/Telegram/Common/AsyncMediaPlayer.cs index 7678d63fb6..dbde3012c1 100644 --- a/Telegram/Common/AsyncMediaPlayer.cs +++ b/Telegram/Common/AsyncMediaPlayer.cs @@ -18,6 +18,7 @@ using Windows.Foundation; using Windows.Media.Devices; using Windows.Storage; +using Windows.System; namespace Telegram.Common { @@ -36,7 +37,7 @@ public AsyncMediaTrack(int width, int height) public partial class AsyncMediaPlayer { - private readonly IDispatcherContext _dispatcherQueue; + private readonly DispatcherQueue _dispatcherQueue; private readonly LibVLC _library; private readonly MediaPlayer _player; @@ -51,11 +52,7 @@ public partial class AsyncMediaPlayer public AsyncMediaPlayer(params string[] options) { - _dispatcherQueue = WindowContext.Current.Dispatcher; - - // This should be not needed - _dispatcherQueue ??= WindowContext.Main.Dispatcher; - + _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); _enableDebugLogs = SettingsService.Current.VerbosityLevel >= 4; // Generating plugins cache requires a breakpoint in bank.c#504 @@ -131,6 +128,34 @@ private void PlayImpl(MediaInput input, bool play) } } + public void Play(Uri input) + { + Write(valid => PlayImpl(input, valid)); + } + + private void PlayImpl(Uri input, bool play) + { + if (play) + { + // Not sure whether it's file or network caching and if they make any difference at all + var media = new Media(_library, input, ":file-caching=10000", ":network-caching=10000"); + + _player.Play(media); + + // We need to retain both Media and MediaInput due to the bad (IMHO) design of libvlc API. + // When creating a Media from a MediaInput, some callbacks are registered to access the stream. + // The problem is that the library creates a GC handle in MediaInput that is then used by Media + // to register the aforementioned callbacks. What happens, in my understanding, is that there are + // some good chances that MediaInput is disposed before Media, and due to that the GC handle is deleted + // and this causes an access violation in libvlccore when trying to raise the callbacks for the media. + _media?.Dispose(); + _media = media; + + _input?.Dispose(); + _input = null; + } + } + public void Play() { Write(() => _player.Play()); @@ -202,6 +227,8 @@ public void Close() private void CloseImpl() { + MediaDevice.DefaultAudioRenderDeviceChanged -= OnDefaultAudioRenderDeviceChanged; + _player.ESSelected -= OnESSelected; _player.Vout -= OnVout; _player.Buffering -= OnBuffering; @@ -298,57 +325,69 @@ private AsyncMediaTrack GetTrack() private void OnVout(object sender, MediaPlayerVoutEventArgs e) { - _dispatcherQueue.Dispatch(() => Vout?.Invoke(this, EventArgs.Empty)); + TryEnqueue(() => Vout?.Invoke(this, EventArgs.Empty)); } private void OnESSelected(object sender, MediaPlayerESSelectedEventArgs e) { - _dispatcherQueue.Dispatch(() => ESSelected?.Invoke(this, e)); + TryEnqueue(() => ESSelected?.Invoke(this, e)); } private void OnEndReached(object sender, EventArgs e) { - _dispatcherQueue.Dispatch(() => EndReached?.Invoke(this, EventArgs.Empty)); + TryEnqueue(() => EndReached?.Invoke(this, EventArgs.Empty)); } private void OnBuffering(object sender, MediaPlayerBufferingEventArgs e) { - _dispatcherQueue.Dispatch(() => Buffering?.Invoke(this, e)); + TryEnqueue(() => Buffering?.Invoke(this, e)); } private void OnTimeChanged(object sender, MediaPlayerTimeChangedEventArgs e) { - _dispatcherQueue.Dispatch(() => TimeChanged?.Invoke(this, e)); + TryEnqueue(() => TimeChanged?.Invoke(this, e)); } private void OnLengthChanged(object sender, MediaPlayerLengthChangedEventArgs e) { - _dispatcherQueue.Dispatch(() => LengthChanged?.Invoke(this, e)); + TryEnqueue(() => LengthChanged?.Invoke(this, e)); } private void OnPlaying(object sender, EventArgs e) { - _dispatcherQueue.Dispatch(() => Playing?.Invoke(this, EventArgs.Empty)); + TryEnqueue(() => Playing?.Invoke(this, EventArgs.Empty)); } private void OnPaused(object sender, EventArgs e) { - _dispatcherQueue.Dispatch(() => Paused?.Invoke(this, EventArgs.Empty)); + TryEnqueue(() => Paused?.Invoke(this, EventArgs.Empty)); } private void OnStopped(object sender, EventArgs e) { - _dispatcherQueue.Dispatch(() => Stopped?.Invoke(this, EventArgs.Empty)); + TryEnqueue(() => Stopped?.Invoke(this, EventArgs.Empty)); } private void OnVolumeChanged(object sender, MediaPlayerVolumeChangedEventArgs e) { - _dispatcherQueue.Dispatch(() => VolumeChanged?.Invoke(this, e)); + TryEnqueue(() => VolumeChanged?.Invoke(this, e)); } private void OnEncounteredError(object sender, EventArgs e) { - _dispatcherQueue.Dispatch(() => EncounteredError?.Invoke(this, EventArgs.Empty)); + TryEnqueue(() => EncounteredError?.Invoke(this, EventArgs.Empty)); + } + + private void TryEnqueue(DispatcherQueueHandler action) + { + if (_dispatcherQueue != null) + { + _dispatcherQueue.TryEnqueue(action); + } + else + { + ThreadPool.QueueUserWorkItem(state => action()); + } } #endregion diff --git a/Telegram/Common/AutomaticDragHelper.cs b/Telegram/Common/AutomaticDragHelper.cs index 90d28266ef..ce17592bbf 100644 --- a/Telegram/Common/AutomaticDragHelper.cs +++ b/Telegram/Common/AutomaticDragHelper.cs @@ -1,6 +1,13 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using System.Runtime.InteropServices; using Windows.Devices.Input; +using Windows.UI.Input; using Windows.UI.Xaml; using Windows.UI.Xaml.Input; @@ -33,11 +40,16 @@ public partial class AutomaticDragHelper private Point m_lastMouseLeftButtonDownPosition; private bool m_isLeftButtonPressed; + private bool m_isHoldingCompleted; + + private PointerPoint m_spPointerPoint; + private Pointer m_spPointer; private PointerEventHandler m_dragDropPointerPressedToken; private PointerEventHandler m_dragDropPointerMovedToken; private PointerEventHandler m_dragDropPointerReleasedToken; private PointerEventHandler m_dragDropPointerCaptureLostToken; + private HoldingEventHandler m_dragDropHoldingToken; public AutomaticDragHelper(UIElement pUIElement, bool shouldAddInputHandlers) { @@ -136,6 +148,10 @@ private void RegisterDragPointerEvents() private void HandlePointerPressedEventArgs(object sender, PointerRoutedEventArgs args) { + m_spPointerPoint = null; + m_spPointer = null; + m_isHoldingCompleted = false; + var spPointer = args.Pointer; var pointerDeviceType = spPointer.PointerDeviceType; @@ -158,6 +174,20 @@ private void HandlePointerPressedEventArgs(object sender, PointerRoutedEventArgs RegisterDragPointerEvents(); } } + else + { + m_spPointerPoint = spPointerPoint; + m_spPointer = spPointer; + + if (m_shouldAddInputHandlers && m_dragDropHoldingToken == null) + { + // Touch input occurs, subscribe to holding + m_dragDropHoldingToken = new HoldingEventHandler(HandleHoldingEventArgs); + m_pOwnerNoRef.AddHandler(UIElement.HoldingEvent, m_dragDropHoldingToken, true); + } + + RegisterDragPointerEvents(); + } } private void HandlePointerMovedEventArgs(object sender, PointerRoutedEventArgs args) @@ -210,7 +240,6 @@ private void HandlePointerReleasedEventArgs(object sender, PointerRoutedEventArg } } - private void HandlePointerCaptureLostEventArgs(object sender, PointerRoutedEventArgs args) { var spPointer = args.Pointer; @@ -245,6 +274,44 @@ private void UnregisterEvents() m_pOwnerNoRef.RemoveHandler(UIElement.PointerCaptureLostEvent, m_dragDropPointerCaptureLostToken); m_dragDropPointerCaptureLostToken = null; } + + if (m_dragDropHoldingToken != null) + { + m_pOwnerNoRef.RemoveHandler(UIElement.HoldingEvent, m_dragDropHoldingToken); + m_dragDropHoldingToken = null; + } + } + + void HandleHoldingEventArgs(object sender, HoldingRoutedEventArgs pArgs) + { + PointerDeviceType pointerDeviceType = pArgs.PointerDeviceType; + + if (pointerDeviceType == PointerDeviceType.Touch) + { + HoldingState holdingState = pArgs.HoldingState; + + if (holdingState == HoldingState.Started) + { + m_isHoldingCompleted = true; + } + } + } + + void HandleDirectManipulationDraggingStarted() + { + //ASSERT(m_spPointerPoint && m_spPointer); + + //// Release cross-slide viewport now + //IFC_RETURN(m_pOwnerNoRef->DirectManipulationCrossSlideContainerCompleted()); + //if (m_isHoldingCompleted) + //{ + // IFC_RETURN(m_pOwnerNoRef->OnTouchDragStarted(m_spPointerPoint.Get(), m_spPointer.Get())); + //} + + //m_spPointerPoint = nullptr; + //m_spPointer = nullptr; + + //return S_OK; } } } diff --git a/Telegram/Common/CommonStyles.xaml b/Telegram/Common/CommonStyles.xaml index b28cbf0a43..1a5728a8b8 100644 --- a/Telegram/Common/CommonStyles.xaml +++ b/Telegram/Common/CommonStyles.xaml @@ -3732,230 +3732,6 @@ - - - - - + - 0) + { + var video = item.AlternativeVideos[0]; + window.ClientService.DownloadFile(video.HlsFile.Id, 30); + window.ClientService.DownloadFile(video.Video.Id, 29, 0, (int)((double)video.Video.Size / item.Duration)); + } } private void UpdateFile(object target, File file) @@ -246,7 +253,7 @@ private void UpdateFile(GalleryMedia item, File file) if (item.IsPhoto && item.IsMedia) { - item.ClientService.DownloadFile(file.Id, 1); + item.ClientService.DownloadFile(file.Id, 16); } } else @@ -378,7 +385,7 @@ private void Button_Click(object sender, RoutedEventArgs e) if (file.Local.IsDownloadingActive) { - item.ClientService.Send(new CancelDownloadFile(file.Id, false)); + item.ClientService.CancelDownloadFile(file, false); } else if (file.Local.CanBeDownloaded && !file.Local.IsDownloadingActive && !file.Local.IsDownloadingCompleted) { @@ -558,12 +565,23 @@ private void OnClosed(VideoPlayerBase sender, EventArgs e) } } - public void Stop(out int fileId, out double position) + public void Stop(out GalleryMedia item, out double position) { if (Video != null && !_unloaded) { - fileId = _fileId; - position = Video.Position; + item = _item; + + var time = Video.Position; + var length = Video.Duration; + + if (length >= 30 && time >= 10 && time <= length - 10) + { + position = time; + } + else + { + position = 0; + } _stopped = true; Video.Stop(); @@ -571,7 +589,7 @@ public void Stop(out int fileId, out double position) } else { - fileId = 0; + item = null; position = 0; } diff --git a/Telegram/Controls/Gallery/GalleryTransportControls.xaml.cs b/Telegram/Controls/Gallery/GalleryTransportControls.xaml.cs index 06f15a4c01..2208528bb3 100644 --- a/Telegram/Controls/Gallery/GalleryTransportControls.xaml.cs +++ b/Telegram/Controls/Gallery/GalleryTransportControls.xaml.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using System.Collections.Generic; using System.Linq; using System.Numerics; @@ -685,46 +691,47 @@ public void Stop() } } - public new void ProcessKeyboardAccelerators(ProcessKeyboardAcceleratorEventArgs args) + public new void ProcessKeyboardAccelerators(KeyRoutedEventArgs args) { if (_player == null) { return; } + var modifiers = WindowContext.KeyModifiers(); var keyCode = (int)args.Key; - if (args.Key is VirtualKey.K && args.Modifiers == VirtualKeyModifiers.None) + if (args.Key is VirtualKey.K && modifiers == VirtualKeyModifiers.None) { TogglePlaybackState(); args.Handled = true; } - else if (args.Key is VirtualKey.M && args.Modifiers == VirtualKeyModifiers.None) + else if (args.Key is VirtualKey.M && modifiers == VirtualKeyModifiers.None) { Volume_Click(null, null); args.Handled = true; } - else if (args.Key is VirtualKey.Up && args.Modifiers == VirtualKeyModifiers.None) + else if (args.Key is VirtualKey.Up && modifiers == VirtualKeyModifiers.None) { VolumeSlider.Value += 10; args.Handled = true; } - else if (args.Key is VirtualKey.Down && args.Modifiers == VirtualKeyModifiers.None) + else if (args.Key is VirtualKey.Down && modifiers == VirtualKeyModifiers.None) { VolumeSlider.Value -= 10; args.Handled = true; } - else if ((args.Key is VirtualKey.J && args.Modifiers == VirtualKeyModifiers.None) || (args.Key is VirtualKey.Left && args.Modifiers == VirtualKeyModifiers.Control)) + else if ((args.Key is VirtualKey.J && modifiers == VirtualKeyModifiers.None) || (args.Key is VirtualKey.Left && modifiers == VirtualKeyModifiers.Control)) { - _player.AddTime(-10000); + _player.AddTime(-10); args.Handled = true; } - else if ((args.Key is VirtualKey.L && args.Modifiers == VirtualKeyModifiers.None) || (args.Key is VirtualKey.Right && args.Modifiers == VirtualKeyModifiers.Control)) + else if ((args.Key is VirtualKey.L && modifiers == VirtualKeyModifiers.None) || (args.Key is VirtualKey.Right && modifiers == VirtualKeyModifiers.Control)) { - _player.AddTime(10000); + _player.AddTime(10); args.Handled = true; } - else if (keyCode is 188 or 190 && args.Modifiers == VirtualKeyModifiers.Shift) + else if (keyCode is 188 or 190 && modifiers == VirtualKeyModifiers.Shift) { ChangePlaybackSpeed(keyCode is 188 ? -0.25f : 0.25f); args.Handled = true; diff --git a/Telegram/Controls/Gallery/GalleryWindow.xaml b/Telegram/Controls/Gallery/GalleryWindow.xaml index b3593bd974..bc12db3dd1 100644 --- a/Telegram/Controls/Gallery/GalleryWindow.xaml +++ b/Telegram/Controls/Gallery/GalleryWindow.xaml @@ -15,8 +15,7 @@ VerticalAlignment="Stretch" Loaded="OnLoaded" Unloaded="OnUnloaded" - PreviewKeyDown="OnPreviewKeyDown" - ProcessKeyboardAccelerators="OnProcessKeyboardAccelerators"> + PreviewKeyDown="OnPreviewKeyDown"> diff --git a/Telegram/Controls/Gallery/GalleryWindow.xaml.cs b/Telegram/Controls/Gallery/GalleryWindow.xaml.cs index fa5ec80553..4a0b09a563 100644 --- a/Telegram/Controls/Gallery/GalleryWindow.xaml.cs +++ b/Telegram/Controls/Gallery/GalleryWindow.xaml.cs @@ -5,7 +5,6 @@ // file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) // using System; -using System.Collections.Concurrent; using System.Linq; using System.Numerics; using System.Runtime.CompilerServices; @@ -59,10 +58,8 @@ public sealed partial class GalleryWindow : OverlayWindow, IGalleryDelegate private bool _unloaded; - private readonly ConcurrentDictionary _knownPositions = new(); - private long? _initialPosition; - - public long InitialPosition + private double? _initialPosition; + public double InitialPosition { get => _initialPosition ?? 0; set => _initialPosition = value > 0 ? value : null; @@ -349,7 +346,7 @@ public static Task ShowAsync(ViewModelBase viewModelBase, I return ShowAsync(navigationService.XamlRoot, viewModel, closing); } - public static Task ShowAsync(XamlRoot xamlRoot, GalleryViewModelBase parameter, FrameworkElement closing = null, long timestamp = 0, VideoPlayerBase player = null) + public static Task ShowAsync(XamlRoot xamlRoot, GalleryViewModelBase parameter, FrameworkElement closing = null, double timestamp = 0, VideoPlayerBase player = null) { var popup = new GalleryWindow { @@ -529,8 +526,8 @@ void handler(ConnectedAnimation s, object e) batch.End(); - Unload(); Dispose(); + Unload(); } private void Preview_ImageOpened(object sender, RoutedEventArgs e) @@ -664,12 +661,12 @@ private void Play(GalleryContent content, GalleryMedia item, VideoPlayerBase pla var position = 0d; var file = item.File; - if (_initialPosition is long initialPosition) + if (_initialPosition is double initialPosition) { _initialPosition = null; position = initialPosition; } - else if (_knownPositions.TryGetValue(file.Id, out double knownPosition)) + else if (ViewModel.Settings.Video.TryGetPosition(file, out double knownPosition)) { position = knownPosition; } @@ -695,13 +692,15 @@ private void Dispose() if (_current != null) { - _current.Stop(out int fileId, out double position); - _current = null; + _current.Stop(out var item, out double position); - if (fileId != 0 && position > 0) + if (item is GalleryMessage message) { - _knownPositions[fileId] = position; + ViewModel?.Settings.Video.SetPosition(item.File, position); + ViewModel?.Aggregator.Publish(new UpdateMessageContentOpened(message.ChatId, message.Id)); } + + _current = null; } ViewModel?.PlaybackStopped(); @@ -759,43 +758,58 @@ private void Unload() private void OnPreviewKeyDown(object sender, KeyRoutedEventArgs args) { - if (args.Key is VirtualKey.Space /*&& args.Modifiers == VirtualKeyModifiers.None*/) + var modifiers = WindowContext.KeyModifiers(); + var keyCode = (int)args.Key; + + if (args.Key is VirtualKey.Space && modifiers == VirtualKeyModifiers.None) { Controls.TogglePlaybackState(); args.Handled = true; } - } - - private void OnProcessKeyboardAccelerators(UIElement sender, ProcessKeyboardAcceleratorEventArgs args) - { - var keyCode = (int)args.Key; - - if (args.Key is VirtualKey.Left or VirtualKey.GamepadLeftShoulder && args.Modifiers == VirtualKeyModifiers.None) + else if (args.Key is VirtualKey.Left or VirtualKey.GamepadLeftShoulder && modifiers == VirtualKeyModifiers.None) { + if (args.Key == VirtualKey.Left) + { + var focused = FocusManager.GetFocusedElement(); + if (focused is Slider) + { + return; + } + } + ChangeView(CarouselDirection.Previous, false); args.Handled = true; } - else if (args.Key is VirtualKey.Right or VirtualKey.GamepadRightShoulder && args.Modifiers == VirtualKeyModifiers.None) + else if (args.Key is VirtualKey.Right or VirtualKey.GamepadRightShoulder && modifiers == VirtualKeyModifiers.None) { + if (args.Key == VirtualKey.Right) + { + var focused = FocusManager.GetFocusedElement(); + if (focused is Slider) + { + return; + } + } + ChangeView(CarouselDirection.Next, false); args.Handled = true; } - else if (args.Key is VirtualKey.R && args.Modifiers == VirtualKeyModifiers.Control) + else if (args.Key is VirtualKey.R && modifiers == VirtualKeyModifiers.Control) { args.Handled = true; Rotate_Click(null, null); } - else if (args.Key is VirtualKey.C && args.Modifiers == VirtualKeyModifiers.Control) + else if (args.Key is VirtualKey.C && modifiers == VirtualKeyModifiers.Control) { ViewModel?.Copy(); args.Handled = true; } - else if (args.Key is VirtualKey.S && args.Modifiers == VirtualKeyModifiers.Control) + else if (args.Key is VirtualKey.S && modifiers == VirtualKeyModifiers.Control) { ViewModel?.Save(); args.Handled = true; } - else if (args.Key is VirtualKey.F11 || (args.Key is VirtualKey.F && args.Modifiers == VirtualKeyModifiers.Control)) + else if (args.Key is VirtualKey.F11 || (args.Key is VirtualKey.F && modifiers == VirtualKeyModifiers.Control)) { FullScreen_Click(null, null); args.Handled = true; @@ -1031,7 +1045,7 @@ private void PopulateContextRequested(MenuFlyout flyout, GalleryViewModelBase vi { flyout.CreateFlyoutItem(() => item.CanBeViewed, viewModel.View, Strings.ShowInChat, Icons.ChatEmpty); flyout.CreateFlyoutItem(() => item.CanBeShared, viewModel.Forward, Strings.Forward, Icons.Share); - flyout.CreateFlyoutItem(() => item.CanBeCopied, viewModel.Copy, Strings.Copy, Icons.DocumentCopy, VirtualKey.C); + flyout.CreateFlyoutItem(() => item.CanBeCopied, viewModel.Copy, Strings.Copy, Icons.Copy, VirtualKey.C); flyout.CreateFlyoutItem(() => item.CanBeSaved, viewModel.Save, Strings.SaveAs, Icons.SaveAs, VirtualKey.S); if (viewModel is UserPhotosViewModel userPhotos && userPhotos.CanDelete && userPhotos.SelectedIndex > 0) @@ -1043,7 +1057,7 @@ private void PopulateContextRequested(MenuFlyout flyout, GalleryViewModelBase vi flyout.CreateFlyoutItem(() => viewModel.CanDelete, chatPhotos.SetAsMain, Strings.SetAsMain, Icons.PersonCircle); } - flyout.CreateFlyoutItem(() => viewModel.CanOpenWith, viewModel.OpenWith, Strings.OpenInExternalApp, Icons.OpenIn); + flyout.CreateFlyoutItem(() => viewModel.CanOpenWith, viewModel.OpenWith, Strings.OpenWith, Icons.OpenWith); flyout.CreateFlyoutItem(() => viewModel.CanDelete, viewModel.Delete, Strings.Delete, Icons.Delete, destructive: true); } diff --git a/Telegram/Controls/GlassToggleButton.cs b/Telegram/Controls/GlassToggleButton.cs index d5b828f0ef..7d65b74a20 100644 --- a/Telegram/Controls/GlassToggleButton.cs +++ b/Telegram/Controls/GlassToggleButton.cs @@ -1,4 +1,10 @@ -using Microsoft.Graphics.Canvas; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using Microsoft.Graphics.Canvas; using Microsoft.Graphics.Canvas.Effects; using Microsoft.Graphics.Canvas.Geometry; using System.Numerics; diff --git a/Telegram/Controls/GroupCallActiveHeader.xaml.cs b/Telegram/Controls/GroupCallActiveHeader.xaml.cs index 5d7da8c081..50f15638cd 100644 --- a/Telegram/Controls/GroupCallActiveHeader.xaml.cs +++ b/Telegram/Controls/GroupCallActiveHeader.xaml.cs @@ -79,7 +79,7 @@ public void Update(VoipCallBase value) StartAnimating(); UpdateCurveColors(newGroupCall.IsMuted); - Audio.Visibility = Visibility.Visible; + Audio.Visibility = newGroupCall.IsRtmpStream ? Visibility.Collapsed : Visibility.Visible; Dismiss.Visibility = Visibility.Visible; TitleInfo.Text = newGroupCall.GetTitle(); diff --git a/Telegram/Controls/LoadingTextBlock.cs b/Telegram/Controls/LoadingTextBlock.cs index dfe91ecc99..35e19d1201 100644 --- a/Telegram/Controls/LoadingTextBlock.cs +++ b/Telegram/Controls/LoadingTextBlock.cs @@ -140,8 +140,15 @@ private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedE private async void OnTextChanged(string text, string placeholder) { + var visual1 = ElementComposition.GetElementVisual(_presenter); + var visual2 = ElementComposition.GetElementVisual(_placeholder); + if (string.IsNullOrEmpty(text)) { + _placeholder.Visibility = Visibility.Visible; + + visual1.Clip = null; + visual2.Clip = null; return; } @@ -159,9 +166,6 @@ private async void OnTextChanged(string text, string placeholder) fadeIn.InsertKeyFrame(0, 0); fadeIn.InsertKeyFrame(1, 1); - var visual2 = ElementComposition.GetElementVisual(_placeholder); - var visual1 = ElementComposition.GetElementVisual(_presenter); - visual1.StartAnimation("Opacity", fadeIn); var size1 = _presenter.ActualSize; @@ -185,15 +189,13 @@ private void StartClip(Visual visual, bool show, Vector2 desiredSize) var width = MathF.Max(actualWidth - left, actualHeight - top); var diaginal = MathF.Sqrt((width * width) + (width * width)); - var device = ElementComposition.GetSharedDevice(); - - var rect1 = CanvasGeometry.CreateRectangle(device, 0, 0, show ? 0 : actualWidth, show ? 0 : actualHeight); + var rect1 = CanvasGeometry.CreateRectangle(null, 0, 0, show ? 0 : actualWidth, show ? 0 : actualHeight); - var elli1 = CanvasGeometry.CreateCircle(device, left, top, 0); - var group1 = CanvasGeometry.CreateGroup(device, new[] { elli1, rect1 }, CanvasFilledRegionDetermination.Alternate); + var elli1 = CanvasGeometry.CreateCircle(null, left, top, 0); + var group1 = CanvasGeometry.CreateGroup(null, new[] { elli1, rect1 }, CanvasFilledRegionDetermination.Alternate); - var elli2 = CanvasGeometry.CreateCircle(device, left, top, diaginal); - var group2 = CanvasGeometry.CreateGroup(device, new[] { elli2, rect1 }, CanvasFilledRegionDetermination.Alternate); + var elli2 = CanvasGeometry.CreateCircle(null, left, top, diaginal); + var group2 = CanvasGeometry.CreateGroup(null, new[] { elli2, rect1 }, CanvasFilledRegionDetermination.Alternate); var ellipse = BootStrapper.Current.Compositor.CreatePathGeometry(new CompositionPath(group2)); var clip = BootStrapper.Current.Compositor.CreateGeometricClip(ellipse); diff --git a/Telegram/Controls/LottieView.cs b/Telegram/Controls/LottieView.cs index 6f27753d03..78b813a06b 100644 --- a/Telegram/Controls/LottieView.cs +++ b/Telegram/Controls/LottieView.cs @@ -4,7 +4,6 @@ // Distributed under the GNU General Public License v3.0. (See accompanying // file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) // - namespace Telegram.Controls { public interface IPlayerView diff --git a/Telegram/Controls/Media/Icons.cs b/Telegram/Controls/Media/Icons.cs index e51c71b802..56bdfa8403 100644 --- a/Telegram/Controls/Media/Icons.cs +++ b/Telegram/Controls/Media/Icons.cs @@ -65,7 +65,7 @@ public partial class Icons public const string CodeFilled16 = "\uEA5A"; public const string CodeBlockFilled16 = "\uEA3D"; public const string QuoteBlockFilled16 = "\uEA01"; - public const string QuoteBlock = "\uEA3E"; + public const string QuoteBlock = "\uEAC7"; public const string Globe = "\uE774"; public const string Loading = "\uE1CD"; @@ -133,6 +133,7 @@ public partial class Icons public const string ArrowRedo = "\uE7A6"; public const string Cut = "\uE8C6"; public const string DocumentCopy = "\uE8C8"; + public const string Copy = "\uEAC9"; public const string ClipboardPaste = "\uE77F"; public const string Translate = "\uE97D"; @@ -281,6 +282,7 @@ public partial class Icons public const string SaveAsLocked = "\uEA2B"; public const string FolderOpen = "\uE838"; public const string OpenIn = "\uE7AC"; + public const string OpenWith = "\uEAC8"; public const string PersonCircle = "\uE9A9"; public const string ArrowExit = "\uE999"; diff --git a/Telegram/Controls/MenuFlyoutReadDate.cs b/Telegram/Controls/MenuFlyoutReadDate.cs index f0e05e781c..2636a12a7e 100644 --- a/Telegram/Controls/MenuFlyoutReadDate.cs +++ b/Telegram/Controls/MenuFlyoutReadDate.cs @@ -1,4 +1,10 @@ -using Windows.UI.Xaml; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace Telegram.Controls diff --git a/Telegram/Controls/Messages/Content/AudioContent.xaml b/Telegram/Controls/Messages/Content/AudioContent.xaml index 53152401e6..baa2c4d944 100644 --- a/Telegram/Controls/Messages/Content/AudioContent.xaml +++ b/Telegram/Controls/Messages/Content/AudioContent.xaml @@ -68,7 +68,10 @@ Grid.Column="1" /> diff --git a/Telegram/Controls/Messages/Content/CallContent.xaml b/Telegram/Controls/Messages/Content/CallContent.xaml index 1b4d5221a8..635f5edb07 100644 --- a/Telegram/Controls/Messages/Content/CallContent.xaml +++ b/Telegram/Controls/Messages/Content/CallContent.xaml @@ -157,7 +157,10 @@ Margin="0,1,4,-1" Grid.Row="1" /> diff --git a/Telegram/Controls/Messages/Content/ChecklistTaskContent.xaml b/Telegram/Controls/Messages/Content/ChecklistTaskContent.xaml index c8887f81c6..cda5075a85 100644 --- a/Telegram/Controls/Messages/Content/ChecklistTaskContent.xaml +++ b/Telegram/Controls/Messages/Content/ChecklistTaskContent.xaml @@ -92,42 +92,41 @@ - + + + - + - - + Margin="0,8,0,0" /> - - + diff --git a/Telegram/Controls/Messages/Content/DocumentContent.xaml b/Telegram/Controls/Messages/Content/DocumentContent.xaml index 31faaa8f75..762037659d 100644 --- a/Telegram/Controls/Messages/Content/DocumentContent.xaml +++ b/Telegram/Controls/Messages/Content/DocumentContent.xaml @@ -52,7 +52,10 @@ Grid.Column="1" /> diff --git a/Telegram/Controls/Messages/Content/StickerContent.xaml.cs b/Telegram/Controls/Messages/Content/StickerContent.xaml.cs index b7c731dbb2..285f3c5ec0 100644 --- a/Telegram/Controls/Messages/Content/StickerContent.xaml.cs +++ b/Telegram/Controls/Messages/Content/StickerContent.xaml.cs @@ -230,6 +230,11 @@ private Sticker GetContent(MessageViewModel message, out bool premium) premium = sticker.IsPremium; return sticker.Sticker; } + else if (content is MessageAnimatedEmoji animatedEmoji) + { + premium = false; + return animatedEmoji.AnimatedEmoji.Sticker; + } else if (content is MessageText text && text.LinkPreview?.Type is LinkPreviewTypeSticker previewSticker) { premium = false; diff --git a/Telegram/Controls/Messages/Content/VideoContent.xaml b/Telegram/Controls/Messages/Content/VideoContent.xaml index 08baa76f92..cbc09ff79b 100644 --- a/Telegram/Controls/Messages/Content/VideoContent.xaml +++ b/Telegram/Controls/Messages/Content/VideoContent.xaml @@ -55,6 +55,11 @@ TextWrapping="NoWrap" Typography.NumeralAlignment="Tabular" /> + + diff --git a/Telegram/Controls/Messages/Content/VideoContent.xaml.cs b/Telegram/Controls/Messages/Content/VideoContent.xaml.cs index 6508a8c88c..31363cd5b1 100644 --- a/Telegram/Controls/Messages/Content/VideoContent.xaml.cs +++ b/Telegram/Controls/Messages/Content/VideoContent.xaml.cs @@ -8,6 +8,7 @@ using Telegram.Common; using Telegram.Controls.Media; using Telegram.Converters; +using Telegram.Services; using Telegram.Streams; using Telegram.Td.Api; using Telegram.ViewModels; @@ -52,6 +53,7 @@ public VideoContent(MessageViewModel message, PaidMediaVideo paidMedia = null, b private AnimatedImage Player; private FileButton Overlay; private TextBlock Subtitle; + private ProgressBar Indicator; private bool _templateApplied; protected override void OnApplyTemplate() @@ -63,6 +65,7 @@ protected override void OnApplyTemplate() Player = GetTemplateChild(nameof(Player)) as AnimatedImage; Overlay = GetTemplateChild(nameof(Overlay)) as FileButton; Subtitle = GetTemplateChild(nameof(Subtitle)) as TextBlock; + Indicator = GetTemplateChild(nameof(Indicator)) as ProgressBar; ButtonDrag = new AutomaticDragHelper(Button, true); ButtonDrag.StartDetectingDrag(); @@ -90,7 +93,7 @@ public void UpdateMessage(MessageViewModel message) _message = message; - var video = GetContent(message, out Photo cover, out bool hasSpoiler, out bool isSecret); + var video = GetContent(message, out Photo cover, out AlternativeVideo lowQuality, out bool hasSpoiler, out bool isSecret); if (video == null || !_templateApplied) { _hidden = (prevId != nextId || _hidden) && hasSpoiler; @@ -116,38 +119,67 @@ public void UpdateMessage(MessageViewModel message) minithumbnail = video.Minithumbnail; } + UpdateMessageContentOpened(message); UpdateThumbnail(message, thumbnail, minithumbnail, true, isSecret, hasSpoiler); - UpdateManager.Subscribe(this, message, video.VideoValue, ref _fileToken, UpdateFile); - UpdateFile(message, video.VideoValue); + UpdateManager.Subscribe(this, message, lowQuality?.Video ?? video.VideoValue, ref _fileToken, UpdateFile); + UpdateFile(message, lowQuality?.Video ?? video.VideoValue, video, lowQuality, hasSpoiler, isSecret); + } + + private bool _indicatorCollapsed = true; + + private void UpdatePosition(double position, double duration) + { + if (duration >= 30) + { + if (_indicatorCollapsed) + { + _indicatorCollapsed = false; + Indicator.Visibility = Visibility.Visible; + } + + Indicator.Maximum = duration; + Indicator.Value = position; + } + else if (!_indicatorCollapsed) + { + _indicatorCollapsed = true; + Indicator.Visibility = Visibility.Collapsed; + } } public void UpdateMessageContentOpened(MessageViewModel message) { - if (message.SelfDestructType is MessageSelfDestructTypeTimer) + if (message.Content is MessageVideo video && message.Delegate.Settings.Video.TryGetPosition(video.Video.VideoValue, out double position)) { - //Timer.Maximum = message.Ttl; - //Timer.Value = DateTime.Now.AddSeconds(message.TtlExpiresIn); + UpdatePosition(position, video.Video.Duration); + } + else + { + UpdatePosition(0, 0); } } private void UpdateFile(object target, File file) { - UpdateFile(_message, file); + var video = GetContent(_message, out Photo cover, out var lowQuality, out bool hasSpoiler, out bool isSecret); + if (video != null && _templateApplied) + { + UpdateFile(_message, file, video, lowQuality, hasSpoiler, isSecret); + } } - private void UpdateFile(MessageViewModel message, File file) + private void UpdateFile(MessageViewModel message, File file, Video video, AlternativeVideo lowQuality, bool hasSpoiler, bool isSecret) { - var video = GetContent(message, out _, out bool hasSpoiler, out bool isSecret); if (video == null || !_templateApplied) { return; } - if (video.VideoValue.Id != file.Id) - { - return; - } + //if (video.VideoValue.Id != file.Id) + //{ + // return; + //} if (isSecret) { @@ -215,9 +247,23 @@ private void UpdateFile(MessageViewModel message, File file) } } } - else if (message.Content is MessageVideo messageVideo && messageVideo.IsHls()) + else if (lowQuality != null) { - UpdateSource(null, null); + if (!hasSpoiler && message.Delegate.CanBeDownloaded(video, file)) + { + _message.ClientService.DownloadFile(file.Id, 32); + + if (lowQuality != null) + { + _message.ClientService.DownloadFile(lowQuality.HlsFile.Id, 32); + } + + UpdateSource(message, file); + } + else + { + UpdateSource(null, null); + } Button.SetGlyph(file.Id, message.SendingState is MessageSendingStatePending && message.MediaAlbumId != 0 ? MessageContentState.Confirm : MessageContentState.Play); Button.Progress = 0; @@ -229,7 +275,7 @@ private void UpdateFile(MessageViewModel message, File file) else { var size = Math.Max(file.Size, file.ExpectedSize); - if (file.Local.IsDownloadingActive) + if (file.Local.IsDownloadingActive && !message.ClientService.IsDownloadFilePartial(file.Id)) { if (!hasSpoiler && message.Delegate.CanBeDownloaded(video, file)) { @@ -259,7 +305,7 @@ private void UpdateFile(MessageViewModel message, File file) if (generating) { - Subtitle.Text = video.GetDuration() + Environment.NewLine + string.Format("{0}%", file.Local.DownloadedSize); + Subtitle.Text = video.GetDuration() + Environment.NewLine + Strings.ProcessingVideo; } else { @@ -283,6 +329,11 @@ private void UpdateFile(MessageViewModel message, File file) } else { + if (_message.Delegate.Settings.AutoDownload.PreloadLargeVideos && SettingsService.Current.Diagnostics.VideoPreloadDebug) + { + VideoPreloader.Current.Load(_message.ClientService, file, video.Duration); + } + UpdateSource(null, null); } } @@ -311,7 +362,7 @@ private void UpdateFile(MessageViewModel message, File file) private void UpdateThumbnail(object target, File file) { - var video = GetContent(_message, out Photo cover, out bool hasSpoiler, out bool isSecret); + var video = GetContent(_message, out Photo cover, out _, out bool hasSpoiler, out bool isSecret); if (video == null || !_templateApplied) { return; @@ -416,13 +467,13 @@ private void UpdateSource(MessageViewModel message, File file) private void Player_PositionChanged(object sender, AnimatedImagePositionChangedEventArgs e) { - var video = GetContent(_message, out _, out _, out _); + var video = GetContent(_message, out _, out _, out _, out _); if (video == null) { return; } - var position = TimeSpan.FromSeconds(video.Duration - e.Position); + var position = TimeSpan.FromSeconds(video.Duration - Math.Truncate(e.Position)); if (position.TotalHours >= 1) { Subtitle.Text = position.ToString("h\\:mm\\:ss"); @@ -431,6 +482,8 @@ private void Player_PositionChanged(object sender, AnimatedImagePositionChangedE { Subtitle.Text = position.ToString("mm\\:ss"); } + + UpdatePosition(e.Position, Player.IsPlaying ? video.Duration : 0); } public void Recycle() @@ -464,9 +517,10 @@ public bool IsValid(MessageContent content, bool primary) return false; } - private Video GetContent(MessageViewModel message, out Photo cover, out bool hasSpoiler, out bool isSecret) + private Video GetContent(MessageViewModel message, out Photo cover, out AlternativeVideo lowQuality, out bool hasSpoiler, out bool isSecret) { cover = null; + lowQuality = null; hasSpoiler = false; isSecret = false; @@ -484,6 +538,11 @@ private Video GetContent(MessageViewModel message, out Photo cover, out bool has var content = message.GeneratedContent ?? message.Content; if (content is MessageVideo video) { + if (video.AlternativeVideos.Count > 0) + { + lowQuality = video.AlternativeVideos[0]; + } + cover = video.Cover; hasSpoiler = video.HasSpoiler; isSecret = video.IsSecret; @@ -512,7 +571,7 @@ private Video GetContent(MessageViewModel message, out Photo cover, out bool has private void Button_Click(object sender, RoutedEventArgs e) { - var video = GetContent(_message, out _, out bool hasSpoiler, out bool isSecret); + var video = GetContent(_message, out _, out _, out bool hasSpoiler, out bool isSecret); if (video == null || isSecret) { return; @@ -563,7 +622,7 @@ private void Button_DragStarting(UIElement sender, DragStartingEventArgs args) private void Play_Click(object sender, RoutedEventArgs e) { - var video = GetContent(_message, out _, out bool hasSpoiler, out bool isSecret); + var video = GetContent(_message, out _, out _, out bool hasSpoiler, out bool isSecret); if (video == null) { return; @@ -648,7 +707,14 @@ private void Play_Click(object sender, RoutedEventArgs e) return; } - _message.Delegate.OpenMedia(_message, this); + if (_indicatorCollapsed || _message.Delegate.Settings.Video.HasPosition(video.VideoValue)) + { + _message.Delegate.OpenMedia(_message, this); + } + else + { + _message.Delegate.OpenMedia(_message, this, Indicator.Value); + } } } } diff --git a/Telegram/Controls/Messages/Content/VoiceNoteContent.xaml b/Telegram/Controls/Messages/Content/VoiceNoteContent.xaml index f123d9bdba..2b42e4f961 100644 --- a/Telegram/Controls/Messages/Content/VoiceNoteContent.xaml +++ b/Telegram/Controls/Messages/Content/VoiceNoteContent.xaml @@ -175,21 +175,23 @@ - - - - + + + + + + @@ -242,10 +244,13 @@ + HorizontalAlignment="Stretch" /> diff --git a/Telegram/Controls/Messages/Content/VoiceNoteContent.xaml.cs b/Telegram/Controls/Messages/Content/VoiceNoteContent.xaml.cs index cde8a2187c..aa1842f268 100644 --- a/Telegram/Controls/Messages/Content/VoiceNoteContent.xaml.cs +++ b/Telegram/Controls/Messages/Content/VoiceNoteContent.xaml.cs @@ -377,11 +377,15 @@ private void UpdateFile(MessageViewModel message, File file) Button.SetGlyph(file.Id, MessageContentState.Downloading); Button.Progress = (double)file.Local.DownloadedSize / size; + + UpdateDuration(); } else if (file.Remote.IsUploadingActive || message.SendingState is MessageSendingStateFailed || (message.SendingState is MessageSendingStatePending && !file.Remote.IsUploadingCompleted)) { Button.SetGlyph(file.Id, MessageContentState.Uploading); Button.Progress = (double)file.Remote.UploadedSize / size; + + UpdateDuration(); } else if (canBeDownloaded) { diff --git a/Telegram/Controls/Messages/EmojiMenuFlyout.xaml.cs b/Telegram/Controls/Messages/EmojiMenuFlyout.xaml.cs index 4535c4ca3a..118b2d2545 100644 --- a/Telegram/Controls/Messages/EmojiMenuFlyout.xaml.cs +++ b/Telegram/Controls/Messages/EmojiMenuFlyout.xaml.cs @@ -8,6 +8,7 @@ using System.Numerics; using Telegram.Common; using Telegram.Controls.Drawers; +using Telegram.Navigation; using Telegram.Services; using Telegram.Td.Api; using Telegram.ViewModels; @@ -529,8 +530,6 @@ private void OnStatusClick(object sender, EmojiDrawerItemClickEventArgs e) { _popup.IsOpen = false; - EmojiSelected?.Invoke(this, new EmojiSelectedEventArgs(sticker.ToReactionType())); - if (_story != null) { StoryToggleReaction(sticker.ToReactionType()); @@ -547,6 +546,10 @@ private void OnStatusClick(object sender, EmojiDrawerItemClickEventArgs e) { _clientService.Send(new SetDefaultReactionType(sticker.ToReactionType())); } + else + { + EmojiSelected?.Invoke(this, new EmojiSelectedEventArgs(sticker.ToReactionType())); + } } } @@ -571,6 +574,12 @@ private async void ChooseStatus(StickerViewModel sticker) private async void StoryToggleReaction(ReactionType reaction) { + if (reaction is ReactionTypeCustomEmoji && !_story.ClientService.IsPremium) + { + ToastPopup.ShowFeaturePromo(WindowContext.GetNavigationService(this), new PremiumFeatureUniqueReactions()); + return; + } + if (_story.ChosenReactionType != null && _story.ChosenReactionType.AreTheSame(reaction)) { _story.ClientService.Send(new SetStoryReaction(_story.ChatId, _story.StoryId, null, true)); @@ -588,6 +597,12 @@ private async void StoryToggleReaction(ReactionType reaction) private async void MessageToggleReaction(ReactionType reaction) { + if (reaction is ReactionTypeCustomEmoji && !_message.ClientService.IsPremium) + { + ToastPopup.ShowFeaturePromo(_message.Delegate.NavigationService, new PremiumFeatureUniqueReactions()); + return; + } + if (_message.InteractionInfo != null && _message.InteractionInfo.Reactions.IsChosen(reaction)) { _message.ClientService.Send(new RemoveMessageReaction(_message.ChatId, _message.Id, reaction)); diff --git a/Telegram/Controls/Messages/MessageBubble.xaml b/Telegram/Controls/Messages/MessageBubble.xaml index 1fe2c6581a..13b7c530c4 100644 --- a/Telegram/Controls/Messages/MessageBubble.xaml +++ b/Telegram/Controls/Messages/MessageBubble.xaml @@ -286,12 +286,13 @@ Foreground="{ThemeResource MessageHeaderForegroundBrush}" HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" + BorderThickness="0" Padding="0" Grid.Column="1" Grid.Row="2"> + Padding="10,4,10,6"> @@ -321,11 +322,12 @@ - + diff --git a/Telegram/Controls/Messages/MessageBubble.xaml.cs b/Telegram/Controls/Messages/MessageBubble.xaml.cs index 63d95c583d..2d635e645f 100644 --- a/Telegram/Controls/Messages/MessageBubble.xaml.cs +++ b/Telegram/Controls/Messages/MessageBubble.xaml.cs @@ -99,7 +99,7 @@ public bool HasFloatingElements } var content = _message?.GeneratedContent ?? _message?.Content; - if (content is MessageSticker or MessageDice or MessageVideoNote or MessageBigEmoji) + if (content is MessageSticker or MessageAnimatedEmoji or MessageDice or MessageVideoNote or MessageBigEmoji) { return true; } @@ -159,7 +159,7 @@ public void UpdateQuery(string text, bool invalidate = true) private ReactionsPanel Reactions; private ReactionsPanel MediaReactions; - private ReplyMarkupPanel Markup; + private ReplyMarkupInlinePanel Markup; private Border Action; private GlyphButton ActionButton; @@ -524,7 +524,7 @@ public void UpdateAttach(MessageViewModel message) var content = message.GeneratedContent ?? message.Content; if (message.ReplyMarkup is ReplyMarkupInlineKeyboard) { - if (content is MessageSticker or MessageDice or MessageVideoNote or MessageBigEmoji) + if (content is MessageSticker or MessageAnimatedEmoji or MessageDice or MessageVideoNote or MessageBigEmoji) { _hasReplyMarkup = false; SetCorners(0, 0, 0, 0); @@ -537,12 +537,12 @@ public void UpdateAttach(MessageViewModel message) if (Markup != null) { - Markup.CornerRadius = new CornerRadius(small, small, bottomRight, bottomLeft); + Markup.CornerRadius = new Vector2(bottomRight, bottomLeft); } } else { - if (content is MessageSticker or MessageDice or MessageVideoNote or MessageBigEmoji) + if (content is MessageSticker or MessageAnimatedEmoji or MessageDice or MessageVideoNote or MessageBigEmoji) { _hasReplyMarkup = false; SetCorners(0, 0, 0, 0); @@ -563,7 +563,7 @@ public void UpdateAttach(MessageViewModel message) { if (message.IsOutgoing && !message.IsSaved) { - if (message.Content is MessageSticker or MessageVideoNote) + if (message.Content is MessageSticker or MessageAnimatedEmoji or MessageVideoNote) { Margin = new Thickness(12, top, 12, 0); } @@ -574,7 +574,7 @@ public void UpdateAttach(MessageViewModel message) } else { - if (message.Content is MessageSticker or MessageVideoNote) + if (message.Content is MessageSticker or MessageAnimatedEmoji or MessageVideoNote) { Margin = new Thickness(12, top, 12, 0); } @@ -586,7 +586,7 @@ public void UpdateAttach(MessageViewModel message) } else { - if (message.Content is MessageSticker or MessageVideoNote) + if (message.Content is MessageSticker or MessageAnimatedEmoji or MessageVideoNote) { Margin = new Thickness(12, top, 12, 0); } @@ -1315,7 +1315,7 @@ private void UpdateMessageReplyMarkup(MessageViewModel message) { if (Markup == null) { - Markup = GetTemplateChild(nameof(Markup)) as ReplyMarkupPanel; + Markup = GetTemplateChild(nameof(Markup)) as ReplyMarkupInlinePanel; Markup.InlineButtonClick += ReplyMarkup_ButtonClick; } @@ -1394,7 +1394,7 @@ public void UpdateMessageInteractionInfo(MessageViewModel message) UpdateAction(message); var content = message.GeneratedContent ?? message.Content; - if (content is MessageSticker or MessageDice or MessageVideoNote or MessageBigEmoji) + if (content is MessageSticker or MessageAnimatedEmoji or MessageDice or MessageVideoNote or MessageBigEmoji) { if (Thread != null) { @@ -1495,7 +1495,7 @@ public void UpdateMessageReactions(MessageViewModel message, bool animate) var footer = Grid.GetRow(Footer); var content = message.GeneratedContent ?? message.Content; - if (content is MessageSticker or MessageDice or MessageVideoNote or MessageBigEmoji || (media == footer && IsFullMedia(content))) + if (content is MessageSticker or MessageAnimatedEmoji or MessageDice or MessageVideoNote or MessageBigEmoji || (media == footer && IsFullMedia(content))) { Reactions?.UpdateMessageReactions(null); @@ -1629,7 +1629,7 @@ public void UpdateMessageContent(MessageViewModel message) Grid.SetRow(Message, caption ? 4 : aboveMedia ? 2 : 5); Panel.Placeholder = caption; } - else if (content is MessageSticker or MessageDice or MessageVideoNote or MessageBigEmoji) + else if (content is MessageSticker or MessageAnimatedEmoji or MessageDice or MessageVideoNote or MessageBigEmoji) { ContentPanel.Padding = new Thickness(0); Media.Margin = new Thickness(0); @@ -1773,11 +1773,7 @@ public void UpdateMessageContent(MessageViewModel message) { Constraint = message }, - MessageAnimatedEmoji => new Border - { - Width = 180 * message.ClientService.Config.GetNamedNumber("emojies_animated_zoom", 0.625f), - Height = 180 * message.ClientService.Config.GetNamedNumber("emojies_animated_zoom", 0.625f) - }, + MessageAnimatedEmoji => new StickerContent(message), MessageUnsupported => new UnsupportedContent(message), _ => null }; @@ -1808,7 +1804,7 @@ public void UpdateMessageText(MessageViewModel message) _ => message.Text }; - if (textz != null) + if (textz != null && message.Content is not MessageAnimatedEmoji) { var fontSize = 0d; @@ -2290,7 +2286,7 @@ public void AnimateSendout(float xTranslate, float xScale, float yScale, float f var textOffsetX = 0f; var textOffsetY = 0f; - if (content is MessageSticker or MessageDice) + if (content is MessageSticker or MessageAnimatedEmoji or MessageDice) { headerOffsetY = reply ? 46 : 0; textOffsetX = ContentPanel.ActualSize.X - Media.ActualSize.X; // - 10; @@ -2320,7 +2316,7 @@ public void AnimateSendout(float xTranslate, float xScale, float yScale, float f textOffset.DelayTime = TimeSpan.FromMilliseconds(delay); textOffset.DelayBehavior = AnimationDelayBehavior.SetInitialValueBeforeDelay; - if (content is MessageSticker or MessageDice) + if (content is MessageSticker or MessageAnimatedEmoji or MessageDice) { media.StartAnimation("Translation", textOffset); } @@ -2354,7 +2350,7 @@ private void OnSizeChanged(object sender, SizeChangedEventArgs e) } var content = message.GeneratedContent ?? message.Content; - if (content is MessageSticker or MessageDice or MessageVideoNote or MessageBigEmoji) + if (content is MessageSticker or MessageAnimatedEmoji or MessageDice or MessageVideoNote or MessageBigEmoji) { return; } @@ -3401,6 +3397,18 @@ protected override Size MeasureOverride(Size availableSize) { constraint = stickerMessage.Sticker; } + else if (constraint is MessageAnimatedEmoji animatedEmojiMessage) + { + if (animatedEmojiMessage.AnimatedEmoji.Sticker != null) + { + constraint = animatedEmojiMessage.AnimatedEmoji.Sticker; + } + else + { + width = animatedEmojiMessage.AnimatedEmoji.StickerWidth; + height = animatedEmojiMessage.AnimatedEmoji.StickerHeight; + } + } else if (constraint is MessageAsyncStory storyMessage) { width = 720; diff --git a/Telegram/Controls/Messages/MessageForwardHeader.xaml.cs b/Telegram/Controls/Messages/MessageForwardHeader.xaml.cs index 84d2e746bb..1f96dfa040 100644 --- a/Telegram/Controls/Messages/MessageForwardHeader.xaml.cs +++ b/Telegram/Controls/Messages/MessageForwardHeader.xaml.cs @@ -1,4 +1,10 @@ -using Microsoft.Graphics.Canvas.Geometry; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using Microsoft.Graphics.Canvas.Geometry; using System; using System.Collections.Generic; using System.Linq; diff --git a/Telegram/Controls/Messages/MessageReferenceBase.cs b/Telegram/Controls/Messages/MessageReferenceBase.cs index 4c8a3d2c82..bd7395756c 100644 --- a/Telegram/Controls/Messages/MessageReferenceBase.cs +++ b/Telegram/Controls/Messages/MessageReferenceBase.cs @@ -167,29 +167,34 @@ public void UpdateMessage(MessageViewModel message, bool loading, string title) } } - public void UpdateFile(MessageViewModel message, File file) - { - // TODO: maybe something better... - UpdateMessageReply(message); - } - - private void UpdateThumbnail(MessageViewModel message, PhotoSize photoSize, Minithumbnail minithumbnail) + private void UpdateThumbnail(MessageViewModel message, PhotoSize photoSize, Minithumbnail minithumbnail, bool hasSpoiler = false) { if (photoSize != null && photoSize.Photo.Local.IsDownloadingCompleted) { - double ratioX = (double)36 / photoSize.Width; - double ratioY = (double)36 / photoSize.Height; - double ratio = Math.Max(ratioX, ratioY); + BitmapImage source; + if (hasSpoiler) + { + source = new BitmapImage(); + PlaceholderHelper.GetBlurred(source, photoSize.Photo.Local.Path, 15); + } + else + { + double ratioX = (double)36 / photoSize.Width; + double ratioY = (double)36 / photoSize.Height; + double ratio = Math.Max(ratioX, ratioY); + + var width = (int)(photoSize.Width * ratio); + var height = (int)(photoSize.Height * ratio); - var width = (int)(photoSize.Width * ratio); - var height = (int)(photoSize.Height * ratio); + source = UriEx.ToBitmap(photoSize.Photo.Local.Path, width, height); + } ShowThumbnail(); - SetThumbnail(UriEx.ToBitmap(photoSize.Photo.Local.Path, width, height)); + SetThumbnail(source); } else { - UpdateThumbnail(minithumbnail); + UpdateThumbnail(minithumbnail, hasSpoiler); if (photoSize != null && photoSize.Photo.Local.CanBeDownloaded && !photoSize.Photo.Local.IsDownloadingActive) { @@ -198,23 +203,34 @@ private void UpdateThumbnail(MessageViewModel message, PhotoSize photoSize, Mini } } - private void UpdateThumbnail(MessageViewModel message, Thumbnail thumbnail, Minithumbnail minithumbnail, CornerRadius radius = default) + private void UpdateThumbnail(MessageViewModel message, Thumbnail thumbnail, Minithumbnail minithumbnail, bool hasSpoiler = false, CornerRadius radius = default) { if (thumbnail != null && thumbnail.File.Local.IsDownloadingCompleted && thumbnail.Format is ThumbnailFormatJpeg) { - double ratioX = (double)36 / thumbnail.Width; - double ratioY = (double)36 / thumbnail.Height; - double ratio = Math.Max(ratioX, ratioY); + BitmapImage source; + if (hasSpoiler) + { + source = new BitmapImage(); + PlaceholderHelper.GetBlurred(source, thumbnail.File.Local.Path, 15); + } + else + { + double ratioX = (double)36 / thumbnail.Width; + double ratioY = (double)36 / thumbnail.Height; + double ratio = Math.Max(ratioX, ratioY); + + var width = (int)(thumbnail.Width * ratio); + var height = (int)(thumbnail.Height * ratio); - var width = (int)(thumbnail.Width * ratio); - var height = (int)(thumbnail.Height * ratio); + source = UriEx.ToBitmap(thumbnail.File.Local.Path, width, height); + } ShowThumbnail(radius); - SetThumbnail(UriEx.ToBitmap(thumbnail.File.Local.Path, width, height)); + SetThumbnail(source); } else { - UpdateThumbnail(minithumbnail); + UpdateThumbnail(minithumbnail, hasSpoiler, radius); if (thumbnail != null && thumbnail.File.Local.CanBeDownloaded && !thumbnail.File.Local.IsDownloadingActive) { @@ -223,35 +239,44 @@ private void UpdateThumbnail(MessageViewModel message, Thumbnail thumbnail, Mini } } - private void UpdateThumbnail(Minithumbnail thumbnail, CornerRadius radius = default) + private void UpdateThumbnail(Minithumbnail thumbnail, bool hasSpoiler, CornerRadius radius = default) { if (thumbnail != null) { - double ratioX = (double)36 / thumbnail.Width; - double ratioY = (double)36 / thumbnail.Height; - double ratio = Math.Max(ratioX, ratioY); + BitmapImage source; + if (hasSpoiler) + { + source = new BitmapImage(); + PlaceholderHelper.GetBlurred(source, thumbnail.Data, 15); + } + else + { + double ratioX = (double)36 / thumbnail.Width; + double ratioY = (double)36 / thumbnail.Height; + double ratio = Math.Max(ratioX, ratioY); - var width = (int)(thumbnail.Width * ratio); - var height = (int)(thumbnail.Height * ratio); + var width = (int)(thumbnail.Width * ratio); + var height = (int)(thumbnail.Height * ratio); - var bitmap = new BitmapImage { DecodePixelWidth = width, DecodePixelHeight = height, DecodePixelType = DecodePixelType.Logical }; + source = new BitmapImage { DecodePixelWidth = width, DecodePixelHeight = height, DecodePixelType = DecodePixelType.Logical }; - using (var stream = new InMemoryRandomAccessStream()) - { - try + using (var stream = new InMemoryRandomAccessStream()) { - PlaceholderImageHelper.WriteBytes(thumbnail.Data, stream); - bitmap.SetSource(stream); - } - catch - { - // Throws when the data is not a valid encoded image, - // not so frequent, but if it happens during ContainerContentChanging it crashes the app. + try + { + PlaceholderImageHelper.WriteBytes(thumbnail.Data, stream); + source.SetSource(stream); + } + catch + { + // Throws when the data is not a valid encoded image, + // not so frequent, but if it happens during ContainerContentChanging it crashes the app. + } } } ShowThumbnail(radius); - SetThumbnail(bitmap); + SetThumbnail(source); } else { @@ -521,7 +546,7 @@ private void SetPhotoTemplate(MessageViewModel message, MessageSender sender, Fo if (thumbnail) { - UpdateThumbnail(message, photo.Photo.GetSmall(), photo.Photo.Minithumbnail); + UpdateThumbnail(message, photo.Photo.GetSmall(), photo.Photo.Minithumbnail, photo.HasSpoiler); } else { @@ -830,11 +855,11 @@ private void SetVideoTemplate(MessageViewModel message, MessageSender sender, Fo { if (video.Cover != null) { - UpdateThumbnail(message, video.Cover.GetSmall(), video.Cover.Minithumbnail); + UpdateThumbnail(message, video.Cover.GetSmall(), video.Cover.Minithumbnail, video.HasSpoiler); } else { - UpdateThumbnail(message, video.Video.Thumbnail, video.Video.Minithumbnail); + UpdateThumbnail(message, video.Video.Thumbnail, video.Video.Minithumbnail, video.HasSpoiler); } } else @@ -854,7 +879,7 @@ private void SetVideoNoteTemplate(MessageViewModel message, MessageSender sender false, white); - UpdateThumbnail(message, videoNote.VideoNote.Thumbnail, videoNote.VideoNote.Minithumbnail, new CornerRadius(18)); + UpdateThumbnail(message, videoNote.VideoNote.Thumbnail, videoNote.VideoNote.Minithumbnail, radius: new CornerRadius(18)); } private void SetAnimatedEmojiTemplate(MessageViewModel message, MessageSender sender, MessageAnimatedEmoji animatedEmoji, string title, bool outgoing, bool white) @@ -882,7 +907,7 @@ private void SetAnimationTemplate(MessageViewModel message, MessageSender sender manual, white); - UpdateThumbnail(message, animation.Animation.Thumbnail, animation.Animation.Minithumbnail); + UpdateThumbnail(message, animation.Animation.Thumbnail, animation.Animation.Minithumbnail, animation.HasSpoiler); } private void SetStickerTemplate(MessageViewModel message, MessageSender sender, MessageSticker sticker, string title, bool outgoing, bool white) diff --git a/Telegram/Controls/Messages/MessageService.cs b/Telegram/Controls/Messages/MessageService.cs index 85eb3fe059..4baa3e643a 100644 --- a/Telegram/Controls/Messages/MessageService.cs +++ b/Telegram/Controls/Messages/MessageService.cs @@ -251,8 +251,16 @@ private void UpdateContent(MessageViewModel message) var info = FindName("AttributeInfo") as TextBlock; var text = FindName("AttributeText") as TextBlock; - title.Text = string.Format(Strings.Gift2UniqueTitle, message.IsOutgoing ? self.FirstName : user.FullName(true)); - subtitle.Text = string.Format("{0} #{1}", upgradedGift.Gift.Title, upgradedGift.Gift.Number); + if (upgradedGift.ReceiverId.IsUser(message.ClientService.Options.MyId) && upgradedGift.ReceiverId.AreTheSame(upgradedGift.SenderId)) + { + title.Text = Strings.Gift2ActionSelfTitle; + } + else + { + title.Text = string.Format(Strings.Gift2UniqueTitle, message.IsOutgoing ? self.FirstName : user.FullName(true)); + } + + subtitle.Text = upgradedGift.Gift.ToName(); info.Text = Strings.Gift2AttributeModel + "\n" + Strings.Gift2AttributeBackdrop + "\n" + Strings.Gift2AttributeSymbol; text.Text = upgradedGift.Gift.Model.Name + "\n" + upgradedGift.Gift.Backdrop.Name + "\n" + upgradedGift.Gift.Symbol.Name; @@ -2323,7 +2331,7 @@ private static FormattedText UpdateUpgradedGift(MessageWithOwner message, Messag { if (upgradedGift.ReceiverId.IsUser(message.ClientService.Options.MyId)) { - if (message.ClientService.TryGetMessageSender(upgradedGift.SenderId, out Object outboundUser)) + if (!upgradedGift.ReceiverId.AreTheSame(upgradedGift.SenderId) && message.ClientService.TryGetMessageSender(upgradedGift.SenderId, out Object outboundUser)) { return ReplaceWithLink(Strings.ActionUniqueGiftUpgradeOutbound, outboundUser); } @@ -2380,7 +2388,7 @@ private static FormattedText UpdateUsersShared(MessageWithOwner message, Message var chat = message.Chat; if (chat != null) { - var content = ReplaceWithLink(Strings.ActionRequestedPeer, "un1", usersShared.Users.Select(x => x.UserId), message.ClientService); + var content = ReplaceWithLinks(Strings.ActionRequestedPeer, "un1", usersShared.Users.Select(x => x.UserId), message.ClientService); return ReplaceWithLink(content, "un2", chat); } else if (chat != null) diff --git a/Telegram/Controls/Messages/ReactionAsTagButton.cs b/Telegram/Controls/Messages/ReactionAsTagButton.cs index acbbd5f5e8..73c9824856 100644 --- a/Telegram/Controls/Messages/ReactionAsTagButton.cs +++ b/Telegram/Controls/Messages/ReactionAsTagButton.cs @@ -62,16 +62,11 @@ protected override void UpdateInteraction(MessageViewModel message, MessageReact if (string.IsNullOrEmpty(_tag?.Label)) { - if (Count != null) - { - Count.Visibility = Visibility.Collapsed; - } + Count.Visibility = Visibility.Collapsed; } else { - Count ??= GetTemplateChild(nameof(Count)) as AnimatedTextBlock; Count.Visibility = Visibility.Visible; - Count.Text = _tag.Label; } } @@ -88,16 +83,11 @@ private void UpdateLabel() { if (string.IsNullOrEmpty(_tag?.Label)) { - if (Count != null) - { - Count.Visibility = Visibility.Collapsed; - } + Count.Visibility = Visibility.Collapsed; } else { - Count ??= GetTemplateChild(nameof(Count)) as AnimatedTextBlock; Count.Visibility = Visibility.Visible; - Count.Text = _tag.Label; } } diff --git a/Telegram/Controls/Messages/ReactionButton.cs b/Telegram/Controls/Messages/ReactionButton.cs index e11192b79a..b39c428411 100644 --- a/Telegram/Controls/Messages/ReactionButton.cs +++ b/Telegram/Controls/Messages/ReactionButton.cs @@ -145,7 +145,7 @@ public void SetReaction(MessageViewModel message, MessageReaction reaction) Icon.Source = new ReactionFileSource(message.ClientService, reaction.Type) { UseCenterAnimation = true, - IsUnique = true + IsAnimated = false }; } } @@ -218,6 +218,7 @@ protected override void OnApplyTemplate() Overlay = GetTemplateChild(nameof(Overlay)) as Popup; Icon = GetTemplateChild(nameof(Icon)) as CustomEmojiIcon; Icon.Ready += OnReady; + Icon.LoopCompleted += OnLoopCompleted; if (_reaction != null) { @@ -234,6 +235,19 @@ private void OnReady(object sender, EventArgs e) SetUnread(_unread); } + private void OnLoopCompleted(object sender, AnimatedImageLoopCompletedEventArgs e) + { + this.BeginOnUIThread(OnLoopCompleted); + } + + private void OnLoopCompleted() + { + if (Icon?.Source is ReactionFileSource reaction && Icon.Source.IsAnimated && this.IsConnected()) + { + Icon.Source = reaction.Clone(false); + } + } + protected override void OnToggle() { //base.OnToggle(); @@ -356,6 +370,11 @@ protected async void Animate() protected void Animate(File around, bool cache) { + if (Icon?.Source is ReactionFileSource reaction && !Icon.Source.IsAnimated) + { + Icon.Source = reaction.Clone(true); + } + Icon?.Play(); var popup = Overlay; diff --git a/Telegram/Controls/Messages/ReplyMarkupInlinePanel.cs b/Telegram/Controls/Messages/ReplyMarkupInlinePanel.cs new file mode 100644 index 0000000000..4342b727df --- /dev/null +++ b/Telegram/Controls/Messages/ReplyMarkupInlinePanel.cs @@ -0,0 +1,225 @@ +// +// Copyright (c) Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; +using System.Collections.Generic; +using System.Numerics; +using Telegram.Common; +using Telegram.Controls.Media; +using Telegram.Native; +using Telegram.Td.Api; +using Telegram.ViewModels; +using Windows.Foundation; +using Windows.UI.Composition; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Hosting; + +namespace Telegram.Controls.Messages +{ + public partial class ReplyMarkupInlinePanel : Panel + { + private readonly double _keyboardHeight = 260; + + private CompositionGeometricClip _clip; + + private bool _empty = true; + + public List Rows { get; } = new(); + + public Vector2 CornerRadius { get; set; } + + public bool Update(MessageViewModel message, ReplyMarkup markup, bool inline = true) + { + if (_empty && (message == null || markup == null)) + { + return false; + } + + _empty = message == null || markup == null; + + Children.Clear(); + Rows.Clear(); + + if (markup is ReplyMarkupShowKeyboard keyboardMarkup && !inline) + { + return Update(message, keyboardMarkup); + } + else if (markup is ReplyMarkupInlineKeyboard inlineMarkup && inline) + { + return Update(message, inlineMarkup); + } + + return false; + } + + public bool Update(MessageViewModel message, ReplyMarkupInlineKeyboard inlineMarkup) + { + var rows = inlineMarkup.Rows; + + Tag = message; + + var receipt = false; + if (message != null && message.Content is MessageInvoice invoice) + { + receipt = invoice.ReceiptMessageId != 0; + + if (invoice.PaidMedia is not PaidMediaUnsupported and not null) + { + rows = null; + } + } + + if (rows == null) + { + return false; + } + + foreach (var row in rows) + { + foreach (var item in row) + { + var button = new ReplyMarkupInlineButton(item); + button.HorizontalAlignment = HorizontalAlignment.Stretch; + button.VerticalAlignment = VerticalAlignment.Stretch; + button.Text = item.Text.Replace('\n', ' '); + button.Click += Button_Click; + + switch (item.Type) + { + case InlineKeyboardButtonTypeUrl typeUrl: + button.Glyph = "\uE9B7"; + Extensions.SetToolTip(button, typeUrl.Url); + break; + case InlineKeyboardButtonTypeLoginUrl: + button.Glyph = "\uE9B7"; + break; + case InlineKeyboardButtonTypeSwitchInline: + button.Glyph = "\uEE35"; + break; + case InlineKeyboardButtonTypeBuy: + if (receipt) + { + button.Content = Strings.PaymentReceipt; + } + else + { + button.Content = item.Text.Replace("\u2B50", Icons.Premium + "\u200A"); + } + break; + case InlineKeyboardButtonTypeWebApp: + button.Glyph = Icons.Window16; + break; + case InlineKeyboardButtonTypeCopyText: + button.Glyph = Icons.CopyFilled16; + break; + } + + Children.Add(button); + } + + Rows.Add(row.Count); + } + + return false; + } + + private void Button_Click(object sender, RoutedEventArgs e) + { + if (sender is ReplyMarkupInlineButton button) + { + InlineButtonClick?.Invoke(this, new ReplyMarkupInlineButtonClickEventArgs(button.Button)); + } + } + + public event EventHandler InlineButtonClick; + + protected override Size MeasureOverride(Size availableSize) + { + var j = 0; + var w = 0d; + var h = 0d; + + var spacing = 2; + + foreach (var row in Rows) + { + var column = new Size(Math.Max(0, (availableSize.Width - spacing * (row - 1)) / row), availableSize.Height / Rows.Count); + var width = 0d; + var height = 0d; + + for (int i = 0; i < row; i++) + { + var child = Children[j + i]; + child.Measure(column); + width = Math.Max(width, child.DesiredSize.Width); + height = Math.Max(height, child.DesiredSize.Height); + } + + var final = (width * row) + (spacing * (row - 1)); + if (final > availableSize.Width) + { + w = availableSize.Width; + } + else + { + w = Math.Max(w, final); + } + + h += height + spacing; + j += row; + } + + return new Size(w, h); + } + + protected override Size ArrangeOverride(Size finalSize) + { + var j = 0; + var y = 0d; + + var spacing = 2; + + if (_clip == null) + { + var visual = ElementComposition.GetElementVisual(this); + visual.Clip = _clip = visual.Compositor.CreateGeometricClip(); + } + + var rows = new List>(Rows.Count); + + foreach (var row in Rows) + { + var column = (finalSize.Width - spacing * (row - 1)) / row; + var height = 0d; + + var x = 0d; + + var clip = new List(row); + + y += spacing; + + for (int i = 0; i < row; i++) + { + var child = Children[j + i]; + child.Arrange(new Rect(x, y, column, child.DesiredSize.Height)); + clip.Add(new Rect(x, y, column, child.DesiredSize.Height)); + + height = Math.Max(height, child.DesiredSize.Height); + x += column + spacing; + } + + rows.Add(clip); + + y += height; + j += row; + } + + _clip.Geometry = _clip.Compositor.CreatePathGeometry(PlaceholderImageHelper.Foreground.GetReplyMarkupClip(rows, CornerRadius.X, CornerRadius.Y)); + return finalSize; + } + } +} diff --git a/Telegram/Controls/Messages/SavedMessagesTagButton.cs b/Telegram/Controls/Messages/SavedMessagesTagButton.cs index 659108fc60..310fb61c0f 100644 --- a/Telegram/Controls/Messages/SavedMessagesTagButton.cs +++ b/Telegram/Controls/Messages/SavedMessagesTagButton.cs @@ -99,7 +99,7 @@ public void SetReaction(ChatSearchViewModel viewModel, SavedMessagesTag reaction Icon.Source = new ReactionFileSource(viewModel.ClientService, reaction.Tag) { UseCenterAnimation = true, - IsUnique = true + IsAnimated = false }; } } @@ -122,9 +122,6 @@ private void UpdateInteraction(ChatSearchViewModel viewModel, SavedMessagesTag t //} //else if (interaction.TotalCount > interaction.RecentSenderIds.Count) //{ - Count ??= GetTemplateChild(nameof(Count)) as AnimatedTextBlock; - Count.Visibility = Visibility.Visible; - var builder = new StringBuilder(tag.Label); if (builder.Length > 0) { @@ -171,6 +168,7 @@ protected override void OnApplyTemplate() { LayoutRoot = GetTemplateChild(nameof(LayoutRoot)) as Grid; Icon = GetTemplateChild(nameof(Icon)) as CustomEmojiIcon; + Count = GetTemplateChild(nameof(Count)) as AnimatedTextBlock; if (_reaction != null) { diff --git a/Telegram/Controls/NativeVideoPlayer.xaml.cs b/Telegram/Controls/NativeVideoPlayer.xaml.cs index bb32375a2b..62854906f1 100644 --- a/Telegram/Controls/NativeVideoPlayer.xaml.cs +++ b/Telegram/Controls/NativeVideoPlayer.xaml.cs @@ -1,7 +1,15 @@ -using LibVLCSharp.Platforms.Windows; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using LibVLCSharp.Platforms.Windows; using LibVLCSharp.Shared; using System; +using System.Diagnostics; using Telegram.Common; +using Telegram.Services; using Telegram.Streams; using Telegram.Td.Api; using Telegram.ViewModels.Gallery; @@ -15,6 +23,7 @@ public sealed partial class NativeVideoPlayer : VideoPlayerBase private GalleryMedia _video; private long _bufferedToken; + private long _httpServerToken; private long _initialPosition; @@ -58,6 +67,7 @@ private void OnDisconnected(object sender, RoutedEventArgs e) _core = null; } + MediaHttpServer.Stop(ref _httpServerToken); UpdateManager.Unsubscribe(this, ref _bufferedToken); } @@ -78,7 +88,15 @@ public override void Play(GalleryMedia video, double position) } else { - _core.Play(new RemoteFileStream(video.ClientService, video.File)); + if (SettingsService.Current.Diagnostics.MediaServerDebug) + { + _core.Play(MediaHttpServer.Start(video, ref _httpServerToken)); + } + else + { + _core.Play(new RemoteFileStream(video.ClientService, video.File)); + } + _core.Time = (long)(position * 1000); } @@ -88,7 +106,7 @@ public override void Play(GalleryMedia video, double position) private void UpdateBuffered(object target, File update) { var offset = update.Local.DownloadOffset + update.Local.DownloadedPrefixSize; - OnBufferedChanged(_buffered = update.Local.IsDownloadingCompleted || offset == update.Size ? 0 : offset / update.Size); + OnBufferedChanged(_buffered = update.Local.IsDownloadingCompleted || offset == update.Size ? 0 : (double)offset / update.Size); } public override void Play() @@ -147,7 +165,7 @@ public override void Clear() public override void AddTime(double value) { - _core?.AddTime((long)value); + _core?.AddTime((long)value * 1000); } public override double Position @@ -230,7 +248,15 @@ private void OnInitialized(object sender, InitializedEventArgs e) if (_video != null) { - _core.Play(new RemoteFileStream(_video.ClientService, _video.File)); + if (SettingsService.Current.Diagnostics.MediaServerDebug) + { + _core.Play(MediaHttpServer.Start(_video, ref _httpServerToken)); + } + else + { + _core.Play(new RemoteFileStream(_video.ClientService, _video.File)); + } + _core.Time = _initialPosition; } diff --git a/Telegram/Controls/PatternBackground.xaml.cs b/Telegram/Controls/PatternBackground.xaml.cs index a9c22f9b36..8b407f5d87 100644 --- a/Telegram/Controls/PatternBackground.xaml.cs +++ b/Telegram/Controls/PatternBackground.xaml.cs @@ -1,4 +1,10 @@ -using Microsoft.Graphics.Canvas.Effects; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using Microsoft.Graphics.Canvas.Effects; using Microsoft.UI.Xaml.Media; using System; using System.Collections.Generic; diff --git a/Telegram/Controls/PlaybackSlider.cs b/Telegram/Controls/PlaybackSlider.cs index 54511a7f11..becc6aaa6a 100644 --- a/Telegram/Controls/PlaybackSlider.cs +++ b/Telegram/Controls/PlaybackSlider.cs @@ -69,7 +69,7 @@ public void UpdateValue(TimeSpan position, TimeSpan duration, PlaybackState stat _duration = duration; _state = state; - if (ProgressBarIndicator == null || duration - position <= TimeSpan.Zero) + if (ProgressBarIndicator == null) { return; } @@ -80,8 +80,18 @@ public void UpdateValue(TimeSpan position, TimeSpan duration, PlaybackState stat var clip = (visual.Clip ??= compositor.CreateInsetClip()) as InsetClip; var step = (float)(position.TotalSeconds / duration.TotalSeconds); + if (double.IsNaN(step)) + { + step = 0; + } + + if (_props == null) + { + _props = compositor.CreatePropertySet(); + _props.InsertScalar("Progress", 0); + } - if (state == PlaybackState.Playing) + if (state == PlaybackState.Playing && duration - position > TimeSpan.Zero) { var linearEasing = compositor.CreateLinearEasingFunction(); var animation = compositor.CreateScalarKeyFrameAnimation(); @@ -89,24 +99,19 @@ public void UpdateValue(TimeSpan position, TimeSpan duration, PlaybackState stat animation.InsertKeyFrame(0, step, linearEasing); animation.InsertKeyFrame(1, 1, linearEasing); - if (_props == null) - { - _props = compositor.CreatePropertySet(); - _props.InsertScalar("Progress", 0); - } - - var progressAnimation = compositor.CreateExpressionAnimation("visual.Size.X - (_.Progress * visual.Size.X)"); - progressAnimation.SetReferenceParameter("_", _props); - progressAnimation.SetReferenceParameter("visual", visual); - _props.StartAnimation("Progress", animation); - clip.StartAnimation("RightInset", progressAnimation); } else { - clip.StopAnimation("RightInset"); - clip.RightInset = visual.Size.X - step * visual.Size.X; + _props.StopAnimation("Progress"); + _props.InsertScalar("Progress", step); } + + var progressAnimation = compositor.CreateExpressionAnimation("visual.Size.X - (_.Progress * visual.Size.X)"); + progressAnimation.SetReferenceParameter("_", _props); + progressAnimation.SetReferenceParameter("visual", visual); + + clip.StartAnimation("RightInset", progressAnimation); } protected override void OnPointerEntered(PointerRoutedEventArgs e) diff --git a/Telegram/Controls/ProfileGiftsCover.xaml.cs b/Telegram/Controls/ProfileGiftsCover.xaml.cs index 7092a54308..012ab420bf 100644 --- a/Telegram/Controls/ProfileGiftsCover.xaml.cs +++ b/Telegram/Controls/ProfileGiftsCover.xaml.cs @@ -1,10 +1,9 @@ -// +// // Copyright (c) Fela Ameghino 2015-2025 // // Distributed under the GNU General Public License v3.0. (See accompanying // file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) // - using Microsoft.UI.Xaml.Media; using System; using System.Collections.Generic; @@ -17,7 +16,6 @@ using Telegram.ViewModels; using Windows.Foundation; using Windows.UI; -using Windows.UI.Composition; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Hosting; @@ -55,29 +53,69 @@ public void Update(float avatarTransitionFraction, UIElement titleRoot) if (_gifts != hash || _positions == null || _frameWidth != newSize.X || _frameHeight != newSize.Y) { - var titleTransform = titleRoot.TransformToVector2(this); + GeneratePositions(avatarTransitionFraction, titleRoot); + } + + var i = 0; - var excludeRects = new RectangleF[] + foreach (var child in RootGrid.Children) + { + if (_positions == null || _positions.Count <= i) { - new RectangleF(titleTransform.X - 4, titleTransform.Y, titleRoot.ActualSize.X + 8, titleRoot.ActualSize.Y), - }; + child.Opacity = 0; + continue; + } - var positionGenerator = new OrbitGenerator( - containerSize: newSize, - centerFrame: centerFrame, - exclusionZones: excludeRects, - minimumDistance: 42.0f, - edgePadding: 5.0f, - seed: seed - ); + var iconPosition = _positions[i++]; + var itemDistanceFraction = Math.Max(0.0f, Math.Min(0.5f, (iconPosition.Distance - avatarSize.X / 2.0f) / 144.0f)); + var itemScaleFraction = OrbitGenerator.PatternScaleValueAt(fraction: Math.Min(1.0f, avatarTransitionFraction * 1.33f), t: itemDistanceFraction, reverse: false); + + var toAngle = MathF.PI * 0.18f; + var centerPosition = new OrbitGenerator.Position(distance: 0.0f, angle: iconPosition.Angle + toAngle, scale: iconPosition.Scale); + var effectivePosition = OrbitGenerator.InterpolatePosition(from: iconPosition, to: centerPosition, t: itemScaleFraction); + var effectiveAngle = toAngle * itemScaleFraction; - _positions = positionGenerator.GeneratePositions(count: 12, itemSize: new Vector2(28)); - _gifts = hash; - _frameWidth = newSize.X; - _frameHeight = newSize.Y; + var absolutePosition = effectivePosition.GetAbsolutePosition(centerFrame.Center); - RootGrid.Children.Clear(); + var visual = ElementComposition.GetElementVisual(child); + visual.Offset = new Vector3(absolutePosition, 0); + visual.Scale = new Vector3(iconPosition.Scale * (1.0f - itemScaleFraction)); + visual.RotationAngle = effectiveAngle; } + } + + private void GeneratePositions(float avatarTransitionFraction, UIElement titleRoot) + { + var newSize = new Vector2(ActualSize.X + 36, ActualSize.Y); + var seed = _seed; + + var gifts = GetPinnedGifts(out long hash); + + var avatarSize = new Vector2(120, 120); + var centerFrame = new RectangleF((-72 + newSize.X - avatarSize.X) / 2f, (-36 + 204 - avatarSize.Y) / 2f, avatarSize.X, avatarSize.Y); + + var titleTransform = titleRoot.TransformToVector2(this); + + var excludeRects = new RectangleF[] + { + new RectangleF(titleTransform.X - 4, titleTransform.Y, titleRoot.ActualSize.X + 8, titleRoot.ActualSize.Y), + }; + + var positionGenerator = new OrbitGenerator( + containerSize: newSize, + centerFrame: centerFrame, + exclusionZones: excludeRects, + minimumDistance: 42.0f, + edgePadding: 5.0f, + seed: seed + ); + + _positions = positionGenerator.GeneratePositions(count: 12, itemSize: new Vector2(28)); + _gifts = hash; + _frameWidth = newSize.X; + _frameHeight = newSize.Y; + + RootGrid.Children.Clear(); var iconPositions = _positions; if (iconPositions == null) @@ -87,13 +125,8 @@ public void Update(float avatarTransitionFraction, UIElement titleRoot) for (int i = 0; i < Math.Max(iconPositions.Count, gifts.Count); i++) { - if (i >= gifts.Count || i >= iconPositions.Count) + if (i >= gifts.Count || i >= iconPositions.Count || gifts[i].Gift is not SentGiftUpgraded upgraded) { - if (RootGrid.Children.Count > i) - { - RootGrid.Children.RemoveAt(i); - } - continue; } @@ -108,96 +141,76 @@ public void Update(float avatarTransitionFraction, UIElement titleRoot) var absolutePosition = effectivePosition.GetAbsolutePosition(centerFrame.Center); - if (gifts[i].Gift is SentGiftUpgraded upgraded) + var centerColor = upgraded.Gift.Backdrop.Colors.CenterColor.ToColor().WithBrightness(0.3f); + + var gradient = new RadialGradientBrush(); + gradient.Center = new Point(0.5, 0.5); + gradient.GradientStops.Add(new GradientStop { Color = Color.FromArgb(166, centerColor.R, centerColor.G, centerColor.B) }); + gradient.GradientStops.Add(new GradientStop { Color = Color.FromArgb(166, centerColor.R, centerColor.G, centerColor.B), Offset = 0.3 }); + gradient.GradientStops.Add(new GradientStop { Color = Color.FromArgb(0, centerColor.R, centerColor.G, centerColor.B), Offset = 1 }); + + var particles = new AnimatedImage { - Visual visual; - - if (RootGrid.Children.Count - 1 < i) - { - static Color MakeLuminous(Color color, double intensity) - { - intensity = Math.Max(0, Math.Min(1, intensity)); - - var hsl = color.ToHSL(); - - // Increase lightness and reduce saturation for a glowing effect - hsl.L = Math.Min(1, hsl.L + intensity * (1 - hsl.L) * 0.8); - hsl.S = hsl.S * (1 - intensity * 0.4); - - return hsl.ToRGB(); - } - - var centerColor = MakeLuminous(upgraded.Gift.Backdrop.Colors.CenterColor.ToColor(), 0.3); - - var gradient = new RadialGradientBrush(); - gradient.Center = new Point(0.5, 0.5); - gradient.GradientStops.Add(new GradientStop { Color = Color.FromArgb(166, centerColor.R, centerColor.G, centerColor.B) }); - gradient.GradientStops.Add(new GradientStop { Color = Color.FromArgb(166, centerColor.R, centerColor.G, centerColor.B), Offset = 0.3 }); - gradient.GradientStops.Add(new GradientStop { Color = Color.FromArgb(0, centerColor.R, centerColor.G, centerColor.B), Offset = 1 }); - - var particles = new AnimatedImage - { - Source = new ParticlesImageSource(Colors.White, ParticlesType.Status), - IsViewportAware = false, - Stretch = Stretch.UniformToFill, - DecodeFrameType = Windows.UI.Xaml.Media.Imaging.DecodePixelType.Logical, - FrameSize = new Size(36, 36), - Width = 36, - Height = 36, - Margin = new Thickness(-4) - }; - - var icon = new CustomEmojiIcon - { - Source = DelayedFileSource.FromSticker(ViewModel.ClientService, upgraded.Gift.Model.Sticker), - Width = 28, - Height = 28, - FrameSize = new Size(28, 28), - IsViewportAware = false - }; - - icon.Ready += OnReady; - - var root = new Grid - { - Opacity = 0, - Width = 28, - Height = 28, - HorizontalAlignment = HorizontalAlignment.Left, - VerticalAlignment = VerticalAlignment.Top - }; - - root.Children.Add(new Border - { - Background = gradient, - Width = 32, - Height = 32, - Margin = new Thickness(-2) - }); - - root.Children.Add(particles); - root.Children.Add(icon); - - RootGrid.Children.Add(root); - - visual = ElementComposition.GetElementVisual(root); - } - else - { - visual = ElementComposition.GetElementVisual(RootGrid.Children[i]); - } + Source = new ParticlesImageSource(Colors.White, ParticlesType.Status), + IsViewportAware = false, + Stretch = Stretch.UniformToFill, + DecodeFrameType = Windows.UI.Xaml.Media.Imaging.DecodePixelType.Logical, + FrameSize = new Size(36, 36), + Width = 36, + Height = 36, + Margin = new Thickness(-4) + }; - visual.Offset = new Vector3(absolutePosition, 0); - visual.Scale = new Vector3(iconPosition.Scale * (1.0f - itemScaleFraction)); - visual.RotationAngle = effectiveAngle; - } + var icon = new CustomEmojiIcon + { + Source = DelayedFileSource.FromSticker(ViewModel.ClientService, upgraded.Gift.Model.Sticker), + Width = 28, + Height = 28, + FrameSize = new Size(28, 28), + IsViewportAware = false + }; + + icon.Ready += OnReady; + + var root = new Grid + { + Opacity = 0, + Width = 28, + Height = 28, + HorizontalAlignment = HorizontalAlignment.Left, + VerticalAlignment = VerticalAlignment.Top + }; + + root.Children.Add(new Border + { + Background = gradient, + Width = 32, + Height = 32, + Margin = new Thickness(-2) + }); + + root.Children.Add(particles); + root.Children.Add(icon); + + RootGrid.Children.Add(root); + + var visual = ElementComposition.GetElementVisual(root); + visual.Offset = new Vector3(absolutePosition, 0); + visual.Scale = new Vector3(iconPosition.Scale * (1.0f - itemScaleFraction)); + visual.RotationAngle = effectiveAngle; } } private void OnReady(object sender, EventArgs e) { var icon = sender as CustomEmojiIcon; + var root = icon.Parent as Grid; + if (root == null) + { + return; + } + var visual = ElementComposition.GetElementVisual(root); var scale = visual.Compositor.CreateVector3KeyFrameAnimation(); diff --git a/Telegram/Controls/ProfileHeader.xaml b/Telegram/Controls/ProfileHeader.xaml index 24fe241ee0..cfacf5ed8a 100644 --- a/Telegram/Controls/ProfileHeader.xaml +++ b/Telegram/Controls/ProfileHeader.xaml @@ -85,13 +85,13 @@ Margin="-430,-32,-430,-156" /> + Loaded="Pattern_Loaded" + VerticalAlignment="Top" + Margin="-430,-32,-430,-156" + Width="1000" + Height="320" + Opacity="0" + RenderTransformOrigin="0.5,0.5" /> - - - - - - - + + + + + @@ -391,7 +388,7 @@ - @@ -408,7 +405,6 @@ @@ -416,7 +412,7 @@ - @@ -429,23 +425,30 @@ - + + + + + + @@ -453,7 +456,7 @@ - @@ -474,7 +477,7 @@ - diff --git a/Telegram/Controls/ProfileHeader.xaml.cs b/Telegram/Controls/ProfileHeader.xaml.cs index d343323cdd..eef1de55f7 100644 --- a/Telegram/Controls/ProfileHeader.xaml.cs +++ b/Telegram/Controls/ProfileHeader.xaml.cs @@ -579,6 +579,10 @@ public void UpdateChatTitle(Chat chat) { Title.Text = ViewModel.ForumTopic.Info.Name; } + else if (ViewModel.SavedMessagesTopic != null) + { + Title.Text = ViewModel.ClientService.GetTitle(ViewModel.SavedMessagesTopic); + } else if (chat.Id == ViewModel.ClientService.Options.MyId && !ViewModel.IsSavedMessages) { Title.Text = chat.Title; @@ -675,12 +679,12 @@ public void UpdateUserFullInfo(Chat chat, User user, UserFullInfo fullInfo, bool { UpdateUserStatus(chat, user); - UserPhone.Badge = PhoneNumber.Format(user.PhoneNumber); + UserPhone.Content = PhoneNumber.Format(user.PhoneNumber); UserPhone.Visibility = string.IsNullOrEmpty(user.PhoneNumber) ? Visibility.Collapsed : Visibility.Visible; if (user.HasActiveUsername(out string username)) { - Username.Badge = username; + Username.Content = username; Username.Visibility = Visibility.Visible; } else @@ -690,7 +694,7 @@ public void UpdateUserFullInfo(Chat chat, User user, UserFullInfo fullInfo, bool UpdateUsernames(user.Usernames); - Description.Content = user.Type is UserTypeBot ? Strings.DescriptionPlaceholder : Strings.UserBio; + Description.Description = user.Type is UserTypeBot ? Strings.DescriptionPlaceholder : Strings.UserBio; if (secret is false) { @@ -817,7 +821,7 @@ public void UpdateUserFullInfo(Chat chat, User user, UserFullInfo fullInfo, bool if (fullInfo.BusinessInfo?.Location != null) { Location.Visibility = Visibility.Visible; - Location.Badge = fullInfo.BusinessInfo.Location.Address; + Location.Content = fullInfo.BusinessInfo.Location.Address; } if (fullInfo.Birthdate != null) @@ -827,15 +831,15 @@ public void UpdateUserFullInfo(Chat chat, User user, UserFullInfo fullInfo, bool if (today) { - UserBirthday.Content = Strings.ProfileBirthdayToday; - UserBirthday.Badge = years != 0 + UserBirthday.Description = Strings.ProfileBirthdayToday; + UserBirthday.Content = years != 0 ? Locale.Declension(Strings.R.ProfileBirthdayTodayValueYear, years, Formatter.Birthdate(fullInfo.Birthdate)) : string.Format(Strings.ProfileBirthdayTodayValue, Formatter.Birthdate(fullInfo.Birthdate)); } else { - UserBirthday.Content = Strings.ProfileBirthday; - UserBirthday.Badge = years != 0 + UserBirthday.Description = Strings.ProfileBirthday; + UserBirthday.Content = years != 0 ? Locale.Declension(Strings.R.ProfileBirthdayValueYear, years, Formatter.Birthdate(fullInfo.Birthdate)) : string.Format(Strings.ProfileBirthdayValue, Formatter.Birthdate(fullInfo.Birthdate)); } @@ -931,7 +935,7 @@ public void UpdateBasicGroupFullInfo(Chat chat, BasicGroup group, BasicGroupFull Subtitle.Text = Locale.Declension(Strings.R.Members, group.MemberCount); SubtitleWhen.Visibility = Visibility.Collapsed; - Description.Content = Strings.DescriptionPlaceholder; + Description.Description = Strings.DescriptionPlaceholder; UserPhone.Visibility = Visibility.Collapsed; Location.Visibility = Visibility.Collapsed; @@ -1029,7 +1033,7 @@ public void UpdateSupergroupFullInfo(Chat chat, Supergroup group, SupergroupFull SubtitleWhen.Visibility = Visibility.Collapsed; } - Description.Content = Strings.DescriptionPlaceholder; + Description.Description = Strings.DescriptionPlaceholder; if (ViewModel.ForumTopic != null) { @@ -1039,7 +1043,7 @@ public void UpdateSupergroupFullInfo(Chat chat, Supergroup group, SupergroupFull { this.BeginOnUIThread(() => { - Username.Badge = link.Link; + Username.Content = link.Link; Username.Visibility = Visibility.Visible; if (link.IsPublic) @@ -1060,7 +1064,7 @@ public void UpdateSupergroupFullInfo(Chat chat, Supergroup group, SupergroupFull { if (group.HasActiveUsername(out string username)) { - Username.Badge = username; + Username.Content = username; Username.Visibility = Visibility.Visible; } else @@ -1139,7 +1143,7 @@ public void UpdateSupergroupFullInfo(Chat chat, Supergroup group, SupergroupFull Description.Visibility = string.IsNullOrEmpty(fullInfo.Description) ? Visibility.Collapsed : Visibility.Visible; Location.Visibility = fullInfo.Location != null ? Visibility.Visible : Visibility.Collapsed; - Location.Badge = fullInfo.Location?.Address; + Location.Content = fullInfo.Location?.Address; if (group.IsChannel && group.Status is ChatMemberStatusCreator or ChatMemberStatusAdministrator) { diff --git a/Telegram/Controls/ProfilePicture.cs b/Telegram/Controls/ProfilePicture.cs index 042c7d687c..c0cc2d2967 100644 --- a/Telegram/Controls/ProfilePicture.cs +++ b/Telegram/Controls/ProfilePicture.cs @@ -479,18 +479,24 @@ private object GetChat(IClientService clientService, Chat chat, File file, int s _parameters = new ChatParameters(clientService, chat, side); UpdateManager.Subscribe(this, clientService, file, ref _fileToken, UpdateFile, true); } - } - else if (clientService.TryGetUser(chat, out User user) && user.Type is UserTypeDeleted) - { - return PlaceholderImage.GetGlyph(Icons.GhostFilled, long.MinValue); + + var minithumbnail = chat.Photo?.Minithumbnail; + if (minithumbnail != null) + { + var bitmap = new BitmapImage(); + PlaceholderHelper.GetBlurred(bitmap, minithumbnail.Data); + return bitmap; + } } - var minithumbnail = chat.Photo?.Minithumbnail; - if (minithumbnail != null) + if (clientService.TryGetUser(chat, out User user)) { - var bitmap = new BitmapImage(); - PlaceholderHelper.GetBlurred(bitmap, minithumbnail.Data); - return bitmap; + if (user.Type is UserTypeDeleted) + { + return PlaceholderImage.GetGlyph(Icons.GhostFilled, long.MinValue); + } + + return PlaceholderImage.GetUser(clientService, user); } return PlaceholderImage.GetChat(clientService, chat); @@ -553,18 +559,19 @@ private object GetUser(IClientService clientService, User user, File file, int s _parameters = new UserParameters(clientService, user, side); UpdateManager.Subscribe(this, clientService, file, ref _fileToken, UpdateFile, true); } - } - else if (user.Type is UserTypeDeleted) - { - return PlaceholderImage.GetGlyph(Icons.GhostFilled, long.MinValue); + + var minithumbnail = user.ProfilePhoto?.Minithumbnail; + if (minithumbnail != null) + { + var bitmap = new BitmapImage(); + PlaceholderHelper.GetBlurred(bitmap, minithumbnail.Data); + return bitmap; + } } - var minithumbnail = user.ProfilePhoto?.Minithumbnail; - if (minithumbnail != null) + if (user.Type is UserTypeDeleted) { - var bitmap = new BitmapImage(); - PlaceholderHelper.GetBlurred(bitmap, minithumbnail.Data); - return bitmap; + return PlaceholderImage.GetGlyph(Icons.GhostFilled, long.MinValue); } return PlaceholderImage.GetUser(clientService, user); @@ -838,7 +845,7 @@ public static CompositionBrush GetBrush(Compositor compositor, long i) public static PlaceholderImage GetChat(IClientService clientService, Chat chat) { - return new PlaceholderImage(InitialNameStringConverter.Convert(chat), false, clientService.GetAccentColor(chat.AccentColorId)); + return new PlaceholderImage(InitialNameStringConverter.Convert(chat.Title), false, clientService.GetAccentColor(chat.AccentColorId)); } public static PlaceholderImage GetChat(IClientService clientService, ChatInviteLinkInfo chat) @@ -848,7 +855,7 @@ public static PlaceholderImage GetChat(IClientService clientService, ChatInviteL public static PlaceholderImage GetUser(IClientService clientService, User user) { - return new PlaceholderImage(InitialNameStringConverter.Convert(user), false, clientService.GetAccentColor(user.AccentColorId)); + return new PlaceholderImage(InitialNameStringConverter.Convert(user.FirstName, user.LastName), false, clientService.GetAccentColor(user.AccentColorId)); } public static PlaceholderImage GetNameForUser(string firstName, string lastName, long id = 5) @@ -858,7 +865,7 @@ public static PlaceholderImage GetNameForUser(string firstName, string lastName, public static PlaceholderImage GetNameForUser(string name, long id = 5) { - return new PlaceholderImage(InitialNameStringConverter.Convert((object)name), false, id); + return new PlaceholderImage(InitialNameStringConverter.Convert(name), false, id); } public static PlaceholderImage GetNameForChat(string title, long id = 5) diff --git a/Telegram/Controls/ProgressBarRing.cs b/Telegram/Controls/ProgressBarRing.cs index 986504be74..29ddff9db1 100644 --- a/Telegram/Controls/ProgressBarRing.cs +++ b/Telegram/Controls/ProgressBarRing.cs @@ -6,20 +6,16 @@ // using System; using System.Numerics; -using Telegram.Common; using Telegram.Navigation; using Windows.UI.Composition; using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Hosting; using Windows.UI.Xaml.Media; namespace Telegram.Controls { - public partial class ProgressBarRing : Control + public partial class ProgressBarRing : ControlEx { - private readonly FrameworkElementState _manager; - private readonly ShapeVisual _visual; private readonly CompositionSpriteShape _shape; private readonly CompositionEllipseGeometry _ellipse; @@ -31,9 +27,8 @@ public ProgressBarRing() { DefaultStyleKey = typeof(ProgressBarRing); - _manager = new FrameworkElementState(this); - _manager.Loaded += OnLoaded; - _manager.Unloaded += OnUnloaded; + Connected += OnLoaded; + Disconnected += OnUnloaded; var ellipse = BootStrapper.Current.Compositor.CreateEllipseGeometry(); ellipse.Radius = new Vector2((float)Radius); diff --git a/Telegram/Controls/ProgressVoice.cs b/Telegram/Controls/ProgressVoice.cs index 1eadf987e7..2dfbdbc2b2 100644 --- a/Telegram/Controls/ProgressVoice.cs +++ b/Telegram/Controls/ProgressVoice.cs @@ -6,8 +6,13 @@ // using System; using System.Collections.Generic; +using Telegram.Native; +using Telegram.Navigation; using Telegram.Td.Api; using Windows.Foundation; +using Windows.UI.Composition; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Hosting; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Shapes; @@ -17,135 +22,81 @@ public record PlaybackSliderPositionChanged(TimeSpan NewPosition); public partial class ProgressVoice : PlaybackSlider { - private Path ProgressBarIndicator; - private Path HorizontalTrackRect; + private Grid RootGrid; + private Rectangle ProgressBarIndicator; + private Rectangle HorizontalTrackRect; - private GeometryGroup _group1; - private GeometryGroup _group2; + private CompositionGeometricClip _clip; public ProgressVoice() { DefaultStyleKey = typeof(ProgressVoice); - _group1 = new GeometryGroup(); - _group2 = new GeometryGroup(); + _clip = BootStrapper.Current.Compositor.CreateGeometricClip(); } protected override void OnApplyTemplate() { - ProgressBarIndicator = GetTemplateChild("ProgressBarIndicator") as Path; - HorizontalTrackRect = GetTemplateChild("HorizontalTrackRect") as Path; + RootGrid = GetTemplateChild(nameof(RootGrid)) as Grid; + ProgressBarIndicator = GetTemplateChild("ProgressBarIndicator") as Rectangle; + HorizontalTrackRect = GetTemplateChild("HorizontalTrackRect") as Rectangle; - ProgressBarIndicator.Data = _group1; - HorizontalTrackRect.Data = _group2; - - if (_deferred != null && _deferred.Duration != -1) - { - UpdateWaveform(_deferred); - //_deferred = null; - } + var visual = ElementComposition.GetElementVisual(RootGrid); + visual.Clip = _clip; base.OnApplyTemplate(); } - protected override Size ArrangeOverride(Size finalSize) - { - if (_deferred != null && _deferred.Duration == -1) - { - UpdateWaveform(_deferred.Waveform, 0, finalSize.Width); - } - - return base.ArrangeOverride(finalSize); - } - private VoiceNote _deferred; - public void UpdateWaveform(VoiceNote voiceNote) + protected override Size MeasureOverride(Size availableSize) { - _deferred = voiceNote; - - if (voiceNote.Duration == -1) + if (_deferred is VoiceNote voiceNote) { - // Recording - InvalidateArrange(); - } - else - { - // Bubble var maxVoiceLength = 30.0; var minVoiceLength = 2.0; var minVoiceWidth = 72.0; var maxVoiceWidth = 226.0; - var calcDuration = Math.Max(minVoiceLength, Math.Min(maxVoiceLength, voiceNote.Duration)); var waveformWidth = minVoiceWidth + (maxVoiceWidth - minVoiceWidth) * (calcDuration - minVoiceLength) / (maxVoiceLength - minVoiceLength); - UpdateWaveform(voiceNote.Waveform, calcDuration, waveformWidth); + availableSize = new Size(waveformWidth, 20); + + RootGrid.Measure(availableSize); + return availableSize; } + + return base.MeasureOverride(availableSize); } - private void UpdateWaveform(IList waveform, double duration, double waveformWidth) + protected override Size ArrangeOverride(Size finalSize) { - if (waveform.Count < 1) - { - waveform = new byte[1] { 0 }; - } - - var result = new double[waveform.Count * 8 / 5]; - for (int i = 0; i < result.Length; i++) + if (_deferred != null) { - int j = (i * 5) / 8, shift = (i * 5) % 8; - result[i] = ((waveform[j] | ((j + 1 < waveform.Count ? waveform[j + 1] : 0) << 8)) >> shift & 0x1F) / 31.0; + UpdateWaveform(_deferred.Waveform, 0, finalSize.Width); } - //var maxVoiceLength = 30.0; - //var minVoiceLength = 2.0; - - //var minVoiceWidth = 72.0; - //var maxVoiceWidth = 226.0; - - //var calcDuration = Math.Max(minVoiceLength, Math.Min(maxVoiceLength, duration)); - //var waveformWidth = minVoiceWidth + (maxVoiceWidth - minVoiceWidth) * (calcDuration - minVoiceLength) / (maxVoiceLength - minVoiceLength); - - //var imageWidth = 209.0; - //var imageHeight = 24; - var imageWidth = waveformWidth; // 142d; // double.IsNaN(ActualWidth) ? 142 : ActualWidth; - var imageHeight = 20; - - var space = 1.0; - var lineWidth = 2.0; - var lines = waveform.Count * 8 / 5; - var maxLines = (imageWidth - space) / (lineWidth + space); - var maxWidth = lines / maxLines; + return base.ArrangeOverride(finalSize); + } - _group1.Children.Clear(); - _group2.Children.Clear(); + public void UpdateWaveform(VoiceNote voiceNote) + { + _deferred = voiceNote; + InvalidateMeasure(); + InvalidateArrange(); + } - for (int index = 0; index < maxLines; index++) + private void UpdateWaveform(IList waveform, double duration, double waveformWidth) + { + if (waveform.Count < 1) { - var lineIndex = (int)(index * maxWidth); - var lineHeight = result[lineIndex] * (double)(imageHeight - 2.0) + 2.0; - - var x1 = (int)(index * (lineWidth + space)); - var y1 = (imageHeight - (int)lineHeight) / 2; - var x2 = (int)(index * (lineWidth + space) + lineWidth); - var y2 = imageHeight - y1; - - _group1.Children.Add(new RectangleGeometry { Rect = new Rect(new Point(x1, y1), new Point(x2, y2)) }); - _group2.Children.Add(new RectangleGeometry { Rect = new Rect(new Point(x1, y1), new Point(x2, y2)) }); + waveform = new byte[1] { 0 }; } - //ProgressBarIndicator.Data = geometry1; - //HorizontalTrackRect.Data = geometry2; - - //Width = waveformWidth; - - if (duration != 0) - { - Width = waveformWidth; - } + var clip = PlaceholderImageHelper.Foreground.GetVoiceNoteClip(waveform, waveformWidth); + _clip.Geometry = BootStrapper.Current.Compositor.CreatePathGeometry(clip); } } } diff --git a/Telegram/Controls/QrCode.cs b/Telegram/Controls/QrCode.cs index a46a703cac..f48a85d54d 100644 --- a/Telegram/Controls/QrCode.cs +++ b/Telegram/Controls/QrCode.cs @@ -18,6 +18,8 @@ namespace Telegram.Controls { + public record QrCodeGeometry(CompositionPath Data, CompositionPath Position = null); + public partial class QrCode : Control { private string _text; @@ -70,11 +72,53 @@ private void Draw(string text, Color foreground, bool fadeIn) return; } + var geometry = CreateGeometry(text); + var size = 222; + + var compositor = BootStrapper.Current.Compositor; + var blackBrush = compositor.CreateColorBrush(foreground); + + var path1 = compositor.CreatePathGeometry(geometry.Position); + var path2 = compositor.CreatePathGeometry(geometry.Data); + + var shape1 = compositor.CreateSpriteShape(path1); + shape1.FillBrush = blackBrush; + + var shape2 = compositor.CreateSpriteShape(path2); + shape2.FillBrush = blackBrush; + + var visual1 = compositor.CreateShapeVisual(); + visual1.Size = new Vector2(size, size); + visual1.Shapes.Add(shape1); + + var visual2 = compositor.CreateShapeVisual(); + visual2.Size = new Vector2(size, size); + visual2.Shapes.Add(shape2); + + var container = compositor.CreateContainerVisual(); + container.Size = new Vector2(size, size); + container.Children.InsertAtTop(visual1); + container.Children.InsertAtTop(visual2); + + ElementCompositionPreview.SetElementChildVisual(this, container); + + if (fadeIn) + { + var opacity = compositor.CreateScalarKeyFrameAnimation(); + opacity.InsertKeyFrame(0, 0); + opacity.InsertKeyFrame(1, 1); + + visual2.StartAnimation("Opacity", opacity); + } + } + + public static QrCodeGeometry CreateGeometry(string text, int minVersion = 1, int maxVersion = 40, bool combine = false) + { var data = QrBuffer.FromString(text); var replaceFrom = data.ReplaceFrom; var replaceTill = data.ReplaceTill; - var size = 222; + var size = 222f; var pixel = size / data.Size; bool value(int row, int column) @@ -121,8 +165,16 @@ void large(float x, float y) var rect2 = CanvasGeometry.CreateRoundedRectangle(null, x + pixel, y + pixel, pixel * 5 + 2, pixel * 5 + 2, 9, 9); var rect3 = CanvasGeometry.CreateRoundedRectangle(null, x + pixel * 2, y + pixel * 2, pixel * 3 + 2, pixel * 3 + 2, pixel, pixel); - geometries[geometry++] = rect1.CombineWith(rect2, Matrix3x2.Identity, CanvasGeometryCombine.Exclude); - geometries[geometry++] = rect3; + if (combine) + { + builder.AddGeometry(rect1.CombineWith(rect2, Matrix3x2.Identity, CanvasGeometryCombine.Exclude)); + builder.AddGeometry(rect3); + } + else + { + geometries[geometry++] = rect1.CombineWith(rect2, Matrix3x2.Identity, CanvasGeometryCombine.Exclude); + geometries[geometry++] = rect3; + } } void brect(float x, float y, float width, float height) { @@ -261,41 +313,17 @@ void warch(float x, float y, float width, float height, int direction) large((data.Size - 7) * pixel - 2, 0); large(0, (data.Size - 7) * pixel - 2); - var compositor = BootStrapper.Current.Compositor; - var blackBrush = compositor.CreateColorBrush(foreground); - - var path1 = compositor.CreatePathGeometry(new CompositionPath(CanvasGeometry.CreateGroup(null, geometries, CanvasFilledRegionDetermination.Winding))); - var path2 = compositor.CreatePathGeometry(new CompositionPath(CanvasGeometry.CreatePath(builder))); - - var shape1 = compositor.CreateSpriteShape(path1); - shape1.FillBrush = blackBrush; - - var shape2 = compositor.CreateSpriteShape(path2); - shape2.FillBrush = blackBrush; - - var visual1 = compositor.CreateShapeVisual(); - visual1.Size = new Vector2(size, size); - visual1.Shapes.Add(shape1); - - var visual2 = compositor.CreateShapeVisual(); - visual2.Size = new Vector2(size, size); - visual2.Shapes.Add(shape2); - - var container = compositor.CreateContainerVisual(); - container.Size = new Vector2(size, size); - container.Children.InsertAtTop(visual1); - container.Children.InsertAtTop(visual2); - - ElementCompositionPreview.SetElementChildVisual(this, container); - - if (fadeIn) + if (combine) { - var opacity = compositor.CreateScalarKeyFrameAnimation(); - opacity.InsertKeyFrame(0, 0); - opacity.InsertKeyFrame(1, 1); + // If combined we add the inner circle + builder.AddGeometry(CanvasGeometry.CreateCircle(null, 111, 111, 16)); - visual2.StartAnimation("Opacity", opacity); + return new QrCodeGeometry(new CompositionPath(CanvasGeometry.CreatePath(builder))); } + + return new QrCodeGeometry( + new CompositionPath(CanvasGeometry.CreatePath(builder)), + new CompositionPath(CanvasGeometry.CreateGroup(null, geometries, CanvasFilledRegionDetermination.Winding))); } #region Text diff --git a/Telegram/Controls/ReplyMarkupPanel.cs b/Telegram/Controls/ReplyMarkupPanel.cs index abea761d57..afb797abd5 100644 --- a/Telegram/Controls/ReplyMarkupPanel.cs +++ b/Telegram/Controls/ReplyMarkupPanel.cs @@ -7,7 +7,6 @@ using System; using Telegram.Common; using Telegram.Controls.Media; -using Telegram.Navigation; using Telegram.Td.Api; using Telegram.ViewModels; using Windows.Foundation; @@ -85,130 +84,6 @@ public bool Update(MessageViewModel message, ReplyMarkup markup, bool inline = t { return Update(message, keyboardMarkup); } - else if (markup is ReplyMarkupInlineKeyboard inlineMarkup && inline) - { - return Update(message, inlineMarkup); - } - - return false; - } - - public bool Update(MessageViewModel message, ReplyMarkupInlineKeyboard inlineMarkup) - { - var rows = inlineMarkup.Rows; - - _oneTime = false; - Tag = message; - - var receipt = false; - if (message != null && message.Content is MessageInvoice invoice) - { - receipt = invoice.ReceiptMessageId != 0; - - if (invoice.PaidMedia is not PaidMediaUnsupported and not null) - { - rows = null; - } - } - - if (rows == null) - { - return false; - } - - for (int j = 0; j < rows.Count; j++) - { - var row = rows[j]; - - var panel = new ReplyMarkupRow(); - panel.HorizontalAlignment = HorizontalAlignment.Stretch; - panel.VerticalAlignment = VerticalAlignment.Stretch; - panel.Margin = new Thickness(-1, 0, -1, 0); - - for (int i = 0; i < row.Count; i++) - { - var item = row[i]; - //var builder = new StringBuilder(); - - //foreach (var line in item.Text.Split('\n')) - //{ - // if (builder.Length > 0) - // { - // builder.Append(" "); - // } - - // builder.Append(line.Trim()); - //} - - var button = new GlyphButton(); - button.Tag = item; - button.HorizontalAlignment = HorizontalAlignment.Stretch; - button.VerticalAlignment = VerticalAlignment.Stretch; - button.Click += Button_Click; - - button.Style = BootStrapper.Current.Resources["ReplyInlineMarkupButtonStyle"] as Style; - button.Margin = new Thickness(1, 2, 1, 0); - - button.Content = item.Text.Replace('\n', ' '); - - switch (item.Type) - { - case InlineKeyboardButtonTypeUrl typeUrl: - button.Glyph = "\uE9B7"; - Extensions.SetToolTip(button, typeUrl.Url); - break; - case InlineKeyboardButtonTypeLoginUrl: - button.Glyph = "\uE9B7"; - break; - case InlineKeyboardButtonTypeSwitchInline: - button.Glyph = "\uEE35"; - break; - case InlineKeyboardButtonTypeBuy: - if (receipt) - { - button.Content = Strings.PaymentReceipt; - } - else - { - button.Content = item.Text.Replace("\u2B50", Icons.Premium + "\u200A"); - } - break; - case InlineKeyboardButtonTypeWebApp: - button.Glyph = Icons.Window16; - break; - case InlineKeyboardButtonTypeCopyText: - button.Glyph = Icons.CopyFilled16; - break; - } - - var topLeft = 4d; - var topRight = 4d; - var bottomRight = 4d; - var bottomLeft = 4d; - - if (j == rows.Count - 1) - { - if (i == 0) - { - bottomLeft = CornerRadius.BottomLeft; - } - - if (i == row.Count - 1) - { - bottomRight = CornerRadius.BottomRight; - } - } - - button.CornerRadius = new CornerRadius(topLeft, topRight, bottomRight, bottomLeft); - - panel.Children.Add(button); - } - - SetRow(panel, j); - - RowDefinitions.Add(1, GridUnitType.Star); - Children.Add(panel); - } return false; } @@ -245,17 +120,13 @@ public bool Update(MessageViewModel message, ReplyMarkupShowKeyboard keyboardMar for (int i = 0; i < row.Count; i++) { var item = row[i]; - var button = new GlyphButton(); - button.Tag = item; + var button = new ReplyMarkupButton(item); button.HorizontalAlignment = HorizontalAlignment.Stretch; button.VerticalAlignment = VerticalAlignment.Stretch; - button.Click += Button_Click; - - button.Style = BootStrapper.Current.Resources["ReplyKeyboardMarkupButtonStyle"] as Style; button.Margin = new Thickness(4, 8, 4, 0); button.Height = resize ? 36 : double.NaN; - - button.Content = item.Text; + button.Text = item.Text; + button.Click += Button_Click; if (item.Type is KeyboardButtonTypeWebApp) { @@ -283,19 +154,13 @@ public bool Update(MessageViewModel message, ReplyMarkupShowKeyboard keyboardMar private void Button_Click(object sender, RoutedEventArgs e) { - var button = sender as Button; - if (button.Tag is KeyboardButton btn) - { - ButtonClick?.Invoke(this, new ReplyMarkupButtonClickEventArgs(btn, _oneTime)); - } - else if (button.Tag is InlineKeyboardButton inlineBtn) + if (sender is ReplyMarkupButton button) { - InlineButtonClick?.Invoke(this, new ReplyMarkupInlineButtonClickEventArgs(inlineBtn)); + ButtonClick?.Invoke(this, new ReplyMarkupButtonClickEventArgs(button.Button, _oneTime)); } } public event EventHandler ButtonClick; - public event EventHandler InlineButtonClick; } public partial class ReplyMarkupRow : Panel @@ -337,4 +202,52 @@ protected override Size ArrangeOverride(Size finalSize) return finalSize; } } + + public class ReplyMarkupButton : GlyphButton + { + public ReplyMarkupButton(KeyboardButton button) + { + DefaultStyleKey = typeof(ReplyMarkupButton); + Button = button; + } + + public KeyboardButton Button { get; } + + #region Text + + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register("Text", typeof(string), typeof(ReplyMarkupButton), new PropertyMetadata(string.Empty)); + + #endregion + } + + public class ReplyMarkupInlineButton : GlyphButton + { + public ReplyMarkupInlineButton(InlineKeyboardButton button) + { + DefaultStyleKey = typeof(ReplyMarkupInlineButton); + Button = button; + } + + public InlineKeyboardButton Button { get; } + + #region Text + + public string Text + { + get { return (string)GetValue(TextProperty); } + set { SetValue(TextProperty, value); } + } + + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register("Text", typeof(string), typeof(ReplyMarkupInlineButton), new PropertyMetadata(string.Empty)); + + #endregion + } } diff --git a/Telegram/Controls/SlideTextBlock.cs b/Telegram/Controls/SlideTextBlock.cs index 8548b82840..8033776d28 100644 --- a/Telegram/Controls/SlideTextBlock.cs +++ b/Telegram/Controls/SlideTextBlock.cs @@ -1,4 +1,10 @@ -using System.Numerics; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System.Numerics; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Hosting; diff --git a/Telegram/Controls/Stories/StoriesStrip.xaml.cs b/Telegram/Controls/Stories/StoriesStrip.xaml.cs index a4abc2784e..b40ecb80ab 100644 --- a/Telegram/Controls/Stories/StoriesStrip.xaml.cs +++ b/Telegram/Controls/Stories/StoriesStrip.xaml.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using System.Collections.Specialized; using System.Numerics; using Telegram.Common; diff --git a/Telegram/Controls/Stories/StoriesWindow.xaml.cs b/Telegram/Controls/Stories/StoriesWindow.xaml.cs index 7183861b68..8acbbdc8ac 100644 --- a/Telegram/Controls/Stories/StoriesWindow.xaml.cs +++ b/Telegram/Controls/Stories/StoriesWindow.xaml.cs @@ -1,4 +1,10 @@ -using Microsoft.UI.Xaml.Controls; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using Microsoft.UI.Xaml.Controls; using System; using System.Numerics; using System.Threading.Tasks; diff --git a/Telegram/Controls/Stories/Widgets/StoryWeatherWidget.xaml.cs b/Telegram/Controls/Stories/Widgets/StoryWeatherWidget.xaml.cs index 9ed2a42d12..a8d3f6566e 100644 --- a/Telegram/Controls/Stories/Widgets/StoryWeatherWidget.xaml.cs +++ b/Telegram/Controls/Stories/Widgets/StoryWeatherWidget.xaml.cs @@ -1,4 +1,10 @@ -using Telegram.Common; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using Telegram.Common; using Telegram.Td.Api; using Windows.UI; using Windows.UI.Xaml; diff --git a/Telegram/Controls/SuggestedActionSetBirthdateCard.xaml.cs b/Telegram/Controls/SuggestedActionSetBirthdateCard.xaml.cs index 618ece62bd..93e8f8a0ae 100644 --- a/Telegram/Controls/SuggestedActionSetBirthdateCard.xaml.cs +++ b/Telegram/Controls/SuggestedActionSetBirthdateCard.xaml.cs @@ -1,4 +1,10 @@ -using Windows.UI.Xaml; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace Telegram.Controls diff --git a/Telegram/Controls/TabbedPageHeader.xaml.cs b/Telegram/Controls/TabbedPageHeader.xaml.cs index 5b46c734d9..9b4b33c961 100644 --- a/Telegram/Controls/TabbedPageHeader.xaml.cs +++ b/Telegram/Controls/TabbedPageHeader.xaml.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using Telegram.Common; using Windows.Foundation; using Windows.UI.Input; diff --git a/Telegram/Controls/TableView.cs b/Telegram/Controls/TableView.cs index 1ab10a0ce5..6434506109 100644 --- a/Telegram/Controls/TableView.cs +++ b/Telegram/Controls/TableView.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using Windows.Foundation; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; diff --git a/Telegram/Controls/ToastPopup.cs b/Telegram/Controls/ToastPopup.cs index 4a4ee2f3e1..58357af7df 100644 --- a/Telegram/Controls/ToastPopup.cs +++ b/Telegram/Controls/ToastPopup.cs @@ -84,6 +84,9 @@ public static void ShowFeaturePromo(INavigationService navigationService, Premiu PremiumFeatureAccentColor => Strings.UserColorApplyPremium, PremiumFeatureRealTimeChatTranslation => Strings.ShowTranslateChatButtonLocked, PremiumFeatureChecklists => Strings.TodoPremiumRequired, + PremiumFeatureMessageEffects => Strings.AnimatedEffectPremium, + PremiumFeatureUniqueReactions => Strings.UnlockPremiumEmojiReaction, + PremiumFeatureCustomEmoji => Strings.UnlockPremiumEmojiHint, _ => Strings.UnlockPremium }; diff --git a/Telegram/Controls/VersionLabel.cs b/Telegram/Controls/VersionLabel.cs index 1a21e1a612..90df4490b9 100644 --- a/Telegram/Controls/VersionLabel.cs +++ b/Telegram/Controls/VersionLabel.cs @@ -1,4 +1,10 @@ -using Telegram.Common; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using Telegram.Common; using Telegram.Controls.Media; using Telegram.Views; using Windows.ApplicationModel; @@ -49,7 +55,7 @@ private void OnContextRequested(UIElement sender, ContextRequestedEventArgs args var flyout = new MenuFlyout(); var element = sender as FrameworkElement; - flyout.CreateFlyoutItem(CopyVersion, Strings.Copy, Icons.DocumentCopy); + flyout.CreateFlyoutItem(CopyVersion, Strings.Copy, Icons.Copy); flyout.ShowAt(element, args); } diff --git a/Telegram/Controls/VideoPlayerBase.cs b/Telegram/Controls/VideoPlayerBase.cs index 39e5ec7978..0e6ed881d2 100644 --- a/Telegram/Controls/VideoPlayerBase.cs +++ b/Telegram/Controls/VideoPlayerBase.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using System.Collections.Generic; using Telegram.Common; using Telegram.ViewModels.Gallery; diff --git a/Telegram/Controls/Views/ForumView.xaml b/Telegram/Controls/Views/ForumView.xaml index 7d8f520809..83c87a2da3 100644 --- a/Telegram/Controls/Views/ForumView.xaml +++ b/Telegram/Controls/Views/ForumView.xaml @@ -346,8 +346,8 @@ Width="3" Fill="{ThemeResource NavigationViewSelectionIndicatorForeground}" Opacity="0" - RadiusX="3" - RadiusY="3" /> + RadiusX="1.5" + RadiusY="1.5" /> diff --git a/Telegram/Controls/Views/ForumView.xaml.cs b/Telegram/Controls/Views/ForumView.xaml.cs index 09ed4c3de7..04afa2537e 100644 --- a/Telegram/Controls/Views/ForumView.xaml.cs +++ b/Telegram/Controls/Views/ForumView.xaml.cs @@ -30,7 +30,7 @@ public enum ForumViewType Horizontal } - public sealed partial class ForumView : UserControl, ITopicListDelegate + public sealed partial class ForumView : UserControl, ITopicListDelegate, IAutomationNameProvider { public TopicListViewModel ViewModel { @@ -587,5 +587,39 @@ public event ItemClickEventHandler ItemClick ScrollingHost.ItemClick -= value; } } + + public string GetAutomationName() + { + if (Title == null || Subtitle == null || ChatActionLabel == null) + { + return string.Empty; + } + + var result = Title.Text.TrimEnd('.', ','); + var identity = Identity.CurrentType switch + { + IdentityIconType.Fake => Strings.FakeMessage, + IdentityIconType.Scam => Strings.ScamMessage, + IdentityIconType.Premium => Strings.AccDescrPremium, + IdentityIconType.Verified => Strings.AccDescrVerified, + _ => null + }; + + if (identity != null) + { + result += ", " + identity; + } + + if (ChatActionLabel.Text.Length > 0) + { + result += ", " + ChatActionLabel.Text; + } + else if (Subtitle.Text.Length > 0) + { + result += ", " + Subtitle.Text; + } + + return result; + } } } diff --git a/Telegram/Controls/Views/InteractionsView.xaml.cs b/Telegram/Controls/Views/InteractionsView.xaml.cs index e3021ac966..4a1c928fd2 100644 --- a/Telegram/Controls/Views/InteractionsView.xaml.cs +++ b/Telegram/Controls/Views/InteractionsView.xaml.cs @@ -186,7 +186,30 @@ private void OnContainerContentChanging(ListViewBase sender, ContainerContentCha if (args.Item is AddedReaction addedReaction) { cell.UpdateAddedReaction(_clientService, args, OnContainerContentChanging); - animated.Source = new ReactionFileSource(_clientService, addedReaction.Type); + + if (_reactionType == null) + { + using (animated.BeginBatchUpdate()) + { + var custom = addedReaction.Type is ReactionTypeCustomEmoji; + var size = custom ? 20 : 40; + + animated.Width = animated.Height = size; + animated.Margin = new Thickness(0, 0, custom ? 12 : 2, 0); + animated.FrameSize = new Size(size, size); + animated.LoopCount = custom ? 3 : 1; + animated.IsViewportAware = custom; + + animated.Source = new ReactionFileSource(_clientService, addedReaction.Type) + { + UseCenterAnimation = true + }; + } + } + else + { + animated.Source = null; + } } else if (args.Item is MessageViewer messageViewer) { diff --git a/Telegram/Controls/Views/SearchChatsView.xaml.cs b/Telegram/Controls/Views/SearchChatsView.xaml.cs index 4c142f0a52..92eb7c968c 100644 --- a/Telegram/Controls/Views/SearchChatsView.xaml.cs +++ b/Telegram/Controls/Views/SearchChatsView.xaml.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using System.Collections.Generic; using System.Linq; using System.Numerics; @@ -46,6 +52,17 @@ public SearchChatsView() InitializeComponent(); } + public Thickness PaddingImpl + { + get => TopChats.Padding; + set + { + ItemsHost.Padding = value; + TopChats.Padding = value; + TopChats.Margin = new Thickness(-value.Left, 0, -value.Right, 0); + } + } + public void Activate() { ViewModel.Activate(); @@ -710,4 +727,4 @@ public SearchListViewItem(string typeName) TypeName = typeName; } } -} \ No newline at end of file +} diff --git a/Telegram/Controls/Views/SendMessagesView.xaml.cs b/Telegram/Controls/Views/SendMessagesView.xaml.cs index 68c206184a..ed175a1e7a 100644 --- a/Telegram/Controls/Views/SendMessagesView.xaml.cs +++ b/Telegram/Controls/Views/SendMessagesView.xaml.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; diff --git a/Telegram/Controls/WebVideoPlayer.xaml.cs b/Telegram/Controls/WebVideoPlayer.xaml.cs index 694842dff1..4ee03a8e5b 100644 --- a/Telegram/Controls/WebVideoPlayer.xaml.cs +++ b/Telegram/Controls/WebVideoPlayer.xaml.cs @@ -1,8 +1,13 @@ -using Microsoft.UI.Xaml.Controls; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using Microsoft.UI.Xaml.Controls; using Microsoft.Web.WebView2.Core; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.Linq; using System.Net.Http.Headers; @@ -335,15 +340,16 @@ void CreateWebResourceResponse(IRandomAccessStream Content, int StatusCode, stri } } - Debug.WriteLine(resource + ", offset: " + offset + ", length:" + limit); - if (limit == 0) { limit = file.Size - offset; } + //Logger.Info(resource + ", offset: " + offset + ", count:" + limit); + remote.SeekCallback(offset); await remote.ReadCallbackAsync(limit); + remote.Close(false); if (extension == ".m3u8") { diff --git a/Telegram/Controls/WebViewer.cs b/Telegram/Controls/WebViewer.cs index 2d2c271258..700b95188b 100644 --- a/Telegram/Controls/WebViewer.cs +++ b/Telegram/Controls/WebViewer.cs @@ -1,4 +1,10 @@ -using Microsoft.UI.Xaml.Controls; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using Microsoft.UI.Xaml.Controls; using Microsoft.Web.WebView2.Core; using System; using System.ComponentModel; diff --git a/Telegram/Converters/InitialNameStringConverter.cs b/Telegram/Converters/InitialNameStringConverter.cs index fd9e5c1caf..b41b6eff07 100644 --- a/Telegram/Converters/InitialNameStringConverter.cs +++ b/Telegram/Converters/InitialNameStringConverter.cs @@ -4,77 +4,13 @@ // Distributed under the GNU General Public License v3.0. (See accompanying // file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) // -using System; using System.Globalization; -using Telegram.Td.Api; -using Windows.UI.Xaml.Data; namespace Telegram.Converters { - public partial class InitialNameStringConverter : IValueConverter + public partial class InitialNameStringConverter { - public object Convert(object value, Type targetType, object parameter, string language) - { - return Convert(value); - } - - public static string Convert(object value) - { - if (value == null) - { - return null; - } - - var word1 = string.Empty; - var word2 = string.Empty; - - if (value is User user) - { - word1 = user.FirstName ?? string.Empty; - word2 = user.LastName ?? string.Empty; - } - else if (value is Chat chat) - { - var words = chat.Title.Split(new char[] { ' ' }); - if (words.Length > 1 && chat.Type is ChatTypePrivate || chat.Type is ChatTypeSecret) - { - word1 = words[0]; - word2 = words[words.Length - 1]; - } - else if (words.Length > 0) - { - word1 = words[0]; - word2 = string.Empty; - } - } - else if (value is ChatInviteLinkInfo info) - { - var words = info.Title.Split(new char[] { ' ' }); - if (words.Length > 0) - { - word1 = words[0]; - word2 = string.Empty; - } - } - else if (value is string str) - { - var words = str.Split(new char[] { ' ' }); - if (words.Length > 1) - { - word1 = words[0]; - word2 = words[words.Length - 1]; - } - else - { - word1 = words[0]; - word2 = string.Empty; - } - } - - return Convert(word1, word2); - } - - public static string Convert(string title) + public static string Convert(string title, bool split = false) { title ??= string.Empty; @@ -82,7 +18,12 @@ public static string Convert(string title) var word2 = string.Empty; var words = title.Split(new char[] { ' ' }); - if (words.Length > 0) + if (words.Length > 1 && split) + { + word1 = words[0]; + word2 = words[words.Length - 1]; + } + else { word1 = words[0]; word2 = string.Empty; @@ -104,10 +45,5 @@ public static string Convert(string word1, string word2) return string.Format("{0}{1}", word1, word2).Trim().ToUpperInvariant(); } - - public object ConvertBack(object value, Type targetType, object parameter, string language) - { - throw new NotSupportedException(); - } } } diff --git a/Telegram/CsWinRT.cs b/Telegram/CsWinRT.cs index 64813a4a72..f59fd3b64d 100644 --- a/Telegram/CsWinRT.cs +++ b/Telegram/CsWinRT.cs @@ -1,4 +1,10 @@ -global using DispatcherQueue = Windows.System.DispatcherQueue; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +global using DispatcherQueue = Windows.System.DispatcherQueue; global using Object = Telegram.Td.Api.Object; global using Point = Windows.Foundation.Point; global using TimeZone = Telegram.Td.Api.TimeZone; diff --git a/Telegram/Entities/NavigateToHistoryEntryParameters.cs b/Telegram/Entities/NavigateToHistoryEntryParameters.cs index f796cbd241..32a10f6f2a 100644 --- a/Telegram/Entities/NavigateToHistoryEntryParameters.cs +++ b/Telegram/Entities/NavigateToHistoryEntryParameters.cs @@ -1,4 +1,10 @@ -using Newtonsoft.Json; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using Newtonsoft.Json; namespace Telegram.Entities { diff --git a/Telegram/Entities/NavigationHistory.cs b/Telegram/Entities/NavigationHistory.cs index 2f27ef11f4..a2a06f87d8 100644 --- a/Telegram/Entities/NavigationHistory.cs +++ b/Telegram/Entities/NavigationHistory.cs @@ -1,4 +1,10 @@ -using Newtonsoft.Json; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using Newtonsoft.Json; using System.Collections.Generic; namespace Telegram.Entities diff --git a/Telegram/Entities/SourceGenerationContext.cs b/Telegram/Entities/SourceGenerationContext.cs index 71ea14c62e..447635b158 100644 --- a/Telegram/Entities/SourceGenerationContext.cs +++ b/Telegram/Entities/SourceGenerationContext.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/Telegram/Entities/StorageAlbum.cs b/Telegram/Entities/StorageAlbum.cs index 42a023b7e9..2bbb19d12f 100644 --- a/Telegram/Entities/StorageAlbum.cs +++ b/Telegram/Entities/StorageAlbum.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using System.Collections.Generic; using System.Linq; using Telegram.Common; diff --git a/Telegram/Entities/VideoGeneration.cs b/Telegram/Entities/VideoGeneration.cs index 22fae2421e..27c31f2555 100644 --- a/Telegram/Entities/VideoGeneration.cs +++ b/Telegram/Entities/VideoGeneration.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using Windows.Foundation; namespace Telegram.Entities diff --git a/Telegram/Logger.cs b/Telegram/Logger.cs index ff3fdf305b..850642e6aa 100644 --- a/Telegram/Logger.cs +++ b/Telegram/Logger.cs @@ -53,7 +53,7 @@ public static void Error(Exception exception, [CallerMemberName] string member = #if !DEBUG var report = WatchDog.BuildReport(exception); - //Microsoft.AppCenter.Crashes.Crashes.TrackError(exception, attachments: Microsoft.AppCenter.Crashes.ErrorAttachmentLog.AttachmentWithText(report, "crash.txt")); + Microsoft.AppCenter.Crashes.Crashes.TrackError(exception, attachments: Microsoft.AppCenter.Crashes.ErrorAttachmentLog.AttachmentWithText(report, "crash.txt")); #endif } diff --git a/Telegram/Navigation/Services/NavigationService.cs b/Telegram/Navigation/Services/NavigationService.cs index 8242dd2a6f..22a5054a5b 100644 --- a/Telegram/Navigation/Services/NavigationService.cs +++ b/Telegram/Navigation/Services/NavigationService.cs @@ -76,7 +76,7 @@ public interface INavigationService ToastPopup ShowToast(FormattedText text, ElementTheme requestedTheme = ElementTheme.Dark, TimeSpan? dismissAfter = null); ToastPopup ShowToast(FormattedText text, ToastPopupIcon icon, ElementTheme requestedTheme = ElementTheme.Dark, TimeSpan? dismissAfter = null); - void ShowGallery(GalleryViewModelBase parameter, FrameworkElement closing = null, long timestamp = 0); + void ShowGallery(GalleryViewModelBase parameter, FrameworkElement closing = null, double timestamp = 0); object CurrentPageParam { get; } Type CurrentPageType { get; } @@ -578,7 +578,7 @@ public ToastPopup ShowToast(FormattedText text, ToastPopupIcon icon, ElementThem return ToastPopup.Show(XamlRoot, text, icon, requestedTheme, dismissAfter); } - public void ShowGallery(GalleryViewModelBase parameter, FrameworkElement closing = null, long timestamp = 0) + public void ShowGallery(GalleryViewModelBase parameter, FrameworkElement closing = null, double timestamp = 0) { parameter.NavigationService = this; _ = GalleryWindow.ShowAsync(XamlRoot, parameter, closing, timestamp); diff --git a/Telegram/Navigation/WindowContext.cs b/Telegram/Navigation/WindowContext.cs index 044e44b729..61c886ffcf 100644 --- a/Telegram/Navigation/WindowContext.cs +++ b/Telegram/Navigation/WindowContext.cs @@ -691,35 +691,35 @@ private void ClearTitleBar(ApplicationView view) #region Static code - public static bool IsKeyDown(Windows.System.VirtualKey key) + public static bool IsKeyDown(VirtualKey key) { //return (InputKeyboardSource.GetKeyStateForCurrentThread(key) & Windows.UI.Core.CoreVirtualKeyStates.Down) != 0; return (Window.Current.CoreWindow.GetKeyState(key) & CoreVirtualKeyStates.Down) != 0; } - public static bool IsKeyDownAsync(Windows.System.VirtualKey key) + public static bool IsKeyDownAsync(VirtualKey key) { //return (InputKeyboardSource.GetKeyStateForCurrentThread(key) & Windows.UI.Core.CoreVirtualKeyStates.Down) != 0; return (Window.Current.CoreWindow.GetAsyncKeyState(key) & CoreVirtualKeyStates.Down) != 0; } - public static Windows.System.VirtualKeyModifiers KeyModifiers() + public static VirtualKeyModifiers KeyModifiers() { //return (InputKeyboardSource.GetKeyStateForCurrentThread(key) & Windows.UI.Core.CoreVirtualKeyStates.Down) != 0; - var modifiers = Windows.System.VirtualKeyModifiers.None; + var modifiers = VirtualKeyModifiers.None; - if ((Window.Current.CoreWindow.GetAsyncKeyState(Windows.System.VirtualKey.Control) & CoreVirtualKeyStates.Down) != 0) + if ((Window.Current.CoreWindow.GetAsyncKeyState(VirtualKey.Control) & CoreVirtualKeyStates.Down) != 0) { modifiers |= Windows.System.VirtualKeyModifiers.Control; } - if ((Window.Current.CoreWindow.GetAsyncKeyState(Windows.System.VirtualKey.Menu) & CoreVirtualKeyStates.Down) != 0) + if ((Window.Current.CoreWindow.GetAsyncKeyState(VirtualKey.Menu) & CoreVirtualKeyStates.Down) != 0) { modifiers |= Windows.System.VirtualKeyModifiers.Menu; } - if ((Window.Current.CoreWindow.GetAsyncKeyState(Windows.System.VirtualKey.Shift) & CoreVirtualKeyStates.Down) != 0) + if ((Window.Current.CoreWindow.GetAsyncKeyState(VirtualKey.Shift) & CoreVirtualKeyStates.Down) != 0) { modifiers |= Windows.System.VirtualKeyModifiers.Shift; } diff --git a/Telegram/Package.appxmanifest b/Telegram/Package.appxmanifest index bb1ef6029a..fe57e04b06 100644 --- a/Telegram/Package.appxmanifest +++ b/Telegram/Package.appxmanifest @@ -11,7 +11,7 @@ xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10" xmlns:desktop4="http://schemas.microsoft.com/appx/manifest/desktop/windows10/4" IgnorableNamespaces="mp uap uap3 uap4 uap5 uap6 uap11 rescap desktop desktop4"> - + Unigram Experimental diff --git a/Telegram/Services/Calls/VoipCall.cs b/Telegram/Services/Calls/VoipCall.cs index c6da383833..7d1e6bd4d0 100644 --- a/Telegram/Services/Calls/VoipCall.cs +++ b/Telegram/Services/Calls/VoipCall.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; diff --git a/Telegram/Services/Calls/VoipCallAudioLevelUpdatedEventArgs.cs b/Telegram/Services/Calls/VoipCallAudioLevelUpdatedEventArgs.cs index 0e48bd580e..834a8cd39d 100644 --- a/Telegram/Services/Calls/VoipCallAudioLevelUpdatedEventArgs.cs +++ b/Telegram/Services/Calls/VoipCallAudioLevelUpdatedEventArgs.cs @@ -1,4 +1,10 @@ -namespace Telegram.Services.Calls +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +namespace Telegram.Services.Calls { public partial class VoipCallAudioLevelUpdatedEventArgs { diff --git a/Telegram/Services/Calls/VoipCallBase.cs b/Telegram/Services/Calls/VoipCallBase.cs index 10fdfad999..d885f5a32b 100644 --- a/Telegram/Services/Calls/VoipCallBase.cs +++ b/Telegram/Services/Calls/VoipCallBase.cs @@ -1,4 +1,10 @@ -namespace Telegram.Services.Calls +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +namespace Telegram.Services.Calls { public abstract partial class VoipCallBase : ServiceBase { diff --git a/Telegram/Services/Calls/VoipCallConnectionStateChangedEventArgs.cs b/Telegram/Services/Calls/VoipCallConnectionStateChangedEventArgs.cs index 0c351e5df0..7432b1b132 100644 --- a/Telegram/Services/Calls/VoipCallConnectionStateChangedEventArgs.cs +++ b/Telegram/Services/Calls/VoipCallConnectionStateChangedEventArgs.cs @@ -1,4 +1,10 @@ -namespace Telegram.Services.Calls +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +namespace Telegram.Services.Calls { public partial class VoipCallConnectionStateChangedEventArgs { diff --git a/Telegram/Services/Calls/VoipCallMediaStateChangedEventArgs.cs b/Telegram/Services/Calls/VoipCallMediaStateChangedEventArgs.cs index 750dc942e0..2256b8ff69 100644 --- a/Telegram/Services/Calls/VoipCallMediaStateChangedEventArgs.cs +++ b/Telegram/Services/Calls/VoipCallMediaStateChangedEventArgs.cs @@ -1,4 +1,10 @@ -using Telegram.Native.Calls; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using Telegram.Native.Calls; namespace Telegram.Services.Calls { diff --git a/Telegram/Services/Calls/VoipCallRemoteBatteryLevelIsLowChangedEventArgs.cs b/Telegram/Services/Calls/VoipCallRemoteBatteryLevelIsLowChangedEventArgs.cs index bafa58a9ca..10a064bfcd 100644 --- a/Telegram/Services/Calls/VoipCallRemoteBatteryLevelIsLowChangedEventArgs.cs +++ b/Telegram/Services/Calls/VoipCallRemoteBatteryLevelIsLowChangedEventArgs.cs @@ -1,4 +1,10 @@ -namespace Telegram.Services.Calls +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +namespace Telegram.Services.Calls { public partial class VoipCallRemoteBatteryLevelIsLowChangedEventArgs { diff --git a/Telegram/Services/Calls/VoipCallSignalBarsUpdatedEventArgs.cs b/Telegram/Services/Calls/VoipCallSignalBarsUpdatedEventArgs.cs index f30e24cd52..1707d9cd5e 100644 --- a/Telegram/Services/Calls/VoipCallSignalBarsUpdatedEventArgs.cs +++ b/Telegram/Services/Calls/VoipCallSignalBarsUpdatedEventArgs.cs @@ -1,4 +1,10 @@ -namespace Telegram.Services.Calls +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +namespace Telegram.Services.Calls { public partial class VoipCallSignalBarsUpdatedEventArgs { diff --git a/Telegram/Services/Calls/VoipCallStateChangedEventArgs.cs b/Telegram/Services/Calls/VoipCallStateChangedEventArgs.cs index 412e8a75cc..2ea407564c 100644 --- a/Telegram/Services/Calls/VoipCallStateChangedEventArgs.cs +++ b/Telegram/Services/Calls/VoipCallStateChangedEventArgs.cs @@ -1,4 +1,10 @@ -using Telegram.Native.Calls; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using Telegram.Native.Calls; namespace Telegram.Services.Calls { diff --git a/Telegram/Services/Calls/VoipConnectionState.cs b/Telegram/Services/Calls/VoipConnectionState.cs index 8a040a8642..d9331f5802 100644 --- a/Telegram/Services/Calls/VoipConnectionState.cs +++ b/Telegram/Services/Calls/VoipConnectionState.cs @@ -1,4 +1,10 @@ -namespace Telegram.Services.Calls +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +namespace Telegram.Services.Calls { public enum VoipConnectionState { diff --git a/Telegram/Services/Calls/VoipGroupCall.cs b/Telegram/Services/Calls/VoipGroupCall.cs index 0609a8aa4b..7e83dc0799 100644 --- a/Telegram/Services/Calls/VoipGroupCall.cs +++ b/Telegram/Services/Calls/VoipGroupCall.cs @@ -1036,6 +1036,13 @@ public bool IsNoiseSuppressionEnabled } } + private double _volumeLevel = 1; + public double VolumeLevel + { + get => _volumeLevel; + set => _manager?.SetVolume(1, _volumeLevel = value); + } + public bool IsClosed => _isClosed; public void Update(GroupCall call, out bool closed) diff --git a/Telegram/Services/Calls/VoipService.cs b/Telegram/Services/Calls/VoipService.cs index 8f21cfd3f7..ef0dc970da 100644 --- a/Telegram/Services/Calls/VoipService.cs +++ b/Telegram/Services/Calls/VoipService.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -353,7 +359,13 @@ private async Task JoinAsyncInternal(XamlRoot xamlRoot, Chat chat, int groupCall { alias ??= chat.VideoChat.DefaultParticipantId; - if (alias == null) + var response = await ClientService.SendAsync(new GetGroupCall(groupCallId)); + if (response is not GroupCall groupCall) + { + return; + } + + if (alias == null && !groupCall.IsRtmpStream) { MessageSenders availableAliases; availableAliases = await ClientService.SendAsync(new GetVideoChatAvailableParticipants(chat.Id)) as MessageSenders; @@ -370,36 +382,32 @@ private async Task JoinAsyncInternal(XamlRoot xamlRoot, Chat chat, int groupCall alias = popup.SelectedSender ?? new MessageSenderUser(ClientService.Options.MyId); } - var response = await ClientService.SendAsync(new GetGroupCall(groupCallId)); - if (response is GroupCall groupCall) + if (!groupCall.IsRtmpStream) { - if (!groupCall.IsRtmpStream) + var permissions = await MediaDevicePermissions.CheckAccessAsync(xamlRoot, MediaDeviceAccess.Audio); + if (permissions == false) { - var permissions = await MediaDevicePermissions.CheckAccessAsync(xamlRoot, MediaDeviceAccess.Audio); - if (permissions == false) - { - return; - } + return; } + } - ThreadPool.QueueUserWorkItem(state => - { - var changed = false; + ThreadPool.QueueUserWorkItem(state => + { + var changed = false; - lock (_activeLock) - { - _activeCall = new VoipGroupCall(ClientService, Settings, Aggregator, xamlRoot, chat, groupCall, alias, inviteHash); - changed = groupCall.ScheduledStartDate > 0; - } + lock (_activeLock) + { + _activeCall = new VoipGroupCall(ClientService, Settings, Aggregator, xamlRoot, chat, groupCall, alias, inviteHash); + changed = groupCall.ScheduledStartDate > 0; + } - Aggregator.Publish(new UpdateActiveCall()); + Aggregator.Publish(new UpdateActiveCall()); - if (changed) - { - Aggregator.Publish(new UpdateGroupCall(new GroupCall(groupCall.Id, groupCall.Title, groupCall.InviteLink, groupCall.ScheduledStartDate, groupCall.EnabledStartNotification, groupCall.IsActive, groupCall.IsVideoChat, groupCall.IsRtmpStream, true, false, groupCall.IsOwned, groupCall.CanBeManaged, groupCall.ParticipantCount, groupCall.HasHiddenListeners, groupCall.LoadedAllParticipants, groupCall.RecentSpeakers, groupCall.IsMyVideoEnabled, groupCall.IsMyVideoPaused, groupCall.CanEnableVideo, groupCall.MuteNewParticipants, groupCall.CanToggleMuteNewParticipants, groupCall.RecordDuration, groupCall.IsVideoRecorded, groupCall.Duration))); - } - }); - } + if (changed) + { + Aggregator.Publish(new UpdateGroupCall(new GroupCall(groupCall.Id, groupCall.Title, groupCall.InviteLink, groupCall.ScheduledStartDate, groupCall.EnabledStartNotification, groupCall.IsActive, groupCall.IsVideoChat, groupCall.IsRtmpStream, true, false, groupCall.IsOwned, groupCall.CanBeManaged, groupCall.ParticipantCount, groupCall.HasHiddenListeners, groupCall.LoadedAllParticipants, groupCall.RecentSpeakers, groupCall.IsMyVideoEnabled, groupCall.IsMyVideoPaused, groupCall.CanEnableVideo, groupCall.MuteNewParticipants, groupCall.CanToggleMuteNewParticipants, groupCall.RecordDuration, groupCall.IsVideoRecorded, groupCall.Duration))); + } + }); } #endregion diff --git a/Telegram/Services/Calls/VoipState.cs b/Telegram/Services/Calls/VoipState.cs index d6f85552a3..e251426836 100644 --- a/Telegram/Services/Calls/VoipState.cs +++ b/Telegram/Services/Calls/VoipState.cs @@ -1,4 +1,10 @@ -namespace Telegram.Services.Calls +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +namespace Telegram.Services.Calls { public enum VoipState { diff --git a/Telegram/Services/Calls/VoipVideoOutput.cs b/Telegram/Services/Calls/VoipVideoOutput.cs index 1db5895bf2..4a5aeed7c8 100644 --- a/Telegram/Services/Calls/VoipVideoOutput.cs +++ b/Telegram/Services/Calls/VoipVideoOutput.cs @@ -1,4 +1,10 @@ -using System.Numerics; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System.Numerics; using Telegram.Native.Calls; using Telegram.Navigation; using Windows.Foundation; diff --git a/Telegram/Services/Calls/VoipVideoStateChangedEventArgs.cs b/Telegram/Services/Calls/VoipVideoStateChangedEventArgs.cs index efe16966e3..2b1365856e 100644 --- a/Telegram/Services/Calls/VoipVideoStateChangedEventArgs.cs +++ b/Telegram/Services/Calls/VoipVideoStateChangedEventArgs.cs @@ -1,4 +1,10 @@ -using System.Numerics; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System.Numerics; namespace Telegram.Services.Calls { diff --git a/Telegram/Services/CaptureSessionService.cs b/Telegram/Services/CaptureSessionService.cs index c09bc6ea64..c3d7c267e8 100644 --- a/Telegram/Services/CaptureSessionService.cs +++ b/Telegram/Services/CaptureSessionService.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using System.Collections.Generic; using System.Threading.Tasks; using Telegram.Common; @@ -62,12 +68,19 @@ public static async Task ChooseAsync(XamlRoot xamlRoot, b var access = await RequestAccessAsync(); if (access == AppCapabilityAccessStatus.UserPromptRequired) { - var picker = new GraphicsCapturePicker(); + try + { + var picker = new GraphicsCapturePicker(); - var backup = await picker.PickSingleItemAsync(); - if (backup != null) + var backup = await picker.PickSingleItemAsync(); + if (backup != null) + { + return new CaptureSessionOptions(backup, 0); + } + } + catch { - return new CaptureSessionOptions(backup, 0); + // All the remote procedure calls must be wrapped in a try-catch block } return null; diff --git a/Telegram/Services/ClientService.FeedbackChatTopics.cs b/Telegram/Services/ClientService.FeedbackChatTopics.cs index 46387ea072..99e813d624 100644 --- a/Telegram/Services/ClientService.FeedbackChatTopics.cs +++ b/Telegram/Services/ClientService.FeedbackChatTopics.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/Telegram/Services/ClientService.Files.cs b/Telegram/Services/ClientService.Files.cs index 9566c28e65..6be48a337e 100644 --- a/Telegram/Services/ClientService.Files.cs +++ b/Telegram/Services/ClientService.Files.cs @@ -76,6 +76,7 @@ public partial class ClientService */ private readonly HashSet _canceledDownloads = new(); + private readonly HashSet _partialDownloads = new(); private readonly HashSet _completedDownloads = new(); public Task GetFileAsync(int fileId) @@ -187,6 +188,7 @@ public async Task GetPermanentFileAsync(File file) public async void AddFileToDownloads(File file, long chatId, long messageId, int priority = 30) { + _partialDownloads.Remove(file.Id); Send(new AddFileToDownloads(file.Id, chatId, messageId, priority)); if (ApiInfo.HasCacheOnly || !SettingsService.Current.IsDownloadFolderEnabled || Future.Contains(file.Remote.UniqueId, true) || await Future.ContainsAsync(file.Remote.UniqueId)) @@ -281,6 +283,11 @@ public bool IsDownloadFileCanceled(int fileId) return _canceledDownloads.Contains(fileId); } + public bool IsDownloadFilePartial(int fileId) + { + return _partialDownloads.Contains(fileId); + } + private File ProcessFile(File file) { if (_files.TryGetValue(file.Id, out File singleton)) diff --git a/Telegram/Services/ClientService.ForumTopics.cs b/Telegram/Services/ClientService.ForumTopics.cs index 9070f18077..d5f5ae9b4b 100644 --- a/Telegram/Services/ClientService.ForumTopics.cs +++ b/Telegram/Services/ClientService.ForumTopics.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; diff --git a/Telegram/Services/ClientService.cs b/Telegram/Services/ClientService.cs index d6457199e1..31ae707108 100644 --- a/Telegram/Services/ClientService.cs +++ b/Telegram/Services/ClientService.cs @@ -40,17 +40,20 @@ public partial interface IClientService : ICacheService Task GetFileAsync(File file, bool completed = true); Task GetPermanentFileAsync(File file); - void DownloadFile(int fileId, int priority, int offset = 0, int limit = 0, bool synchronous = false); - Task DownloadFileAsync(File file, int priority, int offset = 0, int limit = 0); + void DownloadFile(int fileId, int priority, long offset = 0, long limit = 0, bool synchronous = false); + Task DownloadFileAsync(File file, int priority, long offset = 0, long limit = 0); void AddFileToDownloads(File file, long chatId, long messageId, int priority = 30); void CancelDownloadFile(File file, bool onlyIfPending = false); bool IsDownloadFileCanceled(int fileId); + bool IsDownloadFilePartial(int fileId); Task HasPrivacySettingsRuleAsync(UserPrivacySetting setting) where T : UserPrivacySettingRule; Task GetChatListAsync(ChatList chatList, int offset, int limit); + void LoadFullInfo(Chat chat); + void ViewMessages(long chatId, long messageThreadId, IList messageIds, MessageSource source, bool forceRead); Task GetStarTransactionsAsync(MessageSender ownerId, string subscriptionId, StarTransactionDirection direction, string offset, int limit); @@ -166,6 +169,7 @@ public partial interface ICacheService bool TryGetChat(long chatId, out Chat chat); bool TryGetChat(MessageSender sender, out Chat value); bool TryGetChat(AffiliateType type, out Chat value); + bool TryGetChat(SavedMessagesTopicType type, out Chat chat); bool TryGetChatFromUser(long userId, out long value); bool TryGetChatFromUser(long userId, out Chat value); @@ -970,12 +974,21 @@ public async Task CheckChatInviteLinkAsync(string inviteLink) - public void DownloadFile(int fileId, int priority, int offset = 0, int limit = 0, bool synchronous = false) + public void DownloadFile(int fileId, int priority, long offset = 0, long limit = 0, bool synchronous = false) { + if (limit != 0) + { + _partialDownloads.Add(fileId); + } + else + { + _partialDownloads.Remove(fileId); + } + Send(new DownloadFile(fileId, priority, offset, limit, synchronous)); } - public async Task DownloadFileAsync(File file, int priority, int offset = 0, int limit = 0) + public async Task DownloadFileAsync(File file, int priority, long offset = 0, long limit = 0) { var response = await SendAsync(new DownloadFile(file.Id, priority, offset, limit, true)); if (response is File updated) @@ -1307,6 +1320,22 @@ public ChatMemberStatus GetChatMemberStatus(Chat chat, out bool channel) return new ChatMemberStatusMember(); } + public void LoadFullInfo(Chat chat) + { + if (TryGetUser(chat, out User user)) + { + Send(new GetUserFullInfo(user.Id)); + } + else if (TryGetSupergroup(chat, out Supergroup supergroup)) + { + Send(new GetSupergroupFullInfo(supergroup.Id)); + } + else if (TryGetBasicGroup(chat, out BasicGroup basicGroup)) + { + Send(new GetBasicGroupFullInfo(basicGroup.Id)); + } + } + public string GetTitle(long chatId, bool tiny = false) { if (_chats.TryGetValue(chatId, out var chat)) @@ -1800,6 +1829,17 @@ public bool TryGetChat(AffiliateType type, out Chat value) return false; } + public bool TryGetChat(SavedMessagesTopicType type, out Chat value) + { + if (type is SavedMessagesTopicTypeSavedFromChat fromChat) + { + return TryGetChat(fromChat.ChatId, out value); + } + + value = null; + return false; + } + public bool TryGetChatFromUser(long userId, out long value) { return _usersToChats.TryGetValue(userId, out value); @@ -2448,7 +2488,7 @@ private void UpdateChatLastMessage(Chat chat, Message lastMessage) { chat.LastMessage = lastMessage; - if (lastMessage == null || lastMessage.MediaAlbumId == 0 || lastMessage.Content is not MessagePhoto and not MessageVideo) + if (lastMessage == null || lastMessage.MediaAlbumId == 0 || lastMessage.Content is not MessagePhoto and not MessageVideo || !SettingsService.Current.Diagnostics.AlbumPreloadDebug) { _lastMessageAlbums.TryRemove(chat.Id, out _); return; @@ -2511,10 +2551,10 @@ public void OnResult(Object update) var token = SessionId << 16 | updateFile.File.Id; if (updateFile.File.Local.IsDownloadingCompleted) { - EventAggregator.Current.Publish(updateFile.File, token | 0x01000000, true); + EventAggregator.Current.Publish(updateFile.File, token | 0x01000000); } - EventAggregator.Current.Publish(updateFile.File, token, false); + EventAggregator.Current.Publish(updateFile.File, token); TrackDownloadedFile(updateFile.File); return; } diff --git a/Telegram/Services/EventAggregator.cs b/Telegram/Services/EventAggregator.cs index 9b841faad7..53ac528c3e 100644 --- a/Telegram/Services/EventAggregator.cs +++ b/Telegram/Services/EventAggregator.cs @@ -32,7 +32,7 @@ public interface IEventAggregator void Unsubscribe(object subscriber, long token, bool fireAndForget); void Publish(object message); - void Publish(object message, long token, bool forget); + void Publish(object message, long token); } public partial class EventAggregator : IEventAggregator @@ -90,7 +90,7 @@ public virtual void Publish(object message) if (_typeHandlers.TryGetValue(messageType, out TypeHandler handler)) { - if (handler.Handle(message, false)) + if (handler.Handle(message)) { _typeHandlers.TryRemove(messageType, out _); } @@ -105,7 +105,7 @@ public partial class TypeHandler // collected, so we resynchronize the amount on every handle. protected int _count; - public virtual bool Handle(object message, bool forget) + public virtual bool Handle(object message) { var count = 0; @@ -212,18 +212,11 @@ public virtual void Unsubscribe(object subscriber, long token, bool fireAndForge } } - public virtual void Publish(object message, long token, bool forget) + public virtual void Publish(object message, long token) { - if (forget) + if (_longHandlers.TryGetValue(token, out LongHandler handler)) { - if (_longHandlers.TryRemove(token, out LongHandler handler)) - { - handler.Handle(message, true); - } - } - else if (_longHandlers.TryGetValue(token, out LongHandler handler)) - { - if (handler.Handle(message, false)) + if (handler.Handle(message)) { _longHandlers.TryRemove(token, out _); } @@ -232,7 +225,7 @@ public virtual void Publish(object message, long token, bool forget) public partial class LongHandler : TypeHandler { - public override bool Handle(object message, bool forget) + public override bool Handle(object message) { var count = 0; @@ -254,11 +247,6 @@ public override bool Handle(object message, bool forget) count++; } - if (forget && count > 0) - { - _delegates.Clear(); - } - _count = count; return count == 0; } diff --git a/Telegram/Services/ForumTopicService.cs b/Telegram/Services/ForumTopicService.cs index 8222304331..22ae1db723 100644 --- a/Telegram/Services/ForumTopicService.cs +++ b/Telegram/Services/ForumTopicService.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using System.Collections.Generic; using System.Linq; using System.Threading; diff --git a/Telegram/Services/ProxyService.cs b/Telegram/Services/ProxyService.cs index b11ed26b00..e7b30b6b32 100644 --- a/Telegram/Services/ProxyService.cs +++ b/Telegram/Services/ProxyService.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using System.Collections.Generic; using System.IO; using System.Linq; diff --git a/Telegram/Services/ServiceBase.cs b/Telegram/Services/ServiceBase.cs index da7c4a3380..8ad636af86 100644 --- a/Telegram/Services/ServiceBase.cs +++ b/Telegram/Services/ServiceBase.cs @@ -9,7 +9,14 @@ namespace Telegram.Services { - public partial class ServiceBase : BindableBase + public interface IService + { + IClientService ClientService { get; } + ISettingsService Settings { get; } + IEventAggregator Aggregator { get; } + } + + public partial class ServiceBase : BindableBase, IService { private readonly IClientService _clientService; private readonly ISettingsService _settingsService; diff --git a/Telegram/Services/Settings/AutoDownloadSettings.cs b/Telegram/Services/Settings/AutoDownloadSettings.cs index 75f16fb9f0..df112bf91f 100644 --- a/Telegram/Services/Settings/AutoDownloadSettings.cs +++ b/Telegram/Services/Settings/AutoDownloadSettings.cs @@ -65,6 +65,7 @@ public AutoDownloadSettings(ApplicationDataContainer container) _maximumVideoSize = container.GetInt64("maxVideoSize", 10 * 1024 * 1024); _documents = (AutoDownloadMode)container.GetInt32("documents", (int)AutoDownloadMode.All); _maximumDocumentSize = container.GetInt64("maxDocumentSize", 3 * 1024 * 1024); + _preloadLargeVideos = container.GetBoolean("preloadVideos", true); } public void Save(ApplicationDataContainer container) @@ -75,6 +76,7 @@ public void Save(ApplicationDataContainer container) container.Values["maxVideoSize"] = _maximumVideoSize; container.Values["documents"] = (int)_documents; container.Values["maxDocumentSize"] = _maximumDocumentSize; + container.Values["preloadVideos"] = _preloadLargeVideos; } public static AutoDownloadSettings Default @@ -87,6 +89,7 @@ public static AutoDownloadSettings Default preferences._maximumVideoSize = 10 * 1024 * 1024; preferences._documents = AutoDownloadMode.All; preferences._maximumDocumentSize = 3 * 1024 * 1024; + preferences._preloadLargeVideos = true; return preferences; } } @@ -100,6 +103,7 @@ public static AutoDownloadSettings FromPreset(Telegram.Td.Api.AutoDownloadSettin preferences._maximumVideoSize = preset.MaxVideoFileSize; preferences._documents = AutoDownloadMode.All; preferences._maximumDocumentSize = preset.MaxOtherFileSize; + preferences._preloadLargeVideos = preset.PreloadLargeVideos; return preferences; } @@ -107,7 +111,8 @@ public static AutoDownloadSettings FromPreset(Telegram.Td.Api.AutoDownloadSettin && _videos == AutoDownloadMode.All && _maximumVideoSize == 10 * 1024 * 1024 && _documents == AutoDownloadMode.All - && _maximumDocumentSize == 3 * 1024 * 1024; + && _maximumDocumentSize == 3 * 1024 * 1024 + && _preloadLargeVideos == true; private bool _disabled; public bool Disabled => _disabled; @@ -127,6 +132,9 @@ public static AutoDownloadSettings FromPreset(Telegram.Td.Api.AutoDownloadSettin private long _maximumDocumentSize; public long MaximumDocumentSize => _maximumDocumentSize; + private bool _preloadLargeVideos; + public bool PreloadLargeVideos => _preloadLargeVideos; + public AutoDownloadSettings UpdateDisabled(bool disabled) { var preferences = new AutoDownloadSettings(); @@ -136,6 +144,7 @@ public AutoDownloadSettings UpdateDisabled(bool disabled) preferences._maximumVideoSize = _maximumVideoSize; preferences._documents = _documents; preferences._maximumDocumentSize = _maximumDocumentSize; + preferences._preloadLargeVideos = _preloadLargeVideos; return preferences; } @@ -147,10 +156,11 @@ public AutoDownloadSettings UpdatePhotosMode(AutoDownloadMode mode) preferences._maximumVideoSize = _maximumVideoSize; preferences._documents = _documents; preferences._maximumDocumentSize = _maximumDocumentSize; + preferences._preloadLargeVideos = _preloadLargeVideos; return preferences; } - public AutoDownloadSettings UpdateVideosMode(AutoDownloadMode mode, long maximumSize) + public AutoDownloadSettings UpdateVideosMode(AutoDownloadMode mode, long maximumSize, bool preload) { var preferences = new AutoDownloadSettings(); preferences._photos = _photos; @@ -158,6 +168,7 @@ public AutoDownloadSettings UpdateVideosMode(AutoDownloadMode mode, long maximum preferences._maximumVideoSize = maximumSize; preferences._documents = _documents; preferences._maximumDocumentSize = _maximumDocumentSize; + preferences._preloadLargeVideos = preload; return preferences; } @@ -169,6 +180,7 @@ public AutoDownloadSettings UpdateDocumentsMode(AutoDownloadMode mode, long maxi preferences._maximumVideoSize = _maximumVideoSize; preferences._documents = mode; preferences._maximumDocumentSize = maximumSize; + preferences._preloadLargeVideos = _preloadLargeVideos; return preferences; } diff --git a/Telegram/Services/Settings/DiagnosticsSettings.cs b/Telegram/Services/Settings/DiagnosticsSettings.cs index 09e6343fc5..2b2797bb0e 100644 --- a/Telegram/Services/Settings/DiagnosticsSettings.cs +++ b/Telegram/Services/Settings/DiagnosticsSettings.cs @@ -4,6 +4,7 @@ // Distributed under the GNU General Public License v3.0. (See accompanying // file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) // +using Telegram.Common; namespace Telegram.Services.Settings { @@ -140,13 +141,6 @@ public bool DisablePackageManager set => AddOrUpdateValue(ref _disablePackageManager, "DisablePackageManager", value); } - private bool? _sendLargePhotos; - public bool SendLargePhotos - { - get => _sendLargePhotos ??= GetValueOrDefault("SendLargePhotos", false); - set => AddOrUpdateValue(ref _sendLargePhotos, "SendLargePhotos", value); - } - private bool? _useSpeexResampler; public bool UseSpeexResampler { @@ -192,10 +186,38 @@ public bool SparseMessagesDebug private bool? _savedMessagesDebug; public bool SavedMessagesDebug { - get => _savedMessagesDebug ??= GetValueOrDefault("SavedMessagesDebug", Constants.DEBUG); + get => _savedMessagesDebug ??= GetValueOrDefault("SavedMessagesDebug", ApiInfo.IsPackagedRelease); set => AddOrUpdateValue(ref _savedMessagesDebug, "SavedMessagesDebug", value); } + private bool? _deleteFilesDebug; + public bool DeleteFilesDebug + { + get => _deleteFilesDebug ??= GetValueOrDefault("DeleteFilesDebug", Constants.DEBUG); + set => AddOrUpdateValue(ref _deleteFilesDebug, "DeleteFilesDebug", value); + } + + private bool? _mediaServerDebug; + public bool MediaServerDebug + { + get => _mediaServerDebug ??= GetValueOrDefault("MediaServerDebug", ApiInfo.IsPackagedRelease); + set => AddOrUpdateValue(ref _mediaServerDebug, "MediaServerDebug", value); + } + + private bool? _albumPreloadDebug; + public bool AlbumPreloadDebug + { + get => _albumPreloadDebug ??= GetValueOrDefault("AlbumPreloadDebug", ApiInfo.IsPackagedRelease); + set => AddOrUpdateValue(ref _albumPreloadDebug, "AlbumPreloadDebug", value); + } + + private bool? _videoPreloadDebug; + public bool VideoPreloadDebug + { + get => _videoPreloadDebug ??= GetValueOrDefault("VideoPreloadDebug", ApiInfo.IsPackagedRelease); + set => AddOrUpdateValue(ref _videoPreloadDebug, "VideoPreloadDebug", value); + } + public bool IsLastErrorDiskFull { get; set; } } } diff --git a/Telegram/Services/Settings/PlaybackSettings.cs b/Telegram/Services/Settings/PlaybackSettings.cs index cb682c2150..7eb6819314 100644 --- a/Telegram/Services/Settings/PlaybackSettings.cs +++ b/Telegram/Services/Settings/PlaybackSettings.cs @@ -13,7 +13,6 @@ public partial class PlaybackSettings : SettingsServiceBase public PlaybackSettings(ApplicationDataContainer container) : base(container) { - } private int? _repeatMode; diff --git a/Telegram/Services/Settings/VideoSettings.cs b/Telegram/Services/Settings/VideoSettings.cs new file mode 100644 index 0000000000..b3d981d22c --- /dev/null +++ b/Telegram/Services/Settings/VideoSettings.cs @@ -0,0 +1,48 @@ +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using Telegram.Common; +using Telegram.Td.Api; +using Windows.Storage; + +namespace Telegram.Services.Settings +{ + public class VideoSettings : SettingsServiceBase + { + public VideoSettings(ApplicationDataContainer container) + : base(container.CreateContainer("Video", ApplicationDataCreateDisposition.Always)) + { + + } + + public bool HasPosition(File file) + { + return _container.Values.ContainsKey("Video" + file.Remote.UniqueId); + } + + public bool TryGetPosition(File file, out double position) + { + return _container.Values.TryGet("Video" + file.Remote.UniqueId, out position); + } + + public void SetPosition(File file, double position) + { + if (position > 0) + { + _container.Values["Video" + file.Remote.UniqueId] = position; + } + else + { + _container.Values.Remove("Video" + file.Remote.UniqueId); + } + } + + public void RemovePosition(File file) + { + _container.Values.Remove("Video" + file.Remote.UniqueId); + } + } +} diff --git a/Telegram/Services/SettingsService.cs b/Telegram/Services/SettingsService.cs index 7db16567db..c16e4cfe35 100644 --- a/Telegram/Services/SettingsService.cs +++ b/Telegram/Services/SettingsService.cs @@ -45,6 +45,7 @@ public interface ISettingsService AppearanceSettings Appearance { get; } PasscodeLockSettings PasscodeLock { get; } PlaybackSettings Playback { get; } + VideoSettings Video { get; } VoIPSettings VoIP { get; } TranslateSettings Translate { get; } @@ -65,7 +66,6 @@ public interface ISettingsService bool IsSecretPreviewsEnabled { get; set; } bool AutoPlayAnimations { get; set; } bool AutoPlayVideos { get; set; } - bool IsSendGrouped { get; set; } bool IsAccountsSelectorExpanded { get; set; } bool IsAllAccountsNotifications { get; set; } bool AreSmoothTransitionsEnabled { get; set; } @@ -88,6 +88,8 @@ public interface ISettingsService bool FullScreenGallery { get; set; } bool UseSystemSpellChecker { get; set; } + bool SendLargePhotos { get; set; } + bool IsStreamingEnabled { get; set; } double VolumeLevel { get; set; } bool VolumeMuted { get; set; } @@ -136,7 +138,7 @@ public bool AddOrUpdateValue(string key, object value) return AddOrUpdateValue(_container, key, value); } - protected bool AddOrUpdateValue(ref T storage, string key, T value) + public bool AddOrUpdateValue(ref T storage, string key, T value) { storage = value; return AddOrUpdateValue(_container, key, value); @@ -304,6 +306,9 @@ public AutoDownloadSettings AutoDownload private static PlaybackSettings _playback; public PlaybackSettings Playback => _playback ??= new PlaybackSettings(_local); + private static VideoSettings _video; + public VideoSettings Video => _video ??= new VideoSettings(_own); + private static VoIPSettings _voip; public VoIPSettings VoIP => _voip ??= new VoIPSettings(); @@ -577,11 +582,11 @@ public bool IsPowerSavingEnabled set => AddOrUpdateValue(ref _isPowerSavingEnabled, "IsPowerSavingEnabled", value); } - private bool? _isSendGrouped; - public bool IsSendGrouped + private bool? _sendLargePhotos; + public bool SendLargePhotos { - get => _isSendGrouped ??= GetValueOrDefault("IsSendGrouped", true); - set => AddOrUpdateValue(ref _isSendGrouped, "IsSendGrouped", value); + get => _sendLargePhotos ??= Diagnostics.GetValueOrDefault("SendLargePhotos", false); + set => Diagnostics.AddOrUpdateValue(ref _sendLargePhotos, "SendLargePhotos", value); } private bool? _isStreamingEnabled; diff --git a/Telegram/Services/ShortcutsService.cs b/Telegram/Services/ShortcutsService.cs index 3277e938b9..8c8c06a956 100644 --- a/Telegram/Services/ShortcutsService.cs +++ b/Telegram/Services/ShortcutsService.cs @@ -18,9 +18,9 @@ namespace Telegram.Services { public interface IShortcutsService { - InvokedShortcut Process(ProcessKeyboardAcceleratorEventArgs args); + InvokedShortcut Process(KeyRoutedEventArgs args); - bool TryGetShortcut(ProcessKeyboardAcceleratorEventArgs args, out Shortcut shortcut); + bool TryGetShortcut(KeyRoutedEventArgs args, out Shortcut shortcut); IList GetShortcuts(); IList Update(Shortcut shortcut, ShortcutCommand command); @@ -183,14 +183,14 @@ public ShortcutsService(IClientService clientService, ISettingsService settingsS InitializeCustom(); } - public InvokedShortcut Process(ProcessKeyboardAcceleratorEventArgs args) + public InvokedShortcut Process(KeyRoutedEventArgs args) { if (args.Key is >= VirtualKey.NumberPad0 and <= VirtualKey.NumberPad9) { - return Process(args.Modifiers, VirtualKey.Number0 + (args.Key - VirtualKey.NumberPad0)); + return Process(WindowContext.KeyModifiers(), VirtualKey.Number0 + (args.Key - VirtualKey.NumberPad0)); } - return Process(args.Modifiers, args.Key); + return Process(WindowContext.KeyModifiers(), args.Key); } private InvokedShortcut Process(VirtualKeyModifiers modifiers, VirtualKey key) @@ -210,9 +210,9 @@ private InvokedShortcut Process(VirtualKeyModifiers modifiers, VirtualKey key) //int nonVirtualKey = MapVirtualKey((uint)args.VirtualKey, 2); //char mappedChar = Convert.ToChar(nonVirtualKey); - public bool TryGetShortcut(ProcessKeyboardAcceleratorEventArgs args, out Shortcut shortcut) + public bool TryGetShortcut(KeyRoutedEventArgs args, out Shortcut shortcut) { - return TryGetShortcut(args.Modifiers, args.Key, out shortcut); + return TryGetShortcut(WindowContext.KeyModifiers(), args.Key, out shortcut); } private bool TryGetShortcut(VirtualKeyModifiers modifiers, VirtualKey key, out Shortcut shortcut) @@ -543,6 +543,46 @@ private Shortcut ParseMediaKeys(string keys) return null; } + + public static string GetStringRepresentation(VirtualKey key, VirtualKeyModifiers modifiers = VirtualKeyModifiers.None) + { + var builder = new StringBuilder(); + + static void ConcatVirtualKey(VirtualKey key, StringBuilder builder) + { + if (builder.Length > 0) + { + builder.Append("+"); + } + + builder.Append(key switch + { + VirtualKey.Control => Strings.VirtualKeyModifiersControl, + VirtualKey.Menu => Strings.VirtualKeyModifiersMenu, + VirtualKey.Shift => Strings.VirtualKeyModifiersShift, + (VirtualKey)190 => '.', + _ => key.ToString() + }); + } + + if ((modifiers & VirtualKeyModifiers.Control) != 0) + { + ConcatVirtualKey(VirtualKey.Control, builder); + } + + if ((modifiers & VirtualKeyModifiers.Menu) != 0) + { + ConcatVirtualKey(VirtualKey.Menu, builder); + } + + if ((modifiers & VirtualKeyModifiers.Shift) != 0) + { + ConcatVirtualKey(VirtualKey.Shift, builder); + } + + ConcatVirtualKey(key, builder); + return builder.ToString(); + } } public sealed partial class ShortcutList : KeyedList @@ -602,8 +642,7 @@ public override bool Equals(object obj) public override int GetHashCode() { - return Modifiers.GetHashCode() - ^ Key.GetHashCode(); + return HashCode.Combine(Modifiers, Key); } public override string ToString() diff --git a/Telegram/Services/Stripe/CardUtils.cs b/Telegram/Services/Stripe/CardUtils.cs index b2ad61e5c0..e4439bbb70 100644 --- a/Telegram/Services/Stripe/CardUtils.cs +++ b/Telegram/Services/Stripe/CardUtils.cs @@ -4,7 +4,6 @@ // Distributed under the GNU General Public License v3.0. (See accompanying // file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) // - namespace Telegram.Services.Stripe { public partial class CardUtils diff --git a/Telegram/Services/TranslateService.cs b/Telegram/Services/TranslateService.cs index 6a6f3042c9..5879a3aed9 100644 --- a/Telegram/Services/TranslateService.cs +++ b/Telegram/Services/TranslateService.cs @@ -16,7 +16,7 @@ namespace Telegram.Services { - public interface ITranslateService + public interface ITranslateService : IService { bool CanTranslateText(string text, bool entireChat = false); bool CanTranslateText(FormattedText text, bool entireChat = false); @@ -30,21 +30,15 @@ public interface ITranslateService bool Translate(MessageViewModel message, string toLanguage); } - public partial class TranslateService : ITranslateService + public partial class TranslateService : ServiceBase, ITranslateService { - private readonly IClientService _clientService; - private readonly ISettingsService _settings; - private readonly IEventAggregator _aggregator; - private const string LANG_UND = "und"; private const string LANG_AUTO = "auto"; private const string LANG_LATN = "latn"; public TranslateService(IClientService clientService, ISettingsService settings, IEventAggregator aggregator) + : base(clientService, settings, aggregator) { - _clientService = clientService; - _settings = settings; - _aggregator = aggregator; } public static string LanguageName(string locale) @@ -99,8 +93,8 @@ public bool CanTranslateText(string text, bool entireChat = false) public bool CanTranslate(string language, bool entireChat) { var allowed = entireChat - ? _settings.Translate.Chats - : _settings.Translate.Messages; + ? Settings.Translate.Chats + : Settings.Translate.Messages; if (string.IsNullOrEmpty(language) || !allowed) { @@ -108,7 +102,7 @@ public bool CanTranslate(string language, bool entireChat) } var split = language.Split('-'); - var exclude = _settings.Translate.DoNot; + var exclude = Settings.Translate.DoNot; if (entireChat) { @@ -134,12 +128,12 @@ public Task TranslateAsync(string text, string toLanguage) public async Task TranslateAsync(FormattedText text, string toLanguage) { - return await _clientService.SendAsync(new TranslateText(text, toLanguage)); + return await ClientService.SendAsync(new TranslateText(text, toLanguage)); } public async Task TranslateAsync(long chatId, long messageId, string toLanguage) { - return await _clientService.SendAsync(new TranslateMessageText(chatId, messageId, toLanguage)); + return await ClientService.SendAsync(new TranslateMessageText(chatId, messageId, toLanguage)); } private readonly ConcurrentDictionary _translations = new(); @@ -173,7 +167,7 @@ public bool Translate(MessageViewModel message, string toLanguage) message.TranslatedText = new MessageTranslateResultPending(); _translations[key] = new TranslatedMessage(cached, null); - _clientService.Send(new TranslateMessageText(message.ChatId, message.Id, toLanguage), handler => + ClientService.Send(new TranslateMessageText(message.ChatId, message.Id, toLanguage), handler => { if (handler is FormattedText text && string.Equals(message.Text?.Text, cached)) { @@ -186,7 +180,7 @@ public bool Translate(MessageViewModel message, string toLanguage) message.TranslatedText = result; _translations[key] = new TranslatedMessage(cached, result); - _aggregator.Publish(new UpdateMessageTranslatedText(message.ChatId, message.Id, result)); + Aggregator.Publish(new UpdateMessageTranslatedText(message.ChatId, message.Id, result)); } }); diff --git a/Telegram/Services/ViewService/ViewLifetimeControl.cs b/Telegram/Services/ViewService/ViewLifetimeControl.cs index 552a4b83cf..31b6442e97 100644 --- a/Telegram/Services/ViewService/ViewLifetimeControl.cs +++ b/Telegram/Services/ViewService/ViewLifetimeControl.cs @@ -9,7 +9,6 @@ // Copyright (c) Microsoft. All rights reserved. // //********************************************************* - // The objects defined here demonstrate how to make sure each of the views created remains alive as long as // the app needs them, but only when they're being used by the app or the user. Many of the scenarios contained in this // sample use these functions to keep track of the views available and ensure that the view is not closed while @@ -25,7 +24,6 @@ // Each view lives on its own thread, so concurrency control is necessary. Also, as you'll see in the sample, // certain objects may be bound to UI on given threads. Properties of those objects should only be updated // on that UI thread. - using System; using System.Collections.Concurrent; using System.Threading.Tasks; diff --git a/Telegram/Streams/AnimatedEmojiFileSource.cs b/Telegram/Streams/AnimatedEmojiFileSource.cs index 437aa13e30..a2d420b23b 100644 --- a/Telegram/Streams/AnimatedEmojiFileSource.cs +++ b/Telegram/Streams/AnimatedEmojiFileSource.cs @@ -4,6 +4,7 @@ // Distributed under the GNU General Public License v3.0. (See accompanying // file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) // +using System; using Telegram.Common; using Telegram.Services; using Telegram.Td.Api; @@ -73,7 +74,7 @@ public override bool Equals(object obj) { if (obj is CustomEmojiFileSource y && !y.IsUnique && !IsUnique) { - return y.Id == Id; + return y.Id == Id && y.IsAnimated == IsAnimated; } return base.Equals(obj); @@ -86,7 +87,7 @@ public override int GetHashCode() return base.GetHashCode(); } - return _emoji.GetHashCode(); + return HashCode.Combine(_emoji, IsAnimated); } } } diff --git a/Telegram/Streams/AnimatedImageSource.cs b/Telegram/Streams/AnimatedImageSource.cs index dcfe234631..ddda940164 100644 --- a/Telegram/Streams/AnimatedImageSource.cs +++ b/Telegram/Streams/AnimatedImageSource.cs @@ -66,11 +66,13 @@ public virtual void RequestOutline() public bool IsUnique { get; set; } + public bool IsAnimated { get; set; } = true; + public override bool Equals(object obj) { if (obj is AnimatedImageSource y && !y.IsUnique && !IsUnique) { - return y.Id == Id; + return y.Id == Id && y.IsAnimated == IsAnimated; } return base.Equals(obj); @@ -83,7 +85,7 @@ public override int GetHashCode() return base.GetHashCode(); } - return Id.GetHashCode(); + return HashCode.Combine(Id, IsAnimated); } } diff --git a/Telegram/Streams/CustomEmojiFileSource.cs b/Telegram/Streams/CustomEmojiFileSource.cs index d50c7e5f8a..72533a5f5b 100644 --- a/Telegram/Streams/CustomEmojiFileSource.cs +++ b/Telegram/Streams/CustomEmojiFileSource.cs @@ -4,6 +4,7 @@ // Distributed under the GNU General Public License v3.0. (See accompanying // file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) // +using System; using Telegram.Common; using Telegram.Services; using Telegram.Td.Api; @@ -88,7 +89,7 @@ public override bool Equals(object obj) { if (obj is CustomEmojiFileSource y && !y.IsUnique && !IsUnique) { - return y.Id == Id; + return y.Id == Id && y.IsAnimated == IsAnimated; } return base.Equals(obj); @@ -101,7 +102,7 @@ public override int GetHashCode() return base.GetHashCode(); } - return Id.GetHashCode(); + return HashCode.Combine(Id, IsAnimated); } } } diff --git a/Telegram/Streams/DelayedFileSource.cs b/Telegram/Streams/DelayedFileSource.cs index d307361e3a..8c411b50ff 100644 --- a/Telegram/Streams/DelayedFileSource.cs +++ b/Telegram/Streams/DelayedFileSource.cs @@ -203,7 +203,7 @@ public override bool Equals(object obj) { if (obj is DelayedFileSource y && !y.IsUnique && !IsUnique) { - return y.Id == Id; + return y.Id == Id && y.IsAnimated == IsAnimated; } return base.Equals(obj); @@ -216,7 +216,7 @@ public override int GetHashCode() return base.GetHashCode(); } - return Id.GetHashCode(); + return HashCode.Combine(Id, IsAnimated); } } } diff --git a/Telegram/Streams/LocalFileSource.cs b/Telegram/Streams/LocalFileSource.cs index def6a7e7c8..2dba2e151b 100644 --- a/Telegram/Streams/LocalFileSource.cs +++ b/Telegram/Streams/LocalFileSource.cs @@ -86,7 +86,7 @@ public override bool Equals(object obj) { if (obj is LocalFileSource y && !y.IsUnique && !IsUnique) { - return y.FilePath == FilePath; + return y.FilePath == FilePath && y.IsAnimated == IsAnimated; } return base.Equals(obj); @@ -99,7 +99,7 @@ public override int GetHashCode() return base.GetHashCode(); } - return FilePath.GetHashCode(); + return HashCode.Combine(FilePath, IsAnimated); } } } diff --git a/Telegram/Streams/ReactionFileSource.cs b/Telegram/Streams/ReactionFileSource.cs index 43d8b1a7ae..149ba44404 100644 --- a/Telegram/Streams/ReactionFileSource.cs +++ b/Telegram/Streams/ReactionFileSource.cs @@ -4,6 +4,7 @@ // Distributed under the GNU General Public License v3.0. (See accompanying // file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) // +using System; using Telegram.Common; using Telegram.Services; using Telegram.Td.Api; @@ -22,6 +23,31 @@ public ReactionFileSource(IClientService clientService, ReactionType reaction) DownloadFile(null, null); } + private ReactionFileSource(IClientService clientService, ReactionType reaction, File file) + : base(clientService, file) + { + _reaction = reaction; + + if (file == null) + { + DownloadFile(null, null); + } + } + + public ReactionFileSource Clone(bool animated) + { + return new ReactionFileSource(_clientService, _reaction, _file) + { + IsUnique = !animated, + IsAnimated = animated, + UseCenterAnimation = true, + Format = Format, + Width = Width, + Height = Height, + NeedsRepainting = NeedsRepainting + }; + } + public bool UseCenterAnimation { get; set; } public override long Id => GetHashCode(); @@ -101,7 +127,7 @@ public override bool Equals(object obj) { if (obj is CustomEmojiFileSource y && !y.IsUnique && !IsUnique) { - return y.Id == Id; + return y.Id == Id && y.IsAnimated == IsAnimated; } return base.Equals(obj); @@ -116,8 +142,9 @@ public override int GetHashCode() return _reaction switch { - ReactionTypeEmoji emoji => emoji.Emoji.GetHashCode(), - ReactionTypeCustomEmoji customEmoji => customEmoji.CustomEmojiId.GetHashCode(), + ReactionTypeEmoji emoji => HashCode.Combine(emoji.Emoji, IsAnimated), + ReactionTypeCustomEmoji customEmoji => HashCode.Combine(customEmoji.CustomEmojiId, IsAnimated), + ReactionTypePaid paid => HashCode.Combine("\u2B50", IsAnimated), _ => base.GetHashCode() }; } diff --git a/Telegram/Streams/RemoteFileSource.cs b/Telegram/Streams/RemoteFileSource.cs index 1175da9d71..e84fb664df 100644 --- a/Telegram/Streams/RemoteFileSource.cs +++ b/Telegram/Streams/RemoteFileSource.cs @@ -15,6 +15,7 @@ namespace Telegram.Streams public partial class RemoteFileSource : AnimatedImageSource { private readonly ManualResetEvent _event; + private readonly object _stateLock = new object(); private readonly IClientService _clientService; @@ -23,7 +24,7 @@ public partial class RemoteFileSource : AnimatedImageSource private bool _canceled; private long _offset; - private long _next; + private long _count; private bool _closed; @@ -42,20 +43,19 @@ public RemoteFileSource(IClientService clientService, File file, int priority = _limit = limit; Format = new StickerFormatWebm(); - - //if (file.Local.CanBeDownloaded && !file.Local.IsDownloadingCompleted) - { - UpdateManager.Subscribe(this, clientService, file, ref _fileToken, UpdateFile); - } + UpdateManager.Subscribe(this, clientService, file, ref _fileToken, UpdateFile); } public override void SeekCallback(long offset) { - _offset = offset; - - if (_file.Local.CanBeDownloaded && !_file.Local.IsDownloadingCompleted && !_limit) + lock (_stateLock) { - _clientService.Send(new DownloadFile(_file.Id, _priority, offset, 0, false)); + _offset = offset; + + if (_file.Local.CanBeDownloaded && !_file.Local.IsDownloadingCompleted && !_limit) + { + _clientService.DownloadFile(_file.Id, _priority, offset, 0, false); + } } } @@ -79,31 +79,35 @@ public Task ReadCallbackAsync(long count) protected bool MustWait(long count) { - var begin = _file.Local.DownloadOffset; - var end = _file.Local.DownloadOffset + _file.Local.DownloadedPrefixSize; - - var inBegin = _offset >= begin; - var inEnd = end >= _offset + count || end == _file.Size; - var difference = end - _offset; - - if (_canceled) + lock (_stateLock) { - return false; - } + if (_canceled) + { + //Logger.Info("Canceled"); + return false; + } - if (_file.Local.Path.Length > 0 && (inBegin && inEnd || _file.Local.IsDownloadingCompleted)) - { - return false; - } + var begin = _file.Local.DownloadOffset; + var end = _file.Local.DownloadOffset + _file.Local.DownloadedPrefixSize; + + var inBegin = _offset >= begin; + var inEnd = end >= _offset + count /*|| end == _file.Size*/; - _event.Reset(); + if (_file.Local.Path.Length > 0 && ((inBegin && inEnd) || _file.Local.IsDownloadingCompleted)) + { + Logger.Debug($"Next chunk is available, offset: {_offset}, count: {count}, prefix: {_file.Local.DownloadedPrefixSize}, size: {_file.Size}"); + return false; + } - _clientService.Send(new DownloadFile(_file.Id, 32, _offset, _limit ? count : 0, false)); - _next = count; + // Reset event before requesting download to avoid race condition + _event.Reset(); + _count = count; - //Logger.Debug($"Not enough data available, offset: {_offset}, next: {_next}, size: {_file.Size}"); + _clientService.DownloadFile(_file.Id, 32, _offset, _limit ? count : 0, false); - return true; + Logger.Debug($"Not enough data available, offset: {_offset}, count: {count}, size: {_file.Size}"); + return true; + } } public override string FilePath => _file.Local.Path; @@ -122,52 +126,67 @@ private void UpdateFile(object target, File file) return; } - var enough = file.Local.DownloadedPrefixSize >= _next; - var end = file.Local.DownloadOffset + file.Local.DownloadedPrefixSize == file.Size; - - if (file.Local.Path.Length > 0 && (file.Local.DownloadOffset == _offset && (enough || end) || file.Local.IsDownloadingCompleted)) - { - //Logger.Debug($"Next chunk is available, offset: {_offset}, prefix: {file.Local.DownloadedPrefixSize}, size: {_file.Size}"); - _event.Set(); - } - else if (!file.Local.IsDownloadingActive) + lock (_stateLock) { - Logger.Info("Download was canceled for " + file.Id); - _event.Set(); + var begin = _file.Local.DownloadOffset; + var end = _file.Local.DownloadOffset + _file.Local.DownloadedPrefixSize; + + var inBegin = _offset >= begin; + var inEnd = end >= _offset + _count /*|| end == _file.Size*/; + + if (_file.Local.Path.Length > 0 && ((inBegin && inEnd) || _file.Local.IsDownloadingCompleted)) + { + Logger.Debug($"Next chunk is available, offset: {_offset}, count: {_count}, prefix: {file.Local.DownloadedPrefixSize}, size: {_file.Size}"); + _event.Set(); + } + else if (_canceled || !file.Local.IsDownloadingActive) + { + Logger.Info("Download was canceled for " + file.Id); + _event.Set(); + } + //else + //{ + // Logger.Debug($"Not enough data available, expected offset: {_offset}, expected count: {_count}, offset: {file.Local.DownloadOffset}, prefix: {file.Local.DownloadedPrefixSize}, size: {_file.Size}, completed: {file.Local.IsDownloadingCompleted}"); + //} } - //else - //{ - // Logger.Debug($"Next chunk is not available, offset: {_offset}, real: {file.Local.DownloadOffset}, prefix: {file.Local.DownloadedPrefixSize}, size: {_file.Size}, completed: {file.Local.IsDownloadingCompleted}"); - //} } public void Open() { - _closed = false; - _canceled = false; + lock (_stateLock) + { + _closed = false; + _canceled = false; + } SeekCallback(0); } - public void Close() + public void Close(bool cancel) { - if (_closed) + lock (_stateLock) { - return; - } + if (_closed) + { + return; + } - _closed = true; + _closed = true; - //Logger.Debug($"Disposing the stream"); - UpdateManager.Unsubscribe(this, ref _fileToken); + //Logger.Debug($"Disposing the stream"); + UpdateManager.Unsubscribe(this, ref _fileToken); - _canceled = true; - _clientService.Send(new CancelDownloadFile(_file.Id, false)); + if (cancel) + { + _canceled = true; + _clientService.Send(new CancelDownloadFile(_file.Id, false)); + } - _event.Set(); + _event.Set(); + } //_event.Dispose(); //_readLock.Dispose(); } } -} +} \ No newline at end of file diff --git a/Telegram/Streams/RemoteFileStream.cs b/Telegram/Streams/RemoteFileStream.cs index 5ea6f476c5..b19eae7f91 100644 --- a/Telegram/Streams/RemoteFileStream.cs +++ b/Telegram/Streams/RemoteFileStream.cs @@ -14,8 +14,6 @@ namespace Telegram.Streams { public partial class RemoteFileStream : MediaInput { - private readonly File _file; - private readonly RemoteFileSource _source; private FileStreamFromApp _fileStream; @@ -25,9 +23,7 @@ public partial class RemoteFileStream : MediaInput public RemoteFileStream(IClientService clientService, File file) { - _file = file; _source = new RemoteFileSource(clientService, file); - CanSeek = true; } @@ -38,7 +34,7 @@ public RemoteFileStream(IClientService clientService, File file) /// true if the stream opened successfully public override bool Open(out ulong size) { - size = (ulong)_file.Size; + size = (ulong)_source.FileSize; _source.Open(); return true; @@ -56,12 +52,12 @@ public unsafe override int Read(IntPtr buf, uint len) { _source.ReadCallback((int)len); - if (_disposed || _source.Offset == _file.Size) + if (_disposed || _source.Offset == _source.FileSize) { return 0; } - var path = _file.Local.Path; + var path = _source.FilePath; if (path.Length > 0 && !_source.IsCanceled && (_fileStream == null || _filePath != path)) { _fileStream?.Close(); @@ -113,7 +109,7 @@ public override void Close() _fileStream?.Close(); _fileStream = null; - _source.Close(); + _source.Close(true); } catch (Exception) { diff --git a/Telegram/Strings/en/Resources.cs b/Telegram/Strings/en/Resources.cs index 610ea85353..eee00e094c 100644 --- a/Telegram/Strings/en/Resources.cs +++ b/Telegram/Strings/en/Resources.cs @@ -2,7 +2,7 @@ // // This code was generated by TdParseOptions (http://github.com/UnigramDev/UnigramUtils/) // -// Generated: 07/07/2025 22:33:02 +// Generated: 07/20/2025 17:32:47 // // -------------------------------------------------------------------------------------------------- namespace Telegram @@ -2731,6 +2731,16 @@ public static class R /// public static string AutoDownloadPm => Resource.GetString("AutoDownloadPm"); + /// + /// Localized resource similar to "Preload larger videos" + /// + public static string AutoDownloadPreloadVideo => Resource.GetString("AutoDownloadPreloadVideo"); + + /// + /// Localized resource similar to "Preload the first few seconds (1-2 MB) of videos larger than %1$s for instant playback." + /// + public static string AutoDownloadPreloadVideoInfo => Resource.GetString("AutoDownloadPreloadVideoInfo"); + /// /// Localized resource similar to "Private Chats" /// @@ -6802,6 +6812,11 @@ public static class R /// public static string CopyEmojiPreview => Resource.GetString("CopyEmojiPreview"); + /// + /// Localized resource similar to "Copy Hashtag" + /// + public static string CopyHashtag => Resource.GetString("CopyHashtag"); + /// /// Localized resource similar to "Copy Image" /// @@ -6812,6 +6827,21 @@ public static class R /// public static string CopyLink => Resource.GetString("CopyLink"); + /// + /// Localized resource similar to "Copy E-Mail" + /// + public static string CopyMail => Resource.GetString("CopyMail"); + + /// + /// Localized resource similar to "Copy Number" + /// + public static string CopyNumber => Resource.GetString("CopyNumber"); + + /// + /// Localized resource similar to "Copy Selected" + /// + public static string CopySelected => Resource.GetString("CopySelected"); + /// /// Localized resource similar to "Copy Selected as Text" /// @@ -9719,6 +9749,11 @@ public static class R /// public static string Gift2ActionSavedInfo => Resource.GetString("Gift2ActionSavedInfo"); + /// + /// Localized resource similar to "Saved Gift" + /// + public static string Gift2ActionSelfTitle => Resource.GetString("Gift2ActionSelfTitle"); + /// /// Localized resource similar to "Gift from {0}" /// @@ -11089,6 +11124,11 @@ public static class R /// public static string HideSenderNames => Resource.GetString("HideSenderNames"); + /// + /// Localized resource similar to "Hide sender’s name" + /// + public static string HideSendersName => Resource.GetString("HideSendersName"); + /// /// Localized resource similar to "History was cleared" /// @@ -11119,6 +11159,11 @@ public static class R /// public static string IfInactiveFor => Resource.GetString("IfInactiveFor"); + /// + /// Localized resource similar to "Image copied to clipboard." + /// + public static string ImageCopied => Resource.GetString("ImageCopied"); + /// /// Localized resource similar to "Import" /// @@ -11225,7 +11270,7 @@ public static class R public static string InviteLink => Resource.GetString("InviteLink"); /// - /// Localized resource similar to "Invite Link only works for group members" + /// Localized resource similar to "This topic link will only work for group members" /// public static string InviteLinkPrivate => Resource.GetString("InviteLinkPrivate"); @@ -11670,6 +11715,11 @@ public static class R /// public static string LimitReachedPinDialogsPremium => Resource.GetString("LimitReachedPinDialogsPremium"); + /// + /// Localized resource similar to "Sorry, you can't pin more than {0} topics to the top." + /// + public static string LimitReachedPinnedTopics => Resource.GetString("LimitReachedPinnedTopics"); + /// /// Localized resource similar to "You have reserved too many public links. Try revoking the link from an older group or channel, or subscribe to **Telegram Premium** to double the limit to **{1}** public links." /// @@ -13409,6 +13459,11 @@ public static class R /// public static string NoWordsRecognized => Resource.GetString("NoWordsRecognized"); + /// + /// Localized resource similar to "This number is not on Telegram" + /// + public static string NumberNotOnTelegram => Resource.GetString("NumberNotOnTelegram"); + /// /// Localized resource similar to "October" /// @@ -15360,6 +15415,11 @@ public static class R /// public static string PrivacySettings => Resource.GetString("PrivacySettings"); + /// + /// Localized resource similar to "You have changed some privacy settings. Apply changes?" + /// + public static string PrivacySettingsChangedAlert => Resource.GetString("PrivacySettingsChangedAlert"); + /// /// Localized resource similar to "Privacy" /// @@ -15467,6 +15527,11 @@ public static class R /// public static string PrivateStory => Resource.GetString("PrivateStory"); + /// + /// Localized resource similar to "Processing..." + /// + public static string ProcessingVideo => Resource.GetString("ProcessingVideo"); + /// /// Localized resource similar to "Date of Birth" /// @@ -16590,6 +16655,16 @@ public static class R /// public static string RevokeLinkAlertChannel => Resource.GetString("RevokeLinkAlertChannel"); + /// + /// Localized resource similar to "Revoke Stream Key" + /// + public static string RevokeStreamKey => Resource.GetString("RevokeStreamKey"); + + /// + /// Localized resource similar to "Are you sure you want to revoke your Stream Key?" + /// + public static string RevokeStreamKeyAlert => Resource.GetString("RevokeStreamKeyAlert"); + /// /// Localized resource similar to "Saturation" /// @@ -16975,6 +17050,11 @@ public static class R /// public static string SendByEnterKey => Resource.GetString("SendByEnterKey"); + /// + /// Localized resource similar to "Send direct message" + /// + public static string SendDirectMessage => Resource.GetString("SendDirectMessage"); + /// /// Localized resource similar to "Send Emoji" /// @@ -17365,6 +17445,11 @@ public static class R /// public static string SharePhoneNumberWith => Resource.GetString("SharePhoneNumberWith"); + /// + /// Localized resource similar to "Share QR Code" + /// + public static string ShareQrCode => Resource.GetString("ShareQrCode"); + /// /// Localized resource similar to "Send to..." /// @@ -19102,22 +19187,22 @@ public static class R public static string TodoPremiumRequired => Resource.GetString("TodoPremiumRequired"); /// - /// Localized resource similar to "un1 marked "{0}" as done" + /// Localized resource similar to "un1 marked **"{0}"** as done" /// public static string TodoTaskCompleted => Resource.GetString("TodoTaskCompleted"); /// - /// Localized resource similar to "You marked "{0}" as done" + /// Localized resource similar to "You marked **"{0}"** as done" /// public static string TodoTaskCompletedOut => Resource.GetString("TodoTaskCompletedOut"); /// - /// Localized resource similar to "un1 marked "{0}" as not done" + /// Localized resource similar to "un1 marked **"{0}"** as not done" /// public static string TodoTaskNotCompleted => Resource.GetString("TodoTaskNotCompleted"); /// - /// Localized resource similar to "You marked "{0}" as not done" + /// Localized resource similar to "You marked **"{0}"** as not done" /// public static string TodoTaskNotCompletedOut => Resource.GetString("TodoTaskNotCompletedOut"); @@ -19781,6 +19866,16 @@ public static class R /// public static string UnlockPremium => Resource.GetString("UnlockPremium"); + /// + /// Localized resource similar to "Subscribe to **Telegram Premium** to unlock this emoji." + /// + public static string UnlockPremiumEmojiHint => Resource.GetString("UnlockPremiumEmojiHint"); + + /// + /// Localized resource similar to "Subscribe to **Telegram Premium** to unlock this reaction." + /// + public static string UnlockPremiumEmojiReaction => Resource.GetString("UnlockPremiumEmojiReaction"); + /// /// Localized resource similar to "Unlock Premium Stickers" /// @@ -20573,6 +20668,11 @@ public static class R /// public static string ViewPhotoAction => Resource.GetString("ViewPhotoAction"); + /// + /// Localized resource similar to "View Profile >" + /// + public static string ViewProfile => Resource.GetString("ViewProfile"); + /// /// Localized resource similar to "Views by source" /// @@ -20603,6 +20703,21 @@ public static class R /// public static string ViewWallpaperAction => Resource.GetString("ViewWallpaperAction"); + /// + /// Localized resource similar to "Ctrl" + /// + public static string VirtualKeyModifiersControl => Resource.GetString("VirtualKeyModifiersControl"); + + /// + /// Localized resource similar to "Alt" + /// + public static string VirtualKeyModifiersMenu => Resource.GetString("VirtualKeyModifiersMenu"); + + /// + /// Localized resource similar to "Shift" + /// + public static string VirtualKeyModifiersShift => Resource.GetString("VirtualKeyModifiersShift"); + /// /// Localized resource similar to "**{0}** doesn't accept voice messages." /// diff --git a/Telegram/Strings/en/Resources.resw b/Telegram/Strings/en/Resources.resw index 564f4763e2..e7f1532955 100644 --- a/Telegram/Strings/en/Resources.resw +++ b/Telegram/Strings/en/Resources.resw @@ -1622,6 +1622,12 @@ All new messages in this chat will be automatically deleted {0} after they were PM + + Preload larger videos + + + Preload the first few seconds (1-2 MB) of videos larger than %1$s for instant playback. + Private Chats @@ -4531,12 +4537,24 @@ Minimum length is 5 characters. Copy Emoji + + Copy Hashtag + Copy Image Copy Link + + Copy E-Mail + + + Copy Number + + + Copy Selected + Copy Selected as Text @@ -6432,6 +6450,9 @@ Duration: {1} You added this gift to your profile. + + Saved Gift + Gift from {0} @@ -7442,6 +7463,9 @@ This setting does not affect group chats. Hide Sender Names + + Hide sender’s name + History was cleared @@ -7472,6 +7496,9 @@ This setting does not affect group chats. If inactive for + + Image copied to clipboard. + Import @@ -7560,7 +7587,7 @@ This setting does not affect group chats. {0} invite links - Invite Link only works for group members + This topic link will only work for group members Invite Links @@ -7867,6 +7894,9 @@ Upgrade to **Telegram Premium** to create up to **{1}**. Sorry, you can't pin more than {0} chats to the top. Unpin some that are currently pinned. + + Sorry, you can't pin more than {0} topics to the top. + You have reserved too many public links. Try revoking the link from an older group or channel, or subscribe to **Telegram Premium** to double the limit to **{1}** public links. @@ -9063,6 +9093,9 @@ Stories No words recognized. + + This number is not on Telegram + October @@ -10318,6 +10351,9 @@ seamlessly and efficiently. Privacy and Security + + You have changed some privacy settings. Apply changes? + Privacy @@ -10383,6 +10419,9 @@ seamlessly and efficiently. Private story + + Processing... + Date of Birth @@ -11162,6 +11201,12 @@ The group "**{1}**" will become private. The channel "**{1}**" will become private. + + Revoke Stream Key + + + Are you sure you want to revoke your Stream Key? + Saturation @@ -11405,6 +11450,9 @@ The channel "**{1}**" will become private. Enter + + Send direct message + Send Emoji @@ -11645,6 +11693,9 @@ The channel "**{1}**" will become private. Share my phone number with {0} + + Share QR Code + {0} share @@ -12903,16 +12954,16 @@ in mini apps on Telegram. Only **Telegram Premium** subscribers can mark tasks as done. - un1 marked "{0}" as done + un1 marked **"{0}"** as done - You marked "{0}" as done + You marked **"{0}"** as done - un1 marked "{0}" as not done + un1 marked **"{0}"** as not done - You marked "{0}" as not done + You marked **"{0}"** as not done Task @@ -13366,6 +13417,12 @@ If you are not comfortable with Telegram's modest needs, it won't be p Subscribe to **Telegram Premium** to unlock this and many other features. + + Subscribe to **Telegram Premium** to unlock this emoji. + + + Subscribe to **Telegram Premium** to unlock this reaction. + Unlock Premium Stickers @@ -13874,6 +13931,9 @@ Anonymously View Photo + + View Profile > + View {0} Reply @@ -13904,6 +13964,15 @@ Anonymously View Wallpaper + + Ctrl + + + Alt + + + Shift + {0} voice message diff --git a/Telegram/Strings/en/Resources.xml b/Telegram/Strings/en/Resources.xml index 0d3d954ef3..97d9a0c848 100644 --- a/Telegram/Strings/en/Resources.xml +++ b/Telegram/Strings/en/Resources.xml @@ -1318,6 +1318,7 @@ Delete for all members Text copied to clipboard. Path copied to clipboard. + Image copied to clipboard. @@ -12249,8 +12250,8 @@ My Gifts Transfer Message - Message for ⭐️{0} - **{0}** charges **⭐️ {1}** per message to its admin. + Message for ⭐️{0} + **{0}** charges **⭐️ {1}** per message to its admin. Send a direct message to the administrator of **{0}**. Buy Stars @@ -12259,7 +12260,7 @@ **un1** sent a gift to **un2** for {0} Stars Collectibles This chat helps you keep track of replies to your comments in Channels. - Third-party services, like websites and stores, can send verification codes to your phone number via Telegram instead of SMS. Such codes will appear in this chat.\n\nIf you didn\'t request any codes — don\'t worry! Most likely, someone made a mistake when entering their number. + Third-party services, like websites and stores, can send verification codes to your phone number via Telegram instead of SMS. Such codes will appear in this chat.\n\nIf you didn\'t request any codes — don\'t worry! Most likely, someone made a mistake when entering their number. You have changed the direct message settings. Apply changes? This set contains premium stickers like this one. View @@ -12303,26 +12304,26 @@ New Tasks {1} of {0} completed {1} of {0} completed - un1 added a new task **"{0}"** to **"{1}"** + un1 added a new task **\"{0}\"** to **\"{1}\"** un1 added **{0} task** to **{1}** un1 added **{0} tasks** to **{1}** - You added a new task **"{0}"** to **"{1}"** - You added **{0} task** to **"{1}"** - You added **{0} tasks** to **"{1}"** - un1 added a new task **"{0}"** to a checklist + You added a new task **\"{0}\"** to **\"{1}\"** + You added **{0} task** to **\"{1}\"** + You added **{0} tasks** to **\"{1}\"** + un1 added a new task **\"{0}\"** to a checklist un1 added **{0} task** to a checklist un1 added **{0} tasks** to a checklist - You added a new task **"{0}"** to a checklist + You added a new task **\"{0}\"** to a checklist You added **{0} task** to a checklist You added **{0} tasks** to a checklist - un1 marked \"{0}\" as done - You marked \"{0}\" as done + un1 marked **\"{0}\"** as done + You marked **\"{0}\"** as done You marked **{0} task** as done You marked **{0} tasks** as done un1 marked **{0} task** as done un1 marked **{0} tasks** as done - un1 marked \"{0}\" as not done - You marked \"{0}\" as not done + un1 marked **\"{0}\"** as not done + You marked **\"{0}\"** as not done You marked **{0} tasks** as not done You marked **{0} tasks** as not done un1 marked **{0} task** as not done @@ -12341,7 +12342,7 @@ Checklist Checklist Group Checklist - {0}'s Checklist + {0}\'s Checklist Task Delete Item Edit Item @@ -12352,7 +12353,7 @@ {0} task {0} tasks Copy as Path - Invite Link only works for group members + This topic link will only work for group members Gift Premium or Stars buy yourself a gift Buy a gift @@ -12363,4 +12364,148 @@ {0} saved message {0} saved messages + + + + **un1** sent a gift to **un2** for {0} Stars + **un1** sent a gift to **un2** for {0} Stars + **un1** sent a gift to **un2** for {0} Stars + **un1** sent a gift to **un2** for {0} Stars + {0} bots + {0} bots + {0} bots + {0} bots + {0} tasks + {0} tasks + {0} tasks + {0} tasks + {0} groups in common + {0} groups in common + {0} groups in common + {0} groups in common + {0} for resale + {0} for resale + {0} for resale + {0} for resale + {0} Backdrops + {0} Backdrops + {0} Backdrops + {0} Backdrops + {0} Models + {0} Models + {0} Models + {0} Models + {0} Symbols + {0} Symbols + {0} Symbols + {0} Symbols + You can add up to {0} participants to a call. + You can add up to {0} participants to a call. + You can add up to {0} participants to a call. + You can add up to {0} participants to a call. + **{1}**, **{2}** and **{0}** more people already joined this call. + **{1}**, **{2}** and **{0}** more people already joined this call. + **{1}**, **{2}** and **{0}** more people already joined this call. + **{1}**, **{2}** and **{0}** more people already joined this call. + {0} Pinned Messages + {0} Pinned Messages + {0} Pinned Messages + {0} Pinned Messages + un1 now accepts direct messages for {0} Stars + un1 now accepts direct messages for {0} Stars + un1 now accepts direct messages for {0} Stars + un1 now accepts direct messages for {0} Stars + {0} gifts + {0} gifts + {0} gifts + {0} gifts + {0} archived stories + {0} archived stories + {0} archived stories + {0} archived stories + {0} stories + {0} stories + {0} stories + {0} stories + Buy for ⭐️{0} + Buy for ⭐️{0} + Buy for ⭐️{0} + Buy for ⭐️{0} + You will receive **{0}** Stars. + You will receive **{0}** Stars. + You will receive **{0}** Stars. + You will receive **{0}** Stars. + Minimum price is **{0}** Stars. + Minimum price is **{0}** Stars. + Minimum price is **{0}** Stars. + Minimum price is **{0}** Stars. + {0} saved messages + {0} saved messages + {0} saved messages + {0} saved messages + un1 added **{0} tasks** to **{1}** + un1 added **{0} tasks** to **{1}** + un1 added **{0} tasks** to **{1}** + un1 added **{0} tasks** to **{1}** + You added **{0} tasks** to **\"{1}\"** + You added **{0} tasks** to **\"{1}\"** + You added **{0} tasks** to **\"{1}\"** + You added **{0} tasks** to **\"{1}\"** + You added **{0} tasks** to a checklist + You added **{0} tasks** to a checklist + You added **{0} tasks** to a checklist + You added **{0} tasks** to a checklist + un1 added **{0} tasks** to a checklist + un1 added **{0} tasks** to a checklist + un1 added **{0} tasks** to a checklist + un1 added **{0} tasks** to a checklist + {1} of {0} completed + {1} of {0} completed + {1} of {0} completed + {1} of {0} completed + You can add {0} more tasks. + You can add {0} more tasks. + You can add {0} more tasks. + You can add {0} more tasks. + un1 marked **{0} tasks** as done + un1 marked **{0} tasks** as done + un1 marked **{0} tasks** as done + un1 marked **{0} tasks** as done + You marked **{0} tasks** as done + You marked **{0} tasks** as done + You marked **{0} tasks** as done + You marked **{0} tasks** as done + un1 marked **{0} tasks** as not done + un1 marked **{0} tasks** as not done + un1 marked **{0} tasks** as not done + un1 marked **{0} tasks** as not done + You marked **{0} tasks** as not done + You marked **{0} tasks** as not done + You marked **{0} tasks** as not done + You marked **{0} tasks** as not done + Send direct message + Subscribe to **Telegram Premium** to unlock this emoji. + Subscribe to **Telegram Premium** to unlock this reaction. + Saved Gift + Hide sender’s name + Ctrl + Shift + Alt + Copy Selected + Processing... + Share QR Code + QR Code + Sorry, you can\'t pin more than {0} topics to the top. + You have changed some privacy settings. Apply changes? + Revoke Stream Key + Are you sure you want to revoke your Stream Key? + Copy Hashtag + Copy Link + Copy E-Mail + Copy Number + View Profile > + This number is not on Telegram + Preload larger videos + Preload the first few seconds (1-2 MB) of videos larger than %1$s for instant playback. + \ No newline at end of file diff --git a/Telegram/Stub.cs b/Telegram/Stub.cs index 32b796dbb4..9fd4ee9102 100644 --- a/Telegram/Stub.cs +++ b/Telegram/Stub.cs @@ -5958,7 +5958,7 @@ public static Telegram.Native.CachedVideoAnimation LoadFromFile_stub(Telegram.Na throw new RuntimeException(ex); } } - public static void RenderSync_stub(this Telegram.Native.CachedVideoAnimation sender, Windows.Storage.Streams.IBuffer bitmap, out int seconds, out bool completed) + public static void RenderSync_stub(this Telegram.Native.CachedVideoAnimation sender, Windows.Storage.Streams.IBuffer bitmap, out double seconds, out bool completed) { try { @@ -6348,6 +6348,18 @@ public static void Encode_stub(this Telegram.Native.PlaceholderImageHelper sende throw new RuntimeException(ex); } } + public static Windows.UI.Composition.CompositionPath GetEllipticalClip_stub(this Telegram.Native.PlaceholderImageHelper sender, float width, float height, float radius, float x, float y) + { + try + { + return sender.GetEllipticalClip(width, height, radius, x, y); + } + catch (Exception ex) + { + Logger.Error(Environment.StackTrace); + throw new RuntimeException(ex); + } + } public static Windows.UI.Composition.CompositionPath GetOutline_stub(this Telegram.Native.PlaceholderImageHelper sender, System.Collections.Generic.IList contours) { try @@ -6360,6 +6372,18 @@ public static Windows.UI.Composition.CompositionPath GetOutline_stub(this Telegr throw new RuntimeException(ex); } } + public static Windows.UI.Composition.CompositionPath GetReplyMarkupClip_stub(this Telegram.Native.PlaceholderImageHelper sender, System.Collections.Generic.IList> buttons, float bottomRightRadius, float bottomLeftRadius) + { + try + { + return sender.GetReplyMarkupClip(buttons, bottomRightRadius, bottomLeftRadius); + } + catch (Exception ex) + { + Logger.Error(Environment.StackTrace); + throw new RuntimeException(ex); + } + } public static Windows.UI.Composition.CompositionPath GetTail_stub(this Telegram.Native.PlaceholderImageHelper sender, float width, float height, float topLeftRadius, float topRightRadius, float bottomRightRadius, float bottomLeftRadius) { try @@ -6372,6 +6396,18 @@ public static Windows.UI.Composition.CompositionPath GetTail_stub(this Telegram. throw new RuntimeException(ex); } } + public static Windows.UI.Composition.CompositionPath GetVoiceNoteClip_stub(this Telegram.Native.PlaceholderImageHelper sender, System.Collections.Generic.IList waveform, double waveformWidth) + { + try + { + return sender.GetVoiceNoteClip(waveform, waveformWidth); + } + catch (Exception ex) + { + Logger.Error(Environment.StackTrace); + throw new RuntimeException(ex); + } + } public static bool IsWebP_stub(string fileName, out int pixelWidth, out int pixelHeight) { try @@ -6462,7 +6498,7 @@ public static Telegram.Native.VideoAnimation LoadFromFile_stub(Telegram.Native.I throw new RuntimeException(ex); } } - public static int RenderSync_stub(this Telegram.Native.VideoAnimation sender, Windows.Storage.Streams.IBuffer bitmap, int width, int height, bool preview, out int seconds) + public static int RenderSync_stub(this Telegram.Native.VideoAnimation sender, Windows.Storage.Streams.IBuffer bitmap, int width, int height, bool preview, out double seconds) { try { diff --git a/Telegram/Td/Api/ErrorStarsNeeded.cs b/Telegram/Td/Api/ErrorStarsNeeded.cs index 31406e35aa..2539efb63e 100644 --- a/Telegram/Td/Api/ErrorStarsNeeded.cs +++ b/Telegram/Td/Api/ErrorStarsNeeded.cs @@ -1,4 +1,10 @@ -namespace Telegram.Td.Api +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +namespace Telegram.Td.Api { public partial class ErrorStarsNeeded : Object { diff --git a/Telegram/Td/Api/InputMessageReplyToTopicMessage.cs b/Telegram/Td/Api/InputMessageReplyToTopicMessage.cs index 20b41b2637..81754104b4 100644 --- a/Telegram/Td/Api/InputMessageReplyToTopicMessage.cs +++ b/Telegram/Td/Api/InputMessageReplyToTopicMessage.cs @@ -1,4 +1,10 @@ -namespace Telegram.Td.Api +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +namespace Telegram.Td.Api { public class InputMessageReplyToTopicMessage : InputMessageReplyTo { diff --git a/Telegram/Td/Api/MessageHeaderAccountInfo.cs b/Telegram/Td/Api/MessageHeaderAccountInfo.cs index c807add4a2..2df861ea29 100644 --- a/Telegram/Td/Api/MessageHeaderAccountInfo.cs +++ b/Telegram/Td/Api/MessageHeaderAccountInfo.cs @@ -1,4 +1,10 @@ -namespace Telegram.Td.Api +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +namespace Telegram.Td.Api { public class MessageHeaderAccountInfo : MessageContent { diff --git a/Telegram/Td/Api/MessageTag.cs b/Telegram/Td/Api/MessageTag.cs index 4796912a0a..6c19453bcb 100644 --- a/Telegram/Td/Api/MessageTag.cs +++ b/Telegram/Td/Api/MessageTag.cs @@ -1,4 +1,10 @@ -using Telegram.Navigation; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using Telegram.Navigation; namespace Telegram.Td.Api { diff --git a/Telegram/Td/Api/MessageTranslateResult.cs b/Telegram/Td/Api/MessageTranslateResult.cs index 27f9173fb8..05b78f2735 100644 --- a/Telegram/Td/Api/MessageTranslateResult.cs +++ b/Telegram/Td/Api/MessageTranslateResult.cs @@ -1,10 +1,9 @@ -// +// // Copyright Fela Ameghino 2015-2025 // // Distributed under the GNU General Public License v3.0. (See accompanying // file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) // - using Telegram.Common; namespace Telegram.Td.Api diff --git a/Telegram/Td/Api/TdExtensions.cs b/Telegram/Td/Api/TdExtensions.cs index efbdf3daa0..8669827fd5 100644 --- a/Telegram/Td/Api/TdExtensions.cs +++ b/Telegram/Td/Api/TdExtensions.cs @@ -1138,6 +1138,8 @@ public static (File File, Thumbnail Thumbnail, string FileName) GetFileAndThumbn break; case MessageSticker sticker: return (sticker.Sticker.StickerValue, null, null); + case MessageAnimatedEmoji animatedEmoji: + return (animatedEmoji.AnimatedEmoji.Sticker?.StickerValue, null, null); case MessageText text: return text.LinkPreview?.Type switch { @@ -1318,6 +1320,8 @@ public static File GetFile(this MessageContent content) return photo.Photo.GetBig()?.Photo; case MessageSticker sticker: return sticker.Sticker.StickerValue; + case MessageAnimatedEmoji animatedEmoji: + return animatedEmoji.AnimatedEmoji.Sticker?.StickerValue; case MessageText text: return text.LinkPreview?.Type switch { @@ -1375,6 +1379,8 @@ public static bool IsAnimatedContentDownloadCompleted(this MessageViewModel mess return animation.Animation.AnimationValue.Local.IsDownloadingCompleted; case MessageSticker sticker: return sticker.Sticker.Format is StickerFormatTgs or StickerFormatWebm && sticker.Sticker.StickerValue.Local.IsDownloadingCompleted; + case MessageAnimatedEmoji animatedEmoji: + return animatedEmoji.AnimatedEmoji.Sticker?.Format is StickerFormatTgs or StickerFormatWebm && animatedEmoji.AnimatedEmoji.Sticker.StickerValue.Local.IsDownloadingCompleted; case MessageVideoNote videoNote: return videoNote.VideoNote.Video.Local.IsDownloadingCompleted; case MessageGame game: @@ -1493,6 +1499,8 @@ public static Thumbnail GetThumbnail(this Message message) return game.Game.Animation?.Thumbnail; case MessageSticker sticker: return sticker.Sticker.Thumbnail; + case MessageAnimatedEmoji animatedEmoji: + return animatedEmoji.AnimatedEmoji.Sticker?.Thumbnail; case MessageText text: return text.LinkPreview?.GetThumbnail(); case MessageVideo video: @@ -2003,6 +2011,74 @@ public static TextEntityType ToTextEntityType(this MessageSender sender) return null; } + public static bool AreTheSame(this NewChatPrivacySettings x, NewChatPrivacySettings y) + { + if (x == null || y == null) + { + return x == y; + } + + return x.AllowNewChatsFromUnknownUsers == y.AllowNewChatsFromUnknownUsers + && x.IncomingPaidMessageStarCount == y.IncomingPaidMessageStarCount; + } + + public static bool AreTheSame(this UserPrivacySettingRules x, UserPrivacySettingRules y) + { + return (x, y) switch + { + (null, null) => true, + (null, _) or (_, null) => false, + _ when IsEmptyOrSingleDisallowAll(x) && IsEmptyOrSingleDisallowAll(y) => true, + _ => x.Rules.Count == y.Rules.Count && + CompareOrderedRules(x.Rules, y.Rules) + }; + } + + private static bool IsEmptyOrSingleDisallowAll(UserPrivacySettingRules rules) + { + return rules.Rules.Count == 0 || + (rules.Rules.Count == 1 && rules.Rules[0] is UserPrivacySettingRuleRestrictAll); + } + + private static bool CompareOrderedRules(IList xRules, IList yRules) + { + var xSorted = xRules.OrderBy(r => r.GetType().Name).ToArray(); + var ySorted = yRules.OrderBy(r => r.GetType().Name).ToArray(); + + for (int i = 0; i < xSorted.Length; i++) + { + if (!xSorted[i].AreTheSame(ySorted[i])) + return false; + } + return true; + } + + public static bool AreTheSame(this UserPrivacySettingRule x, UserPrivacySettingRule y) + { + return (x, y) switch + { + (UserPrivacySettingRuleAllowAll, UserPrivacySettingRuleAllowAll) => true, + (UserPrivacySettingRuleAllowBots, UserPrivacySettingRuleAllowBots) => true, + (UserPrivacySettingRuleAllowContacts, UserPrivacySettingRuleAllowContacts) => true, + (UserPrivacySettingRuleAllowPremiumUsers, UserPrivacySettingRuleAllowPremiumUsers) => true, + (UserPrivacySettingRuleRestrictAll, UserPrivacySettingRuleRestrictAll) => true, + (UserPrivacySettingRuleRestrictBots, UserPrivacySettingRuleRestrictBots) => true, + (UserPrivacySettingRuleRestrictContacts, UserPrivacySettingRuleRestrictContacts) => true, + + (UserPrivacySettingRuleAllowChatMembers xAllow, UserPrivacySettingRuleAllowChatMembers yAllow) + => xAllow.ChatIds.OrderBy(x => x).SequenceEqual(yAllow.ChatIds.OrderBy(x => x)), + (UserPrivacySettingRuleRestrictChatMembers xRestrict, UserPrivacySettingRuleRestrictChatMembers yRestrict) + => xRestrict.ChatIds.OrderBy(x => x).SequenceEqual(yRestrict.ChatIds.OrderBy(x => x)), + + (UserPrivacySettingRuleAllowUsers xAllow, UserPrivacySettingRuleAllowUsers yAllow) + => xAllow.UserIds.OrderBy(x => x).SequenceEqual(yAllow.UserIds.OrderBy(x => x)), + (UserPrivacySettingRuleRestrictUsers xRestrict, UserPrivacySettingRuleRestrictUsers yRestrict) + => xRestrict.UserIds.OrderBy(x => x).SequenceEqual(yRestrict.UserIds.OrderBy(x => x)), + + _ => false + }; + } + public static bool AreTheSame(this EmojiStatus x, EmojiStatus y) { if (x == null || y == null) @@ -2194,6 +2270,11 @@ private static bool AreTheSame(this BusinessBotRights x, BusinessBotRights y) public static bool AreTheSame(this GiftSettings x, GiftSettings y) { + if (x == null || y == null) + { + return x == y; + } + return x.AcceptedGiftTypes.LimitedGifts == y.AcceptedGiftTypes.LimitedGifts && x.AcceptedGiftTypes.PremiumSubscription == y.AcceptedGiftTypes.PremiumSubscription && x.AcceptedGiftTypes.UnlimitedGifts == y.AcceptedGiftTypes.UnlimitedGifts diff --git a/Telegram/Td/Api/UpdateChatAffiliatePrograms.cs b/Telegram/Td/Api/UpdateChatAffiliatePrograms.cs index a38497ac8a..133f6ea151 100644 --- a/Telegram/Td/Api/UpdateChatAffiliatePrograms.cs +++ b/Telegram/Td/Api/UpdateChatAffiliatePrograms.cs @@ -1,4 +1,10 @@ -namespace Telegram.Td.Api +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +namespace Telegram.Td.Api { public partial class UpdateChatAffiliatePrograms { diff --git a/Telegram/Td/Api/UpdateGiftIsSaved.cs b/Telegram/Td/Api/UpdateGiftIsSaved.cs index 9e0eefba64..234c5082a8 100644 --- a/Telegram/Td/Api/UpdateGiftIsSaved.cs +++ b/Telegram/Td/Api/UpdateGiftIsSaved.cs @@ -1,4 +1,10 @@ -namespace Telegram.Td.Api +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +namespace Telegram.Td.Api { public partial class UpdateGiftIsSaved { diff --git a/Telegram/Td/Api/UpdateGiftIsSold.cs b/Telegram/Td/Api/UpdateGiftIsSold.cs index add3cef126..bdcae71214 100644 --- a/Telegram/Td/Api/UpdateGiftIsSold.cs +++ b/Telegram/Td/Api/UpdateGiftIsSold.cs @@ -1,4 +1,10 @@ -namespace Telegram.Td.Api +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +namespace Telegram.Td.Api { public partial class UpdateGiftIsSold { diff --git a/Telegram/Td/Api/UpdateGiftUpgraded.cs b/Telegram/Td/Api/UpdateGiftUpgraded.cs index f8df697f8e..0094b2476d 100644 --- a/Telegram/Td/Api/UpdateGiftUpgraded.cs +++ b/Telegram/Td/Api/UpdateGiftUpgraded.cs @@ -1,4 +1,10 @@ -namespace Telegram.Td.Api +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +namespace Telegram.Td.Api { public partial class UpdateGiftUpgraded { diff --git a/Telegram/Td/Api/UpdateGreetingSticker.cs b/Telegram/Td/Api/UpdateGreetingSticker.cs index ebd148817d..8b6a57cbd8 100644 --- a/Telegram/Td/Api/UpdateGreetingSticker.cs +++ b/Telegram/Td/Api/UpdateGreetingSticker.cs @@ -1,4 +1,10 @@ -namespace Telegram.Td.Api +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +namespace Telegram.Td.Api { public partial class UpdateGreetingSticker { diff --git a/Telegram/Td/Api/UpdateMessageEffect.cs b/Telegram/Td/Api/UpdateMessageEffect.cs index ce3f8a3ad3..ad93f42111 100644 --- a/Telegram/Td/Api/UpdateMessageEffect.cs +++ b/Telegram/Td/Api/UpdateMessageEffect.cs @@ -1,4 +1,10 @@ -namespace Telegram.Td.Api +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +namespace Telegram.Td.Api { public partial class UpdateMessageEffect { diff --git a/Telegram/Td/Api/UpdatePaymentCompleted.cs b/Telegram/Td/Api/UpdatePaymentCompleted.cs index b12d1ca046..ef196b4b52 100644 --- a/Telegram/Td/Api/UpdatePaymentCompleted.cs +++ b/Telegram/Td/Api/UpdatePaymentCompleted.cs @@ -1,4 +1,10 @@ -namespace Telegram.Td.Api +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +namespace Telegram.Td.Api { public partial class UpdatePaymentCompleted { diff --git a/Telegram/Telegram.csproj b/Telegram/Telegram.csproj index da0ce481d7..9aabf29fa3 100644 --- a/Telegram/Telegram.csproj +++ b/Telegram/Telegram.csproj @@ -207,10 +207,13 @@ + + + @@ -343,6 +346,7 @@ MessageFactCheck.xaml + NativeVideoPlayer.xaml @@ -394,6 +398,7 @@ + @@ -1295,6 +1300,9 @@ FactCheckPopup.xaml + + QrCodePopup.xaml + JoinGroupCallPopup.xaml @@ -2193,6 +2201,8 @@ + + @@ -3290,6 +3300,10 @@ MSBuild:Compile Designer + + MSBuild:Compile + Designer + MSBuild:Compile Designer diff --git a/Telegram/Themes/BadgeButton_themeresources.xaml b/Telegram/Themes/BadgeButton_themeresources.xaml index 2e3ccb3709..a8483d6dad 100644 --- a/Telegram/Themes/BadgeButton_themeresources.xaml +++ b/Telegram/Themes/BadgeButton_themeresources.xaml @@ -101,24 +101,45 @@ FontFamily="{ThemeResource ContentControlThemeFontFamily}" AutomationProperties.AccessibilityView="Raw" ContentTransitions="{TemplateBinding ContentTransitions}" - Content="{TemplateBinding Badge}" - ContentTemplate="{TemplateBinding BadgeTemplate}" - Visibility="{TemplateBinding BadgeVisibility}" + Content="{TemplateBinding Content}" + ContentTemplate="{TemplateBinding ContentTemplate}" IsTabStop="False" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" Padding="0,11,12,12" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" Grid.Column="1" /> - + + + + + + + + + + + + + + + + - + + + - + + + + + + + + @@ -1883,15 +1906,21 @@ diff --git a/Telegram/Views/ChatView.xaml.cs b/Telegram/Views/ChatView.xaml.cs index 3a41b8e297..c2d58b34f7 100644 --- a/Telegram/Views/ChatView.xaml.cs +++ b/Telegram/Views/ChatView.xaml.cs @@ -66,7 +66,7 @@ public interface IChatPage : INavigablePage, ISearchablePage, IActivablePage DialogViewModel ViewModel { get; } } - public sealed partial class ChatView : UserControlEx, INavigablePage, ISearchablePage, IDialogDelegate + public sealed partial class ChatView : UserControlEx, INavigablePage, ISearchablePage, IDialogDelegate, IAutomationNameProvider { private DialogViewModel _viewModel; public DialogViewModel ViewModel => _viewModel ??= DataContext as DialogViewModel; @@ -310,7 +310,7 @@ public void HideStickers() private ChatBackgroundControl FindBackgroundControl() { - var masterDetailPanel = this.GetParent(); + var masterDetailPanel = ViewModel.NavigationService.XamlRoot?.Content?.GetChild(); if (masterDetailPanel != null) { return masterDetailPanel.GetChild(); @@ -480,8 +480,8 @@ public void Activate(DialogViewModel viewModel) BackButton.Visibility = Visibility.Collapsed; Options.Visibility = Visibility.Collapsed; - // TODO: collapse this but provide a background for the header ClipperOuter.Visibility = Visibility.Collapsed; + HeaderBackground.Visibility = Visibility.Visible; HeaderLeft.SizeChanged += Options_SizeChanged; HeaderLeft.Padding = new Thickness(12, 0, 0, 0); @@ -667,14 +667,14 @@ private async void OnCollectionChanged(object sender, NotifyCollectionChangedEve var xOffset = content switch { MessageBigEmoji => 48 + more, - MessageSticker or MessageDice => 48 + more, + MessageSticker or MessageAnimatedEmoji or MessageDice => 48 + more, _ => 48 + more - 12f }; var yOffset = content switch { MessageBigEmoji => 66, - MessageSticker or MessageDice => 36, + MessageSticker or MessageAnimatedEmoji or MessageDice => 36, _ => reply ? 29 : 44f }; @@ -702,7 +702,7 @@ private async void OnCollectionChanged(object sender, NotifyCollectionChangedEve var fontScale = content switch { MessageBigEmoji => 14 / 32f, - MessageSticker => 20 / (180 * message.ClientService.Config.GetNamedNumber("emojies_animated_zoom", 0.625f)), + MessageSticker or MessageAnimatedEmoji => 20 / (180 * message.ClientService.Config.GetNamedNumber("emojies_animated_zoom", 0.625f)), _ => 1 }; @@ -842,7 +842,7 @@ private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) private void Segments_Click(object sender, RoutedEventArgs e) { var chat = ViewModel.Chat; - if (chat == null || chat.Id == ViewModel.ClientService.Options.MyId || sender is not ActiveStoriesSegments segments || _fromPreview) + if (chat == null || (chat.Id == ViewModel.ClientService.Options.MyId && ViewModel.SavedMessagesTopic == null) || sender is not ActiveStoriesSegments segments || _fromPreview) { return; } @@ -859,7 +859,14 @@ private void Segments_Click(object sender, RoutedEventArgs e) } else { - GalleryWindow.ShowAsync(ViewModel, ViewModel.StorageService, chat, Photo); + if (ViewModel.ClientService.TryGetChat(ViewModel.SavedMessagesTopic?.Type, out Chat savedMessagesTopicChat)) + { + GalleryWindow.ShowAsync(ViewModel, ViewModel.StorageService, savedMessagesTopicChat, Photo); + } + else + { + GalleryWindow.ShowAsync(ViewModel, ViewModel.StorageService, chat, Photo); + } } } @@ -1122,7 +1129,9 @@ private void OnCharacterReceived(CoreWindow sender, CharacterReceivedEventArgs a private void OnPreviewKeyDown(object sender, KeyRoutedEventArgs args) { - if (args.Key == VirtualKey.Space /*&& args.RepeatCount == 1 && args.Modifiers == VirtualKeyModifiers.None*/) + var modifiers = WindowContext.KeyModifiers(); + + if (args.Key == VirtualKey.Space && modifiers == VirtualKeyModifiers.None /*&& args.RepeatCount == 1*/) { if (btnVoiceMessage.IsLocked) { @@ -1130,7 +1139,7 @@ private void OnPreviewKeyDown(object sender, KeyRoutedEventArgs args) args.Handled = true; } } - else if (args.Key == VirtualKey.C && WindowContext.IsKeyDownAsync(VirtualKey.Control)) + else if (args.Key == VirtualKey.C && modifiers == VirtualKeyModifiers.Control) { if (ViewModel.IsSelectionEnabled && ViewModel.SelectedItems.Count > 0 && ViewModel.CanCopySelectedMessage) { @@ -1177,11 +1186,7 @@ private void OnPreviewKeyDown(object sender, KeyRoutedEventArgs args) } } } - } - - private void OnProcessKeyboardAccelerators(UIElement sender, ProcessKeyboardAcceleratorEventArgs args) - { - if (args.Key == VirtualKey.Delete) + else if (args.Key == VirtualKey.Delete && modifiers == VirtualKeyModifiers.None) { if (ViewModel.IsSelectionEnabled && ViewModel.SelectedItems.Count > 0 && ViewModel.CanDeleteSelectedMessages) { @@ -1198,29 +1203,29 @@ private void OnProcessKeyboardAccelerators(UIElement sender, ProcessKeyboardAcce } } } - else if (args.Key == VirtualKey.E && args.Modifiers == (VirtualKeyModifiers.Control | VirtualKeyModifiers.Shift)) + else if (args.Key == VirtualKey.E && modifiers == (VirtualKeyModifiers.Control | VirtualKeyModifiers.Shift)) { ButtonStickers.Show(Services.Settings.StickersTab.Emoji); } - else if (args.Key == VirtualKey.G && args.Modifiers == (VirtualKeyModifiers.Control | VirtualKeyModifiers.Shift)) + else if (args.Key == VirtualKey.G && modifiers == (VirtualKeyModifiers.Control | VirtualKeyModifiers.Shift)) { ButtonStickers.Show(Services.Settings.StickersTab.Animations); } - else if (args.Key == VirtualKey.S && args.Modifiers == (VirtualKeyModifiers.Control | VirtualKeyModifiers.Shift)) + else if (args.Key == VirtualKey.S && modifiers == (VirtualKeyModifiers.Control | VirtualKeyModifiers.Shift)) { ButtonStickers.Show(Services.Settings.StickersTab.Stickers); } - else if (args.Key == VirtualKey.R && /*args.RepeatCount == 1 &&*/ args.Modifiers == VirtualKeyModifiers.Control) + else if (args.Key == VirtualKey.R && /*args.RepeatCount == 1 &&*/ modifiers == VirtualKeyModifiers.Control) { btnVoiceMessage.ToggleRecording(); args.Handled = true; } - else if (args.Key == VirtualKey.D && /*args.RepeatCount == 1 &&*/ args.Modifiers == VirtualKeyModifiers.Control) + else if (args.Key == VirtualKey.D && /*args.RepeatCount == 1 &&*/ modifiers == VirtualKeyModifiers.Control) { btnVoiceMessage.StopRecording(true); args.Handled = true; } - else if (args.Key == VirtualKey.P && /*args.RepeatCount == 1 &&*/ args.Modifiers == VirtualKeyModifiers.Control) + else if (args.Key == VirtualKey.P && /*args.RepeatCount == 1 &&*/ modifiers == VirtualKeyModifiers.Control) { if (btnVoiceMessage.IsLocked) { @@ -1228,12 +1233,12 @@ private void OnProcessKeyboardAccelerators(UIElement sender, ProcessKeyboardAcce args.Handled = true; } } - if (args.Key == VirtualKey.O && /*args.RepeatCount == 1 &&*/ args.Modifiers == VirtualKeyModifiers.Control) + if (args.Key == VirtualKey.O && /*args.RepeatCount == 1 &&*/ modifiers == VirtualKeyModifiers.Control) { ViewModel.SendDocument(); args.Handled = true; } - else if (args.Key == VirtualKey.PageUp && args.Modifiers == VirtualKeyModifiers.None && TextField.Document.Selection.StartPosition == 0 && ViewModel.Autocomplete == null) + else if (args.Key == VirtualKey.PageUp && modifiers == VirtualKeyModifiers.None && TextField.Document.Selection.StartPosition == 0 && ViewModel.Autocomplete == null) { var popups = VisualTreeHelper.GetOpenPopupsForXamlRoot(XamlRoot); if (popups.Count > 0) @@ -1276,7 +1281,7 @@ private void OnProcessKeyboardAccelerators(UIElement sender, ProcessKeyboardAcce target.Focus(FocusState.Keyboard); args.Handled = true; } - else if (args.Key is VirtualKey.PageDown or VirtualKey.Down && args.Modifiers == VirtualKeyModifiers.None && TextField.Document.Selection.StartPosition == TextField.Document.GetRange(int.MaxValue, int.MaxValue).EndPosition && ViewModel.Autocomplete == null) + else if (args.Key is VirtualKey.PageDown or VirtualKey.Down && modifiers == VirtualKeyModifiers.None && TextField.Document.Selection.StartPosition == TextField.Document.GetRange(int.MaxValue, int.MaxValue).EndPosition && ViewModel.Autocomplete == null) { var popups = VisualTreeHelper.GetOpenPopupsForXamlRoot(XamlRoot); if (popups.Count > 0) @@ -1411,13 +1416,13 @@ private void CheckButtonsVisibility() { if (_oldEditing) { - elementHide = btnEdit; + elementHide = EditMessageButton; SendMessageButton.Visibility = Visibility.Collapsed; } else { elementHide = SendMessageButton; - btnEdit.Visibility = Visibility.Collapsed; + EditMessageButton.Visibility = Visibility.Collapsed; } elementShow = ButtonRecord; @@ -1426,13 +1431,13 @@ private void CheckButtonsVisibility() { if (_oldEditing) { - elementHide = btnEdit; + elementHide = EditMessageButton; ButtonRecord.Visibility = Visibility.Collapsed; } else { elementHide = ButtonRecord; - btnEdit.Visibility = Visibility.Collapsed; + EditMessageButton.Visibility = Visibility.Collapsed; } elementShow = SendMessageButton; @@ -1453,7 +1458,7 @@ private void CheckButtonsVisibility() ButtonRecord.Visibility = Visibility.Collapsed; } - elementShow = btnEdit; + elementShow = EditMessageButton; } else { @@ -1468,7 +1473,7 @@ private void CheckButtonsVisibility() ButtonRecord.Visibility = Visibility.Collapsed; } - elementHide = btnEdit; + elementHide = EditMessageButton; } } //else @@ -1495,11 +1500,24 @@ private void CheckButtonsVisibility() var batch = visualShow.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation); batch.Completed += (s, args) => { - elementHide.Visibility = Visibility.Collapsed; - elementShow.Visibility = Visibility.Visible; - - visualHide.Scale = visualShow.Scale = new Vector3(1); - visualHide.Opacity = visualShow.Opacity = 1; + if (_oldEditing) + { + EditMessageButton.Visibility = Visibility.Visible; + SendMessageButton.Visibility = Visibility.Collapsed; + ButtonRecord.Visibility = Visibility.Collapsed; + } + else if (_oldEmpty) + { + EditMessageButton.Visibility = Visibility.Collapsed; + SendMessageButton.Visibility = Visibility.Collapsed; + ButtonRecord.Visibility = Visibility.Visible; + } + else + { + EditMessageButton.Visibility = Visibility.Collapsed; + SendMessageButton.Visibility = Visibility.Visible; + ButtonRecord.Visibility = Visibility.Collapsed; + } }; var hide1 = visualShow.Compositor.CreateVector3KeyFrameAnimation(); @@ -2045,7 +2063,7 @@ public void ChangeTheme() private void ChatThemeDrawer_ThemeChanged(object sender, ChatThemeChangedEventArgs e) { - UpdateChatTheme(e.Theme); + UpdateChatTheme(ViewModel.Chat, e.Theme); } private void ChatThemeDrawer_ThemeSelected(object sender, ChatThemeSelectedEventArgs e) @@ -2230,7 +2248,7 @@ private void Menu_ContextRequested(object sender, RoutedEventArgs e) flyout.CreateFlyoutItem(ViewModel.ShowTranslate, Strings.TranslateMessage, Icons.Translate); } - if (user != null && user.Type is not UserTypeDeleted && !secret) + if (user != null && user.Type is not UserTypeDeleted && !secret && ViewModel.SavedMessagesTopic == null) { flyout.CreateFlyoutItem(ViewModel.ChangeTheme, Strings.SetWallpapers, Icons.PaintBrush); } @@ -2435,8 +2453,7 @@ private void MessageEffectFlyout_Selected(object sender, MessageEffect e) public void PlayInteraction(File interaction) { - var file = interaction; - if (file.Local.IsDownloadingCompleted && SendEffectInteractions.Children.Count < 4) + if (SendEffectInteractions.Children.Count < 4) { var dispatcher = Windows.System.DispatcherQueue.GetForCurrentThread(); @@ -2449,7 +2466,7 @@ public void PlayInteraction(File interaction) player.IsHitTestVisible = false; player.FrameSize = new Size(512, 512); player.AutoPlay = true; - player.Source = new LocalFileSource(file); + player.Source = new DelayedFileSource(ViewModel.ClientService, interaction); player.LoopCompleted += (s, args) => { dispatcher.TryEnqueue(() => @@ -2487,13 +2504,6 @@ public void PlayInteraction(File interaction) SendEffectInteractions.Children.Add(player); SendEffectInteractionsPopup.IsOpen = true; } - else if (file.Local.CanBeDownloaded && !file.Local.IsDownloadingActive) - { - //message.Interaction = interaction; - ViewModel.ClientService.DownloadFile(file.Id, 16); - - //UpdateManager.Subscribe(this, message, file, ref _interactionToken, UpdateFile, true); - } } private void Reply_ContextRequested(UIElement sender, ContextRequestedEventArgs args) @@ -2827,7 +2837,7 @@ private async void Message_ContextRequested(UIElement sender, ContextRequestedEv flyout.CreateFlyoutItem(ViewModel.DeleteSelectedMessages, Strings.DeleteSelected, Icons.Delete, destructive: true); flyout.CreateFlyoutItem(ViewModel.UnselectMessages, Strings.ClearSelection); flyout.CreateFlyoutSeparator(); - flyout.CreateFlyoutItem(ViewModel.CopySelectedMessages, Strings.CopySelectedMessages, Icons.DocumentCopy); + flyout.CreateFlyoutItem(ViewModel.CopySelectedMessages, Strings.CopySelectedMessages, Icons.Copy); } else { @@ -2841,7 +2851,7 @@ private async void Message_ContextRequested(UIElement sender, ContextRequestedEv flyout.CreateFlyoutItem(MessageRetry_Loaded, ViewModel.ResendMessage, message, Strings.Retry, Icons.ArrowClockwise); } - flyout.CreateFlyoutItem(MessageCopy_Loaded, ViewModel.CopyMessage, message, Strings.Copy, Icons.DocumentCopy); + flyout.CreateFlyoutItem(MessageCopy_Loaded, ViewModel.CopyMessage, message, Strings.Copy, Icons.Copy); if (MessageDelete_Loaded(message, properties)) { @@ -3024,7 +3034,7 @@ private async void Message_ContextRequested(UIElement sender, ContextRequestedEv checklistTaskItem.CreateFlyoutItem(ViewModel.MarkChecklistTask, messageTask, checklistTask.CompletionDate != 0 ? Strings.TodoUncheck : Strings.TodoCheck, checklistTask.CompletionDate != 0 ? Icons.DismissCircle : Icons.CheckmarkCircle); } - checklistTaskItem.CreateFlyoutItem(ViewModel.CopyText, checklistTask.Text, Strings.Copy, Icons.DocumentCopy); + checklistTaskItem.CreateFlyoutItem(ViewModel.CopyText, checklistTask.Text, Strings.Copy, Icons.Copy); if (properties.CanBeEdited) { @@ -3048,11 +3058,11 @@ private async void Message_ContextRequested(UIElement sender, ContextRequestedEv if (quote != null) { // TODO: copy selection - flyout.CreateFlyoutItem(MessageCopy_Loaded, ViewModel.CopyMessage, quote, Strings.Copy, Icons.DocumentCopy); + flyout.CreateFlyoutItem(MessageCopy_Loaded, ViewModel.CopyMessage, quote, Strings.Copy, Icons.Copy); } else { - flyout.CreateFlyoutItem(MessageCopy_Loaded, ViewModel.CopyMessage, message, Strings.Copy, Icons.DocumentCopy); + flyout.CreateFlyoutItem(MessageCopy_Loaded, ViewModel.CopyMessage, message, Strings.Copy, Icons.Copy); } flyout.CreateFlyoutItem(MessageCopyLink_Loaded, ViewModel.CopyMessageLink, message, Strings.CopyLink, Icons.Link); @@ -3086,17 +3096,17 @@ private async void Message_ContextRequested(UIElement sender, ContextRequestedEv flyout.CreateFlyoutItem(MessageSaveAnimation_Loaded, ViewModel.SaveMessageAnimation, message, Strings.SaveToGIFs, Icons.Gif); flyout.CreateFlyoutItem(MessageSaveSound_Loaded, ViewModel.SaveMessageNotificationSound, message, Strings.SaveForNotifications, Icons.MusicNote2); flyout.CreateFlyoutItem(MessageSaveMedia_Loaded, ViewModel.SaveMessageMedia, message, Strings.SaveAs, Icons.SaveAs); - flyout.CreateFlyoutItem(MessageOpenMedia_Loaded, ViewModel.OpenMessageWith, message, Strings.OpenWith, Icons.OpenIn); + flyout.CreateFlyoutItem(MessageOpenMedia_Loaded, ViewModel.OpenMessageWith, message, Strings.OpenWith, Icons.OpenWith); flyout.CreateFlyoutItem(MessageOpenFolder_Loaded, ViewModel.OpenMessageFolder, message, Strings.ShowInFolder, Icons.FolderOpen); // Contacts flyout.CreateFlyoutItem(MessageAddContact_Loaded, ViewModel.AddToContacts, message, Strings.AddContactTitle, Icons.Person); //CreateFlyoutItem(ref flyout, MessageSaveDownload_Loaded, ViewModel.MessageSaveDownloadCommand, messageCommon, Strings.SaveToDownloads); - if (Constants.DEBUG) + if (SettingsService.Current.Diagnostics.DeleteFilesDebug) { var file = message.GetFile(); - if (file != null && (file.Local.IsDownloadingActive || file.Local.IsDownloadingCompleted || message.Content is MessageVideo { AlternativeVideos.Count: > 0 })) + if (file != null && (file.Local.DownloadedSize > 0 || (message.Content is MessageVideo video && video.AlternativeVideos.Any(x => x.HlsFile.Local.DownloadedSize > 0 || x.Video.Local.DownloadedSize > 0)))) { flyout.CreateFlyoutItem(x => { @@ -3106,6 +3116,9 @@ private async void Message_ContextRequested(UIElement sender, ContextRequestedEv return; } + ViewModel.Settings.Video.RemovePosition(file); + ViewModel.Aggregator.Publish(new UpdateMessageContentOpened(message.ChatId, message.Id)); + ViewModel.ClientService.CancelDownloadFile(file); ViewModel.ClientService.Send(new DeleteFile(file.Id)); @@ -4108,8 +4121,15 @@ public void Emojis_ItemClick(object emoji) } else if (emoji is Sticker sticker && sticker.FullType is StickerFullTypeCustomEmoji customEmoji) { - ViewModel.InsertedCustomEmojiIds.Add(customEmoji.CustomEmojiId); - TextField.InsertEmoji(sticker); + if (ViewModel.IsPremium) + { + ViewModel.InsertedCustomEmojiIds.Add(customEmoji.CustomEmojiId); + TextField.InsertEmoji(sticker); + } + else + { + ToastPopup.ShowFeaturePromo(ViewModel.NavigationService, new PremiumFeatureCustomEmoji()); + } } _focusState.Set(FocusState.Programmatic); @@ -4867,7 +4887,12 @@ public async void UpdateChatTheme(Chat chat) return; } - UpdateChatTheme(ViewModel.ClientService.GetChatTheme(chat.ThemeName)); + if (ViewModel.SavedMessagesTopic != null && ViewModel.ClientService.TryGetChat(ViewModel.SavedMessagesTopic.Type, out Chat savedMessagesChat)) + { + chat = savedMessagesChat; + } + + UpdateChatTheme(chat, ViewModel.ClientService.GetChatTheme(chat.ThemeName)); } public void UpdateChatBackground(Chat chat) @@ -4886,11 +4911,11 @@ public void UpdateChatBackground(Chat chat) } } - private void UpdateChatTheme(ChatTheme theme) + private void UpdateChatTheme(Chat chat, ChatTheme theme) { - if (Theme.Current.Update(ActualTheme, theme, _viewModel.Chat.Background)) + if (Theme.Current.Update(ActualTheme, theme, chat.Background)) { - var current = _viewModel.Chat.Background?.Background; + var current = chat.Background?.Background; if (current?.Type is BackgroundTypeChatTheme typeChatTheme) { theme ??= ViewModel.ClientService.GetChatTheme(typeChatTheme.ThemeName); @@ -6856,14 +6881,17 @@ private void UpdateNewestItemAsFooter(bool clear = true) UpdateNewestOldestItemAsFooterHeader(_newestItemAsFooterNeeded, ViewModel.IsNewestSliceLoaded, ref _newestItem, ref _newestItemAsFooter, ^1, clear); } - public float AnimatedHeight => GroupCall.AnimatedHeight + public float AnimatedHeight => ClipperOuter.Visibility == Visibility.Visible + ? GroupCall.AnimatedHeight + JoinRequests.AnimatedHeight + TranslateHeader.AnimatedHeight + ActionBar.AnimatedHeight + ConnectedBot.AnimatedHeight + PinnedMessage.AnimatedHeight + AccountInfoHeader.AnimatedHeight - + Sponsored.AnimatedHeight; + + Sponsored.AnimatedHeight + + (_forumCollapsed == ForumViewType.Horizontal ? 40 : 0) + : 0; public bool HasMessagesPadding => _messagesHeaderRootPadding > 0; @@ -7131,6 +7159,7 @@ void Complete() if (IsLoaded is false) { _forumCollapsed = type; + UpdateMessagesHeaderPadding(); Complete(); return; @@ -7250,6 +7279,7 @@ void Complete() batch.End(); _forumCollapsed = type; + UpdateMessagesHeaderPadding(); } private IEnumerable GetAnimatableVisuals() @@ -7481,7 +7511,7 @@ protected override string GetNameCore() //protected override string GetFullDescriptionCore() //{ - var view = _owner.GetParent(); + var view = _owner.GetParent(); if (view != null) { return view.GetAutomationName(); @@ -7490,4 +7520,9 @@ protected override string GetNameCore() return base.GetNameCore(); } } + + public interface IAutomationNameProvider + { + string GetAutomationName(); + } } diff --git a/Telegram/Views/Chats/ChatAffiliatePage.xaml.cs b/Telegram/Views/Chats/ChatAffiliatePage.xaml.cs index a2b1fd2a0b..b5c07b4e7f 100644 --- a/Telegram/Views/Chats/ChatAffiliatePage.xaml.cs +++ b/Telegram/Views/Chats/ChatAffiliatePage.xaml.cs @@ -1,4 +1,10 @@ -using Telegram.Common; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using Telegram.Common; using Telegram.Controls; using Telegram.Controls.Cells; using Telegram.Controls.Media; @@ -48,7 +54,7 @@ private void OnContextRequested(UIElement sender, ContextRequestedEventArgs args var flyout = new MenuFlyout(); flyout.CreateFlyoutItem(ViewModel.LaunchProgram, program, Strings.ProfileBotOpenApp, Icons.Bot); - flyout.CreateFlyoutItem(ViewModel.CopyProgram, program, Strings.CopyLink, Icons.DocumentCopy); + flyout.CreateFlyoutItem(ViewModel.CopyProgram, program, Strings.CopyLink, Icons.Copy); flyout.CreateFlyoutItem(ViewModel.DisconnectProgram, program, Strings.LeaveAffiliateLinkButton, Icons.Delete, destructive: true); flyout.ShowAt(sender, args); diff --git a/Telegram/Views/Chats/ChatInviteLinksPage.xaml.cs b/Telegram/Views/Chats/ChatInviteLinksPage.xaml.cs index 2a3b2ae0f6..ab3b6bc7ea 100644 --- a/Telegram/Views/Chats/ChatInviteLinksPage.xaml.cs +++ b/Telegram/Views/Chats/ChatInviteLinksPage.xaml.cs @@ -72,7 +72,7 @@ private void OnContextRequested(UIElement sender, ContextRequestedEventArgs args } else { - flyout.CreateFlyoutItem(ViewModel.CopyLink, inviteLink, Strings.CopyLink, Icons.DocumentCopy); + flyout.CreateFlyoutItem(ViewModel.CopyLink, inviteLink, Strings.CopyLink, Icons.Copy); flyout.CreateFlyoutItem(ViewModel.ShareLink, inviteLink, Strings.ShareLink, Icons.Share); flyout.CreateFlyoutItem(ViewModel.EditLink, inviteLink, Strings.EditLink, Icons.Edit); flyout.CreateFlyoutItem(ViewModel.RevokeLink, inviteLink, Strings.RevokeLink, Icons.Delete, destructive: true); diff --git a/Telegram/Views/Chats/ChatRevenuePage.xaml.cs b/Telegram/Views/Chats/ChatRevenuePage.xaml.cs index 5c0c9b4a49..cc6949d285 100644 --- a/Telegram/Views/Chats/ChatRevenuePage.xaml.cs +++ b/Telegram/Views/Chats/ChatRevenuePage.xaml.cs @@ -1,4 +1,10 @@ -using System.Globalization; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System.Globalization; using Telegram.Charts; using Telegram.Controls; using Telegram.Controls.Cells; diff --git a/Telegram/Views/Chats/ChatStarsPage.xaml.cs b/Telegram/Views/Chats/ChatStarsPage.xaml.cs index 2668caece5..4575e083f4 100644 --- a/Telegram/Views/Chats/ChatStarsPage.xaml.cs +++ b/Telegram/Views/Chats/ChatStarsPage.xaml.cs @@ -1,4 +1,10 @@ -using Telegram.Charts; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using Telegram.Charts; using Telegram.Controls; using Telegram.Controls.Cells; using Telegram.Controls.Cells.Revenue; diff --git a/Telegram/Views/Chats/ChatStoriesPage.xaml.cs b/Telegram/Views/Chats/ChatStoriesPage.xaml.cs index 48dc84b869..afeb02c77d 100644 --- a/Telegram/Views/Chats/ChatStoriesPage.xaml.cs +++ b/Telegram/Views/Chats/ChatStoriesPage.xaml.cs @@ -1,4 +1,10 @@ -using System.Numerics; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System.Numerics; using Telegram.Common; using Telegram.Controls.Cells; using Telegram.Controls.Media; diff --git a/Telegram/Views/Chats/Popups/ChatBoostFeaturesPopup.xaml b/Telegram/Views/Chats/Popups/ChatBoostFeaturesPopup.xaml index 87cdd20a71..da6a198f18 100644 --- a/Telegram/Views/Chats/Popups/ChatBoostFeaturesPopup.xaml +++ b/Telegram/Views/Chats/Popups/ChatBoostFeaturesPopup.xaml @@ -46,7 +46,7 @@ - + + + + + + + + diff --git a/Telegram/Views/Folders/FolderPage.xaml b/Telegram/Views/Folders/FolderPage.xaml index c442ed7b02..6962ae9560 100644 --- a/Telegram/Views/Folders/FolderPage.xaml +++ b/Telegram/Views/Folders/FolderPage.xaml @@ -198,7 +198,9 @@ Padding="0" Margin="0,2,0,2"> + Content="{x:Bind ConvertRemanining(ViewModel.Include.RemainingCount), Mode=OneWay}" + Style="{StaticResource GlyphBadgeButtonStyle}" + Glyph="" /> + Content="{x:Bind ConvertRemanining(ViewModel.Exclude.RemainingCount), Mode=OneWay}" + Style="{StaticResource GlyphBadgeButtonStyle}" + Glyph="" /> + PreviewKeyDown="OnPreviewKeyDown"> - - + VerticalAlignment="Top" /> + diff --git a/Telegram/Views/Popups/CalendarPopup.xaml.cs b/Telegram/Views/Popups/CalendarPopup.xaml.cs index 973b20b5c6..1b6dd0dc59 100644 --- a/Telegram/Views/Popups/CalendarPopup.xaml.cs +++ b/Telegram/Views/Popups/CalendarPopup.xaml.cs @@ -15,11 +15,8 @@ using Telegram.Services; using Telegram.Td.Api; using Windows.System.UserProfile; -using Windows.UI; -using Windows.UI.Text; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Media; namespace Telegram.Views.Popups { @@ -133,10 +130,10 @@ private void UpdateDayItem(CalendarViewDayItem item, Message message) var text = grid.Children[1] as TextBlock; var original = children[^1] as TextBlock; - grid.Background = new SolidColorBrush(Colors.Black); - text.Foreground = new SolidColorBrush(Colors.White); + //grid.Background = new SolidColorBrush(Colors.Black); + //text.Foreground = new SolidColorBrush(Colors.White); text.Text = original.Text; - text.FontWeight = FontWeights.SemiBold; + //text.FontWeight = FontWeights.SemiBold; grid.Visibility = Visibility.Visible; original.Visibility = Visibility.Collapsed; diff --git a/Telegram/Views/Popups/CallsPopup.xaml.cs b/Telegram/Views/Popups/CallsPopup.xaml.cs index 5b0468e206..e1fb0b0094 100644 --- a/Telegram/Views/Popups/CallsPopup.xaml.cs +++ b/Telegram/Views/Popups/CallsPopup.xaml.cs @@ -1,4 +1,10 @@ -using Telegram.Common; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using Telegram.Common; using Telegram.Controls; using Telegram.Controls.Cells; using Telegram.Controls.Media; diff --git a/Telegram/Views/Popups/ChooseCapturePopup.xaml.cs b/Telegram/Views/Popups/ChooseCapturePopup.xaml.cs index 76c7dac801..e2122fae77 100644 --- a/Telegram/Views/Popups/ChooseCapturePopup.xaml.cs +++ b/Telegram/Views/Popups/ChooseCapturePopup.xaml.cs @@ -1,4 +1,10 @@ -using Rg.DiffUtils; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using Rg.DiffUtils; using Telegram.Controls; using Telegram.Controls.Cells; using Telegram.Navigation; diff --git a/Telegram/Views/Popups/ChooseChatsPopup.xaml b/Telegram/Views/Popups/ChooseChatsPopup.xaml index 9d093cf0e0..d90e5c6b77 100644 --- a/Telegram/Views/Popups/ChooseChatsPopup.xaml +++ b/Telegram/Views/Popups/ChooseChatsPopup.xaml @@ -163,7 +163,7 @@ DataContext="{x:Bind ViewModel.SearchChats}" AreTabsVisible="False" ItemClick="ListView_ItemClick" - Margin="12,0" /> + PaddingImpl="12,0" /> @@ -197,12 +197,6 @@ - - - - - 1; } @@ -638,10 +646,9 @@ public MessageToShare(long chatId, long id, Type contentType, bool canBeCopied, CanBeCopied = canBeCopied; CanBeCopiedToSecretChat = canBeCopiedtoSecretChat; HasCaption = hasCaption; - HasSenderId = hasSenderId; } - public MessageToShare(Message message, MessageProperties properties, bool hasSenderId) + public MessageToShare(Message message, MessageProperties properties) { ChatId = message.ChatId; Id = message.Id; @@ -649,10 +656,12 @@ public MessageToShare(Message message, MessageProperties properties, bool hasSen CanBeCopied = properties.CanBeCopied; CanBeCopiedToSecretChat = properties.CanBeCopiedToSecretChat; HasCaption = message.Content is not MessageText && message.HasCaption(); - HasSenderId = hasSenderId; + SenderId = message.SenderId; + ForwardInfo = message.ForwardInfo; + ImportInfo = message.ImportInfo; } - public MessageToShare(MessageWithOwner message, MessageProperties properties, bool hasSenderId) + public MessageToShare(MessageWithOwner message, MessageProperties properties) { ChatId = message.ChatId; Id = message.Id; @@ -660,7 +669,9 @@ public MessageToShare(MessageWithOwner message, MessageProperties properties, bo CanBeCopied = properties.CanBeCopied; CanBeCopiedToSecretChat = properties.CanBeCopiedToSecretChat; HasCaption = message.Content is not MessageText && message.HasCaption(); - HasSenderId = hasSenderId; + SenderId = message.SenderId; + ForwardInfo = message.ForwardInfo; + ImportInfo = message.ImportInfo; } public long ChatId { get; } @@ -675,7 +686,51 @@ public MessageToShare(MessageWithOwner message, MessageProperties properties, bo public bool HasCaption { get; } - public bool HasSenderId { get; } + public MessageSender SenderId { get; } + + public MessageForwardInfo ForwardInfo { get; } + + public MessageImportInfo ImportInfo { get; } + + public string GetSenderId(IClientService clientService) + { + if (ChatId == clientService.Options.MyId && ForwardInfo != null) + { + return null; + } + + // TODO: Not beautiful but it does the trick + if (ForwardInfo?.Origin is MessageOriginUser fromUser) + { + return "MessageOriginUser" + fromUser.SenderUserId; + } + else if (ForwardInfo?.Origin is MessageOriginChat fromChat) + { + return "MessageOriginChat" + fromChat.SenderChatId; + } + else if (ForwardInfo?.Origin is MessageOriginChannel fromChannel) + { + return "MessageOriginChannel" + fromChannel.ChatId; + } + else if (ForwardInfo?.Origin is MessageOriginHiddenUser hiddenUser) + { + return "MessageOriginHiddenUser" + hiddenUser.SenderName; + } + else if (ImportInfo != null) + { + return "MessageImportInfo" + ImportInfo.SenderName; + } + else if (SenderId is MessageSenderChat senderChat) + { + return "MessageSenderChat" + senderChat.ChatId; + } + else if (SenderId is MessageSenderUser senderUser) + { + return "MessageSenderUser" + senderUser.UserId; + } + + return null; + } } public partial class ChooseChatsConfigurationShareMessages : ChooseChatsConfiguration @@ -965,7 +1020,11 @@ private void Send_ContextRequested(object sender, ContextRequestedEventArgs args { var flyout = new MenuFlyout(); - if (shareMessages.Messages.Any(x => x.HasSenderId && x.CanBeCopied)) + var senders = shareMessages.Messages + .GroupBy(x => x.GetSenderId(ViewModel.ClientService)) + .ToList(); + + if (senders[0].Key != null && shareMessages.Messages.Any(x => x.CanBeCopied)) { void SendAsCopy() { @@ -973,7 +1032,7 @@ void SendAsCopy() Hide(ContentDialogResult.Primary); } - flyout.CreateFlyoutItem(SendAsCopy, Strings.HideSenderNames, Icons.DocumentCopy); + flyout.CreateFlyoutItem(SendAsCopy, senders.Count > 1 ? Strings.HideSenderNames : Strings.HideSendersName, Icons.Copy); } if (shareMessages.Messages.Any(x => x.HasCaption && x.CanBeCopied)) diff --git a/Telegram/Views/Popups/ContactsPopup.xaml.cs b/Telegram/Views/Popups/ContactsPopup.xaml.cs index 024942754e..fcca82abfc 100644 --- a/Telegram/Views/Popups/ContactsPopup.xaml.cs +++ b/Telegram/Views/Popups/ContactsPopup.xaml.cs @@ -1,4 +1,10 @@ -using System; +// +// Copyright Fela Ameghino 2015-2025 +// +// Distributed under the GNU General Public License v3.0. (See accompanying +// file LICENSE or copy at https://www.gnu.org/licenses/gpl-3.0.txt) +// +using System; using Telegram.Collections; using Telegram.Common; using Telegram.Controls; diff --git a/Telegram/Views/Popups/CreateChecklistPopup.xaml.cs b/Telegram/Views/Popups/CreateChecklistPopup.xaml.cs index c9d32bf063..8ea7f93885 100644 --- a/Telegram/Views/Popups/CreateChecklistPopup.xaml.cs +++ b/Telegram/Views/Popups/CreateChecklistPopup.xaml.cs @@ -39,12 +39,18 @@ public sealed partial class CreateChecklistPopup : ContentPopup private readonly Checklist _checklist; - public CreateChecklistPopup(IClientService clientService) - : this(clientService, null, true, false, null) + public CreateChecklistPopup(IClientService clientService, FormattedText title) + : this(clientService, title, null, true, false, null) { } public CreateChecklistPopup(IClientService clientService, Checklist checklist, bool canBeEdited, bool addTask, ChecklistTask taskToEdit) + : this(clientService, null, checklist, canBeEdited, addTask, taskToEdit) + { + + } + + private CreateChecklistPopup(IClientService clientService, FormattedText title, Checklist checklist, bool canBeEdited, bool addTask, ChecklistTask taskToEdit) { InitializeComponent(); @@ -56,6 +62,11 @@ public CreateChecklistPopup(IClientService clientService, Checklist checklist, b TitleText.DataContext = _viewModel; TitleText.MaxLength = (int)clientService.Options.ChecklistTitleLengthMax; + if (title != null) + { + TitleText.SetText(title); + } + AddTask.DataContext = _viewModel; AddTask.MaxLength = (int)clientService.Options.ChecklistTaskTextLengthMax; diff --git a/Telegram/Views/Popups/CreatePollPopup.xaml.cs b/Telegram/Views/Popups/CreatePollPopup.xaml.cs index 975a29b646..d580a3c9b3 100644 --- a/Telegram/Views/Popups/CreatePollPopup.xaml.cs +++ b/Telegram/Views/Popups/CreatePollPopup.xaml.cs @@ -36,7 +36,7 @@ public sealed partial class CreatePollPopup : ContentPopup { private readonly CreatePollViewModel _viewModel; - public CreatePollPopup(IClientService clientService, bool forceQuiz, bool forceRegular, bool forceAnonymous) + public CreatePollPopup(IClientService clientService, FormattedText question, bool forceQuiz, bool forceRegular, bool forceAnonymous) { InitializeComponent(); @@ -47,6 +47,11 @@ public CreatePollPopup(IClientService clientService, bool forceQuiz, bool forceR QuestionText.DataContext = _viewModel; EmojiPanel.DataContext = EmojiDrawerViewModel.Create(clientService.SessionId); + if (question != null) + { + QuestionText.SetText(question); + } + Title = Strings.NewPoll; PrimaryButtonText = Strings.OK; SecondaryButtonText = Strings.Cancel; diff --git a/Telegram/Views/Popups/DeleteMessagesPopup.xaml b/Telegram/Views/Popups/DeleteMessagesPopup.xaml index 52c571c3b1..b5d9cd31fc 100644 --- a/Telegram/Views/Popups/DeleteMessagesPopup.xaml +++ b/Telegram/Views/Popups/DeleteMessagesPopup.xaml @@ -165,11 +165,6 @@ - - ViewModel.GiftsTab.SortByPrice = !ViewModel.GiftsTab.SortByPrice; - unlimited.Click += (s, args) => ViewModel.GiftsTab.ExcludeUnlimited = !ViewModel.GiftsTab.ExcludeUnlimited; - limited.Click += (s, args) => ViewModel.GiftsTab.ExcludeLimited = !ViewModel.GiftsTab.ExcludeLimited; - unique.Click += (s, args) => ViewModel.GiftsTab.ExcludeUpgraded = !ViewModel.GiftsTab.ExcludeUpgraded; + async void UpdateFilters(Action action) + { + _programmaticChange = true; + action(); + await ScrollingHost.WaitForViewChangedAsync(false); + _programmaticChange = false; + } + + sort.Click += (s, args) => UpdateFilters(() => ViewModel.GiftsTab.SortByPrice = !ViewModel.GiftsTab.SortByPrice); + unlimited.Click += (s, args) => UpdateFilters(() => ViewModel.GiftsTab.ExcludeUnlimited = !ViewModel.GiftsTab.ExcludeUnlimited); + limited.Click += (s, args) => UpdateFilters(() => ViewModel.GiftsTab.ExcludeLimited = !ViewModel.GiftsTab.ExcludeLimited); + unique.Click += (s, args) => UpdateFilters(() => ViewModel.GiftsTab.ExcludeUpgraded = !ViewModel.GiftsTab.ExcludeUpgraded); flyout.Items.Add(sort); flyout.CreateFlyoutSeparator(); @@ -1041,20 +1083,20 @@ private void Menu_ContextRequested(object sender, RoutedEventArgs e) if (ViewModel.ClientService.IsSavedMessages(ViewModel.Chat) || ViewModel.ClientService.TryGetSupergroup(ViewModel.Chat, out Supergroup supergroup) && supergroup.CanPostMessages()) { - var displayed = new ToggleMenuFlyoutItem + var displayed = new MenuFlyoutItem { Text = Strings.Gift2FilterDisplayed, - IsChecked = !ViewModel.GiftsTab.ExcludeSaved + Icon = ViewModel.GiftsTab.ExcludeSaved ? null : MenuFlyoutHelper.CreateIcon(Icons.Checkmark) }; - var hidden = new ToggleMenuFlyoutItem + var hidden = new MenuFlyoutItem { Text = Strings.Gift2FilterHidden, - IsChecked = !ViewModel.GiftsTab.ExcludeUnsaved + Icon = ViewModel.GiftsTab.ExcludeUnsaved ? null : MenuFlyoutHelper.CreateIcon(Icons.Checkmark) }; - displayed.Click += (s, args) => ViewModel.GiftsTab.ExcludeSaved = !ViewModel.GiftsTab.ExcludeSaved; - hidden.Click += (s, args) => ViewModel.GiftsTab.ExcludeUnsaved = !ViewModel.GiftsTab.ExcludeUnsaved; + displayed.Click += (s, args) => UpdateFilters(() => ViewModel.GiftsTab.ExcludeSaved = !ViewModel.GiftsTab.ExcludeSaved); + hidden.Click += (s, args) => UpdateFilters(() => ViewModel.GiftsTab.ExcludeUnsaved = !ViewModel.GiftsTab.ExcludeUnsaved); flyout.CreateFlyoutSeparator(); flyout.Items.Add(displayed); diff --git a/Telegram/Views/Settings/Popups/SettingsDataAutoPopup.xaml b/Telegram/Views/Settings/Popups/SettingsDataAutoPopup.xaml index 15d5330867..10eb27b76a 100644 --- a/Telegram/Views/Settings/Popups/SettingsDataAutoPopup.xaml +++ b/Telegram/Views/Settings/Popups/SettingsDataAutoPopup.xaml @@ -13,7 +13,8 @@ - + @@ -32,7 +33,7 @@ + Margin="0,8,0,0" /> + + + + + + + Margin="0,4,0,0" /> diff --git a/Telegram/Views/Settings/Popups/SettingsSessionPopup.xaml b/Telegram/Views/Settings/Popups/SettingsSessionPopup.xaml index c78c83b707..c9d746af55 100644 --- a/Telegram/Views/Settings/Popups/SettingsSessionPopup.xaml +++ b/Telegram/Views/Settings/Popups/SettingsSessionPopup.xaml @@ -52,19 +52,19 @@ -