Skip to content

Improve portfolio layout#4

Open
1254455745 wants to merge 1 commit into
TNT-Likely:mainfrom
1254455745:split/portfolio-ui
Open

Improve portfolio layout#4
1254455745 wants to merge 1 commit into
TNT-Likely:mainfrom
1254455745:split/portfolio-ui

Conversation

@1254455745
Copy link
Copy Markdown

@1254455745 1254455745 commented May 31, 2026

Summary by CodeRabbit

更新说明

  • 新功能

    • 持仓列表现已显示"今日盈亏"数据。
  • 样式优化

    • 重构持仓行 UI 布局,优化列宽控制与对齐方式。
    • 调整汇总卡片显示顺序。
    • 改进投资组合窗格的可滚动布局与动态列宽配置。
    • 精细化成本价格、数量等数据的格式显示。

@pullfrog
Copy link
Copy Markdown

pullfrog Bot commented May 31, 2026

Review mode finished without calling create_pull_request_review after 3 retry attempts

Pullfrog  | Rerun failed job ➔View workflow run | via Pullfrog | Using DeepSeek Flash𝕏

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Review Change Stack

📝 Walkthrough

总览

本PR重构了持仓列表的布局系统,引入动态列宽计算以统一表头与行对齐;持仓行新增"今日盈亏"数据展示,原累计盈亏与本位币换算被抽取为独立列;编辑按钮交互改为始终渲染通过不透明度控制;摘要卡片顺序调整。

变更

持仓列表与行UI重构

层级 / 文件 总结
动态列宽计算系统
PanBar/Settings/Panes/PortfolioPane.swift
新增 PortfolioColumnWidths 结构体根据总宽度与持仓名称动态计算列宽;holdingsList 改用 GeometryReader + ScrollView 包装;holdingRow 接收 columns 参数,各单元格与间距依赖动态列宽;行 padding 改为使用 columns.horizontalPadding
持仓行盈亏数据与列宽
PanBar/Popover/Views/HoldingsTab.swift
新增列宽常量 priceColumnWidthallTimeColumnWidth;新增 nativeTodayPnL 计算属性;新增 labeledPnL 视图统一渲染盈亏标签与数值;成本价格格式化精度改为三位小数。
列布局与今日盈亏展示
PanBar/Popover/Views/HoldingsTab.swift
右侧价格列改为展示 nativeTodayPnL,通过 layoutPriority(2) 优先级确保布局;新增 allTimeColumn 视图集中管理累计盈亏与本位币换算,使用 layoutPriority(1) 控制右侧第二/第三行。
编辑按钮与顶部调整
PanBar/Popover/Views/HoldingsTab.swift
编辑按钮改为始终渲染,通过 opacitydisabled 控制可见与交互;调整顶部 HStack 间距与布局约束;左侧文本容器使用 frame(minWidth:0, maxWidth:.infinity, alignment:.leading) 实现伸缩。
摘要卡片顺序调整
PanBar/Popover/Views/SummaryCards.swift
summary.allTime 卡片前置于 summary.totalAssets;各卡片配置与计算保持不变。

预估审查工作量

🎯 3 (中等) | ⏱️ ~25 分钟

🐰 列表展新颜,
宽度动态算,
盈亏左右分,
按钮隐现间,
布局更通畅!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了变更的主要内容:重构投资组合视图布局,包括新增今日盈亏展示、调整列宽控制和优化组件结构。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
PanBar/Popover/Views/HoldingsTab.swift (1)

134-135: 💤 Low value

硬编码的列宽常量可能需要动态调整。

