From f7c10d0b33bb96f9f11cea971db392bba3f68fd1 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 3 Jun 2025 21:37:47 +0800 Subject: [PATCH] feature: supports to load avatar from local image and save it to disk Signed-off-by: leo --- src/Models/AvatarManager.cs | 43 ++++++++++-- src/Resources/Locales/de_DE.axaml | 2 +- src/Resources/Locales/en_US.axaml | 3 +- src/Resources/Locales/es_ES.axaml | 2 +- src/Resources/Locales/fr_FR.axaml | 2 +- src/Resources/Locales/it_IT.axaml | 2 +- src/Resources/Locales/ja_JP.axaml | 2 +- src/Resources/Locales/pt_BR.axaml | 2 +- src/Resources/Locales/ru_RU.axaml | 2 +- src/Resources/Locales/ta_IN.axaml | 2 +- src/Resources/Locales/uk_UA.axaml | 2 +- src/Resources/Locales/zh_CN.axaml | 3 +- src/Resources/Locales/zh_TW.axaml | 3 +- src/Views/Avatar.cs | 106 ++++++++++++++++++++++++++---- 14 files changed, 145 insertions(+), 31 deletions(-) diff --git a/src/Models/AvatarManager.cs b/src/Models/AvatarManager.cs index a9ef3eb4..2edcb619 100644 --- a/src/Models/AvatarManager.cs +++ b/src/Models/AvatarManager.cs @@ -17,7 +17,7 @@ namespace SourceGit.Models { public interface IAvatarHost { - void OnAvatarResourceChanged(string email); + void OnAvatarResourceChanged(string email, Bitmap image); } public partial class AvatarManager @@ -119,7 +119,7 @@ namespace SourceGit.Models Dispatcher.UIThread.InvokeAsync(() => { _resources[email] = img; - NotifyResourceChanged(email); + NotifyResourceChanged(email, img); }); } @@ -151,7 +151,7 @@ namespace SourceGit.Models if (File.Exists(localFile)) File.Delete(localFile); - NotifyResourceChanged(email); + NotifyResourceChanged(email, null); } else { @@ -186,6 +186,37 @@ namespace SourceGit.Models return null; } + public void SetFromLocal(string email, string file) + { + try + { + Bitmap image = null; + + using (var stream = File.OpenRead(file)) + { + image = Bitmap.DecodeToWidth(stream, 128); + } + + if (image == null) + return; + + if (_resources.ContainsKey(email)) + _resources[email] = image; + else + _resources.Add(email, image); + + _requesting.Remove(email); + + var store = Path.Combine(_storePath, GetEmailHash(email)); + File.Copy(file, store, true); + NotifyResourceChanged(email, image); + } + catch + { + // ignore + } + } + private void LoadDefaultAvatar(string key, string img) { var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/Images/{img}", UriKind.RelativeOrAbsolute)); @@ -203,12 +234,10 @@ namespace SourceGit.Models return builder.ToString(); } - private void NotifyResourceChanged(string email) + private void NotifyResourceChanged(string email, Bitmap image) { foreach (var avatar in _avatars) - { - avatar.OnAvatarResourceChanged(email); - } + avatar.OnAvatarResourceChanged(email, image); } } } diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml index 3c900d42..b7874216 100644 --- a/src/Resources/Locales/de_DE.axaml +++ b/src/Resources/Locales/de_DE.axaml @@ -39,6 +39,7 @@ ALS UNVERÄNDERT ANGENOMMENE DATEIEN KEINE ALS UNVERÄNDERT ANGENOMMENEN DATEIEN ENTFERNEN + Aktualisieren BINÄRE DATEI NICHT UNTERSTÜTZT!!! Bisect Abbrechen @@ -550,7 +551,6 @@ Lokale Änderungen stashen & wieder anwenden Auf: Rebase: - Aktualisieren Remote hinzufügen Remote bearbeiten Name: diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 4834aef9..765b6f8c 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -35,6 +35,8 @@ FILES ASSUME UNCHANGED NO FILES ASSUMED AS UNCHANGED REMOVE + Load Image... + Refresh BINARY FILE NOT SUPPORTED!!! Bisect Abort @@ -561,7 +563,6 @@ Stash & reapply local changes On: Rebase: - Refresh Add Remote Edit Remote Name: diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml index 36c60346..c1687f9c 100644 --- a/src/Resources/Locales/es_ES.axaml +++ b/src/Resources/Locales/es_ES.axaml @@ -39,6 +39,7 @@ ARCHIVOS ASUMIDOS COMO SIN CAMBIOS NO HAY ARCHIVOS ASUMIDOS COMO SIN CAMBIOS REMOVER + Refrescar ¡ARCHIVO BINARIO NO SOPORTADO! Bisect Abortar @@ -565,7 +566,6 @@ Stash & reaplicar cambios locales En: Rebase: - Refrescar Añadir Remoto Editar Remoto Nombre: diff --git a/src/Resources/Locales/fr_FR.axaml b/src/Resources/Locales/fr_FR.axaml index 25e6ca3e..24dd365e 100644 --- a/src/Resources/Locales/fr_FR.axaml +++ b/src/Resources/Locales/fr_FR.axaml @@ -39,6 +39,7 @@ FICHIERS PRÉSUMÉS INCHANGÉS PAS DE FICHIERS PRÉSUMÉS INCHANGÉS SUPPRIMER + Rafraîchir FICHIER BINAIRE NON SUPPORTÉ !!! Blâme LE BLÂME SUR CE FICHIER N'EST PAS SUPPORTÉ!!! @@ -532,7 +533,6 @@ Stash & réappliquer changements locaux Sur : Rebase : - Rafraîchir Ajouter dépôt distant Modifier dépôt distant Nom : diff --git a/src/Resources/Locales/it_IT.axaml b/src/Resources/Locales/it_IT.axaml index 22c4e8fe..1484201c 100644 --- a/src/Resources/Locales/it_IT.axaml +++ b/src/Resources/Locales/it_IT.axaml @@ -39,6 +39,7 @@ FILE ASSUNTI COME INVARIATI NESSUN FILE ASSUNTO COME INVARIATO RIMUOVI + Aggiorna FILE BINARIO NON SUPPORTATO!!! Biseca Annulla @@ -553,7 +554,6 @@ Stasha e Riapplica modifiche locali Su: Riallinea: - Aggiorna Aggiungi Remoto Modifica Remoto Nome: diff --git a/src/Resources/Locales/ja_JP.axaml b/src/Resources/Locales/ja_JP.axaml index 945cf2d9..db395d86 100644 --- a/src/Resources/Locales/ja_JP.axaml +++ b/src/Resources/Locales/ja_JP.axaml @@ -39,6 +39,7 @@ 変更されていないとみなされるファイル 変更されていないとみなされるファイルはありません 削除 + 更新 バイナリファイルはサポートされていません!!! Blame BLAMEではこのファイルはサポートされていません!!! @@ -531,7 +532,6 @@ ローカルの変更をスタッシュして再適用 On: リベース: - 更新 リモートを追加 リモートを編集 名前: diff --git a/src/Resources/Locales/pt_BR.axaml b/src/Resources/Locales/pt_BR.axaml index a75d6777..2d34f612 100644 --- a/src/Resources/Locales/pt_BR.axaml +++ b/src/Resources/Locales/pt_BR.axaml @@ -33,6 +33,7 @@ ARQUIVOS CONSIDERADOS SEM ALTERAÇÕES NENHUM ARQUIVO CONSIDERADO SEM ALTERAÇÕES REMOVER + Atualizar ARQUIVO BINÁRIO NÃO SUPORTADO!!! Blame BLAME NESTE ARQUIVO NÃO É SUPORTADO!!! @@ -488,7 +489,6 @@ Guardar & reaplicar alterações locais Em: Rebase: - Atualizar Adicionar Remoto Editar Remoto Nome: diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml index 0a6ccba4..537a3deb 100644 --- a/src/Resources/Locales/ru_RU.axaml +++ b/src/Resources/Locales/ru_RU.axaml @@ -39,6 +39,7 @@ НЕОТСЛЕЖИВАЕМЫЕ ФАЙЛЫ СПИСОК ПУСТ УДАЛИТЬ + Обновить ДВОИЧНЫЙ ФАЙЛ НЕ ПОДДЕРЖИВАЕТСЯ!!! Раздвоить О @@ -564,7 +565,6 @@ Отложить и применить повторно локальные изменения На: Переместить: - Обновить Добавить внешний репозиторий Редактировать внешний репозиторий Имя: diff --git a/src/Resources/Locales/ta_IN.axaml b/src/Resources/Locales/ta_IN.axaml index e66af3ff..7f3542c3 100644 --- a/src/Resources/Locales/ta_IN.axaml +++ b/src/Resources/Locales/ta_IN.axaml @@ -39,6 +39,7 @@ கோப்புகள் மாற்றப்படவில்லை எனக் கருதப்படுகிறது எந்த கோப்புகளும் மாற்றப்படவில்லை எனக் கருதப்படுகிறது நீக்கு + புதுப்பி இருமம் கோப்பு ஆதரிக்கப்படவில்லை!!! குற்றச்சாட்டு இந்த கோப்பில் குற்றம் சாட்ட ஆதரிக்கப்படவில்லை!!! @@ -531,7 +532,6 @@ உள்ளக மாற்றங்களை பதுக்கிவை & மீண்டும் இடு மேல்: மறுதளம்: - புதுப்பி தொலையைச் சேர் தொலையைத் திருத்து பெயர்: diff --git a/src/Resources/Locales/uk_UA.axaml b/src/Resources/Locales/uk_UA.axaml index 2e7b399b..62842054 100644 --- a/src/Resources/Locales/uk_UA.axaml +++ b/src/Resources/Locales/uk_UA.axaml @@ -39,6 +39,7 @@ ФАЙЛИ, ЩО ВВАЖАЮТЬСЯ НЕЗМІНЕНИМИ НЕМАЄ ФАЙЛІВ, ЩО ВВАЖАЮТЬСЯ НЕЗМІНЕНИМИ ВИДАЛИТИ + Оновити БІНАРНИЙ ФАЙЛ НЕ ПІДТРИМУЄТЬСЯ!!! Автор рядка ПОШУК АВТОРА РЯДКА ДЛЯ ЦЬОГО ФАЙЛУ НЕ ПІДТРИМУЄТЬСЯ!!! @@ -536,7 +537,6 @@ Сховати та застосувати локальні зміни На: Перебазувати: - Оновити Додати віддалене сховище Редагувати віддалене сховище Назва: diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 77a55f75..c2a44bbf 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -39,6 +39,8 @@ 不跟踪更改的文件 没有不跟踪更改的文件 移除 + 加载本地图片 + 重新加载 二进制文件不支持该操作!!! 二分定位(bisect) 终止 @@ -565,7 +567,6 @@ 自动贮藏并恢复本地变更 目标提交 : 分支 : - 重新加载 添加远程仓库 编辑远程仓库 远程名 : diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index aef769de..a4e6074b 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -39,6 +39,8 @@ 不追蹤變更的檔案 沒有不追蹤變更的檔案 移除 + 載入本機圖片... + 重新載入 二進位檔案不支援該操作! 二分搜尋 (bisect) 中止 @@ -565,7 +567,6 @@ 自動擱置變更並復原本機變更 目標提交: 分支: - 重新載入 新增遠端存放庫 編輯遠端存放庫 遠端名稱: diff --git a/src/Views/Avatar.cs b/src/Views/Avatar.cs index 5dacac69..d8d8c564 100644 --- a/src/Views/Avatar.cs +++ b/src/Views/Avatar.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.IO; using System.Security.Cryptography; using System.Text; @@ -8,6 +9,7 @@ using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Media; using Avalonia.Media.Imaging; +using Avalonia.Platform.Storage; namespace SourceGit.Views { @@ -24,16 +26,6 @@ namespace SourceGit.Views public Avatar() { - var refetch = new MenuItem() { Header = App.Text("RefetchAvatar") }; - refetch.Click += (_, _) => - { - if (User != null) - Models.AvatarManager.Instance.Request(User.Email, true); - }; - - ContextMenu = new ContextMenu(); - ContextMenu.Items.Add(refetch); - RenderOptions.SetBitmapInterpolationMode(this, BitmapInterpolationMode.HighQuality); } @@ -102,11 +94,11 @@ namespace SourceGit.Views clip.Dispose(); } - public void OnAvatarResourceChanged(string email) + public void OnAvatarResourceChanged(string email, Bitmap image) { if (User.Email.Equals(email, StringComparison.Ordinal)) { - _img = Models.AvatarManager.Instance.Request(User.Email, false); + _img = image; InvalidateVisual(); } } @@ -115,11 +107,13 @@ namespace SourceGit.Views { base.OnLoaded(e); Models.AvatarManager.Instance.Subscribe(this); + ContextRequested += OnContextRequested; } protected override void OnUnloaded(RoutedEventArgs e) { base.OnUnloaded(e); + ContextRequested -= OnContextRequested; Models.AvatarManager.Instance.Unsubscribe(this); } @@ -138,6 +132,94 @@ namespace SourceGit.Views } } + private void OnContextRequested(object sender, ContextRequestedEventArgs e) + { + var toplevel = TopLevel.GetTopLevel(this); + if (toplevel == null) + { + e.Handled = true; + return; + } + + var refetch = new MenuItem(); + refetch.Icon = App.CreateMenuIcon("Icons.Loading"); + refetch.Header = App.Text("Avatar.Refetch"); + refetch.Click += (_, ev) => + { + if (User != null) + Models.AvatarManager.Instance.Request(User.Email, true); + + ev.Handled = true; + }; + + var load = new MenuItem(); + load.Icon = App.CreateMenuIcon("Icons.Folder.Open"); + load.Header = App.Text("Avatar.Load"); + load.Click += async (_, ev) => + { + var options = new FilePickerOpenOptions() + { + FileTypeFilter = [new FilePickerFileType("PNG") { Patterns = ["*.png"] }], + AllowMultiple = false, + }; + + var selected = await toplevel.StorageProvider.OpenFilePickerAsync(options); + if (selected.Count == 1) + { + var localFile = selected[0].Path.LocalPath; + Models.AvatarManager.Instance.SetFromLocal(User.Email, localFile); + } + + ev.Handled = true; + }; + + var saveAs = new MenuItem(); + saveAs.Icon = App.CreateMenuIcon("Icons.Save"); + saveAs.Header = App.Text("SaveAs"); + saveAs.Click += async (_, ev) => + { + var options = new FilePickerSaveOptions(); + options.Title = App.Text("SaveAs"); + options.DefaultExtension = ".png"; + options.FileTypeChoices = [new FilePickerFileType("PNG") { Patterns = ["*.png"] }]; + + var storageFile = await toplevel.StorageProvider.SaveFilePickerAsync(options); + if (storageFile != null) + { + var saveTo = storageFile.Path.LocalPath; + using (var writer = File.OpenWrite(saveTo)) + { + if (_img != null) + { + _img.Save(writer); + } + else + { + var pixelSize = new PixelSize((int)Bounds.Width, (int)Bounds.Height); + var dpi = new Vector(96, 96); + + using (var rt = new RenderTargetBitmap(pixelSize, dpi)) + using (var ctx = rt.CreateDrawingContext()) + { + this.Render(ctx); + rt.Save(writer); + } + } + } + } + + ev.Handled = true; + }; + + var menu = new ContextMenu(); + menu.Items.Add(refetch); + menu.Items.Add(load); + menu.Items.Add(new MenuItem() { Header = "-" }); + menu.Items.Add(saveAs); + + menu.Open(this); + } + private Bitmap _img = null; } }