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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/OpenUtauMobile/Controls/OptionConfirmPopup.axaml.cs b/OpenUtauMobile/Controls/OptionConfirmPopup.axaml.cs
new file mode 100644
index 00000000..c9f9f3a7
--- /dev/null
+++ b/OpenUtauMobile/Controls/OptionConfirmPopup.axaml.cs
@@ -0,0 +1,11 @@
+namespace OpenUtauMobile.Controls;
+
+public partial class OptionConfirmPopup : PopupDialogControl
+{
+ protected override PopupDialogWidthPreset WidthPreset => PopupDialogWidthPreset.Regular;
+
+ public OptionConfirmPopup()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/OpenUtauMobile/Services/OptionConfirmPopupService.cs b/OpenUtauMobile/Services/OptionConfirmPopupService.cs
new file mode 100644
index 00000000..d227ff74
--- /dev/null
+++ b/OpenUtauMobile/Services/OptionConfirmPopupService.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using OpenUtauMobile.Controls;
+using OpenUtauMobile.ViewModels;
+
+namespace OpenUtauMobile.Services;
+
+///
+/// 显示通用选项确认弹窗。
+///
+public static class OptionConfirmPopupService
+{
+ ///
+ /// 使用相同的显示文本和返回值展示字符串选项。
+ ///
+ public static Task ShowAsync(
+ string title,
+ string content,
+ IReadOnlyList options)
+ {
+ ArgumentNullException.ThrowIfNull(options);
+ List mappedOptions = options
+ .Select(option => new OptionConfirmOption(option, option))
+ .ToList();
+ return ShowAsync(title, content, mappedOptions);
+ }
+
+ /// 用户所选项的字符串值;关闭弹窗时返回 null。
+ public static Task ShowAsync(
+ string title,
+ string content,
+ IReadOnlyList options)
+ {
+ OptionConfirmPopupViewModel viewModel = new(title, content, options);
+ return PopupService.Show(new OptionConfirmPopup(), viewModel);
+ }
+}
diff --git a/OpenUtauMobile/Themes/OpenUtauMobile/OpenUtauMobileTheme.axaml b/OpenUtauMobile/Themes/OpenUtauMobile/OpenUtauMobileTheme.axaml
index 27c4d0b9..7bc41a7f 100644
--- a/OpenUtauMobile/Themes/OpenUtauMobile/OpenUtauMobileTheme.axaml
+++ b/OpenUtauMobile/Themes/OpenUtauMobile/OpenUtauMobileTheme.axaml
@@ -38,6 +38,7 @@
+
diff --git a/OpenUtauMobile/Themes/OpenUtauMobile/Runtime/ThemeStaticTokens.cs b/OpenUtauMobile/Themes/OpenUtauMobile/Runtime/ThemeStaticTokens.cs
index ce154f82..6a6347ad 100644
--- a/OpenUtauMobile/Themes/OpenUtauMobile/Runtime/ThemeStaticTokens.cs
+++ b/OpenUtauMobile/Themes/OpenUtauMobile/Runtime/ThemeStaticTokens.cs
@@ -296,6 +296,18 @@ public static class ThemeSemErrorDialogTokens
public static Thickness ActionSpacing => new(8, 0, 0, 0);
}
+public static class ThemeSemOptionConfirmTokens
+{
+ public static double CloseIconSize => ThemeBaseTypographyTokens.SizeL;
+ public static double MessageMaxHeight => 160d;
+ public static double OptionsHeight => ThemeBaseLayoutTokens.SpaceXXL;
+ public static double OptionMinHeight => ThemeSemLayoutTokens.SizeNavToggleMin;
+ public static double SectionSpacing => ThemeSemLayoutTokens.SpaceMD;
+ public static double OptionSpacing => ThemeSemLayoutTokens.SpaceSM;
+
+ public static Thickness ContentInset => ThemeSemLayoutTokens.InsetL;
+}
+
public static class ThemeSemOpacityTokens
{
// ── Semantic Opacity Levels ──
diff --git a/OpenUtauMobile/Themes/OpenUtauMobile/Styles/Controls/OptionConfirmPopup.axaml b/OpenUtauMobile/Themes/OpenUtauMobile/Styles/Controls/OptionConfirmPopup.axaml
new file mode 100644
index 00000000..7febcd60
--- /dev/null
+++ b/OpenUtauMobile/Themes/OpenUtauMobile/Styles/Controls/OptionConfirmPopup.axaml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
diff --git a/OpenUtauMobile/ViewModels/OptionConfirmPopupViewModel.cs b/OpenUtauMobile/ViewModels/OptionConfirmPopupViewModel.cs
new file mode 100644
index 00000000..54672e25
--- /dev/null
+++ b/OpenUtauMobile/ViewModels/OptionConfirmPopupViewModel.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reactive;
+using ReactiveUI;
+
+namespace OpenUtauMobile.ViewModels;
+
+///
+/// 通用选项确认弹窗中的单个选项。
+///
+public sealed class OptionConfirmOption
+{
+ public string Label { get; }
+ public string Value { get; }
+ public bool IsPrimary { get; }
+ public bool IsDestructive { get; }
+
+ public OptionConfirmOption(
+ string label,
+ string value,
+ bool isPrimary = false,
+ bool isDestructive = false)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(label);
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ Label = label;
+ Value = value;
+ IsPrimary = isPrimary;
+ IsDestructive = isDestructive;
+ }
+}
+
+///
+/// 展示标题、正文和若干选项,并返回所选项的字符串值。
+///
+public sealed class OptionConfirmPopupViewModel : PopupViewModelBase
+{
+ public string Title { get; }
+ public string Content { get; }
+ public IReadOnlyList Options { get; }
+
+ public ReactiveCommand SelectOptionCommand { get; }
+ public ReactiveCommand CancelCommand { get; }
+
+ public OptionConfirmPopupViewModel(
+ string title,
+ string content,
+ IEnumerable options)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(title);
+ ArgumentNullException.ThrowIfNull(options);
+
+ List optionList = options.ToList();
+ if (optionList.Count == 0)
+ {
+ throw new ArgumentException("At least one confirmation option is required.", nameof(options));
+ }
+
+ Title = title;
+ Content = content ?? string.Empty;
+ Options = optionList;
+ SelectOptionCommand = ReactiveCommand.Create(SelectOption);
+ CancelCommand = ReactiveCommand.Create(Cancel);
+ }
+
+ private void SelectOption(OptionConfirmOption option)
+ {
+ RaiseClose(option.Value);
+ }
+
+ private void Cancel()
+ {
+ RaiseClose(null);
+ }
+
+ public override void RequestBack()
+ {
+ Cancel();
+ }
+}
diff --git a/OpenUtauMobile/ViewModels/SingerDetailViewModel.cs b/OpenUtauMobile/ViewModels/SingerDetailViewModel.cs
index fadbcf1b..fded4f9e 100644
--- a/OpenUtauMobile/ViewModels/SingerDetailViewModel.cs
+++ b/OpenUtauMobile/ViewModels/SingerDetailViewModel.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.IO;
using System.Reactive;
using System.Threading.Tasks;
@@ -15,6 +16,9 @@ namespace OpenUtauMobile.ViewModels;
public class SingerDetailViewModel : NavigateViewModelBase
{
+ private const string CancelUninstallOption = "cancel";
+ private const string ConfirmUninstallOption = "uninstall";
+
public ReactiveCommand BackCommand { get; }
public ReactiveCommand DeleteCommand { get; }
public ReactiveCommand OpenWebCommand { get; }
@@ -108,6 +112,23 @@ private void OnBack()
private async Task OnDeleteAsync()
{
+ List options =
+ [
+ new(L.S("Common.Cancel"), CancelUninstallOption),
+ new(
+ L.S("Common.Uninstall"),
+ ConfirmUninstallOption,
+ isDestructive: true),
+ ];
+ string? selectedOption = await OptionConfirmPopupService.ShowAsync(
+ L.S("SingerDetail.UninstallConfirmTitle"),
+ string.Format(L.S("SingerDetail.UninstallConfirmMessage"), SingerName),
+ options);
+ if (selectedOption != ConfirmUninstallOption)
+ {
+ return;
+ }
+
try
{
await LoadingPopupService.RunAsync(
From 767a77f3e6ee4120b2007f66ead6f31aff22b395 Mon Sep 17 00:00:00 2001
From: vocoder712 <100213316+vocoder712@users.noreply.github.com>
Date: Thu, 3 Sep 2026 13:00:50 +0800
Subject: [PATCH 3/3] =?UTF-8?q?ui:=20=E5=A2=9E=E5=BC=BAUI?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
OpenUtauMobile/Controls/NotesCanvas.cs | 2 +-
OpenUtauMobile/Controls/OptionConfirmPopup.axaml | 3 ++-
.../Themes/OpenUtauMobile/Runtime/ThemeStaticTokens.cs | 2 +-
.../OpenUtauMobile/Styles/Controls/OptionConfirmPopup.axaml | 2 ++
4 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/OpenUtauMobile/Controls/NotesCanvas.cs b/OpenUtauMobile/Controls/NotesCanvas.cs
index a5cf7b7f..5d9a8de5 100644
--- a/OpenUtauMobile/Controls/NotesCanvas.cs
+++ b/OpenUtauMobile/Controls/NotesCanvas.cs
@@ -395,7 +395,7 @@ private static void DrawNoteResizeHandle(DrawingContext context, Rect noteRect)
private void RenderFinalPitch(int leftTick, int rightTick, DrawingContext context)
{
if (ViewModel == null || Part == null) return;
- IPen pen = ThemeResources.GetPen("Sem.Color.Outline", 2);
+ IPen pen = ViewModel.EditMode == PianoRollEditMode.PitchPen ? ThemeResources.GetPen("Sem.Color.Primary", 2) : ThemeResources.GetPen("Sem.Color.Outline", 2);
StreamGeometry geometry = new();
bool hasVisibleSegment = false;
lock (Part)
diff --git a/OpenUtauMobile/Controls/OptionConfirmPopup.axaml b/OpenUtauMobile/Controls/OptionConfirmPopup.axaml
index 12da0888..adbaec02 100644
--- a/OpenUtauMobile/Controls/OptionConfirmPopup.axaml
+++ b/OpenUtauMobile/Controls/OptionConfirmPopup.axaml
@@ -50,6 +50,7 @@
@@ -59,7 +60,7 @@
diff --git a/OpenUtauMobile/Themes/OpenUtauMobile/Runtime/ThemeStaticTokens.cs b/OpenUtauMobile/Themes/OpenUtauMobile/Runtime/ThemeStaticTokens.cs
index 6a6347ad..f52adee3 100644
--- a/OpenUtauMobile/Themes/OpenUtauMobile/Runtime/ThemeStaticTokens.cs
+++ b/OpenUtauMobile/Themes/OpenUtauMobile/Runtime/ThemeStaticTokens.cs
@@ -301,7 +301,7 @@ public static class ThemeSemOptionConfirmTokens
public static double CloseIconSize => ThemeBaseTypographyTokens.SizeL;
public static double MessageMaxHeight => 160d;
public static double OptionsHeight => ThemeBaseLayoutTokens.SpaceXXL;
- public static double OptionMinHeight => ThemeSemLayoutTokens.SizeNavToggleMin;
+ public static double OptionHeight => ThemeBaseLayoutTokens.SpaceXL;
public static double SectionSpacing => ThemeSemLayoutTokens.SpaceMD;
public static double OptionSpacing => ThemeSemLayoutTokens.SpaceSM;
diff --git a/OpenUtauMobile/Themes/OpenUtauMobile/Styles/Controls/OptionConfirmPopup.axaml b/OpenUtauMobile/Themes/OpenUtauMobile/Styles/Controls/OptionConfirmPopup.axaml
index 7febcd60..0bd6ca26 100644
--- a/OpenUtauMobile/Themes/OpenUtauMobile/Styles/Controls/OptionConfirmPopup.axaml
+++ b/OpenUtauMobile/Themes/OpenUtauMobile/Styles/Controls/OptionConfirmPopup.axaml
@@ -4,6 +4,8 @@