allTimeColumnWidthpriceColumnWidth 使用固定值(84 和 104),这在不同语言环境或字体大小下可能不够灵活。建议验证这些宽度在以下场景下是否足够:

  1. 中英文标签切换时的文本宽度差异
  2. 大额盈亏数字的显示(例如超过 6 位数)
  3. 不同 density 设置下的布局
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@PanBar/Popover/Views/HoldingsTab.swift` around lines 134 - 135, 当前在
HoldingsTab.swift 中用固定常量 allTimeColumnWidth 和 priceColumnWidth(84 和
104)定义列宽,可能在不同语言、动态类型或大数值下不够用;替换为基于显示内容和字体度量的动态计算:在构造列宽时(使用
allTimeColumnWidth/priceColumnWidth 的地方)用 UIFont.preferredFont(forTextStyle:) +
UIFontMetrics 或 NSString/NSAttributedString 的 size(withAttributes:)
来测量列头本地化字符串和一个代表性的大额数字(例如 "999,999" 或本地化货币示例)的宽度,取两者的最大值并加上适当的内边距;确保在
traitCollectionDidChange / Dynamic Type 变化或本地化切换时重新计算并更新布局,以替换原有硬编码常量。
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@PanBar/Popover/Views/HoldingsTab.swift`:
- Around line 168-178: The edit Button's interactive frame is too small
(currently .frame(width: 10, height: 12)); update the Button UI so the tap/click
target is at least 16x16 while keeping the visible icon size small: adjust the
.frame to a larger minimum (e.g., 16x16 or larger) and/or add transparent
padding or a contentShape around the Button that preserves the Image(systemName:
"pencil") sizing but increases the hit area; ensure the existing modifiers
(.opacity(showEditButton), .disabled(!showEditButton),
.accessibilityHidden(!showEditButton), .help(...)) remain applied to the same
Button (referencing Button(action: onEdit) and the showEditButton binding).

In `@PanBar/Settings/Panes/PortfolioPane.swift`:
- Around line 37-41: The measuredNameWidth function is using
NSFont.systemFont(ofSize: 13) which doesn't match the header rendering
(.font(.system(size: 11, weight: .semibold))); update measuredNameWidth to use
the same font metrics as the header by constructing an NSFont with size 11 and
weight .semibold (e.g., NSFont.systemFont(ofSize: 11, weight: .semibold)) and
use that in the attributes when measuring the string so measured widths align
with the actual rendered header; keep the existing ceil(width) + 8 return
behavior.
- Line 203: The code currently renders Holding.quantity using
Text(NSDecimalNumber(decimal: h.quantity).stringValue) which can yield
inconsistent decimals and scientific notation; change it to format quantity with
a NumberFormatter similar to Currency.format(_:fractionDigits:) (or add a
dedicated Quantity.format function) to enforce fixed fractionDigits and disable
scientific notation, then use that formatted string in PortfolioPane's Text;
locate references to h.quantity and replace the direct
NSDecimalNumber.stringValue usage in PortfolioPane.swift to use the new/existing
formatter.

---

Nitpick comments:
In `@PanBar/Popover/Views/HoldingsTab.swift`:
- Around line 134-135: 当前在 HoldingsTab.swift 中用固定常量 allTimeColumnWidth 和
priceColumnWidth(84 和
104)定义列宽,可能在不同语言、动态类型或大数值下不够用;替换为基于显示内容和字体度量的动态计算:在构造列宽时(使用
allTimeColumnWidth/priceColumnWidth 的地方)用 UIFont.preferredFont(forTextStyle:) +
UIFontMetrics 或 NSString/NSAttributedString 的 size(withAttributes:)
来测量列头本地化字符串和一个代表性的大额数字(例如 "999,999" 或本地化货币示例)的宽度,取两者的最大值并加上适当的内边距;确保在
traitCollectionDidChange / Dynamic Type 变化或本地化切换时重新计算并更新布局,以替换原有硬编码常量。
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 63e651ae-436f-4b01-b92f-4c2a2651c5b8

📥 Commits

Reviewing files that changed from the base of the PR and between b6c2792 and 7ca5705.

📒 Files selected for processing (3)
  • PanBar/Popover/Views/HoldingsTab.swift
  • PanBar/Popover/Views/SummaryCards.swift
  • PanBar/Settings/Panes/PortfolioPane.swift

Comment on lines +168 to +178
Button(action: onEdit) {
Image(systemName: "pencil")
.font(.system(size: 10, weight: .semibold))
.foregroundColor(.accentColor)
}
.buttonStyle(.plain)
.help(L("action.edit", comment: ""))
.opacity(showEditButton ? 1 : 0)
.disabled(!showEditButton)
.frame(width: 10, height: 12)
.accessibilityHidden(!showEditButton)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

编辑按钮点击区域过小。

