From cb0e612b9819e693c64d074962157a76b0a3e417 Mon Sep 17 00:00:00 2001
From: vocoder712 <100213316+vocoder712@users.noreply.github.com>
Date: Mon, 27 Jul 2026 20:18:44 +0800
Subject: [PATCH 1/2] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BE=9D=E8=B5=96?=
=?UTF-8?q?=E9=A1=B9=E7=AE=A1=E7=90=86=E7=95=8C=E9=9D=A2UI?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.agent/DECISIONS.md | 12 ++
OpenUtauMobile/App.axaml.cs | 3 +-
.../OpenUtauMobile/OpenUtauMobileTheme.axaml | 5 +-
.../Runtime/ThemeStaticTokens.cs | 38 +++-
.../OpenUtauMobile/Styles/TabItem.axaml | 76 ++++++++
.../OpenUtauMobile/Styles/TextBox.axaml | 143 ++++++++++++++
.../OpenUtauMobile/Styles/TextInput.axaml | 35 ----
.../Styles/Views/DependencyManagerView.axaml | 61 +++++-
.../Views/DependencyManagerView.axaml | 174 +++++++-----------
9 files changed, 398 insertions(+), 149 deletions(-)
create mode 100644 OpenUtauMobile/Themes/OpenUtauMobile/Styles/TabItem.axaml
create mode 100644 OpenUtauMobile/Themes/OpenUtauMobile/Styles/TextBox.axaml
delete mode 100644 OpenUtauMobile/Themes/OpenUtauMobile/Styles/TextInput.axaml
diff --git a/.agent/DECISIONS.md b/.agent/DECISIONS.md
index c7c1f51..14764ac 100644
--- a/.agent/DECISIONS.md
+++ b/.agent/DECISIONS.md
@@ -84,3 +84,15 @@ Record meaningful technical decisions here. Use one entry per decision.
- Alternatives considered: Launch the system Storage Access Framework picker; request permissions through an application context.
- Impacted areas: Android 10 and earlier storage permission checks, runtime permission requests, and raw external-storage access.
+- Date: 2026-07-25
+- Decision: Give the dependency manager a page-scoped MD3 component token set and render its tabs with a compact custom state-layer template.
+- Rationale: The default desktop tab indicator, unconstrained count badges, and filled hover treatment produced inconsistent geometry and excessive contrast. Page-scoped tokens keep responsive spacing, badge shape, state opacity, and the short pill indicator consistent across generated color themes.
+- Alternatives considered: Modify the global Avalonia TabItem theme; continue using per-control literal values.
+- Impacted areas: Dependency manager layout and styles; shared static theme tokens.
+
+- Date: 2026-07-27
+- Decision: Supersede the dependency-manager-specific tab header template with a global MD3 secondary TabItem style.
+- Rationale: Secondary tabs are a shared component pattern. A global style gives every TabItem the same 48dp container, Title Small label, state layer, semantic colors, and full-width 2dp active indicator while allowing each view to supply arbitrary header content.
+- Alternatives considered: Keep the dependency manager's page-scoped short pill indicator; duplicate the secondary-tab template in each view.
+- Impacted areas: Global OpenUtau Mobile TabItem headers; dependency manager tab header markup and tokens.
+
diff --git a/OpenUtauMobile/App.axaml.cs b/OpenUtauMobile/App.axaml.cs
index a51b27b..41fe07d 100644
--- a/OpenUtauMobile/App.axaml.cs
+++ b/OpenUtauMobile/App.axaml.cs
@@ -1,6 +1,7 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
+using Avalonia.Media;
using Avalonia.Styling;
using OpenUtau.Core.Util;
using OpenUtauMobile.Helpers;
@@ -31,7 +32,7 @@ public override void OnFrameworkInitializationCompleted()
// Initialize runtime theme resources before creating UI.
ThemeManagerV2.Initialize();
- var seed = ThemeSeedResolver.ResolveSeed(
+ Color seed = ThemeSeedResolver.ResolveSeed(
ServiceHub.SystemAccentColorProvider,
out _,
out _);
diff --git a/OpenUtauMobile/Themes/OpenUtauMobile/OpenUtauMobileTheme.axaml b/OpenUtauMobile/Themes/OpenUtauMobile/OpenUtauMobileTheme.axaml
index d4764b2..571a715 100644
--- a/OpenUtauMobile/Themes/OpenUtauMobile/OpenUtauMobileTheme.axaml
+++ b/OpenUtauMobile/Themes/OpenUtauMobile/OpenUtauMobileTheme.axaml
@@ -15,8 +15,9 @@
-
+
+
@@ -41,4 +42,4 @@
-
\ No newline at end of file
+
diff --git a/OpenUtauMobile/Themes/OpenUtauMobile/Runtime/ThemeStaticTokens.cs b/OpenUtauMobile/Themes/OpenUtauMobile/Runtime/ThemeStaticTokens.cs
index 597283d..262b1db 100644
--- a/OpenUtauMobile/Themes/OpenUtauMobile/Runtime/ThemeStaticTokens.cs
+++ b/OpenUtauMobile/Themes/OpenUtauMobile/Runtime/ThemeStaticTokens.cs
@@ -1,5 +1,6 @@
using System;
using Avalonia;
+using Avalonia.Media;
namespace OpenUtauMobile.Themes.OpenUtauMobile.Runtime;
@@ -8,6 +9,7 @@ namespace OpenUtauMobile.Themes.OpenUtauMobile.Runtime;
///
public static class ThemeBaseLayoutTokens
{
+ public static double SpaceXXS => 2d;
public static double SpaceXS => 4d;
public static double SpaceS => 8d;
public static double SpaceM => 16d;
@@ -203,6 +205,40 @@ public static class ThemeSemComponentTokens
public static double SliderMinHeight => 48d;
}
+public static class ThemeSemTabItemTokens
+{
+ public static double ContainerHeight => 48d;
+ public static double ActiveIndicatorHeight => 2d;
+ public static double LabelSize => ThemeBaseTypographyTokens.LabelLSize;
+
+ public static Thickness HeaderInset => new(24, 0);
+
+ public static CornerRadius ActiveIndicatorCorner => ThemeBaseShapeTokens.CornerNone;
+
+ public static FontWeight LabelWeight => FontWeight.Medium;
+}
+
+public static class ThemeSemDependencyManagerTokens
+{
+ public static double ContentMaxWidth => 1248d;
+ public static double BadgeMinWidth => 16d;
+ public static double BadgeHeight => 16d;
+ public static double SearchFieldHeight => 48d;
+ public static double IconButtonSize => 48d;
+
+ public static Thickness PageInset => new(24, 16, 24, 24);
+ public static Thickness BadgeInset => new(4, 0);
+ public static Thickness SearchFieldInset => new(40, 0, 16, 0);
+ public static Thickness SearchFieldEndSpacing => new(0, 0, 12, 0);
+ public static Thickness ToolbarBottomSpacing => new(0, 0, 0, 16);
+ public static Thickness CardInset => new(16);
+ public static Thickness CardBottomSpacing => new(0, 0, 0, 12);
+
+ public static CornerRadius SearchFieldCorner => ThemeBaseShapeTokens.CornerXXXXL;
+ public static CornerRadius IconButtonCorner => ThemeBaseShapeTokens.CornerXXXXL;
+ public static CornerRadius BadgeCorner => ThemeBaseShapeTokens.CornerM;
+}
+
public static class ThemeSemOpacityTokens
{
// ── Semantic Opacity Levels ──
@@ -221,4 +257,4 @@ public static class ThemeSemOpacityTokens
// ── Overlay Opacity ──
public static double Scrim => ThemeBaseOpacityTokens.OverlayScrim;
public static double LightScrim => ThemeBaseOpacityTokens.OverlayLightScrim;
-}
\ No newline at end of file
+}
diff --git a/OpenUtauMobile/Themes/OpenUtauMobile/Styles/TabItem.axaml b/OpenUtauMobile/Themes/OpenUtauMobile/Styles/TabItem.axaml
new file mode 100644
index 0000000..3e5b926
--- /dev/null
+++ b/OpenUtauMobile/Themes/OpenUtauMobile/Styles/TabItem.axaml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/OpenUtauMobile/Themes/OpenUtauMobile/Styles/TextBox.axaml b/OpenUtauMobile/Themes/OpenUtauMobile/Styles/TextBox.axaml
new file mode 100644
index 0000000..0a13c0c
--- /dev/null
+++ b/OpenUtauMobile/Themes/OpenUtauMobile/Styles/TextBox.axaml
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/OpenUtauMobile/Themes/OpenUtauMobile/Styles/TextInput.axaml b/OpenUtauMobile/Themes/OpenUtauMobile/Styles/TextInput.axaml
deleted file mode 100644
index 98b716a..0000000
--- a/OpenUtauMobile/Themes/OpenUtauMobile/Styles/TextInput.axaml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/OpenUtauMobile/Themes/OpenUtauMobile/Styles/Views/DependencyManagerView.axaml b/OpenUtauMobile/Themes/OpenUtauMobile/Styles/Views/DependencyManagerView.axaml
index ddfbeb0..1514bed 100644
--- a/OpenUtauMobile/Themes/OpenUtauMobile/Styles/Views/DependencyManagerView.axaml
+++ b/OpenUtauMobile/Themes/OpenUtauMobile/Styles/Views/DependencyManagerView.axaml
@@ -3,15 +3,64 @@
xmlns:theme="clr-namespace:OpenUtauMobile.Themes.OpenUtauMobile.Runtime">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/OpenUtauMobile/Views/DependencyManagerView.axaml b/OpenUtauMobile/Views/DependencyManagerView.axaml
index 4238e66..3053192 100644
--- a/OpenUtauMobile/Views/DependencyManagerView.axaml
+++ b/OpenUtauMobile/Views/DependencyManagerView.axaml
@@ -15,24 +15,20 @@
-
-
-
+
-
-
+
-
-
+
-
-
+
-
-
-
+ Classes="DependencyTabs">
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
-
+
-
+
-
+
-
+
@@ -142,19 +131,15 @@
-
-
+
+
-
-
-
-
+
-
-
+
-
+
@@ -189,18 +173,18 @@
FontWeight="Medium"
Foreground="{DynamicResource Sem.Color.OnSurfaceVariant}" />
-
-
+ Margin="0,0,0,10" >
+
+
+
-
+
@@ -223,11 +207,9 @@
-
-
+
-
-
+
-
-
+
-
-
+
-
-
+
@@ -310,41 +289,30 @@
CornerRadius="10"
ShowProgressText="True" />
-
-
-
-
-
+
-
-
-
-
+
+
+
+
-
-
+ HorizontalScrollBarVisibility="Disabled">
+
-
+
-
-
-
-
+
-
+
@@ -416,7 +380,7 @@
Foreground="{DynamicResource Sem.Color.Outline}" />
-
+
-
\ No newline at end of file
+
From f82c452935e14be497055fb03bc870933c955faf Mon Sep 17 00:00:00 2001
From: vocoder712 <100213316+vocoder712@users.noreply.github.com>
Date: Wed, 29 Jul 2026 11:22:08 +0800
Subject: [PATCH 2/2] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E4=BA=86=E4=BB=8E?=
=?UTF-8?q?=E6=96=87=E4=BB=B6=E5=AE=89=E8=A3=85=E4=BE=9D=E8=B5=96=E9=A1=B9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.agent/DECISIONS.md | 12 ++
OpenUtauMobile/Assets/Lang/Strings.en.resx | 28 +++-
OpenUtauMobile/Assets/Lang/Strings.ja.resx | 28 +++-
OpenUtauMobile/Assets/Lang/Strings.ru.resx | 28 +++-
OpenUtauMobile/Assets/Lang/Strings.uk.resx | 28 +++-
.../Assets/Lang/Strings.zh-Hans.resx | 28 +++-
OpenUtauMobile/Controls/LoadingPopup.axaml | 41 +++++
OpenUtauMobile/Controls/LoadingPopup.axaml.cs | 11 ++
.../Services/LoadingPopupService.cs | 81 ++++++++++
.../ViewModels/DependencyManagerViewModel.cs | 105 ++++++++++---
.../ViewModels/LoadingPopupViewModel.cs | 146 ++++++++++++++++++
.../Views/DependencyManagerView.axaml | 11 +-
12 files changed, 517 insertions(+), 30 deletions(-)
create mode 100644 OpenUtauMobile/Controls/LoadingPopup.axaml
create mode 100644 OpenUtauMobile/Controls/LoadingPopup.axaml.cs
create mode 100644 OpenUtauMobile/Services/LoadingPopupService.cs
create mode 100644 OpenUtauMobile/ViewModels/LoadingPopupViewModel.cs
diff --git a/.agent/DECISIONS.md b/.agent/DECISIONS.md
index 14764ac..a45b40b 100644
--- a/.agent/DECISIONS.md
+++ b/.agent/DECISIONS.md
@@ -96,3 +96,15 @@ Record meaningful technical decisions here. Use one entry per decision.
- Alternatives considered: Keep the dependency manager's page-scoped short pill indicator; duplicate the secondary-tab template in each view.
- Impacted areas: Global OpenUtau Mobile TabItem headers; dependency manager tab header markup and tokens.
+- Date: 2026-07-29
+- Decision: Implement dependency installation from local files entirely in the Mobile layer without modifying the upstream-derived Core package manager.
+- Rationale: The existing Core API already supports file installation, while keeping this feature's UI state and error presentation outside Core avoids increasing the cost of future upstream synchronization.
+- Alternatives considered: Add transactional extraction, package metadata results, or typed installation errors to `OpenUtau.Core`; duplicate package parsing in a Mobile adapter.
+- Impacted areas: Dependency manager ViewModel, view state, and localization resources. Core installation behavior remains unchanged.
+
+- Date: 2026-07-29
+- Decision: Run non-cancellable background UI operations through a reusable LoadingPopup service with determinate and indeterminate progress modes.
+- Rationale: Centralizing first-frame yielding, UI-thread progress updates, and guaranteed dialog closure prevents operation-specific loading overlays from racing with completion or subsequent error dialogs.
+- Alternatives considered: Keep page-local busy indicators; open and close DialogHost directly in each ViewModel; add loading notifications to the upstream-derived Core.
+- Impacted areas: Shared popup controls and services; dependency installation now uses the indeterminate loading mode.
+
diff --git a/OpenUtauMobile/Assets/Lang/Strings.en.resx b/OpenUtauMobile/Assets/Lang/Strings.en.resx
index 72e59c1..4cc4283 100644
--- a/OpenUtauMobile/Assets/Lang/Strings.en.resx
+++ b/OpenUtauMobile/Assets/Lang/Strings.en.resx
@@ -837,6 +837,12 @@
Dependency Management
+
+ Please wait
+
+
+ Working in the background…
+
Refresh List
@@ -882,8 +888,26 @@
Failed to load installed list: {0}
-
- Install from file not implemented yet
+
+ Select dependency package
+
+
+ Installing the dependency package…
+
+
+ Installed dependency package "{0}"
+
+
+ The selected file is not a valid dependency package or is damaged.
+
+
+ Unable to read the package or write to dependency storage. Check storage permissions.
+
+
+ The selected package is no longer available. Choose the file again.
+
+
+ Unable to install the package. Check available storage space and whether the file is in use.
Uninstalled {0}
diff --git a/OpenUtauMobile/Assets/Lang/Strings.ja.resx b/OpenUtauMobile/Assets/Lang/Strings.ja.resx
index 611000e..c6584d8 100644
--- a/OpenUtauMobile/Assets/Lang/Strings.ja.resx
+++ b/OpenUtauMobile/Assets/Lang/Strings.ja.resx
@@ -834,6 +834,12 @@
依存関係管理
+
+ しばらくお待ちください
+
+
+ バックグラウンド処理を実行しています…
+
リストを更新
@@ -879,8 +885,26 @@
インストール済みリストの読み込みに失敗: {0}
-
- ファイルからのインストールは未実装です
+
+ 依存関係パッケージを選択
+
+
+ 依存関係パッケージをインストールしています…
+
+
+ 依存関係パッケージ「{0}」をインストールしました
+
+
+ 選択したファイルは有効な依存関係パッケージではないか、破損しています。
+
+
+ パッケージの読み込みまたは依存関係ストレージへの書き込みができません。ストレージ権限を確認してください。
+
+
+ 選択したパッケージは利用できなくなりました。ファイルを再度選択してください。
+
+
+ パッケージをインストールできません。空き容量とファイルが使用中でないか確認してください。
{0} をアンインストールしました
diff --git a/OpenUtauMobile/Assets/Lang/Strings.ru.resx b/OpenUtauMobile/Assets/Lang/Strings.ru.resx
index 7ef7e65..d23c81a 100644
--- a/OpenUtauMobile/Assets/Lang/Strings.ru.resx
+++ b/OpenUtauMobile/Assets/Lang/Strings.ru.resx
@@ -819,6 +819,12 @@
Управление зависимостями
+
+ Пожалуйста, подождите
+
+
+ Выполняется фоновая операция…
+
Обновить список
@@ -864,8 +870,26 @@
Не удалось загрузить список установленных: {0}
-
- Установка из файла ещё не реализована
+
+ Выберите пакет зависимостей
+
+
+ Установка пакета зависимостей…
+
+
+ Пакет зависимостей «{0}» установлен
+
+
+ Выбранный файл не является допустимым пакетом зависимостей или повреждён.
+
+
+ Не удалось прочитать пакет или записать данные зависимостей. Проверьте разрешения хранилища.
+
+
+ Выбранный пакет больше недоступен. Выберите файл ещё раз.
+
+
+ Не удалось установить пакет. Проверьте свободное место и не используется ли файл.
Удалено: {0}
diff --git a/OpenUtauMobile/Assets/Lang/Strings.uk.resx b/OpenUtauMobile/Assets/Lang/Strings.uk.resx
index 61b3171..2a22562 100644
--- a/OpenUtauMobile/Assets/Lang/Strings.uk.resx
+++ b/OpenUtauMobile/Assets/Lang/Strings.uk.resx
@@ -819,6 +819,12 @@
Керування залежностями
+
+ Будь ласка, зачекайте
+
+
+ Виконується фонова операція…
+
Оновити список
@@ -864,8 +870,26 @@
Не вдалося завантажити список встановлених: {0}
-
- Встановлення з файла ще не реалізовано
+
+ Виберіть пакет залежностей
+
+
+ Встановлення пакета залежностей…
+
+
+ Пакет залежностей «{0}» встановлено
+
+
+ Вибраний файл не є припустимим пакетом залежностей або пошкоджений.
+
+
+ Не вдалося прочитати пакет або записати дані залежностей. Перевірте дозволи сховища.
+
+
+ Вибраний пакет більше недоступний. Виберіть файл ще раз.
+
+
+ Не вдалося встановити пакет. Перевірте вільне місце та чи не використовується файл.
Видалено {0}
diff --git a/OpenUtauMobile/Assets/Lang/Strings.zh-Hans.resx b/OpenUtauMobile/Assets/Lang/Strings.zh-Hans.resx
index e019fd2..5a4052f 100644
--- a/OpenUtauMobile/Assets/Lang/Strings.zh-Hans.resx
+++ b/OpenUtauMobile/Assets/Lang/Strings.zh-Hans.resx
@@ -837,6 +837,12 @@
依赖项管理
+
+ 请稍候
+
+
+ 正在进行后台操作…
+
刷新列表
@@ -882,8 +888,26 @@
加载已安装列表失败: {0}
-
- 从文件安装功能待实现
+
+ 选择依赖包
+
+
+ 正在安装依赖包…
+
+
+ 已安装依赖包“{0}”
+
+
+ 所选文件不是有效的依赖包,或文件已损坏。
+
+
+ 无法读取依赖包或写入依赖项存储,请检查存储权限。
+
+
+ 所选依赖包已不可用,请重新选择文件。
+
+
+ 无法安装依赖包,请检查可用存储空间以及文件是否被占用。
已卸载 {0}
diff --git a/OpenUtauMobile/Controls/LoadingPopup.axaml b/OpenUtauMobile/Controls/LoadingPopup.axaml
new file mode 100644
index 0000000..e5dbd11
--- /dev/null
+++ b/OpenUtauMobile/Controls/LoadingPopup.axaml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/OpenUtauMobile/Controls/LoadingPopup.axaml.cs b/OpenUtauMobile/Controls/LoadingPopup.axaml.cs
new file mode 100644
index 0000000..be4f9e4
--- /dev/null
+++ b/OpenUtauMobile/Controls/LoadingPopup.axaml.cs
@@ -0,0 +1,11 @@
+namespace OpenUtauMobile.Controls;
+
+public partial class LoadingPopup : PopupDialogControl
+{
+ protected override PopupDialogWidthPreset WidthPreset => PopupDialogWidthPreset.Compact;
+
+ public LoadingPopup()
+ {
+ InitializeComponent();
+ }
+}
diff --git a/OpenUtauMobile/Services/LoadingPopupService.cs b/OpenUtauMobile/Services/LoadingPopupService.cs
new file mode 100644
index 0000000..3047625
--- /dev/null
+++ b/OpenUtauMobile/Services/LoadingPopupService.cs
@@ -0,0 +1,81 @@
+using System;
+using System.Threading.Tasks;
+using Avalonia.Threading;
+using OpenUtauMobile.Controls;
+using OpenUtauMobile.ViewModels;
+
+namespace OpenUtauMobile.Services;
+
+///
+/// 在通用加载弹窗中运行后台操作
+///
+///
+/// 适用于安装、导入、导出等需要阻止用户继续操作,且通常无法立即完成的后台任务。
+/// 短暂操作或不阻塞交互的状态提示应优先使用 Toast 或页面内加载状态。
+///
+/// 无法获取进度时调用 ;
+/// 可获取 0–100 进度时调用带初始进度的重载,并通过
+/// 持续更新进度和说明。
+///
+/// Service 会先显示弹窗,再运行任务,并在任务成功或抛出异常时关闭弹窗。
+/// 异常会在弹窗完全关闭后继续向调用方传播,因此成功提示和错误弹窗应由调用方在
+/// await RunAsync(...) 之后处理。调用方不应自行关闭 LoadingPopup。
+///
+///
+///
+/// await LoadingPopupService.RunAsync(message, _ => InstallAsync());
+///
+/// await LoadingPopupService.RunAsync(message, 0d, async loading =>
+/// {
+/// loading.UpdateProgress(50d, nextMessage);
+/// await ExportAsync();
+/// });
+///
+///
+public static class LoadingPopupService
+{
+ /// 使用无确定进度的加载弹窗运行后台操作
+ public static async Task RunAsync(
+ string message,
+ Func operation)
+ {
+ LoadingPopupViewModel viewModel = new(message);
+ await RunAsync(viewModel, operation);
+ }
+
+ /// 使用确定进度的加载弹窗运行后台操作
+ public static async Task RunAsync(
+ string message,
+ double initialProgress,
+ Func operation)
+ {
+ LoadingPopupViewModel viewModel = new(message, initialProgress);
+ await RunAsync(viewModel, operation);
+ }
+
+ private static async Task RunAsync(
+ LoadingPopupViewModel viewModel,
+ Func operation)
+ {
+ ArgumentNullException.ThrowIfNull(operation);
+
+ Task