From 1b85e9b8c8fdf1b5f8be586d92f7bcdcd00f9eca Mon Sep 17 00:00:00 2001 From: vocoder712 <100213316+vocoder712@users.noreply.github.com> Date: Wed, 2 Sep 2026 08:42:42 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feature:=20=E5=88=9D=E6=AD=A5=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=E6=AD=8C=E6=89=8B=E5=8D=B8=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agent/DECISIONS.md | 6 ++++ OpenUtau.Core/SingerManager.cs | 35 +++++++++++++++++++ OpenUtauMobile/Assets/Lang/Strings.en.resx | 9 +++++ OpenUtauMobile/Assets/Lang/Strings.ja.resx | 9 +++++ OpenUtauMobile/Assets/Lang/Strings.ru.resx | 9 +++++ OpenUtauMobile/Assets/Lang/Strings.uk.resx | 9 +++++ .../Assets/Lang/Strings.zh-Hans.resx | 9 +++++ .../ViewModels/SingerDetailViewModel.cs | 32 ++++++++++++----- OpenUtauMobile/Views/SingerDetailView.axaml | 4 +-- 9 files changed, 111 insertions(+), 11 deletions(-) diff --git a/.agent/DECISIONS.md b/.agent/DECISIONS.md index 9834fda2..90ff3151 100644 --- a/.agent/DECISIONS.md +++ b/.agent/DECISIONS.md @@ -12,6 +12,12 @@ Record meaningful technical decisions here. Use one entry per decision. ## Entries +- Date: 2026-09-02 +- Decision: Centralize installed-singer removal in `SingerManager.UninstallSinger`. Remove directory-based voicebanks recursively and single-file Vogen packages directly, release singer memory first, and always rescan installed singers in `finally`. Run the operation from the singer detail page inside the shared loading popup. +- Rationale: Core owns singer location semantics and the installed-singer index, while Mobile owns progress and error presentation. Keeping the rescan in Core guarantees that success and failure paths expose the current filesystem state. +- Alternatives considered: Delete `USinger.Location` directly in the Mobile ViewModel; refresh only after successful deletion; route removal through a platform storage service. +- Impacted areas: Core singer lifecycle, Mobile singer detail interaction, and singer-detail localization. + - Date: 2026-09-01 - Decision: Add a dedicated batch-lyric popup to the Hand, Note, and MultiSelect piano-roll context menus. Initialize it from the earliest selected note through the end of the active voice part, and apply one undoable multi-note lyric command either sequentially or to the selected-note snapshot. - Rationale: A focused text editor supports fast paste-and-replace workflows while preserving the current note selection and Core's existing undo/validation behavior. Keeping parsing and scope selection in the Mobile ViewModel avoids changes to upstream-derived Core. diff --git a/OpenUtau.Core/SingerManager.cs b/OpenUtau.Core/SingerManager.cs index 37c94fa1..9a1d7919 100644 --- a/OpenUtau.Core/SingerManager.cs +++ b/OpenUtau.Core/SingerManager.cs @@ -52,6 +52,41 @@ public USinger GetSinger(string name) { return null; } + /// + /// 卸载歌手,并在操作结束后重新扫描已安装歌手。 + /// + /// 要卸载的已安装歌手。 + public void UninstallSinger(USinger singer) + { + try + { + ArgumentNullException.ThrowIfNull(singer); + string location = singer.Location; + if (string.IsNullOrWhiteSpace(location)) + { + throw new InvalidOperationException("Singer location is empty."); + } + + singer.FreeMemory(); + if (Directory.Exists(location)) + { + Directory.Delete(location, true); + } + else if (File.Exists(location)) + { + File.Delete(location); + } + else + { + throw new DirectoryNotFoundException($"Singer location does not exist: {location}"); + } + } + finally + { + SearchAllSingers(); + } + } + public void ScheduleReload(USinger singer) { reloadQueue.Enqueue(singer); ScheduleReload(); diff --git a/OpenUtauMobile/Assets/Lang/Strings.en.resx b/OpenUtauMobile/Assets/Lang/Strings.en.resx index 913ba85e..49be238d 100644 --- a/OpenUtauMobile/Assets/Lang/Strings.en.resx +++ b/OpenUtauMobile/Assets/Lang/Strings.en.resx @@ -612,6 +612,15 @@ Delete Singer + + Uninstalling singer… + + + Uninstalled {0} + + + Failed to uninstall {0}. + Undo diff --git a/OpenUtauMobile/Assets/Lang/Strings.ja.resx b/OpenUtauMobile/Assets/Lang/Strings.ja.resx index 3aaaa136..aa08129b 100644 --- a/OpenUtauMobile/Assets/Lang/Strings.ja.resx +++ b/OpenUtauMobile/Assets/Lang/Strings.ja.resx @@ -609,6 +609,15 @@ シンガーを削除 + + シンガーをアンインストールしています… + + + {0} をアンインストールしました + + + {0} のアンインストールに失敗しました。 + 元に戻す diff --git a/OpenUtauMobile/Assets/Lang/Strings.ru.resx b/OpenUtauMobile/Assets/Lang/Strings.ru.resx index 962913d1..9fa6eff4 100644 --- a/OpenUtauMobile/Assets/Lang/Strings.ru.resx +++ b/OpenUtauMobile/Assets/Lang/Strings.ru.resx @@ -535,6 +535,15 @@ Удалить банк + + Удаление банка… + + + Банк {0} удалён + + + Не удалось удалить банк {0}. + Отменить diff --git a/OpenUtauMobile/Assets/Lang/Strings.uk.resx b/OpenUtauMobile/Assets/Lang/Strings.uk.resx index 04e488ec..27e9539d 100644 --- a/OpenUtauMobile/Assets/Lang/Strings.uk.resx +++ b/OpenUtauMobile/Assets/Lang/Strings.uk.resx @@ -535,6 +535,15 @@ Видалити банк + + Видалення банку… + + + Банк {0} видалено + + + Не вдалося видалити банк {0}. + Скасувати diff --git a/OpenUtauMobile/Assets/Lang/Strings.zh-Hans.resx b/OpenUtauMobile/Assets/Lang/Strings.zh-Hans.resx index 604b2729..19f3e363 100644 --- a/OpenUtauMobile/Assets/Lang/Strings.zh-Hans.resx +++ b/OpenUtauMobile/Assets/Lang/Strings.zh-Hans.resx @@ -612,6 +612,15 @@ 删除歌手 + + 正在卸载歌手… + + + 已卸载 {0} + + + 卸载 {0} 失败。 + 撤销 diff --git a/OpenUtauMobile/ViewModels/SingerDetailViewModel.cs b/OpenUtauMobile/ViewModels/SingerDetailViewModel.cs index 8bec275e..fadbcf1b 100644 --- a/OpenUtauMobile/ViewModels/SingerDetailViewModel.cs +++ b/OpenUtauMobile/ViewModels/SingerDetailViewModel.cs @@ -1,8 +1,11 @@ using System; using System.IO; using System.Reactive; +using System.Threading.Tasks; using Avalonia.Media.Imaging; +using OpenUtau.Core; using OpenUtau.Core.Ustx; +using OpenUtauMobile.Helpers; using OpenUtauMobile.Services; using ReactiveUI; using ReactiveUI.Fody.Helpers; @@ -51,7 +54,7 @@ public SingerDetailViewModel(MainViewModel navigator, USinger singer) : base(nav _singer = singer; BackCommand = ReactiveCommand.Create(OnBack); - DeleteCommand = ReactiveCommand.Create(OnDelete); + DeleteCommand = ReactiveCommand.CreateFromTask(OnDeleteAsync); // Create OpenWebCommand - enabled only when HasWeb is true IObservable canOpenWeb = this.WhenAnyValue(x => x.HasWeb); @@ -103,15 +106,26 @@ private void OnBack() Navigator.NavigateBack(this); } - private void OnDelete() + private async Task OnDeleteAsync() { - // TODO: Implement singer deletion - // This should: - // 1. Show a confirmation dialog - // 2. Delete the singer folder from disk - // 3. Refresh the singer list - // 4. Navigate back - ToastService.Enqueue("删除功能暂未实现,建议将歌手安装至外部存储"); + try + { + await LoadingPopupService.RunAsync( + L.S("SingerDetail.Uninstalling"), + _ => Task.Run(() => SingerManager.Inst.UninstallSinger(_singer))); + ToastService.Enqueue(string.Format( + L.S("SingerDetail.UninstallSucceeded"), + SingerName)); + Navigator.NavigateBack(this); + } + catch (Exception exception) + { + Log.Error(exception, "Failed to uninstall singer {Singer}", _singer.Name); + ErrorMessageNotification notification = new( + string.Format(L.S("SingerDetail.UninstallFailed"), SingerName), + exception); + ErrorDialogService.Show(new ErrorDialogViewModel(notification)); + } } private void OnOpenWeb() diff --git a/OpenUtauMobile/Views/SingerDetailView.axaml b/OpenUtauMobile/Views/SingerDetailView.axaml index 7599f955..ad783d16 100644 --- a/OpenUtauMobile/Views/SingerDetailView.axaml +++ b/OpenUtauMobile/Views/SingerDetailView.axaml @@ -224,7 +224,7 @@ Stretch="Uniform" Fill="{DynamicResource Sem.Color.Error}" Data="{StaticResource IconDelete}" /> - @@ -235,4 +235,4 @@ - \ No newline at end of file + From ba94954a7a776e9ddae7cc0127ee388736fc779b Mon Sep 17 00:00:00 2001 From: vocoder712 <100213316+vocoder712@users.noreply.github.com> Date: Wed, 2 Sep 2026 10:57:31 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feature:=20=E5=8A=A0=E5=85=A5=E9=80=9A?= =?UTF-8?q?=E7=94=A8=E6=8F=90=E7=A4=BA=E5=BC=B9=E7=AA=97=EF=BC=8C=E5=B9=B6?= =?UTF-8?q?=E8=A6=81=E6=B1=82=E5=88=A0=E9=99=A4=E6=AD=8C=E6=89=8B=E5=89=8D?= =?UTF-8?q?=E4=BA=8C=E6=AC=A1=E7=A1=AE=E8=AE=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .agent/DECISIONS.md | 6 ++ OpenUtauMobile/Assets/Lang/Strings.en.resx | 6 ++ OpenUtauMobile/Assets/Lang/Strings.ja.resx | 6 ++ OpenUtauMobile/Assets/Lang/Strings.ru.resx | 6 ++ OpenUtauMobile/Assets/Lang/Strings.uk.resx | 6 ++ .../Assets/Lang/Strings.zh-Hans.resx | 6 ++ .../Controls/ErrorDialogPopup.axaml | 2 - .../Controls/OptionConfirmPopup.axaml | 73 +++++++++++++++++ .../Controls/OptionConfirmPopup.axaml.cs | 11 +++ .../Services/OptionConfirmPopupService.cs | 39 +++++++++ .../OpenUtauMobile/OpenUtauMobileTheme.axaml | 1 + .../Runtime/ThemeStaticTokens.cs | 12 +++ .../Styles/Controls/OptionConfirmPopup.axaml | 22 +++++ .../ViewModels/OptionConfirmPopupViewModel.cs | 81 +++++++++++++++++++ .../ViewModels/SingerDetailViewModel.cs | 21 +++++ 15 files changed, 296 insertions(+), 2 deletions(-) create mode 100644 OpenUtauMobile/Controls/OptionConfirmPopup.axaml create mode 100644 OpenUtauMobile/Controls/OptionConfirmPopup.axaml.cs create mode 100644 OpenUtauMobile/Services/OptionConfirmPopupService.cs create mode 100644 OpenUtauMobile/Themes/OpenUtauMobile/Styles/Controls/OptionConfirmPopup.axaml create mode 100644 OpenUtauMobile/ViewModels/OptionConfirmPopupViewModel.cs diff --git a/.agent/DECISIONS.md b/.agent/DECISIONS.md index 90ff3151..737eca41 100644 --- a/.agent/DECISIONS.md +++ b/.agent/DECISIONS.md @@ -12,6 +12,12 @@ Record meaningful technical decisions here. Use one entry per decision. ## Entries +- Date: 2026-09-02 +- Decision: Add a reusable mobile-first option confirmation popup that accepts a title, content, and typed display/value options, lays actions out as full-width touch targets, and returns the selected stable string value or null when dismissed. Require this confirmation before singer uninstall. +- Rationale: The existing exit-editor confirmation is fixed to editor-specific labels and a byte register. A generic string-valued popup supports localized labels without coupling callers to displayed text and keeps destructive operations explicit on narrow screens. +- Alternatives considered: Reuse the fixed exit-editor popup; add a singer-only confirmation popup; compare localized option labels as results. +- Impacted areas: Mobile popup controls/services/theme tokens, singer uninstall interaction, and localization. + - Date: 2026-09-02 - Decision: Centralize installed-singer removal in `SingerManager.UninstallSinger`. Remove directory-based voicebanks recursively and single-file Vogen packages directly, release singer memory first, and always rescan installed singers in `finally`. Run the operation from the singer detail page inside the shared loading popup. - Rationale: Core owns singer location semantics and the installed-singer index, while Mobile owns progress and error presentation. Keeping the rescan in Core guarantees that success and failure paths expose the current filesystem state. diff --git a/OpenUtauMobile/Assets/Lang/Strings.en.resx b/OpenUtauMobile/Assets/Lang/Strings.en.resx index 49be238d..fe82f27e 100644 --- a/OpenUtauMobile/Assets/Lang/Strings.en.resx +++ b/OpenUtauMobile/Assets/Lang/Strings.en.resx @@ -621,6 +621,12 @@ Failed to uninstall {0}. + + Uninstall singer? + + + Uninstall {0} and permanently remove all of its files? This action cannot be undone. + Undo diff --git a/OpenUtauMobile/Assets/Lang/Strings.ja.resx b/OpenUtauMobile/Assets/Lang/Strings.ja.resx index aa08129b..a10bd56c 100644 --- a/OpenUtauMobile/Assets/Lang/Strings.ja.resx +++ b/OpenUtauMobile/Assets/Lang/Strings.ja.resx @@ -618,6 +618,12 @@ {0} のアンインストールに失敗しました。 + + シンガーをアンインストールしますか? + + + {0} をアンインストールし、すべてのファイルを完全に削除しますか?この操作は元に戻せません。 + 元に戻す diff --git a/OpenUtauMobile/Assets/Lang/Strings.ru.resx b/OpenUtauMobile/Assets/Lang/Strings.ru.resx index 9fa6eff4..e45fcfb5 100644 --- a/OpenUtauMobile/Assets/Lang/Strings.ru.resx +++ b/OpenUtauMobile/Assets/Lang/Strings.ru.resx @@ -544,6 +544,12 @@ Не удалось удалить банк {0}. + + Удалить банк? + + + Удалить банк {0} и навсегда стереть все его файлы? Это действие нельзя отменить. + Отменить diff --git a/OpenUtauMobile/Assets/Lang/Strings.uk.resx b/OpenUtauMobile/Assets/Lang/Strings.uk.resx index 27e9539d..97e82bfa 100644 --- a/OpenUtauMobile/Assets/Lang/Strings.uk.resx +++ b/OpenUtauMobile/Assets/Lang/Strings.uk.resx @@ -544,6 +544,12 @@ Не вдалося видалити банк {0}. + + Видалити банк? + + + Видалити банк {0} і назавжди стерти всі його файли? Цю дію неможливо скасувати. + Скасувати diff --git a/OpenUtauMobile/Assets/Lang/Strings.zh-Hans.resx b/OpenUtauMobile/Assets/Lang/Strings.zh-Hans.resx index 19f3e363..d2b48582 100644 --- a/OpenUtauMobile/Assets/Lang/Strings.zh-Hans.resx +++ b/OpenUtauMobile/Assets/Lang/Strings.zh-Hans.resx @@ -621,6 +621,12 @@ 卸载 {0} 失败。 + + 确认卸载歌手? + + + 确定卸载 {0} 并永久移除其全部文件吗?此操作不可撤销。 + 撤销 diff --git a/OpenUtauMobile/Controls/ErrorDialogPopup.axaml b/OpenUtauMobile/Controls/ErrorDialogPopup.axaml index 7da6d96c..a6a6f517 100644 --- a/OpenUtauMobile/Controls/ErrorDialogPopup.axaml +++ b/OpenUtauMobile/Controls/ErrorDialogPopup.axaml @@ -12,9 +12,7 @@ Classes="PopupDialogRoot"> - M12,2 C6.48,2 2,6.48 2,12 C2,17.52 6.48,22 12,22 C17.52,22 22,17.52 22,12 C22,6.48 17.52,2 12,2 Z M13,17 L11,17 L11,15 L13,15 L13,17 Z M13,13 L11,13 L11,7 L13,7 L13,13 Z M7.41,8.59 L12,13.17 L16.59,8.59 L18,10 L12,16 L6,10 L7.41,8.59 Z - M7.41,15.41 L12,10.83 L16.59,15.41 L18,14 L12,8 L6,14 L7.41,15.41 Z diff --git a/OpenUtauMobile/Controls/OptionConfirmPopup.axaml b/OpenUtauMobile/Controls/OptionConfirmPopup.axaml new file mode 100644 index 00000000..12da0888 --- /dev/null +++ b/OpenUtauMobile/Controls/OptionConfirmPopup.axaml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +