Character
Màn Character hiện các nhân vật và đồng minh (ally) của người chơi, đưa họ vào đội hình (loadout), cho họ lên cấp bằng vật phẩm EXP, và nâng cấp cây kỹ năng cùng các kỹ năng Weapon và Character của họ. Cấp, giá, EXP và những gì mỗi lần nâng cấp mang lại đều do server quyết định.
| Bạn dùng | Nó cho bạn |
|---|---|
IProjectVCharacterScreen | Screen contract: theo dõi view, chọn, lên cấp, nâng cấp và reset |
ProjectVCharacterView | Những gì cần vẽ: trạng thái đang tải, dữ liệu, một lần lên cấp đang chờ, trạng thái nâng cấp và preview EXP |
ProjectVCharacterData | Nhân vật, đội hình, cây kỹ năng, cast skill, kho đồ và số dư |
ProjectVCharacterUpgradeState | Nhân vật có lên cấp được ngay không, và nếu không thì vì sao |
ProjectVCharacterSkillUpgradeDetails | Mọi thứ nút Upgrade của cây kỹ năng cần |
ProjectVCharacterCastSkillUpgradeDetails | Mọi thứ nút Upgrade của kỹ năng Weapon hoặc Character cần |
ProjectVCharacterLevelPolicy | Bộ chọn EXP "+1" và "Max", và preview reset cấp |
ProjectVResult | Kết quả của một command (lệnh) |
Trang bị, ba lô, skin, dùng các vật phẩm khác và hộp thoại xác nhận chưa có trên screen contract; xem Presenter.
Bắt đầu nhanh
using System;
using System.Collections.Generic;
using SilverTiger.ProjectV.Unity.Presentation.Character;
using UnityEngine;
using VContainer;
public sealed class CharacterPage : MonoBehaviour
{
private IProjectVCharacterScreen character = null!;
private IDisposable? observation;
private string selectedId = string.Empty;
[Inject]
public void Construct(IProjectVCharacterScreen screen) => character = screen;
private void OnEnable() => observation = character.Observe(Render);
private void OnDisable()
{
observation?.Dispose();
observation = null;
}
private void Render(ProjectVCharacterView view)
{
if (view.IsLoading || view.Data == null)
{
ShowLoading(view.LoadError); // LoadError is null while the first load is running
return;
}
if (view.Data.IsEmpty)
{
ShowEmpty();
return;
}
if (selectedId.Length == 0)
{
selectedId = view.Data.Characters[0].Id.Value;
}
ShowRoster(view.Data.Characters, view.Data.Loadout);
ShowUpgrade(view.UpgradeState(selectedId)!, view.PendingUpgrade != null);
}
public async void OnLevelUpClicked(IReadOnlyList<ProjectVCharacterXpItemSelection> items)
{
var result = await character.LevelUpAsync(selectedId, items);
if (this != null && !result.IsSuccess)
{
ShowMessage(result.Message);
}
}
private void ShowLoading(string? error) { }
private void ShowEmpty() { }
private void ShowRoster(IReadOnlyList<ProjectVCharacterEntry> characters, IReadOnlyList<ProjectVCharacterLoadoutSlot> loadout) { }
private void ShowUpgrade(ProjectVCharacterUpgradeState upgrade, bool retryOnly) { }
private void ShowMessage(string message) { }
}
Đó là toàn bộ khuôn mẫu: theo dõi, vẽ (render), gọi command. Sau khi thành
công bạn không cần làm gì; Render được gọi lại với cấp, rank và số dư mới, và
Header hiện ví mới.
Cần vẽ gì
Render nhận một ProjectVCharacterView:
| Member | Type | Dùng để |
|---|---|---|
IsLoading | bool | True cho đến khi dữ liệu đến lần đầu. Hiện spinner |
LoadError | string? | Có giá trị khi lần tải đầu đó thất bại. Hiện nó kèm nút thử lại (Retry) đóng rồi mở lại trang |
Data | ProjectVCharacterData? | Mọi thứ bên dưới; null khi đang tải |
PendingUpgrade | ProjectVCharacterProgressionPending? | Một lần lên cấp hoặc reset cấp bị mất câu trả lời. Khi nó có giá trị, hiện nút "Retry"; xem Câu trả lời bị mất |
UpgradeState(characterId) | ProjectVCharacterUpgradeState? | Panel lên cấp; null khi đang tải |
PreviewXp(characterId, items) | ProjectVCharacterXpUpgradePreview? | Lên cấp bằng các vật phẩm EXP này sẽ ra sao; null khi đang tải |
| Để hiện | Đọc |
|---|---|
| Danh sách nhân vật | Characters: Id, Kind (ProjectVCharacterKinds.Character hoặc .Ally), Level, Experience, Stats |
| Danh sách rỗng | IsEmpty |
| Ai đang trong đội hình | Loadout: SlotId và CharacterId cho mỗi ô |
| Cây kỹ năng của một nhân vật | GetSkills(characterId), hoặc một node bằng GetSkill(characterId, skillId) |
| Điểm kỹ năng theo cấp chưa dùng | LevelSkillPoints trong mục của nhân vật đó ở SkillTrees |
| Kỹ năng Weapon và Character | GetCastSkills(characterId) |
| Bộ chọn vật phẩm EXP | XpItems (ItemId, Experience, CharacterKind, SortOrder) với số lượng sở hữu lấy từ Inventory |
| Một số dư hoặc số lượng vật phẩm | GetResourceAmount(resourceId), hoặc Balances và Inventory |
Stats có MaxHealth, AttackDamage, Armor, Energy,
RegenHealthDelay, RegenArmorDelay, RegenArmorInterval,
RegenArmorAmount, MoveSpeed, AttackSpeed và RangeTarget.
Ghép nhân vật với art và text bằng Id.Value trong một bảng của riêng bạn
(ProjectV dùng SOCharacter.V3CharacterId), không bao giờ theo tên hiển thị.
Dùng skill ID nguyên vẹn; chúng gồm 32 ký tự hexa viết thường, ví dụ
ad4b85fb7aa64ad196e1f7e0f330b0aa.
Chọn nhân vật
Task<ProjectVResult> SelectCharacterAsync(string slotId, string characterId)
| Tham số | Type | Giá trị |
|---|---|---|
slotId | string | ProjectVLoadoutSlots.Main.Value hoặc ProjectVLoadoutSlots.ActiveAlly.Value, hoặc SlotId.Value từ Loadout |
characterId | string | Id.Value của một nhân vật đang sở hữu |
Trả về một ProjectVResult:
IsSuccess | Message | Điều gì đã xảy ra |
|---|---|---|
true | rỗng | Server đã xác nhận thay đổi. Trang tự vẽ lại |
false | lý do, sẵn sàng để hiện | Ô không đổi, hoặc không xác nhận được thay đổi |
Khi thất bại — hiện Message nguyên văn:
| Tình huống | Người chơi thấy gì |
|---|---|
| Server từ chối thay đổi | Thông báo của server |
slotId hoặc characterId rỗng | "The request was not confirmed. Please try again." |
| Mất kết nối hoặc timeout | Một thông báo lỗi mạng. Trang vẽ lại theo dữ liệu server đang có |
Lên cấp bằng vật phẩm EXP
Task<ProjectVResult> LevelUpAsync(string characterId, IReadOnlyList<ProjectVCharacterXpItemSelection> items)
| Tham số | Type | Giá trị |
|---|---|---|
characterId | string | Id.Value của một nhân vật hoặc đồng minh đang sở hữu |
items | IReadOnlyList<ProjectVCharacterXpItemSelection> | Các dòng new ProjectVCharacterXpItemSelection(itemId, quantity): tối đa 16 dòng, mỗi vật phẩm một lần, mỗi số lượng từ 1 đến 9999. Có thể rỗng để lên cấp bằng EXP nhân vật đã có |
Một lần bấm, một lần lên cấp. Server tiêu các vật phẩm, cộng EXP của chúng và lấy mọi cấp mà EXP và ví đủ chi trả. Nó dừng ở cấp tối đa, khi hết EXP, hoặc khi cấp tiếp theo đắt hơn số người chơi có; EXP còn dư được giữ lại cho sau. Tất cả xảy ra cùng lúc hoặc không có gì xảy ra. EXP chưa đủ lên cấp tiếp theo vẫn được nhận và giữ lại.
Trả về một ProjectVResult:
IsSuccess | Message | Điều gì đã xảy ra |
|---|---|---|
true | rỗng | Server đã xác nhận. Trang vẽ lại với cấp và EXP mới; so sánh Level trước và sau để hiện "+3 levels" |
false | lý do, sẵn sàng để hiện | Không có gì bị tiêu, hoặc câu trả lời bị mất (xem bên dưới) |
Throw ngay lập tức, trước khi gửi bất cứ gì, khi characterId rỗng hoặc
toàn khoảng trắng (ArgumentException), items là null (ArgumentNullException),
hoặc items có một dòng null, nhiều hơn 16 dòng hoặc cùng một vật phẩm hai lần
(ArgumentException).
Khi thất bại — hiện Message nguyên văn:
| Tình huống | Người chơi thấy gì |
|---|---|
| Không có vật phẩm và không đủ EXP | "Select enough EXP for the next level." |
| Không có vật phẩm và không đủ tiền tệ | "Not enough currency for the next level." |
| Nhân vật đã ở cấp tối đa | "Maximum level reached." |
| Một vật phẩm không sở hữu, không đủ số lượng, hoặc dành cho loại kia (nhân vật hoặc đồng minh) | "The selected EXP items are unavailable for this character kind." |
| Server từ chối | Thông báo của server |
| Mất kết nối hoặc timeout | Một thông báo lỗi mạng. PendingUpgrade có giá trị; xem Câu trả lời bị mất |
Khi các vật phẩm đã chọn cộng thêm EXP nhưng không đạt cấp tiếp theo, hoặc ví không đủ trả cho nó, lệnh gọi vẫn thành công: EXP được cộng và giữ lại.
Trước khi bấm
var preview = view.PreviewXp(characterId, items);
levelUpButton.interactable = preview is { CanSubmit: true };
previewText.text = preview?.Message ?? string.Empty;
ProjectVCharacterXpUpgradePreview
cho GrantedExperience, ProjectedExperience, CurrentLevel,
TargetLevel, AvailableLevelCount (số cấp tăng thêm), các giá trị thanh EXP
và CurrentStats / TargetStats. TargetLevel là cấp mà EXP đạt tới; ví có
thể khiến lần lên cấp dừng sớm hơn.
Với nút "+1" và "Max", để SDK chọn vật phẩm:
var nextLevel = ProjectVCharacterLevelPolicy.SelectForNextLevel(view.Data!, entry.Id);
var maximum = ProjectVCharacterLevelPolicy.SelectMaximum(view.Data!, entry.Id);
Cả hai trả về các vật phẩm đang sở hữu theo thứ tự vật phẩm của server, và một
danh sách rỗng khi không có gì để chọn. SelectForNextLevel có thể thiếu khi
người ch ơi sở hữu quá ít.
Panel nâng cấp
var upgrade = view.UpgradeState(characterId)!;
upgradeButton.interactable = upgrade.CanUpgrade;
blockReasonText.text = upgrade.CanUpgrade ? string.Empty : upgrade.Message;
if (upgrade.Requirements is { } next)
{
xpText.text = $"{upgrade.Experience}/{next.RequiredExperience}";
RenderCosts(upgrade.Balances); // each cost line next to the wallet balance
}
| Để hiện | Đọc |
|---|---|
| Có thể Level Up không | CanUpgrade, và Message khi không thể (ví dụ "Not enough Character XP: 40/100.") |
| Lý do, dưới dạng giá trị | BlockReason: None, NotOwned, Unavailable, Busy, SyncRequired, MaxLevel, InsufficientExperience, InsufficientFunds |
| Thanh EXP | Experience / Requirements.RequiredExperience (cả hai là tổng EXP tính từ cấp 1) |
| Giá | Balances, hoặc Requirements.Costs (có thể rỗng) |
| Trước và sau | Requirements.NextLevel và Requirements.NextStats |
UpgradeState mô tả việc lên cấp tiếp theo bằng EXP nhân vật đã có. Khi đã
chọn vật phẩm EXP, hãy dùng PreviewXp.
Câu trả lời bị mất
Nếu mất kết nối giữa lúc lên cấp hoặc reset cấp, PendingUpgrade giữ nhân vật
và các vật phẩm. Lần LevelUpAsync tiếp theo sẽ hoàn tất hành động đó trước và
bỏ qua characterId và items bạn truyền vào; vật phẩm không bao giờ bị
tiêu hai lần. Việc này vẫn đúng sau khi app khởi động lại.
Khi PendingUpgrade có giá trị, PreviewXp trả về CanSubmit = false với
"Resume the pending Character progression first.", và reset cấp bị từ chối với
cùng thông báo đó. Hiện một nút "Retry" duy nhất gọi LevelUpAsync với
CharacterId đang chờ và một danh sách rỗng.
Nâng cấp kỹ năng
Task<ProjectVResult> UpgradeSkillAsync(string characterId, string skillId)
| Tham số | Type | Giá trị |
|---|---|---|
characterId | string | Id.Value của nhân vật sở hữu cây kỹ năng |
skillId | string | Id.Value của một kỹ năng trong cây của nhân vật đó |
Mỗi nhân vật có cây riêng. Cùng một skill ID có thể có rank và cấp mở khoá khác nhau trong cây của các nhân vật khác nhau.
Trả về một ProjectVResult:
IsSuccess | Message | Điều gì đã xảy ra |
|---|---|---|
true | rỗng | Server đã xác nhận rank. Cây và số dư tự vẽ lại |
false | lý do, sẵn sàng để hiện | Không mua được rank nào |
Khi thất bại — hiện Message nguyên văn:
| Tình huống | Người chơi thấy gì |
|---|---|
| Chưa đạt cấp, điều kiện tiên quyết hoặc giá | Thông báo của server |
characterId hoặc skillId rỗng hoặc sai định dạng | "The request was not confirmed. Please try again." |
| Mất kết nối hoặc timeout | Một thông báo lỗi mạng. Trang tự đồng bộ lại |
Nút Upgrade kỹ năng
Dùng view.Data.GetSkillUpgradeDetails(characterId, skillId)
(ProjectVCharacterSkillUpgradeDetails):
| Để hiện | Đọc |
|---|---|
| Bật Upgrade | CanUpgrade |
| Vì sao không | BlockedMessage, mọi điều kiện còn thiếu trên một dòng, ví dụ Require Lv.20 · Core Dust 28/45, hoặc "Maximum rank reached." |
| Rank | Skill.Rank / Skill.MaxRank, IsMaxed |
| Cấp cần có | RequiredCharacterLevel so với CharacterLevel |
| Giá | Costs: mỗi tài nguyên một dòng với DisplayName, Required, Owned, Missing, IsSatisfied; danh sách rỗng với IsFree = true nghĩa là rank miễn phí |
| Kỹ năng yêu cầu | Prerequisites: SkillId, RequiredRank, CurrentRank, IsSatisfied |
| Lý do khoá | LockReasons: Prerequisite, CharacterLevel, Resources, Unknown |
Điểm kỹ năng đến từ hai nguồn: LevelSkillPoints riêng của nhân vật (mỗi cấp
trên 1 được một điểm, chỉ dùng trong cây của nhân vật đó) và skill_point trong
ví. Costs[i].Owned đã tính cả hai.
ProjectVCharacterResourceNames.GetDisplayName cho tên hiển thị với người chơi
của một tài nguyên.
Reset cây kỹ năng
Task<ProjectVResult> ResetSkillsAsync(string characterId)
| Tham số | Type | Giá trị |
|---|---|---|
characterId | string | Id.Value của nhân vật có cây cần reset |
Chỉ xoá mọi rank trong cây của nhân vật đó. Những gì được hoàn lại do server quyết định.
Trả về một ProjectVResult:
IsSuccess | Message | Điều gì đã xảy ra |
|---|---|---|
true | rỗng | Server đã xác nhận reset. Cây và số dư tự vẽ lại |
false | lý do, sẵn sàng để hiện | Không có gì được reset |
Throw ArgumentException ngay lập tức khi characterId rỗng hoặc toàn
khoảng trắng.
Khi thất bại — hiện Message nguyên văn:
| Tình huống | Người chơi thấy gì |
|---|---|
| Không kỹ năng nào trong cây có rank | "There are no skill ranks to reset." (không gửi gì) |
| Server từ chối | Thông báo của server |
| Mất kết nối hoặc timeout | Một thông báo lỗi mạng. Trang tự đồng bộ lại |
Tắt nút Reset khi không kỹ năng nào trong GetSkills(characterId) có Rank
lớn hơn 0.
Nâng cấp kỹ năng Weapon hoặc Character
Task<ProjectVResult> UpgradeCastSkillAsync(string characterId, ProjectVCharacterCastSkillSlot slot)
| Tham số | Type | Giá trị |
|---|---|---|
characterId | string | Id.Value của nhân vật |
slot | ProjectVCharacterCastSkillSlot | Weapon hoặc Character, không bao giờ Unknown |
Các kỹ năng này tách biệt với cây kỹ năng và mỗi lần gọi tăng một cấp.
Trả về một ProjectVResult:
IsSuccess | Message | Điều gì đã xảy ra |
|---|---|---|
true | rỗng | Server đã xác nhận nâng cấp. Trang tự vẽ lại |
false | lý do, sẵn sàng để hiện | Không có gì được nâng cấp |
Khi thất bại — hiện Message nguyên văn:
| Tình huống | Người chơi thấy gì |
|---|---|
| Bị chặn: thiếu tài nguyên, đã cấp tối đa, hoặc bị khoá | Cùng nội dung với BlockedMessage, ví dụ "Maximum level reached." |
| Không tìm thấy kỹ năng hoặc dữ liệu của nó không đầy đủ | "Skill is unavailable. Sync to continue." |
| Dữ liệu đã cũ và lần đồng bộ trước đó thất bại | Th ông báo của lần đồng bộ, ví dụ lỗi mạng |
characterId rỗng | "The request was not confirmed. Please try again." |
| Server từ chối | Thông báo của server |
Nút Upgrade cast skill
Dùng view.Data.GetCastSkillUpgradeDetails(characterId, slot)
(ProjectVCharacterCastSkillUpgradeDetails):
| Để hiện | Đọc |
|---|---|
| Bật Upgrade | CanUpgrade |
| Vì sao không | BlockedMessage |
| Cấp | Level / MaxLevel (cả hai bắt đầu từ 1), IsMaxed |
| Asset gameplay | CurrentSkillId hiện tại, NextSkillId sau khi nâng cấp (null ở cấp tối đa) |
| Giá | Costs: Kind (vật phẩm hoặc tiền tệ), DisplayName, Required, Owned, Missing, IsSatisfied |
Một nhánh đã max không có ID tiếp theo và không có chi phí. Khi có gì đó chưa rõ, nâng cấp vẫn bị chặn; không bao giờ coi chi phí bị thiếu là nâng cấp miễn phí.
Reset cấp
Task<ProjectVResult> ResetLevelAsync(string characterId)
| Tham số | Type | Giá trị |
|---|---|---|
characterId | string | Id.Value của một nhân vật hoặc đồng minh đang sở hữu |
Đưa một nhân vật hoặc đồng minh về cấp 1 và hoàn lại các vật phẩm EXP đã dùng, khi server cho phép. Kiểm tra trước:
var reset = ProjectVCharacterLevelPolicy.ResetPreview(view.Data!, entry.Id);
resetButton.interactable = reset.CanReset;
resetText.text = reset.Message; // "Ready to reset the level." or why not
ShowRefunds(reset.Refunds); // ItemId and Quantity per line
Trả về một ProjectVResult:
IsSuccess | Message | Điều gì đã xảy ra |
|---|---|---|
true | rỗng | Server đã xác nhận reset. Cấp và kho đồ tự vẽ lại |
false | lý do, sẵn sàng để hiện | Không có gì được reset, hoặc câu trả lời bị mất |
Khi thất bại — hiện Message nguyên văn:
| Tình huống | Người chơi thấy gì |
|---|---|
| Server không cho phép | Lý do của server, giống ResetPreview(...).Message |
| Nhân vật không được sở hữu | "This character is not owned." |
| Server không có thông tin reset | "Server reset requirements are unavailable." |
| Một câu trả lời lên cấp hoặc reset vẫn đang chờ | "Resume the pending Character progression first." |
| Dữ liệu đã cũ và lần đồng bộ trước đó thất bại | Thông báo của lần đồng bộ, ví dụ lỗi mạng |
| Mất kết nối hoặc timeout | Một thông báo lỗi mạng. PendingUpgrade có giá tr ị; xem Câu trả lời bị mất |
Lượt bấm và đồng bộ lại
- Các command chạy lần lượt từng cái một. Bấm lần thứ hai vào một command đang
chạy sẽ trả về cùng kết quả thay vì chạy hai lần. Mọi lần lên cấp dùng chung
một slot: một
LevelUpAsyncthứ hai trong lúc một lần đang chạy, kể cả cho nhân vật khác, sẽ trả về kết quả của lần đầu. - Sau một kết quả thất bại hoặc chưa được xác nhận, màn tự đồng bộ lại và vẽ lại. Bạn không cần nút Sync.
- Các command khác ngoài
LevelUpAsyncvàResetSkillsAsynckhông bao giờ throw khi đầu vào sai; chúng trả về "The request was not confirmed. Please try again."
Lỗi thường gặp
- Hiện
result.Messagesau khi lên cấp thành công. Nó rỗng khi thành công. ĐọcLevelmới từ lầnRendertiếp theo. - Tự tính EXP cần có, chi phí hay cấp tối đa từ asset cục bộ. Dùng
UpgradeState,PreviewXpvà các upgrade details; khi thiếu điều kiện, giữ nút ở trạng thái tắt. - Dùng
Data.CanAffordcho rank kỹ năng. Nó bỏ quaLevelSkillPointsriêng của nhân vật. DùngGetSkillUpgradeDetails(...).CanUpgradehoặcCostscủa nó. - Bắt đầu một lần lên cấp mới khi
PendingUpgradecó giá trị. Lệnh gọi sẽ hoàn tất lần đang chờ; lựa chọn mới của bạn không được dùng. - Mong EXP trong nhiệm vụ làm nhân vật lên cấp. Cấp và EXP kiếm được trong nhiệm vụ chỉ dành cho lượt chơi đó. EXP của nhân vật đến từ vật phẩm EXP.
- Mong thay đổi ảnh hưởng đến một nhiệm vụ đang chạy. Gameplay nhận rank
kỹ năng và kỹ năng Weapon, Character của nhân vật đã xuất phát trong
ProjectVGameplayStartingDataView; thay đổi chúng ở đây sau khi lượt chơi bắt đầu không ảnh hưởng đến nó. - Dùng kết quả sau khi trang đã đóng. Kiểm tra
this != nullsauawait. - Không dispose observation. Giữ
IDisposabletừObservevà dispose (giải phóng) nó trongOnDisablehoặcOnDestroy.
Presenter
IProjectVCharacterPresenter
là interface tầng thấp hơn nằm sau screen contract. Chỉ dùng nó cho những gì
màn không có:
| Nhu cầu | Member của presenter |
|---|---|
| Trang bị hoặc nâng cấp trang bị | EquipEquipmentAsync, UnequipPrimaryWeaponAsync, UpgradeEquipmentAsync |
| Ba lô | SetBackpackAsync(slotId, itemId, token); null làm trống ô |
| Skin | EquipSkinAsync(skinId, token) |
| Vật phẩm khác trong kho | UseInventoryItemAsync(itemId, quantity, token) (vật phẩm EXP dùng LevelUpAsync) |
| Cộng EXP mà không lên cấp | UseCharacterXpItemsAsync(characterId, items, token) |
| Hộp thoại xác nhận giá | PrepareSkillUpgrade, PrepareEquipmentUpgrade, ConfirmPreparedUpgradeAsync, Transactions |
| Mã lỗi và request ID | Current.Error: Category, Code, RequestId, SafeMessage |
Command của presenter trả về một
ProjectVCharacterCommandResult
(IsSuccess, Snapshot). Listener truyền vào SubscribeAsync có thể chạy
ngoài main thread của Unity; hãy chuyển về main thread trước khi đụng vào
object của Unity. Observe trên màn luôn vẽ trên main thread.
Hộp thoại xác nhận
Xác nhận là tuỳ chọn. Một lần nâng cấp kỹ năng đã chuẩn bị nhớ nhân vật và kỹ
năng của nó, nên việc đổi nhân vật không thể thay đổi thứ nó nâng cấp.
PrepareSkillUpgrade throw InvalidOperationException kèm BlockedMessage
khi rank không thể nâng, nên hãy kiểm tra CanUpgrade trước.
presenter.PrepareSkillUpgrade(characterId, skillId); // fills presenter.Transactions.Current
ShowConfirm(presenter.Transactions.Current!); // title, price, balance preview
// on Confirm:
if (presenter.Transactions.CanConfirm)
{
var result = await presenter.ConfirmPreparedUpgradeAsync(token);
if (this != null && !result.IsSuccess)
{
ShowMessage(result.Snapshot.Error?.SafeMessage ?? string.Empty);
}
}
// on Cancel:
if (presenter.Transactions.CanCancel)
{
presenter.Transactions.Cancel();
}
presenter là IProjectVCharacterPresenter được inject; xem
Xác nhận giao dịch để biết trạng
thái của hộp thoại.