using System; using System.Collections.Frozen; using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.Specialized; using System.ComponentModel; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using ArcaneLibs; using Avalonia; using Avalonia.Media.Imaging; using Avalonia.Platform; using LibMatrix; using LibMatrix.EventTypes.Spec.State; using LibMatrix.EventTypes.Spec.State.RoomInfo; using LibMatrix.Homeservers; using ModerationClient.Models.SpaceTreeNodes; namespace ModerationClient.ViewModels; public class RoomViewModel : ViewModelBase { public RoomViewModel(AuthenticatedHomeserverGeneric homeserver, RoomNode room) { Homeserver = homeserver; Room = room; room.Timeline.CollectionChanged += (_, args) => { foreach (var obj in args.NewItems ?? ImmutableList.Empty) { if (obj is StateEventResponse evt) { if (evt.Type == "m.room.member") Users = room.State.Where(x => x.Type == "m.room.member").Select(x => { var tc = x.TypedContent as RoomMemberEventContent; return new RoomMember(homeserver) { AvatarUrl = tc.AvatarUrl, DisplayName = tc.DisplayName, UserId = x.StateKey! }; }).ToFrozenSet(); } } }; Users = room.State.Where(x => x.Type == "m.room.member" && x.ContentAs()?.Membership == "join").Select(x => { var tc = x.TypedContent as RoomMemberEventContent; return new RoomMember(homeserver) { AvatarUrl = tc.AvatarUrl, DisplayName = tc.DisplayName, UserId = x.StateKey! }; }).ToFrozenSet(); } public AuthenticatedHomeserverGeneric Homeserver { get; } public RoomNode Room { get; } public Bitmap? RoomIconBitmap { get; } public FrozenSet Users { get; private set; } = FrozenSet.Empty; } public class RoomMember(AuthenticatedHomeserverGeneric homeserver) : NotifyPropertyChanged { public string UserId { get; set; } public string DisplayName { get; set; } private static readonly string MediaDir = Path.Combine("/tmp", "ModerationClient", "media"); public string AvatarUrl { get; set { field = value; _ = UpdateAvatar(); } } private static readonly SemaphoreSlim MediaFetchLock = new(16, 16); private async Task UpdateAvatar() { if (string.IsNullOrEmpty(AvatarUrl)) { UserAvatarBitmap = new WriteableBitmap(new PixelSize(1, 1), Vector.One); return; } if (!Directory.Exists(MediaDir)) Directory.CreateDirectory(MediaDir); var fsPath = Path.Combine(MediaDir, AvatarUrl.Replace("/", "_")); Console.WriteLine($"Avatar path for {UserId}: {fsPath}"); if (File.Exists(fsPath)) { UserAvatarBitmap = new(fsPath); Console.WriteLine($"Avatar bitmap for {UserId} loaded: {UserAvatarBitmap.GetHashCode()}, Path: {fsPath}"); return; } await MediaFetchLock.WaitAsync(); try { var stream = await homeserver.GetMediaStreamAsync(AvatarUrl); Console.WriteLine($"Avatar stream for {UserId} received: {stream.GetHashCode()}"); var fs = new FileStream(fsPath, FileMode.Create); await stream.CopyToAsync(fs); fs.Close(); UserAvatarBitmap = new Bitmap(fsPath); Console.WriteLine($"Avatar bitmap for {UserId} loaded: {UserAvatarBitmap.GetHashCode()}, Path: {fsPath}"); } catch (Exception e) { Console.WriteLine($"Failed to load avatar for {UserId}: {e}"); UserAvatarBitmap = new WriteableBitmap(new PixelSize(1, 1), Vector.One); } finally { MediaFetchLock.Release(); } } public Bitmap? UserAvatarBitmap { get; private set => SetField(ref field, value); } }