编辑按钮的 frame 设置为 width: 10, height: 12,这个尺寸对于鼠标点击操作来说过小,可能导致用户难以精准点击。建议至少设置为 16x16 或更大的点击热区,即使图标本身较小也应确保足够的交互区域。

🖱️ 建议增大点击区域
     .buttonStyle(.plain)
     .help(L("action.edit", comment: ""))
     .opacity(showEditButton ? 1 : 0)
     .disabled(!showEditButton)
-    .frame(width: 10, height: 12)
+    .frame(width: 16, height: 16)
     .accessibilityHidden(!showEditButton)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Button(action: onEdit) {
Image(systemName: "pencil")
.font(.system(size: 10, weight: .semibold))
.foregroundColor(.accentColor)
}
.buttonStyle(.plain)
.help(L("action.edit", comment: ""))
.opacity(showEditButton ? 1 : 0)
.disabled(!showEditButton)
.frame(width: 10, height: 12)
.accessibilityHidden(!showEditButton)
Button(action: onEdit) {
Image(systemName: "pencil")
.font(.system(size: 10, weight: .semibold))
.foregroundColor(.accentColor)
}
.buttonStyle(.plain)
.help(L("action.edit", comment: ""))
.opacity(showEditButton ? 1 : 0)
.disabled(!showEditButton)
.frame(width: 16, height: 16)
.accessibilityHidden(!showEditButton)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@PanBar/Popover/Views/HoldingsTab.swift` around lines 168 - 178, The edit
Button's interactive frame is too small (currently .frame(width: 10, height:
12)); update the Button UI so the tap/click target is at least 16x16 while
keeping the visible icon size small: adjust the .frame to a larger minimum
(e.g., 16x16 or larger) and/or add transparent padding or a contentShape around
the Button that preserves the Image(systemName: "pencil") sizing but increases
the hit area; ensure the existing modifiers (.opacity(showEditButton),
.disabled(!showEditButton), .accessibilityHidden(!showEditButton), .help(...))
remain applied to the same Button (referencing Button(action: onEdit) and the
showEditButton binding).

Comment on lines +37 to +41
private static func measuredNameWidth(_ value: String) -> CGFloat {
let font = NSFont.systemFont(ofSize: 13)
let width = (value as NSString).size(withAttributes: [.font: font]).width
return ceil(width) + 8
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

字体大小测量与实际渲染不匹配。

文本宽度测量使用的是 NSFont.systemFont(ofSize: 13),但表头实际渲染使用的是 .font(.system(size: 11, weight: .semibold))(第 152 行)。这会导致测量结果偏大,可能造成列宽分配不准确或对齐问题。

🔧 建议修复
 private static func measuredNameWidth(_ value: String) -> CGFloat {
-    let font = NSFont.systemFont(ofSize: 13)
+    let font = NSFont.systemFont(ofSize: 11, weight: .semibold)
     let width = (value as NSString).size(withAttributes: [.font: font]).width
     return ceil(width) + 8
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static func measuredNameWidth(_ value: String) -> CGFloat {
let font = NSFont.systemFont(ofSize: 13)
let width = (value as NSString).size(withAttributes: [.font: font]).width
return ceil(width) + 8
}
private static func measuredNameWidth(_ value: String) -> CGFloat {
let font = NSFont.systemFont(ofSize: 11, weight: .semibold)
let width = (value as NSString).size(withAttributes: [.font: font]).width
return ceil(width) + 8
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@PanBar/Settings/Panes/PortfolioPane.swift` around lines 37 - 41, The
measuredNameWidth function is using NSFont.systemFont(ofSize: 13) which doesn't
match the header rendering (.font(.system(size: 11, weight: .semibold))); update
measuredNameWidth to use the same font metrics as the header by constructing an
NSFont with size 11 and weight .semibold (e.g., NSFont.systemFont(ofSize: 11,
weight: .semibold)) and use that in the attributes when measuring the string so
measured widths align with the actual rendered header; keep the existing
ceil(width) + 8 return behavior.

.frame(width: columns.market, alignment: .leading)
.foregroundColor(.secondary)
Text("\(h.quantity)")
Text(NSDecimalNumber(decimal: h.quantity).stringValue)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 查找 NSDecimalNumber 的其他使用场景,确认格式化模式

rg -nP --type=swift -C3 'NSDecimalNumber.*stringValue'

