diff --git a/Telegram.Msix/Package.appxmanifest b/Telegram.Msix/Package.appxmanifest index cbe88fa608..450bfc89f1 100644 --- a/Telegram.Msix/Package.appxmanifest +++ b/Telegram.Msix/Package.appxmanifest @@ -1,6 +1,6 @@  - + Unigram—Telegram for Windows diff --git a/Telegram.Native/Composition/CompositionDevice.cpp b/Telegram.Native/Composition/CompositionDevice.cpp index 37ada23a44..5d5c3195aa 100644 --- a/Telegram.Native/Composition/CompositionDevice.cpp +++ b/Telegram.Native/Composition/CompositionDevice.cpp @@ -113,6 +113,10 @@ namespace winrt::Telegram::Native::Composition::implementation return layerVisual; } + // The code below definitely works on late Windows 10 builds, but it definitely crashes on 1909 and earlier + // Thus, for now we just disable bubble tails on Windows 10. + return nullptr; + // We are using the thread ID to verify and ensure that we aren't hooking any other ElementCompositionPreview::GetElementVisual call // that happened to be going in another thread at the same time we are hooking the function to return a LayerVisual, // and we use a lock to ensure that only one thread can be hooking at a time so that thread ID doesn't get changed mid-hook. diff --git a/Telegram.Native/MessageBubbleNineGrid.cpp b/Telegram.Native/MessageBubbleNineGrid.cpp index d9262d3878..d869df632b 100644 --- a/Telegram.Native/MessageBubbleNineGrid.cpp +++ b/Telegram.Native/MessageBubbleNineGrid.cpp @@ -41,7 +41,7 @@ namespace winrt::Telegram::Native::implementation Invalidate(m_rasterizationScale); m_xamlRootChanged = m_xamlRoot.Changed({ this, &MessageBubbleNineGrid::OnXamlRootChanged }); - m_context->m_compositionDevice.RenderingDeviceReplaced({ this, &MessageBubbleNineGrid::OnRenderingDeviceReplaced }); + m_renderingDeviceReplaced = m_context->m_compositionDevice.RenderingDeviceReplaced({ this, &MessageBubbleNineGrid::OnRenderingDeviceReplaced }); } MessageBubbleNineGrid::~MessageBubbleNineGrid() diff --git a/Telegram.Native/NativeUtils.cpp b/Telegram.Native/NativeUtils.cpp index e0a80272e8..4c1bfba3af 100644 --- a/Telegram.Native/NativeUtils.cpp +++ b/Telegram.Native/NativeUtils.cpp @@ -245,18 +245,6 @@ namespace winrt::Telegram::Native::implementation moduleFilename = moduleFilename.substr(moduleFilenamePos + 1); } - if (moduleFilename.rfind(L"Telegram", 0) != 0) - { - skipping = true; - continue; - } - - if (skipping) - { - skipping = false; - trace += L" ...\n"; - } - trace += wstrprintf(L" at %s+0x%08lx\n", moduleFilename.c_str(), (uint32_t)((unsigned char*)pointer - moduleBase)); frames.Append({ (intptr_t)pointer, (intptr_t)moduleBase }); } diff --git a/Telegram.Native/PlaceholderImageHelper.cpp b/Telegram.Native/PlaceholderImageHelper.cpp index fa7296ec3e..ba1ddd6402 100644 --- a/Telegram.Native/PlaceholderImageHelper.cpp +++ b/Telegram.Native/PlaceholderImageHelper.cpp @@ -391,7 +391,7 @@ namespace winrt::Telegram::Native::implementation if (result == DXGI_ERROR_DEVICE_REMOVED || result == DXGI_ERROR_DEVICE_RESET) { - ReturnIfFailed(result, HandleDirect3DDeviceLost()); + ReturnIfFailed(result, CreateDeviceResources()); ReturnIfFailed(result, source->CreateDeviceResources(m_d2dDevice.get())); return Invalidate(imageSource, buffer); } @@ -837,7 +837,7 @@ namespace winrt::Telegram::Native::implementation if ((result = m_d2dContext->EndDraw()) == D2DERR_RECREATE_TARGET) { - ReturnIfFailed(result, HandleDirect3DDeviceLost()); + ReturnIfFailed(result, CreateDeviceResources()); return DrawBlurredImpl(wicBitmapSource, blurAmount, bitmap, minithumbnail); } @@ -993,11 +993,20 @@ namespace winrt::Telegram::Native::implementation if (m_compositor) { - auto compositorInterop = m_compositor.as(); - winrt::com_ptr deviceInterop; - ReturnIfFailed(result, compositorInterop->CreateGraphicsDevice(m_d2dDevice.get(), deviceInterop.put())); + // If the composition device already exists, invalidate the rendering device + if (m_compositionDevice) + { + winrt::com_ptr compositionGraphicsDeviceInterop{ m_compositionDevice.as() }; + result = compositionGraphicsDeviceInterop->SetRenderingDevice(m_d2dDevice.get()); + } + else + { + auto compositorInterop = m_compositor.as(); + winrt::com_ptr deviceInterop; + ReturnIfFailed(result, compositorInterop->CreateGraphicsDevice(m_d2dDevice.get(), deviceInterop.put())); - m_compositionDevice = deviceInterop.as(); + m_compositionDevice = deviceInterop.as(); + } } m_deviceLostHelper.WatchDevice(dxgiDevice); @@ -1006,18 +1015,9 @@ namespace winrt::Telegram::Native::implementation return S_OK; } - void PlaceholderImageHelper::OnDirect3DDeviceLost(DeviceLostHelper const* /* sender */, DeviceLostEventArgs const& /* args */) + void PlaceholderImageHelper::OnDirect3DDeviceLost(DeviceLostHelper const* /* sender */, DeviceLostEventArgs const& args) { - HandleDirect3DDeviceLost(); - } - - HRESULT PlaceholderImageHelper::HandleDirect3DDeviceLost() - { - HRESULT result; - ReturnIfFailed(result, CreateDeviceResources()); - - winrt::com_ptr compositionGraphicsDeviceInterop{ m_compositionDevice.as() }; - return compositionGraphicsDeviceInterop->SetRenderingDevice(m_d2dDevice.get()); + CreateDeviceResources(); } HRESULT PlaceholderImageHelper::CreateTextFormat(double fontSize) diff --git a/Telegram.Native/PlaceholderImageHelper.h b/Telegram.Native/PlaceholderImageHelper.h index 8be75fedd7..4bd4ae452c 100644 --- a/Telegram.Native/PlaceholderImageHelper.h +++ b/Telegram.Native/PlaceholderImageHelper.h @@ -214,7 +214,7 @@ namespace winrt::Telegram::Native::implementation void StopWatchingCurrentDevice() { - if (m_dxgiDevice) + if (m_dxgiDevice && m_onDeviceLostHandler) { // QI For the ID3D11Device4 interface. auto d3dDevice{ m_dxgiDevice.as<::ID3D11Device4>() }; @@ -271,7 +271,7 @@ namespace winrt::Telegram::Native::implementation { if (FAILED(m_d3dDevice->GetDeviceRemovedReason())) { - return HandleDirect3DDeviceLost(); + return CreateDeviceResources(); } return S_OK; @@ -339,7 +339,6 @@ namespace winrt::Telegram::Native::implementation HRESULT CreateTextFormat(double fontSize); void OnDirect3DDeviceLost(DeviceLostHelper const* /* sender */, DeviceLostEventArgs const& /* args */); - HRESULT HandleDirect3DDeviceLost(); HRESULT DrawBlurredImpl(IWICBitmapSource* wicBitmapSource, float blurAmount, SoftwareBitmap& bitmap, bool minithumbnail); HRESULT SaveImageToStream(ID2D1Image* image, REFGUID wicFormat, IRandomAccessStream randomAccessStream); diff --git a/Telegram/Controls/Cells/PlaybackItemCell.xaml.cs b/Telegram/Controls/Cells/PlaybackItemCell.xaml.cs index 0f4a947a2f..415f1e78b3 100644 --- a/Telegram/Controls/Cells/PlaybackItemCell.xaml.cs +++ b/Telegram/Controls/Cells/PlaybackItemCell.xaml.cs @@ -106,7 +106,7 @@ public void UpdateItem(PlaybackItem item) } else { - Title.Text = $"{audio.Performer} - {audio.Title}"; + Title.Text = $"{audio.Title} - {audio.Performer}"; } TitleTrim.Text = string.Empty; diff --git a/Telegram/Controls/Cells/SharedAudioCell.xaml.cs b/Telegram/Controls/Cells/SharedAudioCell.xaml.cs index 87c2808102..5847050949 100644 --- a/Telegram/Controls/Cells/SharedAudioCell.xaml.cs +++ b/Telegram/Controls/Cells/SharedAudioCell.xaml.cs @@ -107,7 +107,7 @@ public void UpdateMessage(MessageWithOwner message) } else { - Title.Text = $"{audio.Performer} - {audio.Title}"; + Title.Text = $"{audio.Title} - {audio.Performer}"; } TitleTrim.Text = string.Empty; diff --git a/Telegram/Controls/Messages/Content/AudioContent.xaml.cs b/Telegram/Controls/Messages/Content/AudioContent.xaml.cs index abfca272ee..a97bbc3647 100644 --- a/Telegram/Controls/Messages/Content/AudioContent.xaml.cs +++ b/Telegram/Controls/Messages/Content/AudioContent.xaml.cs @@ -124,7 +124,7 @@ public void UpdateMessage(MessageViewModel message) } else { - Title.Text = $"{audio.Performer} - {audio.Title}"; + Title.Text = $"{audio.Title} - {audio.Performer}"; } TitleTrim.Text = string.Empty; diff --git a/Telegram/Controls/Messages/MessageBubble.xaml b/Telegram/Controls/Messages/MessageBubble.xaml index 9cb00c08f6..7901d91520 100644 --- a/Telegram/Controls/Messages/MessageBubble.xaml +++ b/Telegram/Controls/Messages/MessageBubble.xaml @@ -161,7 +161,8 @@ Grid.Column="1" Grid.RowSpan="3"> + Size="30" + Shape="Ellipse" /> (ProfilePictureSource)GetValue(SourceProperty); - set => SetValue(SourceProperty, value); + get => _source; + set + { + if (_source != value) + { + _source = value; + InvalidateShape(); + Load(); + } + } } - public static readonly DependencyProperty SourceProperty = - DependencyProperty.Register("Source", typeof(ProfilePictureSource), typeof(MessageProfilePicture), new PropertyMetadata(null, OnPropertyChanged)); - - #endregion - - #region Size - + private int _size; public int Size { - get { return (int)GetValue(SizeProperty); } - set { SetValue(SizeProperty, value); } + get => _size; + set + { + if (_size != value) + { + _size = value; + InvalidateMeasure(); + InvalidateShape(); + Load(); + } + } } - public static readonly DependencyProperty SizeProperty = - DependencyProperty.Register("Size", typeof(int), typeof(MessageProfilePicture), new PropertyMetadata(0, OnPropertyChanged)); - - #endregion - - private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + private ProfilePictureShape _shape = ProfilePictureShape.Auto; + public ProfilePictureShape Shape { - if (e.Property == SizeProperty) + get => _shape; + set { - ((MessageProfilePicture)d).InvalidateMeasure(); + if (_shape != value) + { + _shape = value; + InvalidateShape(); + } } + } - ((MessageProfilePicture)d).Load(); + public ProfilePictureShape CalculatedShape + { + get + { + if (_shape == ProfilePictureShape.Auto) + { + if (_source != null) + { + return _source.Shape; + } + + return ProfilePictureShape.Ellipse; + } + + return _shape; + } } private void Invalidate(object newValue) @@ -177,42 +221,109 @@ private void Invalidate(object newValue) _glyph = text.IsGlyph; Initials.Margin = new Thickness(0, 1, 0, _glyph ? 0 : 2); } + + InvalidateFontSize(); } else if (newValue is ImageBrush texture) { LayoutRoot.Background = texture; - Initials.Visibility = Visibility.Collapsed; } else { LayoutRoot.Background = null; - Initials.Visibility = Visibility.Collapsed; } - - UpdateCornerRadius(); - UpdateFontSize(); } - private void UpdateCornerRadius() + private void InvalidateShape() { - if (LayoutRoot == null || Size == 0) + var shape = CalculatedShape; + var size = Size; + + if (shape == _appliedShape && size == _appliedSize) { return; } - LayoutRoot.CornerRadius = new CornerRadius(Size / 2d); + if (LayoutRoot == null || size == 0) + { + return; + } + + _appliedShape = shape; + _appliedSize = size; + + if (shape == ProfilePictureShape.Tail) + { + _tail = true; + + static CompositionPath GetTail(float radius) + { + CanvasGeometry result; + using (var builder = new CanvasPathBuilder(null)) + { + var cy = radius; + var cx = radius; + var r = radius; + + float b = cy + r; + float x = r / 81.0f; + + float startAngle = -180 * (MathF.PI / 180); + float sweepAngle = 270 * (MathF.PI / 180); + + float x1 = cx + MathF.Cos(startAngle) * r; + float y1 = cy + MathF.Sin(startAngle) * r; + + float x2 = cx + MathF.Cos(startAngle + sweepAngle) * r; + float y2 = cy + MathF.Sin(startAngle + sweepAngle) * r; + + builder.BeginFigure(new Vector2(x1, y1)); + builder.AddArc(new Vector2(x2, y2), r, r, 0, CanvasSweepDirection.Clockwise, CanvasArcSize.Large); + builder.AddCubicBezier(new Vector2(cx - 13 * x, b), new Vector2(cx - 25 * x, b - 3 * x), new Vector2(cx - 36f * x, b - 8.42f * x)); + builder.AddCubicBezier(new Vector2(cx - 52 * x, b - x), new Vector2(cx - 56.5f * x, b - x), new Vector2(cx - 78.02f * x, b - x)); + builder.AddCubicBezier(new Vector2(cx - 80 * x, b - x), new Vector2(cx - 81 * x, b - 3 * x), new Vector2(cx - 79.52f * x, b - 4.5f * x)); + builder.AddCubicBezier(new Vector2(cx - 78 * x, b - 6 * x), new Vector2(cx - 63.73f * x, b - 15 * x), new Vector2(cx - 63.73f * x, b - 31 * x)); + builder.AddCubicBezier(new Vector2(cx - 74.5f * x, b - 44.75f * x), new Vector2(cx - r, cy + 18.87f * x), new Vector2(cx - r, cy)); + builder.EndFigure(CanvasFigureLoop.Closed); + result = CanvasGeometry.CreatePath(builder); + } + return new CompositionPath(result); + } + + var compositor = BootStrapper.Current.Compositor; + + var polygon = compositor.CreatePathGeometry(); + polygon.Path = GetTail(Size / 2f); + + var visual = ElementComposition.GetElementVisual(this); + visual.Clip = compositor.CreateGeometricClip(polygon); + } + else if (_tail) + { + _tail = false; + + var visual = ElementComposition.GetElementVisual(this); + visual.Clip = null; + } + + LayoutRoot.CornerRadius = new CornerRadius(shape switch + { + ProfilePictureShape.Superellipse => size / 4d, + ProfilePictureShape.Ellipse => size / 2d, + _ => 0 + }); } - private void UpdateFontSize() + private void InvalidateFontSize() { - if (Initials == null || double.IsNaN(Width)) + if (Initials == null || Size == 0) { return; } - var fontSize = Width switch + var fontSize = Size switch { < 20 => 10, < 30 => 12, @@ -237,7 +348,6 @@ private class MessageProfilePicturePresenter { public enum State { - Template, Download, Update } @@ -271,7 +381,7 @@ public MessageProfilePicturePresenter(MessageProfilePictureLoader loader, Messag _controller = new ThumbnailController(_texture); - _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + _dispatcherQueue = loader.DispatcherQueue; } public MessageProfilePicturePresentation Presentation => _presentation; @@ -281,184 +391,75 @@ public void Load(MessageProfilePicture picture) _pictures.Add(picture); picture.Invalidate(_source); - Invalidate(State.Download); + Load(State.Download); } - private void Invalidate(State state) + private void Load(State state) { var source = _presentation.Source; - if (source is ProfilePictureSourceChat sourceChat) - { - SetChat(source.ClientService, sourceChat.Chat, sourceChat.Chat.Photo?.Small, _presentation.Size, state); - } - else if (source is ProfilePictureSourceUser sourceUser) + if (source is ProfilePictureSourcePhoto sourcePhoto && (_fileId != sourcePhoto.Photo.Id || state != State.Download)) { - SetUser(source.ClientService, sourceUser.User, sourceUser.User.ProfilePhoto?.Small, _presentation.Size, state); - } - else if (source is ProfilePictureSourceText sourceText) - { - // Local handling within MessageProfilePicture would be better... + _fileId = sourcePhoto.Photo.Id; UpdateManager.Unsubscribe(this, ref _fileToken); - _fileId = null; - Source = sourceText; + Invalidate(sourcePhoto, _presentation.Size, state); } - } - - private void SetChat(IClientService clientService, Chat chat, File file, int side, State state = State.Download) - { - var fileId = file?.Id ?? 0; - if (fileId != _fileId || /*Source == null ||*/ state != State.Download) + else if (source is ProfilePictureSourceText sourceText) { + _fileId = null; UpdateManager.Unsubscribe(this, ref _fileToken); - _fileId = file?.Id; - Source = GetChat(clientService, chat, file, side, out var shape, state); + Invalidate(sourceText); } } - private object GetChat(IClientService clientService, Chat chat, File file, int side, out ProfilePictureShape shape, State state = State.Download) + private void Invalidate(ProfilePictureSourcePhoto photo, int side, State state = State.Download) { - // TODO: this method may throw a NullReferenceException in some conditions - - shape = ProfilePictureShape.Ellipse; - - if (chat.Id == clientService.Options.MyId) - { - _controller?.Recycle(); - return ProfilePictureSourceText.GetGlyph(Icons.BookmarkFilled, 5); - } - else if (chat.Id == clientService.Options.RepliesBotChatId) - { - _controller?.Recycle(); - return ProfilePictureSourceText.GetGlyph(Icons.ArrowReplyFilled, 5); - } - - //if (IsShapeEnabled && clientService.TryGetSupergroup(chat, out Supergroup supergroup)) - //{ - // if (supergroup.IsForum) - // { - // shape = ProfilePictureShape.Superellipse; - // } - // else if (supergroup.IsDirectMessagesGroup) - // { - // shape = ProfilePictureShape.Tail; - // } - //} - - if (file != null) + if (photo.Photo.Local.IsDownloadingCompleted) { - if (file.Local.IsDownloadingCompleted) - { - _controller.Bitmap(file.Local.Path, side, side, chat.Id); - - return _texture; - } - else - { - if (file.Local.CanBeDownloaded && !file.Local.IsDownloadingActive && state != State.Update) - { - clientService.DownloadFile(file.Id, 1); - } - - UpdateManager.Subscribe(this, clientService, file, ref _fileToken, UpdateFile, true); - } + _controller.Bitmap(photo.Photo.Local.Path, side, side, photo.Id); + Invalidate(_texture); - var minithumbnail = chat.Photo?.Minithumbnail; - if (minithumbnail != null) - { - _controller.Blur(minithumbnail.Data, 3, chat.Id); - - return _texture; - } + return; } - - _controller.Recycle(); - - if (clientService.TryGetUser(chat, out User user)) + else { - if (user.Type is UserTypeDeleted) + if (photo.Photo.Local.CanBeDownloaded && !photo.Photo.Local.IsDownloadingActive && state != State.Update) { - return ProfilePictureSourceText.GetGlyph(Icons.GhostFilled, long.MinValue); + photo.ClientService.DownloadFile(photo.Photo.Id, 1); } - return ProfilePictureSourceText.GetUser(clientService, user); - } - - return ProfilePictureSourceText.GetChat(clientService, chat); - } - - private void SetUser(IClientService clientService, User user, File file, int side, State state = State.Download) - { - var fileId = file?.Id ?? 0; - if (fileId != _fileId || /*Source == null ||*/ state != State.Download) - { - UpdateManager.Unsubscribe(this, ref _fileToken); - - _fileId = fileId; - Source = GetUser(clientService, user, file, side, state); + UpdateManager.Subscribe(this, photo.ClientService, photo.Photo, ref _fileToken, UpdateFile, true); } - } - private object GetUser(IClientService clientService, User user, File file, int side, State state = State.Download) - { - if (file != null) + if (photo.Minithumbnail != null) { - if (file.Local.IsDownloadingCompleted) - { - _controller.Bitmap(file.Local.Path, side, side, user.Id); - - return _texture; - } - else - { - if (file.Local.CanBeDownloaded && !file.Local.IsDownloadingActive && state != State.Update) - { - clientService.DownloadFile(file.Id, 1); - } - - UpdateManager.Subscribe(this, clientService, file, ref _fileToken, UpdateFile, true); - } - - var minithumbnail = user.ProfilePhoto?.Minithumbnail; - if (minithumbnail != null) - { - _controller.Blur(minithumbnail.Data, 3, user.Id); + _controller.Blur(photo.Minithumbnail.Data, 3, photo.Id); + Invalidate(_texture); - return _texture; - } + return; } _controller.Recycle(); - - if (user.Type is UserTypeDeleted) - { - return ProfilePictureSourceText.GetGlyph(Icons.GhostFilled, long.MinValue); - } - - return ProfilePictureSourceText.GetUser(clientService, user); + Invalidate(photo.Text); } - public object Source + private void Invalidate(object value) { - get => _source; - set + if (_source != value) { - if (_source != value) - { - _source = value; + _source = value; - foreach (var picture in _pictures) - { - picture.Invalidate(value); - } + foreach (var picture in _pictures) + { + picture.Invalidate(value); } } } private void UpdateFile(object target, File file) { - _dispatcherQueue.TryEnqueue(() => Invalidate(State.Update)); + _dispatcherQueue.TryEnqueue(() => Load(State.Update)); } public void Unload(MessageProfilePicture picture) @@ -480,10 +481,17 @@ private class MessageProfilePictureLoader { [ThreadStatic] private static MessageProfilePictureLoader _current; - public static MessageProfilePictureLoader Current => _current ??= new(); private readonly Dictionary _presenters = new(); + private readonly DispatcherQueue _dispatcherQueue; + + private MessageProfilePictureLoader() + { + _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + } + + public DispatcherQueue DispatcherQueue => _dispatcherQueue; public MessageProfilePicturePresenter GetOrCreate(MessageProfilePicturePresentation presentation) { diff --git a/Telegram/Controls/ProfilePicture.cs b/Telegram/Controls/ProfilePicture.cs index 3423a28cdc..c421f40921 100644 --- a/Telegram/Controls/ProfilePicture.cs +++ b/Telegram/Controls/ProfilePicture.cs @@ -29,7 +29,8 @@ public enum ProfilePictureShape None, Ellipse, Superellipse, - Tail + Tail, + Auto } public partial class ProfilePicture : Control @@ -792,7 +793,7 @@ private object GetChatPhoto(IClientService clientService, ChatPhoto photo, File #endregion } - public abstract record ProfilePictureSource(IClientService ClientService) + public abstract record ProfilePictureSource(ProfilePictureShape Shape) { public static ProfilePictureSource Message(MessageViewModel message) { @@ -800,15 +801,15 @@ public static ProfilePictureSource Message(MessageViewModel message) { if (message.ForwardInfo?.Origin is MessageOriginUser fromUser && message.ClientService.TryGetUser(fromUser.SenderUserId, out User fromUserUser)) { - return new ProfilePictureSourceUser(message.ClientService, fromUserUser); + return ProfilePictureSource.User(message.ClientService, fromUserUser); } else if (message.ForwardInfo?.Origin is MessageOriginChat fromChat && message.ClientService.TryGetChat(fromChat.SenderChatId, out Chat fromChatChat)) { - return new ProfilePictureSourceChat(message.ClientService, fromChatChat); + return ProfilePictureSource.Chat(message.ClientService, fromChatChat); } else if (message.ForwardInfo?.Origin is MessageOriginChannel fromChannel && message.ClientService.TryGetChat(fromChannel.ChatId, out Chat fromChannelChat)) { - return new ProfilePictureSourceChat(message.ClientService, fromChannelChat); + return ProfilePictureSource.Chat(message.ClientService, fromChannelChat); } else if (message.ForwardInfo?.Origin is MessageOriginHiddenUser fromHiddenUser) { @@ -821,26 +822,107 @@ public static ProfilePictureSource Message(MessageViewModel message) } else if (message.ClientService.TryGetUser(message.SenderId, out User senderUser)) { - return new ProfilePictureSourceUser(message.ClientService, senderUser); + return ProfilePictureSource.User(message.ClientService, senderUser); } else if (message.ClientService.TryGetChat(message.SenderId, out Chat senderChat)) { - return new ProfilePictureSourceChat(message.ClientService, senderChat); + return ProfilePictureSource.Chat(message.ClientService, senderChat); } return null; } - } - public record ProfilePictureSourceChat(IClientService ClientService, Chat Chat) - : ProfilePictureSource(ClientService); + public static ProfilePictureSource MessageSender(IClientService clientService, MessageSender sender) + { + if (clientService.TryGetUser(sender, out User user)) + { + return ProfilePictureSource.User(clientService, user); + } + else if (clientService.TryGetChat(sender, out Chat chat)) + { + return ProfilePictureSource.Chat(clientService, chat); + } + + return null; + } + + public static ProfilePictureSource User(IClientService clientService, User user) + { + ProfilePictureSourceText text; + if (user.Type is UserTypeDeleted) + { + text = ProfilePictureSourceText.GetGlyph(Icons.GhostFilled, long.MinValue); + } + else + { + text = ProfilePictureSourceText.GetUser(clientService, user); + } + + var photo = user.ProfilePhoto; + if (photo != null) + { + return new ProfilePictureSourcePhoto(clientService, user.Id, photo.Small, photo.Minithumbnail, text, ProfilePictureShape.Ellipse); + } + + return text; + } + + public static ProfilePictureSource Chat(IClientService clientService, Chat chat) + { + if (chat.Id == clientService.Options.MyId) + { + return ProfilePictureSourceText.GetGlyph(Icons.BookmarkFilled, 5); + } + else if (chat.Id == clientService.Options.RepliesBotChatId) + { + return ProfilePictureSourceText.GetGlyph(Icons.ArrowReplyFilled, 5); + } + + var shape = ProfilePictureShape.Ellipse; + if (clientService.TryGetSupergroup(chat, out Supergroup supergroup)) + { + if (supergroup.IsForum) + { + shape = ProfilePictureShape.Superellipse; + } + else if (supergroup.IsDirectMessagesGroup) + { + shape = ProfilePictureShape.Tail; + } + } + + ProfilePictureSourceText text; + if (supergroup == null && clientService.TryGetUser(chat, out User user)) + { + if (user.Type is UserTypeDeleted) + { + text = ProfilePictureSourceText.GetGlyph(Icons.GhostFilled, long.MinValue); + } + else + { + text = ProfilePictureSourceText.GetUser(clientService, user); + } + } + else + { + text = ProfilePictureSourceText.GetChat(clientService, chat, shape); + } - // TODO: this doesn't properly support equality because User is not singleton - public record ProfilePictureSourceUser(IClientService ClientService, User User) - : ProfilePictureSource(ClientService); + var photo = chat.Photo; + if (photo != null) + { + return new ProfilePictureSourcePhoto(clientService, chat.Id, photo.Small, photo.Minithumbnail, text, shape); + } - public record ProfilePictureSourceText(string Initials, bool IsGlyph, Color TopColor, Color BottomColor) - : ProfilePictureSource(ClientService: null) + return text; + } + } + + public record ProfilePictureSourcePhoto(IClientService ClientService, long Id, File Photo, Minithumbnail Minithumbnail, ProfilePictureSourceText Text, ProfilePictureShape Shape) + : ProfilePictureSource(Shape); + + public record ProfilePictureSourceText(string Initials, bool IsGlyph, Color TopColor, Color BottomColor, ProfilePictureShape Shape = ProfilePictureShape.Ellipse) + : ProfilePictureSource(Shape) { private static readonly Color[] _colorsTop = new Color[7] { @@ -890,9 +972,26 @@ public static CompositionBrush GetBrush(Compositor compositor, long i) return compositor.CreateColorBrush(_colors[Math.Abs(i % _colors.Length)]); } - public static ProfilePictureSourceText GetChat(IClientService clientService, Chat chat) + public static ProfilePictureSourceText GetChat(IClientService clientService, Chat chat, ProfilePictureShape shape = ProfilePictureShape.None) { - return ProfilePictureSourceText.FromNameColor(InitialNameStringConverter.Convert(chat.Title), false, clientService.GetAccentColor(chat.AccentColorId)); + if (shape == ProfilePictureShape.None) + { + shape = ProfilePictureShape.Ellipse; + + if (clientService.TryGetSupergroup(chat, out Supergroup supergroup)) + { + if (supergroup.IsForum) + { + shape = ProfilePictureShape.Superellipse; + } + else if (supergroup.IsDirectMessagesGroup) + { + shape = ProfilePictureShape.Tail; + } + } + } + + return ProfilePictureSourceText.FromNameColor(InitialNameStringConverter.Convert(chat.Title), false, clientService.GetAccentColor(chat.AccentColorId), shape); } public static ProfilePictureSourceText GetChat(IClientService clientService, ChatInviteLinkInfo chat) @@ -905,49 +1004,49 @@ public static ProfilePictureSourceText GetUser(IClientService clientService, Use return ProfilePictureSourceText.FromNameColor(InitialNameStringConverter.Convert(user.FirstName, user.LastName), false, clientService.GetAccentColor(user.AccentColorId)); } - public static ProfilePictureSourceText GetNameForUser(string firstName, string lastName, long id = 5) + public static ProfilePictureSourceText GetNameForUser(string firstName, string lastName, long id = 5, ProfilePictureShape shape = ProfilePictureShape.Ellipse) { return ProfilePictureSourceText.FromId(InitialNameStringConverter.Convert(firstName, lastName), false, id); } - public static ProfilePictureSourceText GetNameForUser(string name, long id = 5) + public static ProfilePictureSourceText GetNameForUser(string name, long id = 5, ProfilePictureShape shape = ProfilePictureShape.Ellipse) { return ProfilePictureSourceText.FromId(InitialNameStringConverter.Convert(name), false, id); } - public static ProfilePictureSourceText GetNameForChat(string title, long id = 5) + public static ProfilePictureSourceText GetNameForChat(string title, long id = 5, ProfilePictureShape shape = ProfilePictureShape.Ellipse) { return ProfilePictureSourceText.FromId(InitialNameStringConverter.Convert(title), false, id); } - public static ProfilePictureSourceText GetGlyph(string glyph, long id = 5) + public static ProfilePictureSourceText GetGlyph(string glyph, long id = 5, ProfilePictureShape shape = ProfilePictureShape.Ellipse) { return ProfilePictureSourceText.FromId(glyph, true, id); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ProfilePictureSourceText FromNameColor(string initials, bool isGlyph, NameColor color) + private static ProfilePictureSourceText FromNameColor(string initials, bool isGlyph, NameColor color, ProfilePictureShape shape = ProfilePictureShape.Ellipse) { if (color == null) { - return new ProfilePictureSourceText(initials, isGlyph, _disabledTop, _disabled); + return new ProfilePictureSourceText(initials, isGlyph, _disabledTop, _disabled, shape); } else { - return new ProfilePictureSourceText(initials, isGlyph, _colorsTop[Math.Abs(color.BuiltInAccentColorId % _colors.Length)], _colors[Math.Abs(color.BuiltInAccentColorId % _colors.Length)]); + return new ProfilePictureSourceText(initials, isGlyph, _colorsTop[Math.Abs(color.BuiltInAccentColorId % _colors.Length)], _colors[Math.Abs(color.BuiltInAccentColorId % _colors.Length)], shape); } } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ProfilePictureSourceText FromId(string initials, bool isGlyph, long id) + private static ProfilePictureSourceText FromId(string initials, bool isGlyph, long id, ProfilePictureShape shape = ProfilePictureShape.Ellipse) { if (id == long.MinValue) { - return new ProfilePictureSourceText(initials, isGlyph, _disabledTop, _disabled); + return new ProfilePictureSourceText(initials, isGlyph, _disabledTop, _disabled, shape); } else { - return new ProfilePictureSourceText(initials, isGlyph, _colorsTop[Math.Abs(id % _colors.Length)], _colors[Math.Abs(id % _colors.Length)]); + return new ProfilePictureSourceText(initials, isGlyph, _colorsTop[Math.Abs(id % _colors.Length)], _colors[Math.Abs(id % _colors.Length)], shape); } } } diff --git a/Telegram/Package.appxmanifest b/Telegram/Package.appxmanifest index 85b7d451e9..f1f4319da5 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/Views/ChatView.xaml b/Telegram/Views/ChatView.xaml index da7ecd595a..abb83e4765 100644 --- a/Telegram/Views/ChatView.xaml +++ b/Telegram/Views/ChatView.xaml @@ -1632,7 +1632,8 @@ Height="30" Margin="0,0,0,8"> + Size="30" + Shape="Ellipse" /> + Size="30" + Shape="Ellipse" />