From 5661b566915176e3258e7b494c8a7c24cce1bb3b Mon Sep 17 00:00:00 2001 From: Kimura Youichi Date: Sun, 23 Jun 2024 00:27:50 +0900 Subject: [PATCH] =?UTF-8?q?PostClass.ImageUrl=E3=81=ABIResponsiveImageUri?= =?UTF-8?q?=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Misskey/MisskeyPostFactoryTest.cs | 2 +- .../Twitter/TwitterLegacyTest.cs | 23 ------- .../Twitter/TwitterPostFactoryTest.cs | 4 +- OpenTween/ImageCache.cs | 30 ++++----- OpenTween/ListManage.cs | 5 +- OpenTween/Models/PostClass.cs | 6 +- .../Misskey/MisskeyAvatarUri.cs | 46 +++++++++++++ .../Misskey/MisskeyPostFactory.cs | 2 +- .../SocialProtocol/Twitter/TwitterLegacy.cs | 28 -------- .../Twitter/TwitterPostFactory.cs | 4 +- OpenTween/TimelineListViewDrawer.cs | 17 +++-- OpenTween/Tween.cs | 4 +- OpenTween/TweetDetailsView.cs | 64 +++++++------------ OpenTween/UserInfo.cs | 4 +- OpenTween/UserInfoDialog.cs | 19 +++--- 15 files changed, 114 insertions(+), 144 deletions(-) create mode 100644 OpenTween/SocialProtocol/Misskey/MisskeyAvatarUri.cs diff --git a/OpenTween.Tests/SocialProtocol/Misskey/MisskeyPostFactoryTest.cs b/OpenTween.Tests/SocialProtocol/Misskey/MisskeyPostFactoryTest.cs index 2d7c8d5b8..2e4e37b13 100644 --- a/OpenTween.Tests/SocialProtocol/Misskey/MisskeyPostFactoryTest.cs +++ b/OpenTween.Tests/SocialProtocol/Misskey/MisskeyPostFactoryTest.cs @@ -63,7 +63,7 @@ public void CreateFromNote_LocalNoteTest() Assert.Equal(new MisskeyUserId("ghijkl"), post.UserId); Assert.Equal("bar", post.ScreenName); Assert.Equal("bar", post.Nickname); - Assert.Equal("", post.ImageUrl); + Assert.Null(post.ImageUrl); Assert.Null(post.RetweetedId); Assert.Null(post.RetweetedBy); Assert.Null(post.RetweetedByUserId); diff --git a/OpenTween.Tests/SocialProtocol/Twitter/TwitterLegacyTest.cs b/OpenTween.Tests/SocialProtocol/Twitter/TwitterLegacyTest.cs index 6569e4e23..9245bed82 100644 --- a/OpenTween.Tests/SocialProtocol/Twitter/TwitterLegacyTest.cs +++ b/OpenTween.Tests/SocialProtocol/Twitter/TwitterLegacyTest.cs @@ -302,28 +302,5 @@ public void GetTextLengthRemain_BrokenSurrogateTest() Assert.Equal(278, twitter.GetTextLengthRemain("\ud83d")); Assert.Equal(9999, twitter.GetTextLengthRemain("D twitter \ud83d")); } - - [Theory] - [InlineData("https://pbs.twimg.com/profile_images/00000/foo_normal.jpg", "normal", "https://pbs.twimg.com/profile_images/00000/foo_normal.jpg")] - [InlineData("https://pbs.twimg.com/profile_images/00000/foo_normal.jpg", "bigger", "https://pbs.twimg.com/profile_images/00000/foo_bigger.jpg")] - [InlineData("https://pbs.twimg.com/profile_images/00000/foo_normal.jpg", "mini", "https://pbs.twimg.com/profile_images/00000/foo_mini.jpg")] - [InlineData("https://pbs.twimg.com/profile_images/00000/foo_normal.jpg", "original", "https://pbs.twimg.com/profile_images/00000/foo.jpg")] - [InlineData("https://pbs.twimg.com/profile_images/00000/foo_normal_bar_normal.jpg", "original", "https://pbs.twimg.com/profile_images/00000/foo_normal_bar.jpg")] - public void CreateProfileImageUrl_Test(string normalUrl, string size, string expected) - => Assert.Equal(expected, TwitterLegacy.CreateProfileImageUrl(normalUrl, size)); - - [Fact] - public void CreateProfileImageUrl_InvalidSizeTest() - => Assert.Throws(() => TwitterLegacy.CreateProfileImageUrl("https://pbs.twimg.com/profile_images/00000/foo_normal.jpg", "INVALID")); - - [Theory] - [InlineData(24, "mini")] - [InlineData(25, "normal")] - [InlineData(48, "normal")] - [InlineData(49, "bigger")] - [InlineData(73, "bigger")] - [InlineData(74, "original")] - public void DecideProfileImageSize_Test(int sizePx, string expected) - => Assert.Equal(expected, TwitterLegacy.DecideProfileImageSize(sizePx)); } } diff --git a/OpenTween.Tests/SocialProtocol/Twitter/TwitterPostFactoryTest.cs b/OpenTween.Tests/SocialProtocol/Twitter/TwitterPostFactoryTest.cs index 510da86f0..b8fd3246f 100644 --- a/OpenTween.Tests/SocialProtocol/Twitter/TwitterPostFactoryTest.cs +++ b/OpenTween.Tests/SocialProtocol/Twitter/TwitterPostFactoryTest.cs @@ -110,7 +110,7 @@ public void CreateFromStatus_Test() Assert.Equal(new TwitterUserId(status.User.IdStr), post.UserId); Assert.Equal("tetete", post.ScreenName); Assert.Equal("ててて", post.Nickname); - Assert.Equal("https://example.com/profile.png", post.ImageUrl); + Assert.Equal(new TwitterProfileImageUri("https://example.com/profile.png"), post.ImageUrl); Assert.False(post.IsProtect); Assert.False(post.IsOwl); Assert.False(post.IsMe); @@ -324,7 +324,7 @@ public void CreateFromDirectMessageEvent_Test() Assert.Equal(new TwitterUserId(otherUser.IdStr), post.UserId); Assert.Equal("tetete", post.ScreenName); Assert.Equal("ててて", post.Nickname); - Assert.Equal("https://example.com/profile.png", post.ImageUrl); + Assert.Equal(new TwitterProfileImageUri("https://example.com/profile.png"), post.ImageUrl); Assert.False(post.IsProtect); Assert.True(post.IsOwl); Assert.False(post.IsMe); diff --git a/OpenTween/ImageCache.cs b/OpenTween/ImageCache.cs index 2adae5c5e..959d3d058 100644 --- a/OpenTween/ImageCache.cs +++ b/OpenTween/ImageCache.cs @@ -22,16 +22,13 @@ #nullable enable using System; -using System.Collections.Generic; using System.Drawing; using System.Linq; -using System.Net; using System.Net.Http; -using System.Text; using System.Threading; using System.Threading.Tasks; -using System.Xml.Serialization; using OpenTween.Connection; +using OpenTween.Models; using OpenTween.SocialProtocol.Twitter; namespace OpenTween @@ -85,11 +82,12 @@ public long CacheCount /// 取得先の URL /// キャッシュを使用せずに取得する場合は true /// 非同期に画像を取得するタスク - public Task DownloadImageAsync(string address, bool force = false) + public Task DownloadImageAsync(Uri address, bool force = false) { var cancelToken = this.cancelTokenSource.Token; + var addressStr = address.AbsoluteUri; - this.InnerDictionary.TryGetValue(address, out var cachedImageTask); + this.InnerDictionary.TryGetValue(addressStr, out var cachedImageTask); if (cachedImageTask != null && !force) return cachedImageTask; @@ -97,12 +95,12 @@ public Task DownloadImageAsync(string address, bool force = false) cancelToken.ThrowIfCancellationRequested(); var imageTask = Task.Run(() => this.FetchImageAsync(address, cancelToken)); - this.InnerDictionary[address] = imageTask; + this.InnerDictionary[addressStr] = imageTask; return imageTask; } - private async Task FetchImageAsync(string uri, CancellationToken cancelToken) + private async Task FetchImageAsync(Uri uri, CancellationToken cancelToken) { try { @@ -130,24 +128,24 @@ private MemoryImage CreateBlankImage() return MemoryImage.CopyFromImage(bitmap); } - public MemoryImage? TryGetFromCache(string address) + public MemoryImage? TryGetFromCache(Uri address) { - if (!this.InnerDictionary.TryGetValue(address, out var imageTask) || + var addressStr = address.AbsoluteUri; + + if (!this.InnerDictionary.TryGetValue(addressStr, out var imageTask) || imageTask.Status != TaskStatus.RanToCompletion) return null; return imageTask.Result; } - public MemoryImage? TryGetLargerOrSameSizeFromCache(string normalUrl, string size) + public MemoryImage? TryGetLargerOrSameSizeFromCache(IResponsiveImageUri responseImageUri, int minSizePx) { - var sizes = new[] { "mini", "normal", "bigger", "original" }; - var minimumIndex = sizes.FindIndex(x => x == size); + var imageUris = responseImageUri.GetImageUriLargerOrSameSize(minSizePx); - foreach (var candidateSize in sizes.Skip(minimumIndex)) + foreach (var imageUri in imageUris) { - var imageUrl = TwitterLegacy.CreateProfileImageUrl(normalUrl, candidateSize); - var image = this.TryGetFromCache(imageUrl); + var image = this.TryGetFromCache(imageUri); if (image != null) return image; } diff --git a/OpenTween/ListManage.cs b/OpenTween/ListManage.cs index c7ac91e50..01263f6de 100644 --- a/OpenTween/ListManage.cs +++ b/OpenTween/ListManage.cs @@ -342,7 +342,7 @@ private async void UserList_SelectedIndexChanged(object sender, EventArgs e) } } - private async Task LoadUserIconAsync(Uri imageUri, PersonId userId) + private async Task LoadUserIconAsync(IResponsiveImageUri imageUri, PersonId userId) { var oldImage = this.UserIcon.Image; this.UserIcon.Image = null; @@ -350,8 +350,7 @@ private async Task LoadUserIconAsync(Uri imageUri, PersonId userId) await this.UserIcon.SetImageFromTask(async () => { - var sizeName = TwitterLegacy.DecideProfileImageSize(this.UserIcon.Width); - var uri = TwitterLegacy.CreateProfileImageUrl(imageUri.AbsoluteUri, sizeName); + var uri = imageUri.GetImageUri(this.UserIcon.Width); using var imageStream = await Networking.Http.GetStreamAsync(uri); var image = await MemoryImage.CopyFromStreamAsync(imageStream); diff --git a/OpenTween/Models/PostClass.cs b/OpenTween/Models/PostClass.cs index 33357e731..ba14134cb 100644 --- a/OpenTween/Models/PostClass.cs +++ b/OpenTween/Models/PostClass.cs @@ -29,12 +29,8 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Net; -using System.Text; -using System.Threading; -using System.Threading.Tasks; using OpenTween.Thumbnail; namespace OpenTween.Models @@ -53,7 +49,7 @@ double Latitude /// スクリーンリーダーでの読み上げを考慮したテキスト public string AccessibleText { get; init; } = ""; - public string? ImageUrl { get; init; } + public IResponsiveImageUri? ImageUrl { get; init; } public string ScreenName { get; init; } = ""; diff --git a/OpenTween/SocialProtocol/Misskey/MisskeyAvatarUri.cs b/OpenTween/SocialProtocol/Misskey/MisskeyAvatarUri.cs new file mode 100644 index 000000000..6263f8141 --- /dev/null +++ b/OpenTween/SocialProtocol/Misskey/MisskeyAvatarUri.cs @@ -0,0 +1,46 @@ +// OpenTween - Client of Twitter +// Copyright (c) 2024 kim_upsilon (@kim_upsilon) +// All rights reserved. +// +// This file is part of OpenTween. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY +// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +// for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program. If not, see , or write to +// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, +// Boston, MA 02110-1301, USA. + +#nullable enable + +using System; +using System.IO; +using OpenTween.Models; + +namespace OpenTween.SocialProtocol.Misskey +{ + public record MisskeyAvatarUri( + string ImageUriStr + ) : IResponsiveImageUri + { + public Uri GetImageUri(int sizePx) + => new(this.ImageUriStr); + + public Uri[] GetImageUriLargerOrSameSize(int minSizePx) + => new Uri[] { new(this.ImageUriStr) }; + + public Uri GetOriginalImageUri() + => new(this.ImageUriStr); + + public string GetFilename() + => Path.GetFileName(this.GetOriginalImageUri().AbsolutePath); + } +} diff --git a/OpenTween/SocialProtocol/Misskey/MisskeyPostFactory.cs b/OpenTween/SocialProtocol/Misskey/MisskeyPostFactory.cs index 571b32698..6d246b55f 100644 --- a/OpenTween/SocialProtocol/Misskey/MisskeyPostFactory.cs +++ b/OpenTween/SocialProtocol/Misskey/MisskeyPostFactory.cs @@ -127,7 +127,7 @@ public PostClass CreateFromNote(MisskeyNote note, MisskeyAccountState accountSta UserId = originalNoteUserId, ScreenName = originalNoteUserAcct, Nickname = originalNoteUser.Name ?? originalNoteUser.Username, - ImageUrl = originalNoteUser.AvatarUrl ?? "", + ImageUrl = originalNoteUser.AvatarUrl is { } avatarUrl ? new MisskeyAvatarUri(avatarUrl) : null, // renotedNote から生成 RetweetedId = renotedNote?.Id is { } renotedId ? new MisskeyNoteId(renotedId) : null, diff --git a/OpenTween/SocialProtocol/Twitter/TwitterLegacy.cs b/OpenTween/SocialProtocol/Twitter/TwitterLegacy.cs index 9a135781f..9dcea3a50 100644 --- a/OpenTween/SocialProtocol/Twitter/TwitterLegacy.cs +++ b/OpenTween/SocialProtocol/Twitter/TwitterLegacy.cs @@ -899,34 +899,6 @@ int GetWeightFromCodepoint(int codepoint) return remainWeight / config.Scale; } - /// - /// プロフィール画像のサイズを指定したURLを生成 - /// - /// - /// https://developer.twitter.com/en/docs/twitter-api/v1/accounts-and-users/user-profile-images-and-banners を参照 - /// - public static string CreateProfileImageUrl(string normalUrl, string size) - { - return size switch - { - "original" => normalUrl.Replace("_normal.", "."), - "normal" => normalUrl, - "bigger" or "mini" => normalUrl.Replace("_normal.", $"_{size}."), - _ => throw new ArgumentException($"Invalid size: ${size}", nameof(size)), - }; - } - - public static string DecideProfileImageSize(int sizePx) - { - return sizePx switch - { - <= 24 => "mini", - <= 48 => "normal", - <= 73 => "bigger", - _ => "original", - }; - } - public bool IsDisposed { get; private set; } = false; protected virtual void Dispose(bool disposing) diff --git a/OpenTween/SocialProtocol/Twitter/TwitterPostFactory.cs b/OpenTween/SocialProtocol/Twitter/TwitterPostFactory.cs index c23c876de..e231cb17b 100644 --- a/OpenTween/SocialProtocol/Twitter/TwitterPostFactory.cs +++ b/OpenTween/SocialProtocol/Twitter/TwitterPostFactory.cs @@ -138,7 +138,7 @@ public PostClass CreateFromStatus( var screenName = string.Intern(originalStatusUser.ScreenName); var nickname = string.Intern(originalStatusUser.Name); var imageUrl = originalStatusUser.ProfileImageUrlHttps is { } profileImageUrl - ? string.Intern(profileImageUrl) + ? new TwitterProfileImageUri(string.Intern(profileImageUrl)) : null; // Source整形 @@ -268,7 +268,7 @@ bool firstLoad var screenName = string.Intern(displayUser.ScreenName); var nickname = string.Intern(displayUser.Name); var imageUrl = displayUser.ProfileImageUrlHttps is { } imageUrlStr - ? string.Intern(imageUrlStr) + ? new TwitterProfileImageUri(string.Intern(imageUrlStr)) : null; var source = (string?)null; diff --git a/OpenTween/TimelineListViewDrawer.cs b/OpenTween/TimelineListViewDrawer.cs index 39a99fcd3..096027dc4 100644 --- a/OpenTween/TimelineListViewDrawer.cs +++ b/OpenTween/TimelineListViewDrawer.cs @@ -159,15 +159,15 @@ private void DrawListViewItemStateIcon(Graphics g, PostClass post, Rectangle sta private void DrawListViewItemProfileImage(Graphics g, PostClass post, Size scaledIconSize, Rectangle iconRect) { - if (scaledIconSize.Width <= 0) + var sizePx = scaledIconSize.Width; + if (sizePx <= 0) return; - var normalImageUrl = post.ImageUrl; - if (MyCommon.IsNullOrEmpty(normalImageUrl)) + var imageUri = post.ImageUrl; + if (imageUri == null) return; - var sizeName = TwitterLegacy.DecideProfileImageSize(scaledIconSize.Width); - var cachedImage = this.iconCache.TryGetLargerOrSameSizeFromCache(normalImageUrl, sizeName); + var cachedImage = this.iconCache.TryGetLargerOrSameSizeFromCache(imageUri, minSizePx: sizePx); if (cachedImage != null) { @@ -186,7 +186,7 @@ private void DrawListViewItemProfileImage(Graphics g, PostClass post, Size scale // キャッシュにない画像の場合は読み込みが完了してから再描画する async Task RefreshProfileImageLazy() { - var success = await this.LoadProfileImage(normalImageUrl, sizeName); + var success = await this.LoadProfileImage(imageUri, sizePx); if (!success) return; @@ -206,12 +206,11 @@ async Task RefreshProfileImageLazy() } } - private async Task LoadProfileImage(string normalImageUrl, string sizeName) + private async Task LoadProfileImage(IResponsiveImageUri responsiveImageUri, int sizePx) { try { - var imageUrl = TwitterLegacy.CreateProfileImageUrl(normalImageUrl, sizeName); - await this.iconCache.DownloadImageAsync(imageUrl); + await this.iconCache.DownloadImageAsync(responsiveImageUri.GetImageUri(sizePx)); return true; } diff --git a/OpenTween/Tween.cs b/OpenTween/Tween.cs index 7a2b5b607..21a9fbe20 100644 --- a/OpenTween/Tween.cs +++ b/OpenTween/Tween.cs @@ -972,8 +972,8 @@ private void NotifyNewPosts(PostClass[] notifyPosts, string soundFile, int addCo var bText = sb.ToString(); if (MyCommon.IsNullOrEmpty(bText)) return; - var image = post.ImageUrl is { } imageUrl - ? this.iconCache.TryGetFromCache(imageUrl) + var image = post.ImageUrl is { } responsiveImageUri + ? this.iconCache.TryGetLargerOrSameSizeFromCache(responsiveImageUri, minSizePx: 0) : null; this.gh.Notify(nt, post.StatusId.Id, title.ToString(), bText, image?.Image); diff --git a/OpenTween/TweetDetailsView.cs b/OpenTween/TweetDetailsView.cs index cfb7a4f1e..e48d4fe67 100644 --- a/OpenTween/TweetDetailsView.cs +++ b/OpenTween/TweetDetailsView.cs @@ -268,9 +268,9 @@ public HtmlElement[] GetLinkElements() .ToArray(); } - private async Task SetUserPictureAsync(string? normalImageUrl, bool force = false) + private async Task SetUserPictureAsync(IResponsiveImageUri? responsiveImageUri, bool force = false) { - if (MyCommon.IsNullOrEmpty(normalImageUrl)) + if (responsiveImageUri == null) return; if (this.IconCache == null) @@ -278,10 +278,10 @@ private async Task SetUserPictureAsync(string? normalImageUrl, bool force = fals this.ClearUserPicture(); - var imageSize = TwitterLegacy.DecideProfileImageSize(this.UserPicture.Width); + var sizePx = this.UserPicture.Width; if (!force) { - var cachedImage = this.IconCache.TryGetLargerOrSameSizeFromCache(normalImageUrl, imageSize); + var cachedImage = this.IconCache.TryGetLargerOrSameSizeFromCache(responsiveImageUri, minSizePx: sizePx); if (cachedImage != null) { // 既にキャッシュされていればそれを表示して終了 @@ -290,7 +290,7 @@ private async Task SetUserPictureAsync(string? normalImageUrl, bool force = fals } // 小さいサイズの画像がキャッシュにある場合は高解像度の画像が取得できるまでの間表示する - var fallbackImage = this.IconCache.TryGetLargerOrSameSizeFromCache(normalImageUrl, "mini"); + var fallbackImage = this.IconCache.TryGetLargerOrSameSizeFromCache(responsiveImageUri, minSizePx: 0); if (fallbackImage != null) this.UserPicture.Image = fallbackImage.Clone(); } @@ -298,8 +298,8 @@ private async Task SetUserPictureAsync(string? normalImageUrl, bool force = fals await this.UserPicture.SetImageFromTask( async () => { - var imageUrl = TwitterLegacy.CreateProfileImageUrl(normalImageUrl, imageSize); - var image = await this.IconCache.DownloadImageAsync(imageUrl, force) + var imageUri = responsiveImageUri.GetImageUri(sizePx); + var image = await this.IconCache.DownloadImageAsync(imageUri, force) .ConfigureAwait(false); return image.Clone(); @@ -600,34 +600,15 @@ private void ContextMenuUserPicture_Opening(object sender, CancelEventArgs e) // 発言詳細のアイコン右クリック時のメニュー制御 if (this.CurrentPost != null) { - var name = this.CurrentPost.ImageUrl; - if (!MyCommon.IsNullOrEmpty(name)) + var imageUri = this.CurrentPost.ImageUrl; + if (imageUri != null) { - var idx = name.LastIndexOf('/'); - if (idx != -1) - { - name = Path.GetFileName(name.Substring(idx)); - if (name.Contains("_normal.") || name.EndsWith("_normal", StringComparison.Ordinal)) - { - name = name.Replace("_normal", ""); - this.IconNameToolStripMenuItem.Text = name; - this.IconNameToolStripMenuItem.Enabled = true; - } - else - { - this.IconNameToolStripMenuItem.Enabled = false; - this.IconNameToolStripMenuItem.Text = Properties.Resources.ContextMenuStrip3_OpeningText1; - } - } - else - { - this.IconNameToolStripMenuItem.Enabled = false; - this.IconNameToolStripMenuItem.Text = Properties.Resources.ContextMenuStrip3_OpeningText1; - } + this.IconNameToolStripMenuItem.Text = imageUri.GetFilename(); + this.IconNameToolStripMenuItem.Enabled = true; this.ReloadIconToolStripMenuItem.Enabled = true; - if (this.CurrentPost.ImageUrl is { } imageUrl && this.IconCache.TryGetFromCache(imageUrl) != null) + if (this.IconCache.TryGetLargerOrSameSizeFromCache(imageUri, minSizePx: 0) != null) { this.SaveIconPictureToolStripMenuItem.Enabled = true; } @@ -747,34 +728,33 @@ private void SearchAtPostsDetailNameToolStripMenuItem_Click(object sender, Event private async void IconNameToolStripMenuItem_Click(object sender, EventArgs e) { - var imageNormalUrl = this.CurrentPost?.ImageUrl; - if (MyCommon.IsNullOrEmpty(imageNormalUrl)) + var imageUri = this.CurrentPost?.ImageUrl; + if (imageUri == null) return; - var imageOriginalUrl = TwitterLegacy.CreateProfileImageUrl(imageNormalUrl, "original"); - await MyCommon.OpenInBrowserAsync(this, imageOriginalUrl); + await MyCommon.OpenInBrowserAsync(this, imageUri.GetOriginalImageUri()); } private async void ReloadIconToolStripMenuItem_Click(object sender, EventArgs e) { - var imageUrl = this.CurrentPost?.ImageUrl; - if (MyCommon.IsNullOrEmpty(imageUrl)) + var imageUri = this.CurrentPost?.ImageUrl; + if (imageUri == null) return; - await this.SetUserPictureAsync(imageUrl, force: true); + await this.SetUserPictureAsync(imageUri, force: true); } private void SaveIconPictureToolStripMenuItem_Click(object sender, EventArgs e) { - var imageUrl = this.CurrentPost?.ImageUrl; - if (MyCommon.IsNullOrEmpty(imageUrl)) + var imageUri = this.CurrentPost?.ImageUrl; + if (imageUri == null) return; - var memoryImage = this.IconCache.TryGetFromCache(imageUrl); + var memoryImage = this.IconCache.TryGetFromCache(imageUri.GetOriginalImageUri()); if (memoryImage == null) return; - this.Owner.SaveFileDialog1.FileName = imageUrl.Substring(imageUrl.LastIndexOf('/') + 1); + this.Owner.SaveFileDialog1.FileName = imageUri.GetFilename(); if (this.Owner.SaveFileDialog1.ShowDialog() == DialogResult.OK) { diff --git a/OpenTween/UserInfo.cs b/OpenTween/UserInfo.cs index c23d0f265..a2ce146bb 100644 --- a/OpenTween/UserInfo.cs +++ b/OpenTween/UserInfo.cs @@ -48,7 +48,7 @@ public UserInfo(TwitterUser user) this.Location = WebUtility.HtmlDecode(user.Location); this.Description = WebUtility.HtmlDecode(user.Description); this.ImageUrl = user.ProfileImageUrlHttps is { } imageUrlStr - ? new Uri(imageUrlStr) + ? new TwitterProfileImageUri(imageUrlStr) : null; this.Url = user.Url ?? ""; this.Protect = user.Protected; @@ -70,7 +70,7 @@ public UserInfo(TwitterUser user) public string ScreenName = ""; public string Location = ""; public string Description = ""; - public Uri? ImageUrl = null; + public IResponsiveImageUri? ImageUrl = null; public string Url = ""; public bool Protect = false; public int FriendsCount = 0; diff --git a/OpenTween/UserInfoDialog.cs b/OpenTween/UserInfoDialog.cs index f720bb422..165585201 100644 --- a/OpenTween/UserInfoDialog.cs +++ b/OpenTween/UserInfoDialog.cs @@ -150,12 +150,16 @@ public async Task ShowUserAsync(TwitterUser user) this.ButtonBlockDestroy.Enabled = true; } + var imageUri = user.ProfileImageUrlHttps is { } imageUrlStr + ? new TwitterProfileImageUri(imageUrlStr) + : null; + await Task.WhenAll(new[] { this.SetDescriptionAsync(user.Description, user.Entities?.Description, cancellationToken), this.SetRecentStatusAsync(user.Status, cancellationToken), this.SetLinkLabelWebAsync(user.Url, user.Entities?.Url, cancellationToken), - this.SetUserImageAsync(user.ProfileImageUrlHttps, cancellationToken), + this.SetUserImageAsync(imageUri, cancellationToken), this.LoadFriendshipAsync(user.ScreenName, cancellationToken), }); } @@ -194,7 +198,7 @@ private async Task SetDescriptionAsync(string? descriptionText, TwitterEntities? } } - private async Task SetUserImageAsync(string? imageUri, CancellationToken cancellationToken) + private async Task SetUserImageAsync(IResponsiveImageUri? imageUri, CancellationToken cancellationToken) { var oldImage = this.UserPicture.Image; this.UserPicture.Image = null; @@ -205,8 +209,7 @@ private async Task SetUserImageAsync(string? imageUri, CancellationToken cancell await this.UserPicture.SetImageFromTask(async () => { - var sizeName = TwitterLegacy.DecideProfileImageSize(this.UserPicture.Width); - var uri = TwitterLegacy.CreateProfileImageUrl(imageUri, sizeName); + var uri = imageUri.GetImageUri(this.UserPicture.Width); using var imageStream = await Networking.Http.GetStreamAsync(uri) .ConfigureAwait(false); @@ -465,13 +468,13 @@ private async void ButtonSearchPosts_Click(object sender, EventArgs e) private async void UserPicture_Click(object sender, EventArgs e) { - var imageUrl = this.displayUser.ProfileImageUrlHttps; - if (imageUrl == null) + var imageUrlStr = this.displayUser.ProfileImageUrlHttps; + if (imageUrlStr == null) return; - imageUrl = TwitterLegacy.CreateProfileImageUrl(imageUrl, "original"); + var imageUri = new TwitterProfileImageUri(imageUrlStr); - await MyCommon.OpenInBrowserAsync(this, imageUrl); + await MyCommon.OpenInBrowserAsync(this, imageUri.GetOriginalImageUri()); } private bool isEditing = false;