4using System.Collections.ObjectModel;
24 private readonly ObservableCollection<Photo> photos = Photos;
25 private readonly List<string> attachmentIds = [];
26 private DateTime loadPhotosTimestamp;
32 public PhotosLoader() : this([])
45 return this.LoadPhotos(Attachments,
SignWith, DateTime.UtcNow, WhenDoneAction);
52 public void CancelLoadPhotos()
56 this.loadPhotosTimestamp = DateTime.UtcNow;
57 this.attachmentIds.Clear();
83 return (
null,
string.Empty, 0);
88 if (Attachments is
null || Attachments.Length <= 0)
90 WhenDoneAction?.Invoke();
94 List<Attachment> AttachmentsList = Attachments.GetImageAttachments().ToList();
95 List<string> NewAttachmentIds = AttachmentsList.Select(x => x.Id).ToList();
97 if (this.attachmentIds.HasSameContentAs(NewAttachmentIds))
99 WhenDoneAction?.Invoke();
101 foreach (Photo Photo
in this.photos)
107 this.attachmentIds.Clear();
108 this.attachmentIds.AddRange(NewAttachmentIds);
117 if (this.loadPhotosTimestamp > Now)
119 WhenDoneAction?.Invoke();
121 foreach (Photo Photo
in this.photos)
139 TaskCompletionSource<bool> PhotoAddedTaskSource =
new();
141 MainThread.BeginInvokeOnMainThread(() =>
143 this.photos.Add(Photo);
144 PhotoAddedTaskSource.TrySetResult(
true);
147 await PhotoAddedTaskSource.Task;
156 WhenDoneAction?.Invoke();
164 return (
null,
string.Empty, 0);
169 return (Bin, ContentType, GetImageRotation(Bin));
172 return (
null,
string.Empty, 0);
178 if (this.loadPhotosTimestamp > Now)
179 return (
null,
string.Empty, 0);
181 if (pair.Value.Length >
int.MaxValue)
182 return (
null,
string.Empty, 0);
187 Bin =
new byte[file.Length];
189 if (file.Length != file.Read(Bin, 0, (
int)file.Length))
190 return (
null,
string.Empty, 0);
196 return (Bin, ContentType, GetImageRotation(Bin));
204 public static int GetImageRotation(
byte[] JpegImage)
207 if (DeviceInfo.Platform == DevicePlatform.iOS)
210 if (JpegImage is
null)
216 return GetImageRotation(Tags);
224 public static int GetImageRotation(
ExifTag[] Tags)
230 if (Tag.
Value is ushort Orientation)
232 return Orientation
switch
258 PhotosLoader Loader =
new();
260 (
byte[]?, string, int) Image = await Loader.LoadOnePhoto(
Attachment,
SignWith.LatestApprovedIdOrCurrentKeys);
272 public static Task<(
string?, int, int)> LoadPhotoAsTemporaryFile(
Attachment[] Attachments,
int MaxWith,
int MaxHeight)
288 return Task.FromResult<(
string?, int, int)>((
null, 0, 0));
290 return LoadPhotoAsTemporaryFile(Photo, MaxWith, MaxHeight);
300 public static async Task<(
string?, int, int)> LoadPhotoAsTemporaryFile(
Attachment Attachment,
int MaxWith,
int MaxHeight)
302 (
byte[]? Data,
string _,
int _) = await LoadPhoto(
Attachment);
304 if (Data is not
null)
306 string FileName = await GetTemporaryFile(Data);
310 using (SKBitmap Bitmap = SKBitmap.Decode(Data))
312 Width = Bitmap.Width;
313 Height = Bitmap.Height;
316 double ScaleWidth = ((double)MaxWith) / Width;
317 double ScaleHeight = ((double)MaxHeight) / Height;
318 double Scale = Math.Min(ScaleWidth, ScaleHeight);
322 Width = (int)(Width * Scale + 0.5);
323 Height = (int)(Height * Scale + 0.5);
326 return (FileName, Width, Height);
332 #region From Waher.Content.Markdown.Model.Multimedia.ImageContent, with permission
339 public static Task<string> GetTemporaryFile(
byte[] BinaryImage)
341 return GetTemporaryFile(BinaryImage,
"tmp");
350 public static async Task<string> GetTemporaryFile(
byte[] BinaryImage,
string FileExtension)
352 byte[] Digest = SHA256.HashData(BinaryImage);
353 string FileName = Path.Combine(Path.GetTempPath(),
"tmp" +
Base64Url.
Encode(Digest) +
"." + FileExtension);
355 if (!File.Exists(FileName))
361 if (temporaryFiles is
null)
364 Log.Terminating += CurrentDomain_ProcessExit;
367 temporaryFiles[FileName] =
true;
374 private static Dictionary<string, bool>? temporaryFiles =
null;
375 private static readonly
object synchObject =
new();
377 private static Task CurrentDomain_ProcessExit(
object? sender, EventArgs e)
381 if (temporaryFiles is not
null)
383 foreach (
string FileName
in temporaryFiles.Keys)
387 File.Delete(FileName);
395 temporaryFiles.Clear();
399 return Task.CompletedTask;
const string Png
The PNG MIME type.
static readonly TimeSpan DownloadFile
Download file timeout
A set of never changing property constants and helpful values.
Base class that references services in the app.
static ILogService LogService
Log service.
static INetworkService NetworkService
Network service.
static IAttachmentCacheService AttachmentCacheService
AttachmentCache service.
static IXmppService XmppService
The XMPP service for XMPP communication.
A base class for all view models, inheriting from the BindableObject. NOTE: using this class requir...
Static class that does BASE64URL encoding (using URL and filename safe alphabet), as defined in RFC46...
static string Encode(byte[] Data)
Converts a binary block of data to a Base64URL-encoded string.
Extracts EXIF meta-data from images.
static bool TryExtractFromJPeg(string FileName, out ExifTag[] Tags)
Tries to extract EXIF meta-data from a JPEG image.
Abstract base class for EXIF meta-data tags.
ExifTagName Name
EXIF Tag Name
abstract object Value
EXIF Tag Value
static readonly string[] ImageContentTypes
Image content types.
Contains a reference to an attachment assigned to a legal object.
string LegalId
Legal ID of uploader of the attachment
string ContentType
Internet Content Type of binary attachment.
string Url
URL to retrieve attachment, if provided.
static Task WriteAllBytesAsync(string FileName, byte[] Data)
Creates a binary file asynchronously.
Class managing the contents of a temporary file. When the class is disposed, the temporary file is de...
Task Add(string Url, string ParentId, bool Permanent, byte[] Data, string ContentType)
Adds or updates an attachment in the cache.
Task<(byte[]? Data, string ContentType)> TryGet(string Url)
Tries to retrieve cached attachment data for the specified URL.
class Photo(byte[] Binary, int Rotation, Attachment? Attachment)
Class containing information about a photo.
ExifTagName
Defined EXIF Tag names
SignWith
Options on what keys to use when signing data.
ContentType
DTLS Record content type.