Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ struct AccountDeletionView: View {
var body: some View {
Form {
Section {
Text("删除账号会永久移除你的登录身份,并清除这台设备上的厨房数据,与「退出登录」不同——退出登录不会删除任何数据,仍可随时重新登录。")
Text("删除账号会永久移除你的登录身份,并清除这台设备上的厨房数据。退出登录则不会删除任何数据,仍可随时重新登录。")
} header: {
Text("这与退出登录不同")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ struct TodayRhythmSheet: View {
.frame(minHeight: ChromeMetrics.minimumRowHeight)
.accessibilityIdentifier("today.rhythm.reset.button")
} footer: {
Text("恢复后,今天回到本周默认节奏,两餐回到照常,今晚的份量安排也一并清除。昨晚留给今天的份数不受影响。")
Text("今天的节奏和两餐会回到默认,今晚的份量安排会清除;昨晚留给今天的份数不受影响。")
}
}
}
Expand Down Expand Up @@ -246,7 +246,7 @@ struct WeeklyRhythmSettingsView: View {
.accessibilityIdentifier("settings.weeklyRhythm.\(weekday.rawValue).picker")
}
} footer: {
Text("这是每周的默认安排。今天临时改动只影响当天,不会修改这里。")
Text("今天的临时改动只影响当天,不会修改这里。")
}
}
.navigationTitle("每周用餐节奏")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ struct GuestMergePromptView: View {
.foregroundStyle(AppTheme.onManagementAction)
.accessibilityIdentifier("guestMergePromptButton")
}
} footer: {
Text("购物清单、计划和菜谱不受影响,本次只涉及库存。")
}
.sheet(isPresented: $isShowingSheet) {
NavigationStack {
Expand Down Expand Up @@ -478,14 +476,11 @@ struct InventoryMergePreviewView: View {
LabeledContent("查看处理结果", value: summary.resolvedSummaryText)
}
.accessibilityIdentifier("guestMergeResolvedReviewLink")
} footer: {
Text("仅供查看,不在此修改。")
}
}
Section {
Text("只合并库存,不会上传购物清单、计划或菜谱。")
Text("本步骤不会写入云端;确认后如有冲突不会自动覆盖,需要逐条选择。")
Text("可以随时取消,也可以稍后处理。")
Text("完成后的新增记录可在限定时间内回滚。")
}
.font(.footnote)
Expand Down Expand Up @@ -675,8 +670,6 @@ struct InventoryMergeConflictView: View {
Text("待处理冲突已全部选择")
.font(.body.weight(.semibold))
.accessibilityIdentifier("guestMergePreConfirmAllResolved")
} footer: {
Text("返回上一页即可查看处理结果,或继续确认合并。")
}
}
Section {
Expand Down
35 changes: 15 additions & 20 deletions ios-native/Kitchen Manager/KitchenManager/MainFeatureViews.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1750,7 +1750,7 @@ private struct AddShoppingItemView: View {
Text("买什么")
} footer: {
if let lastAddedName {
Text("已添加「\(lastAddedName)」,可以继续添加下一项。")
Text("已添加「\(lastAddedName)」。")
.accessibilityIdentifier("shopping.add.continue.confirmation")
}
}
Expand Down Expand Up @@ -1873,13 +1873,12 @@ struct SettingsView: View {
/// Kept as a named constant so the guest-mode UI test asserts the same string
/// the screen renders, instead of a hand-copied duplicate.
///
/// Wording is deliberately byte-identical to the pre-UI-5A copy. UI-5A's
/// improvement is *hierarchy* — the mode, local-usability status, and action
/// moved into the row, and this qualification moved into the section footer —
/// not rewording. Two static contracts pin this exact string
/// (`test/ios-native-auth-phase1.test.mjs` and `GuestMergeUIPhase2B3UITests`),
/// and there is no user-facing gain in churning them for one word.
static let guestAccountFooter = "无需登录即可继续使用全部本机功能。登录后可为未来跨设备同步做准备,并可选择将本机库存合并到家庭云端;购物清单、计划和菜谱仍只保存在本机。"
/// UI-5A moved the mode, local-usability status, and action into the row and
/// this qualification into the section footer. The footer's old leading
/// "无需登录即可继续使用全部本机功能。" repeated the row's 本机功能已全部可用,
/// so it was dropped; the sync and inventory-merge wording is unchanged.
/// `GuestMergeUIPhase2B3UITests` pins this exact string.
static let guestAccountFooter = "登录后可为未来跨设备同步做准备,并可选择将本机库存合并到家庭云端;购物清单、计划和菜谱仍只保存在本机。"

@AppStorage("appearance") private var appearanceRawValue = AppAppearance.system.rawValue
@AppStorage("expiryNotificationsEnabled") private var notificationsEnabled = false
Expand Down Expand Up @@ -2049,7 +2048,7 @@ struct SettingsView: View {
Text("系统通知已关闭,提醒不会送达。在 iOS“设置”中允许 Kitchen Manager 发送通知后,这里会恢复你之前的选择。")
.accessibilityIdentifier("settings.notifications.denied")
} else {
Text("首次开启提醒时,会请求系统通知权限。常备食材补货提醒会在早上 9 点送达。")
Text("常备食材补货提醒会在早上 9 点送达。")
}
}
.task { await refreshNotificationAuthorization() }
Expand Down Expand Up @@ -2107,8 +2106,6 @@ struct SettingsView: View {
.accessibilityIdentifier("settings.aiDiagnostics.link")
} header: {
Text("故障排查")
} footer: {
Text("AI 无法使用时,可检查网络、登录状态和模型服务。")
}
}
.navigationTitle("AI 设置")
Expand All @@ -2124,7 +2121,7 @@ struct SettingsView: View {
Section("关于") {
LabeledContent("版本", value: appVersion)
.accessibilityIdentifier("settings.about.version")
Text("联网功能会发送完成操作所需的数据。登录不会自动上传本机厨房数据。")
Text("联网功能会发送完成操作所需的数据。")
.font(.footnote)
.foregroundStyle(.secondary)
}
Expand All @@ -2139,14 +2136,12 @@ struct SettingsView: View {
.frame(minHeight: ChromeMetrics.minimumRowHeight)
.accessibilityIdentifier("settings.cleardata.button")
} footer: {
VStack(alignment: .leading, spacing: 4) {
// Backups leave user recipes out, so this is the one place
// they can be lost for good. Said here, before the tap.
Label("厨房备份不包含用户菜谱、收藏和常做记录,清除后无法从备份恢复。", systemImage: "exclamationmark.triangle.fill")
.foregroundStyle(AppTheme.warningInk)
.accessibilityIdentifier("settings.cleardata.recipeWarning")
Text("此操作无法撤销,且不会影响远端菜谱库。")
}
// Backups leave user recipes out, so this is the one place
// they can be lost for good. Said here, before the tap; the
// confirmation alert carries the rest of the consequences.
Label("厨房备份不包含用户菜谱、收藏和常做记录,清除后无法从备份恢复。", systemImage: "exclamationmark.triangle.fill")
.foregroundStyle(AppTheme.warningInk)
.accessibilityIdentifier("settings.cleardata.recipeWarning")
}

#if DEBUG
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ private struct PreparedComponentEditor: View {
.accessibilityIdentifier("prepared.editor.preparedAt")
DatePicker("建议吃完", selection: $draft.expiryDate, displayedComponents: .date)
.accessibilityIdentifier("prepared.editor.expiry")
} footer: { Text("默认日期只是根据状态和存放方式给的起点,可以按自己的判断修改。") }
}
#if DEBUG
if PreparedPortionGroupingUIFixture.simulateStaleEdit, let expected = draft.expected {
Button("模拟其他入口使用一份") { _ = store.consumePreparedPortion(id: expected.id) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ struct RecordFoodSheet: View {
ContentUnavailableView(
"拍照识别小票",
systemImage: "camera.viewfinder",
description: Text("保持小票平整、文字清晰,避免反光和阴影,并拍下完整内容。过长的小票可分段拍摄后分别导入。")
description: Text("拍下完整、清晰的小票,避免反光;过长的小票可分段拍摄后分别导入。")
)
HStack {
Button("拍照", systemImage: "camera") { requestCamera() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -498,7 +498,7 @@ struct RecipeImageImportView: View {
ContentUnavailableView(
"从图片导入菜谱",
systemImage: "text.viewfinder",
description: Text("保证文字清晰,避免反光和阴影,尽量包含完整菜名、食材和步骤。长菜谱可以分段拍摄,截图不要裁掉关键内容。")
description: Text("拍清楚完整的菜名、食材和步骤,避免反光;长菜谱可分段拍摄。")
)
HStack {
Button("拍照", systemImage: "camera") { requestCamera() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ struct SpecialPlanDetailView: View {
planSummary(plan)
.plannerRow()
} footer: {
Text("时间、人数和要求由 AI 从你的描述里读出,没写到的会按常见情况补上;想改就重新描述一次。")
Text("由 AI 从你的描述里读出,没写到的按常见情况补上;想改就重新描述。")
.plannerFootnote()
}

Expand Down Expand Up @@ -287,9 +287,6 @@ struct SpecialPlanDetailView: View {
.plannerRow()
} header: {
KitchenSectionLabel(title: "菜单", count: plan.dishes.count, tint: KitchenTheme.textSecondary).plannerSectionHeader()
} footer: {
Text("菜品引用菜谱库,删除计划不会删除菜谱。")
.plannerFootnote()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,14 @@ final class GuestMergeSummaryUI5B2BB2AUITests: XCTestCase {
return target.label
}

/// The supporting line sits under the confirm button, and a lazy Form only
/// builds it once it scrolls on screen, so reaching the button is not enough.
private func supportingCopy(_ app: XCUIApplication) -> String? {
let copy = element(app, "guestMergeConfirmSupportingCopy")
_ = scrollTo(app, copy, maxSwipes: 6)
return copy.exists ? copy.label : nil
}

private func group(_ app: XCUIApplication, _ name: String) -> XCUIElement {
element(app, "guestMergeReviewGroup-\(name)")
}
Expand Down Expand Up @@ -287,7 +295,7 @@ final class GuestMergeSummaryUI5B2BB2AUITests: XCTestCase {
let app = launchSummary("UITEST_MERGE_SUMMARY_ALL_SKIP")
XCTAssertEqual(confirmButton(app).label, "完成,不上传任何条目")
XCTAssertEqual(
value(app, "guestMergeConfirmSupportingCopy"),
supportingCopy(app),
"本次不会上传任何库存;家庭和本机数据保持现在的样子。"
)
XCTAssertFalse(confirmButton(app).label.contains("确认合并库存"))
Expand All @@ -311,7 +319,7 @@ final class GuestMergeSummaryUI5B2BB2AUITests: XCTestCase {
XCTAssertEqual(value(app, "guestMergeSummaryStillNeedsDecision"), "仍待处理, 2 条")
XCTAssertEqual(confirmButton(app).label, "先合并其余 3 条")
XCTAssertEqual(
value(app, "guestMergeConfirmSupportingCopy"),
supportingCopy(app),
"还有 2 条待处理,本次不会上传这些条目。"
)
attach(app, named: "summary-unresolved")
Expand All @@ -323,7 +331,7 @@ final class GuestMergeSummaryUI5B2BB2AUITests: XCTestCase {
XCTAssertEqual(title, "确认当前处理结果")
XCTAssertFalse(title.contains("0 条"), "不得出现“先合并其余 0 条”:\(title)")
XCTAssertEqual(
value(app, "guestMergeConfirmSupportingCopy"),
supportingCopy(app),
"目前没有可上传条目;确认后仍有 2 条冲突需要处理。"
)
attach(app, named: "summary-zero-upload-unresolved")
Expand All @@ -332,10 +340,10 @@ final class GuestMergeSummaryUI5B2BB2AUITests: XCTestCase {
func testOrdinaryMergeCopyDoesNotRegress() throws {
let app = launchSummary("UITEST_MERGE_SUMMARY_NO_CONFLICT")
XCTAssertEqual(confirmButton(app).label, "确认合并库存")
XCTAssertEqual(value(app, "guestMergeConfirmSupportingCopy"), "计划新增 2 条、计划更新 1 条。")
// The long-standing preview footnotes are untouched.
XCTAssertEqual(supportingCopy(app), "计划新增 2 条、计划更新 1 条。")
// The preview's trust footnotes stay in place.
XCTAssertTrue(app.staticTexts["只合并库存,不会上传购物清单、计划或菜谱。"].exists)
XCTAssertTrue(app.staticTexts["可以随时取消,也可以稍后处理。"].exists)
XCTAssertTrue(app.staticTexts["本步骤不会写入云端;确认后如有冲突不会自动覆盖,需要逐条选择。"].exists)
// No unresolved conflicts, so no 仍待处理 row.
XCTAssertFalse(element(app, "guestMergeSummaryStillNeedsDecision").exists)
}
Expand Down Expand Up @@ -402,7 +410,7 @@ final class GuestMergeSummaryUI5B2BB2AUITests: XCTestCase {
// must not reuse the definite first-pass wording nor invent a remaining
// count.
XCTAssertEqual(confirmButton(app).label, "确认当前处理计划")
let copy = try XCTUnwrap(value(app, "guestMergeConfirmSupportingCopy"))
let copy = try XCTUnwrap(supportingCopy(app))
XCTAssertEqual(copy, "当前页面汇总本次会话的整体计划,系统会根据当前同步状态继续处理。")
for banned in ["计划新增", "先合并其余", "尚未上传", "已经上传", "已上传", "已经合并", "已合并",
"不会上传任何库存", "文案", "重新定义"] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ final class GuestMergeUIPhase2B3UITests: XCTestCase {
XCTAssertTrue(guestRow.label.contains("游客模式"), "账号入口应仍标明游客模式")

// The explanatory copy must mention future sync prep AND inventory backup,
// matching the exact product copy — unchanged by UI-5A, which moved this
// text into the section footer without rewording it. No merge-specific UI
// (which requires a signed-in account) may ever appear here.
// matching the exact product copy. Its old leading "无需登录即可继续使用
// 全部本机功能。" was dropped because the row above already says
// 本机功能已全部可用. No merge-specific UI (which requires a signed-in
// account) may ever appear here.
let explanation = app.staticTexts[
"无需登录即可继续使用全部本机功能。登录后可为未来跨设备同步做准备,并可选择将本机库存合并到家庭云端;购物清单、计划和菜谱仍只保存在本机。"
"登录后可为未来跨设备同步做准备,并可选择将本机库存合并到家庭云端;购物清单、计划和菜谱仍只保存在本机。"
]
XCTAssertTrue(explanation.waitForExistence(timeout: 3))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ final class PR31VisualAcceptanceUITests: XCTestCase {
assertFullText(footer, contains: "清除后无法从备份恢复", variant, "clear-data footer")
let tabBar = app.tabBars.firstMatch
if tabBar.exists {
// `reveal` stops 40pt above the window edge, which can still be
// behind the taller tab bar; keep scrolling so the page end has
// to carry the footer clear of it.
for _ in 0..<3 where footer.frame.maxY > tabBar.frame.minY + 1 { app.swipeUp() }
XCTAssertLessThanOrEqual(footer.frame.maxY, tabBar.frame.minY + 1,
"\(variant.name): footer \(footer.frame) under tab bar \(tabBar.frame)")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import XCTest
/// Destructive confirmation is opened and cancelled; AI checks are never run.
final class SettingsExperienceUITests: XCTestCase {
/// Must stay identical to `SettingsView.guestAccountFooter`.
private static let guestFooter = "无需登录即可继续使用全部本机功能。登录后可为未来跨设备同步做准备,并可选择将本机库存合并到家庭云端;购物清单、计划和菜谱仍只保存在本机。"
private static let guestFooter = "登录后可为未来跨设备同步做准备,并可选择将本机库存合并到家庭云端;购物清单、计划和菜谱仍只保存在本机。"

override func setUpWithError() throws {
continueAfterFailure = false
Expand Down
2 changes: 1 addition & 1 deletion test/ios-native-auth-phase1.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ test('Guest-first auth is app scoped without creating or switching kitchen persi
assert.match(content, /AuthenticationAssembly\.make\(\)/);
assert.match(content, /await authStore\.start\(\)/);
assert.match(settings, /游客模式/);
assert.match(settings, /无需登录即可继续使用全部本机功能/);
assert.match(settings, /本机功能已全部可用/);
assert.doesNotMatch(authStore, /KitchenStore|KitchenPersistenceFactory|ModelContainer/);
});

Expand Down
Loading