1 // OpenTween - Client of Twitter
2 // Copyright (c) 2023 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
5 // This file is part of OpenTween.
7 // This program is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General Public License as published by the Free
9 // Software Foundation; either version 3 of the License, or (at your option)
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 // You should have received a copy of the GNU General Public License along
18 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20 // Boston, MA 02110-1301, USA.
25 using System.Collections.Generic;
28 using System.Threading;
29 using System.Threading.Tasks;
30 using OpenTween.Models;
31 using OpenTween.Thumbnail;
35 public class TweetThumbnail : NotifyPropertyChangedBase
37 private ThumbnailGenerator? thumbGenerator;
38 private bool thumbnailAvailable;
39 private PostId? currentPostId;
40 private ThumbnailInfo[] thumbnails = Array.Empty<ThumbnailInfo>();
41 private Task<MemoryImage>?[] loadImageTasks = Array.Empty<Task<MemoryImage>?>();
42 private int selectedIndex = 0;
44 public bool ThumbnailAvailable
46 get => this.thumbnailAvailable;
47 set => this.SetProperty(ref this.thumbnailAvailable, value);
50 public int SelectedIndex
52 get => this.selectedIndex;
55 if (this.ThumbnailAvailable)
57 if (value < 0 || value >= this.Thumbnails.Length)
58 throw new ArgumentOutOfRangeException(nameof(value));
63 throw new ArgumentOutOfRangeException(nameof(value));
65 this.SetProperty(ref this.selectedIndex, value);
69 public ThumbnailInfo[] Thumbnails
70 => this.ThumbnailAvailable ? this.thumbnails : throw new InvalidOperationException("Thumbnail is not available.");
72 public ThumbnailInfo CurrentThumbnail
73 => this.Thumbnails[this.selectedIndex];
75 private ThumbnailGenerator ThumbGenerator
76 => this.thumbGenerator ?? throw this.NotInitializedException();
78 public void Initialize(ThumbnailGenerator thumbnailGenerator)
79 => this.thumbGenerator = thumbnailGenerator;
81 public async Task PrepareThumbnails(PostClass post, CancellationToken token)
83 if (this.currentPostId == post.StatusId)
86 this.currentPostId = post.StatusId;
87 this.ThumbnailAvailable = false;
89 var thumbnails = (await this.GetThumbailInfoAsync(post, token)).ToArray();
91 if (this.currentPostId != post.StatusId)
95 this.SelectedIndex = 0;
96 this.thumbnails = thumbnails;
97 this.loadImageTasks = new Task<MemoryImage>?[thumbnails.Length];
99 if (thumbnails.Length > 0)
100 this.ThumbnailAvailable = true;
103 public Task<MemoryImage> LoadSelectedThumbnail()
105 var runningTask = this.loadImageTasks[this.selectedIndex];
106 if (runningTask != null)
109 var newTask = Task.Run(() => this.thumbnails[this.selectedIndex].LoadThumbnailImageAsync());
110 this.loadImageTasks[this.selectedIndex] = newTask;
115 public string GetUrlForImageSearch()
116 => this.CurrentThumbnail.FullSizeImageUrl ?? this.CurrentThumbnail.ThumbnailImageUrl;
118 public Uri GetImageSearchUriGoogle()
120 var imageUrl = this.GetUrlForImageSearch();
121 return new(@"https://lens.google.com/uploadbyurl?url=" + Uri.EscapeDataString(imageUrl));
124 public Uri GetImageSearchUriSauceNao()
126 var imageUrl = this.GetUrlForImageSearch();
127 return new(@"https://saucenao.com/search.php?url=" + Uri.EscapeDataString(imageUrl));
130 public void ScrollUp()
132 if (!this.ThumbnailAvailable)
134 if (this.SelectedIndex == 0)
137 this.SelectedIndex--;
140 public void ScrollDown()
142 if (!this.ThumbnailAvailable)
144 if (this.SelectedIndex == this.Thumbnails.Length - 1)
147 this.SelectedIndex++;
150 private Exception NotInitializedException()
151 => new InvalidOperationException("Cannot call before initialization");
153 private async Task<IEnumerable<ThumbnailInfo>> GetThumbailInfoAsync(PostClass post, CancellationToken token)
154 => await Task.Run(() => this.ThumbGenerator.GetThumbnailsAsync(post, token));
156 private void DisposeImages()
158 var oldImageTasks = this.loadImageTasks.OfType<Task<MemoryImage>>().ToArray();
160 static async Task DisposeTaskResults(Task<MemoryImage>[] tasks)
164 await Task.WhenAll(tasks).ConfigureAwait(false);
170 foreach (var task in tasks)
172 if (task.IsFaulted || task.IsCanceled)
175 task.Result.Dispose();
179 _ = DisposeTaskResults(oldImageTasks);