Repository: TNT-Likely/PanBar

Length of output: 698


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) 查看 PortfolioPane.swift 第203行附近的上下文(含 h.quantity 的展示逻辑)
sed -n '170,230p' PanBar/Settings/Panes/PortfolioPane.swift | nl -ba

# 2) 更全地搜索所有 stringValue 用法(不仅限 NSDecimalNumber.*stringValue)
rg -n --type=swift 'stringValue' PanBar | head -n 200

# 3) 搜索更具体的构造后直接 stringValue:NSDecimalNumber(decimal:...).stringValue
rg -n --type=swift 'NSDecimalNumber\s*\(\s*decimal\s*:\s*.*?\)\s*\.stringValue' PanBar || true

# 4) 反查 h.quantity 的声明/类型:搜索 quantity 在相关模型/结构体中的定义
rg -n --type=swift '\bquantity\b' PanBar/Settings/Panes/PortfolioPane.swift PanBar | head -n 200

Repository: TNT-Likely/PanBar

Length of output: 104


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) 查看 PortfolioPane.swift 第203行附近上下文(用 awk 代替 nl)
awk 'NR>=170 && NR<=230 {printf "%d:%s\n", NR, $0}' PanBar/Settings/Panes/PortfolioPane.swift

# 2) 更全地搜索所有 stringValue 用法(不仅限 NSDecimalNumber)
rg -n --type=swift 'stringValue' PanBar | head -n 200

# 3) 搜索更具体的构造后直接 stringValue:NSDecimalNumber(decimal:...).stringValue
rg -n --type=swift 'NSDecimalNumber\s*\(\s*decimal\s*:\s*.*?\)\s*\.stringValue' PanBar || true

# 4) 反查 h.quantity 的声明/类型:先找 quantity 在相关文件里的定义或属性
rg -n --type=swift '\bquantity\b' PanBar | head -n 200

# 5) 以 PortfolioPane 里 h 的类型为入口:查找 h 在该文件中来自哪个结构体/类
rg -n --type=swift '\bvar\s+h\b|\blet\s+h\b|for\s+h\s+in|\bstruct\b|\bclass\b|\btypealias\b' PanBar/Settings/Panes/PortfolioPane.swift | head -n 200

Repository: TNT-Likely/PanBar

Length of output: 7263


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 查看 Holding.quantity 的类型与定义
awk 'NR>=1 && NR<=120 {print FNR ":" $0}' PanBar/Domain/Models/Holding.swift

# 查看 HoldingEditorSheet 里 quantity 的输入->Decimal 的解析/赋值逻辑(重点:是否保留小数位/是否做归一化)
awk 'NR>=280 && NR<=430 {print FNR ":" $0}' PanBar/Settings/Panes/PortfolioPane.swift

Repository: TNT-Likely/PanBar

Length of output: 5797


🌐 Web query:

NSDecimalNumber decimal stringValue trailing zeros scientific notation behavior

💡 Result:

For NSDecimalNumber, the “scientific notation” vs “trailing zeros” you see is determined by which string you request and by the formatting rules of the API you call. 1) Parsing: NSDecimalNumber can represent scientific notation in its input strings When you create an NSDecimalNumber from a numeric string, that string may include “E”/“e” for a scientific-notation exponent, so values like “-2.5006e3” are valid input. [1] [2] 2) Output: stringValue can use scientific notation (e.g., "1E-4") If you convert an NSDecimalNumber to a plain string using stringValue, it may emit scientific notation for certain magnitudes. For example, one documented observation is: - let dn = NSDecimalNumber(decimalNumberWithString: "0.0001") - dn.stringValue can be "1E-4" [3] So there is no guarantee that stringValue will preserve your original decimal form (including avoiding exponent/scientific notation). 3) Trailing zeros: NSDecimalNumber’s “value” is base-10 exactness, but its string rendering is not “lossless formatting” NSDecimalNumber is an exact base-10 arithmetic wrapper (mantissa × 10^exponent). [4] However, that representation does not mean that the string returned by stringValue will preserve a particular count of fractional trailing zeros from your original input. In practice, the string representation is produced by the formatting/printing behavior of the API you call, not by the raw text you originally provided. The Stack Overflow report above is an example of “formatting choice” changing representation (scientific notation), and the same principle applies to trailing zeros: stringValue is not specified as a “verbatim” formatter. [3] 4) If you need a specific decimal layout (fixed decimals, no exponent, preserved scale), use a formatter/explicit formatting API Because stringValue may choose exponent notation, the usual approach is to format explicitly using NSNumberFormatter (or Swift Decimal formatting) with a decimal style and appropriate settings rather than relying on NSDecimalNumber.stringValue. [3] Practical guidance - Use NSDecimalNumber(decimalNumberWithString:) (or init(string:locale:)) to parse scientific-notation strings reliably. [1] - Do NOT rely on NSDecimalNumber.stringValue for a specific textual layout (scientific notation avoidance, exact trailing-zero count). [3] - If you must control trailing zeros / forbid scientific notation, format the number with NSNumberFormatter (Decimal style / precision settings) or Swift Decimal formatting rules instead of stringValue. [3]

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 查找 currency.format 的实现(以及它是否用于固定小数位/避免科学计数法)
rg -n --type=swift 'func\s+format\s*\(\s*_?\s*[:A-Za-z0-9_<>,\s]*fractionDigits\s*:\s*Int' PanBar || true
rg -n --type=swift '\.format\(' PanBar/Domain PanBar/Settings | head -n 200

