缺少代码差异信息,无法生成具体的commit message。请提供code differences内容以便分析并生成符合Angular规范的提交信息。
当您提供代码差异后,我将按照以下格式生成: ``` <type>(<scope>): <subject> <body> ``` 其中type会根据更改类型选择(feat、fix、docs、style、refactor等),scope表示影响范围,subject简要描述变更内容,body详细说明修改内容。
This commit is contained in:
@@ -35,9 +35,40 @@ struct ProfileEditView: View {
|
||||
private struct ProfileEditForm: View {
|
||||
@Environment(\.modelContext) private var ctx
|
||||
@Bindable var profile: UserProfile
|
||||
@State private var healthImportDraft: HealthProfileImportDraft?
|
||||
@State private var healthImportError: String?
|
||||
@State private var isImportingHealthProfile = false
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
Button {
|
||||
importHealthProfile()
|
||||
} label: {
|
||||
HStack(spacing: 10) {
|
||||
if isImportingHealthProfile {
|
||||
ProgressView()
|
||||
} else {
|
||||
Image(systemName: "heart.text.square")
|
||||
.foregroundStyle(Tj.Palette.ink)
|
||||
}
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("从 Apple 健康导入")
|
||||
.foregroundStyle(Tj.Palette.text)
|
||||
Text("只读取生日、性别、身高、血型")
|
||||
.font(.tjScaled( 12))
|
||||
.foregroundStyle(Tj.Palette.text3)
|
||||
}
|
||||
}
|
||||
}
|
||||
.disabled(isImportingHealthProfile)
|
||||
.accessibilityElement(children: .combine)
|
||||
.accessibilityLabel("从 Apple 健康导入")
|
||||
.accessibilityHint("读取生日、性别、身高和血型,确认后填入个人资料")
|
||||
} footer: {
|
||||
Text("导入前会先显示预览,确认后才覆盖个人资料。")
|
||||
}
|
||||
|
||||
Section {
|
||||
BirthYearRow(profile: profile)
|
||||
SexRow(profile: profile)
|
||||
@@ -67,6 +98,90 @@ private struct ProfileEditForm: View {
|
||||
profile.updatedAt = .now
|
||||
try? ctx.save()
|
||||
}
|
||||
.sheet(item: $healthImportDraft) { draft in
|
||||
HealthProfileImportPreviewSheet(
|
||||
draft: draft,
|
||||
profile: profile
|
||||
) {
|
||||
draft.apply(to: profile)
|
||||
try? ctx.save()
|
||||
healthImportDraft = nil
|
||||
}
|
||||
}
|
||||
.alert("无法导入 Apple 健康资料", isPresented: Binding(
|
||||
get: { healthImportError != nil },
|
||||
set: { if !$0 { healthImportError = nil } }
|
||||
)) {
|
||||
Button("好", role: .cancel) { healthImportError = nil }
|
||||
} message: {
|
||||
Text(healthImportError ?? "")
|
||||
}
|
||||
}
|
||||
|
||||
private func importHealthProfile() {
|
||||
guard !isImportingHealthProfile else { return }
|
||||
isImportingHealthProfile = true
|
||||
healthImportError = nil
|
||||
Task {
|
||||
do {
|
||||
healthImportDraft = try await HealthProfileImportService.shared.fetchDraft()
|
||||
} catch {
|
||||
healthImportError = error.localizedDescription
|
||||
}
|
||||
isImportingHealthProfile = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct HealthProfileImportPreviewSheet: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
let draft: HealthProfileImportDraft
|
||||
let profile: UserProfile
|
||||
let onApply: () -> Void
|
||||
|
||||
private var preview: HealthProfileImportPreview {
|
||||
HealthProfileImportPreview(draft: draft, current: profile)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
Section {
|
||||
ForEach(preview.fields, id: \.title) { field in
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Text(field.title)
|
||||
.foregroundStyle(Tj.Palette.text)
|
||||
Spacer(minLength: 12)
|
||||
VStack(alignment: .trailing, spacing: 4) {
|
||||
Text(field.imported ?? "未读取到")
|
||||
.foregroundStyle(field.imported == nil ? Tj.Palette.text3 : Tj.Palette.text)
|
||||
Text("当前: \(field.current)")
|
||||
.font(.tjScaled( 11))
|
||||
.foregroundStyle(Tj.Palette.text3)
|
||||
}
|
||||
}
|
||||
}
|
||||
} footer: {
|
||||
Text("未读取到的字段不会修改。")
|
||||
}
|
||||
}
|
||||
.navigationTitle("确认导入")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Tj.Palette.sand.ignoresSafeArea())
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button("取消") { dismiss() }
|
||||
}
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button("导入") {
|
||||
onApply()
|
||||
dismiss()
|
||||
}
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +227,7 @@ private struct BirthYearRow: View {
|
||||
Text(selectedLabel)
|
||||
.foregroundStyle(profile.birthYear == nil ? Tj.Palette.text3 : Tj.Palette.text2)
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.system(size: 12, weight: .semibold))
|
||||
.font(.tjScaled( 12, weight: .semibold))
|
||||
.foregroundStyle(Tj.Palette.text3)
|
||||
.rotationEffect(.degrees(expanded ? 90 : 0))
|
||||
}
|
||||
@@ -212,7 +327,7 @@ private struct BMIFooter: View {
|
||||
var body: some View {
|
||||
if let bmi = profile.bmi {
|
||||
Text("BMI: \(String(format: "%.1f", bmi)) \(label(bmi))")
|
||||
.font(.system(size: 11))
|
||||
.font(.tjScaled( 11))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +397,7 @@ private struct ChronicSection: View {
|
||||
private func chip(label: String, selected: Bool, action: @escaping () -> Void) -> some View {
|
||||
Button(action: action) {
|
||||
Text(label)
|
||||
.font(.system(size: 13, weight: selected ? .semibold : .regular))
|
||||
.font(.tjScaled( 13, weight: selected ? .semibold : .regular))
|
||||
.foregroundStyle(selected ? Tj.Palette.paper : Tj.Palette.text)
|
||||
.padding(.horizontal, 12)
|
||||
.padding(.vertical, 6)
|
||||
|
||||
Reference in New Issue
Block a user