Improve portfolio layout#4
Conversation
|
Review mode finished without calling create_pull_request_review after 3 retry attempts
|
📝 Walkthrough总览本PR重构了持仓列表的布局系统,引入动态列宽计算以统一表头与行对齐;持仓行新增"今日盈亏"数据展示,原累计盈亏与本位币换算被抽取为独立列;编辑按钮交互改为始终渲染通过不透明度控制;摘要卡片顺序调整。 变更持仓列表与行UI重构
预估审查工作量🎯 3 (中等) | ⏱️ ~25 分钟 诗
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
PanBar/Popover/Views/HoldingsTab.swift (1)
134-135: 💤 Low value硬编码的列宽常量可能需要动态调整。
allTimeColumnWidth和priceColumnWidth使用固定值(84 和 104),这在不同语言环境或字体大小下可能不够灵活。建议验证这些宽度在以下场景下是否足够:
- 中英文标签切换时的文本宽度差异
- 大额盈亏数字的显示(例如超过 6 位数)
- 不同 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
📒 Files selected for processing (3)
PanBar/Popover/Views/HoldingsTab.swiftPanBar/Popover/Views/SummaryCards.swiftPanBar/Settings/Panes/PortfolioPane.swift
| 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) |
There was a problem hiding this comment.
编辑按钮点击区域过小。
编辑按钮的 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.
| 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).
| 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 | ||
| } |
There was a problem hiding this comment.
字体大小测量与实际渲染不匹配。
文本宽度测量使用的是 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.
| 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) |
There was a problem hiding this comment.
🧩 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 200Repository: 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 200Repository: 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.swiftRepository: 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:
- 1: https://developer.apple.com/documentation/foundation/nsdecimalnumber/init(string:)
- 2: https://developer.apple.com/documentation/foundation/nsdecimalnumber/decimalnumberwithstring:locale:?language=objc
- 3: https://stackoverflow.com/questions/8858035/prevent-scientific-notation-with-nsdecimalnumber-output-as-nsstring
- 4: https://developer.apple.com/documentation/foundation/nsdecimalnumber?language=objc
🏁 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 200Repository: 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' PanBarRepository: TNT-Likely/PanBar
Length of output: 3885
检查 NSDecimalNumber(decimal:).stringValue 用于展示 Holding.quantity 的格式一致性
PanBar/Settings/Panes/PortfolioPane.swift第203行直接用Text(NSDecimalNumber(decimal: h.quantity).stringValue)渲染quantity;stringValue的文本呈现并不保证固定小数位数,且在特定数量级下可能出现科学计数法。- 项目里
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.
想法挺好的 就是有点丑 @1254455745 再想想怎么优化排版吧
|
|
还真是,在我那儿看着还行,到你那儿又有些乱了,我再看看吧 |


Summary by CodeRabbit
更新说明
新功能
样式优化