# 进一步尝试定位 Currency/扩展上对 Decimal/数字格式化的实现
fd -t f 'Currency' PanBar | head -n 50 || true
fd -t f '*Currency*' PanBar | head -n 50 || true
rg -n --type=swift 'extension\s+.*Currency|enum\s+Currency|struct\s+Currency' PanBar || true

# 查找是否存在对 quantity/Decimal 的固定小数位展示方法
rg -n --type=swift '(fractionDigits|monospacedDigit|formatted|NumberFormatter|DecimalFormat|NSDecimalNumber)' PanBar | head -n 200

Repository: TNT-Likely/PanBar

Length of output: 7148


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 读取 Currency.format 实现
awk 'NR>=1 && NR<=120 {print FNR ":" $0}' PanBar/Domain/Models/Currency.swift

# 读取 HoldingsTab 里 quantity 展示相关代码(含 qtyDisplay/quantity UI)
awk 'NR>=240 && NR<=330 {print FNR ":" $0}' PanBar/Popover/Views/HoldingsTab.swift

# 再确认仓库中是否只有这一个 NSDecimalNumber(...).stringValue 用法
rg -n --type=swift 'NSDecimalNumber\s*\(\s*decimal\s*:\s*.*?\)\s*\.stringValue' PanBar

Repository: TNT-Likely/PanBar

Length of output: 3885


检查 NSDecimalNumber(decimal:).stringValue 用于展示 Holding.quantity 的格式一致性

  • PanBar/Settings/Panes/PortfolioPane.swift 第203行直接用 Text(NSDecimalNumber(decimal: h.quantity).stringValue) 渲染 quantitystringValue 的文本呈现并不保证固定小数位数,且在特定数量级下可能出现科学计数法。
  • 项目里 costPrice 已通过 Currency.format(_:fractionDigits:)NumberFormatter 固定小数位)确保展示一致,但 quantity 没有等价的显式格式化;建议为 quantity 也使用统一的 NumberFormatter/固定小数位策略以匹配 UI 预期并避免科学计数法。
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@PanBar/Settings/Panes/PortfolioPane.swift` at line 203, The code currently
renders Holding.quantity using Text(NSDecimalNumber(decimal:
h.quantity).stringValue) which can yield inconsistent decimals and scientific
notation; change it to format quantity with a NumberFormatter similar to
Currency.format(_:fractionDigits:) (or add a dedicated Quantity.format function)
to enforce fixed fractionDigits and disable scientific notation, then use that
formatted string in PortfolioPane's Text; locate references to h.quantity and
replace the direct NSDecimalNumber.stringValue usage in PortfolioPane.swift to
use the new/existing formatter.

@TNT-Likely
Copy link
Copy Markdown
Owner

ima@1254455745 e 想法挺好的 就是有点丑 @1254455745 再想想怎么优化排版吧

@1254455745
Copy link
Copy Markdown
Author

还真是,在我那儿看着还行,到你那儿又有些乱了,我再看看吧

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants