Skip to content
This repository was archived by the owner on Nov 7, 2026. It is now read-only.

Commit b171132

Browse files
committed
Merge branch 'ci-app-build' into xcode-synced-folders
2 parents 5b89613 + c01409f commit b171132

24 files changed

Lines changed: 160 additions & 19 deletions

File tree

‎VisuStruct-iOS/VisuStructXcodeOpen/VisuStruct/Canvas/DiagramCanvasView.swift‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ struct DiagramCanvasView: View {
267267
Color.clear
268268
.frame(width: cw, height: ch)
269269
.contentShape(Rectangle())
270+
.accessibilityHidden(true)
270271
.onTapGesture {
271272
selectedPath = nil
272273
// iPhone: Einfügemarke durch Tap auf leeren Bereich löschen.
@@ -291,13 +292,16 @@ struct DiagramCanvasView: View {
291292

292293
canvasView(layout: layout, cw: cw, ch: ch)
293294
.allowsHitTesting(false)
295+
// Gezeichnete Fläche; VoiceOver liest stattdessen die Blöcke darüber.
296+
.accessibilityHidden(true)
294297

295298
if let firstRoot = layout.elements.first {
296299
rootInsertBeforeFirstStrip(
297300
firstRootFrame: firstRoot.frame,
298301
canvasWidth: cw,
299302
canvasHeight: ch
300303
)
304+
.accessibilityHidden(true)
301305
}
302306

303307
ForEach(flat, id: \.pathKey) { node in
@@ -408,13 +412,30 @@ struct DiagramCanvasView: View {
408412
.draggable(DiagramDragPayload(raw: ElementPalette.moveDragIdentifier(path: node.idPath))) {
409413
nodeDragPreview(for: node)
410414
}
415+
// VoiceOver: ein Element pro Block (statt der unbeschrifteten Drop-Streifen darin).
416+
.accessibilityElement(children: .ignore)
417+
.accessibilityLabel(accessibilityLabel(for: node))
418+
.accessibilityAddTraits(selectedPath == node.idPath ? [.isButton, .isSelected] : .isButton)
419+
.accessibilityAction {
420+
selectedPath = node.idPath
421+
}
411422
.accessibilityHint(
412423
isPlaceholderNode
413424
? lang.tr("a11y_placeholder_drop")
414425
: lang.tr("a11y_node_drop")
415426
)
416427
}
417428

429+
/// Vorgelesener Name eines Blocks: Elementart und Text, z. B. „Anweisung: int x = 5“.
430+
private func accessibilityLabel(for node: VisuStructLayout.Node) -> String {
431+
// Schlüssel wie in der Palette: element_anweisung, element_verzweigung, …
432+
let kind = lang.tr("element_\(node.element.type)")
433+
let text = node.element.textLines
434+
.joined(separator: " ")
435+
.trimmingCharacters(in: .whitespacesAndNewlines)
436+
return text.isEmpty ? kind : "\(kind): \(text)"
437+
}
438+
418439
private func copyElement(at path: [Int]) {
419440
guard let el = try? VisuStructDocumentEditing.element(at: path, in: document) else { return }
420441
elementClipboard = el

‎VisuStruct-iOS/VisuStructXcodeOpen/VisuStruct/ContentView.swift‎

Lines changed: 86 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,14 @@ struct ContentView: View {
5757
}
5858
// iPhone UX: Einfügemarke + Inspector einklappen beim Einfügen.
5959
@State private var insertTarget: DiagramInsertTarget? = nil
60-
@State private var hideInspectorOnPhone: Bool = false
60+
/// iPhone: Inspector startet eingeklappt, damit das Diagramm Platz hat.
61+
@State private var hideInspectorOnPhone: Bool = true
6162
// iPhone UX: Palette automatisch einklappen beim Text-Editieren.
62-
@State private var showPaletteOnPhone: Bool = true
63+
/// iPhone: Palette als Sheet über dem Editor (statt fest über der Navigationsleiste).
64+
@State private var showPaletteOnPhone: Bool = false
65+
/// iPhone: Diagrammtitel wird in einem kleinen Dialog bearbeitet statt in einem eigenen Feld über dem Diagramm.
66+
@State private var showPhoneTitleEditor: Bool = false
67+
@State private var phoneTitleDraft: String = ""
6368
// In-App Zwischenablage für Diagramm-Elemente (Copy/Cut/Paste).
6469
@State private var elementClipboard: VisuStructElement? = nil
6570
#if canImport(UIKit)
@@ -177,7 +182,7 @@ struct ContentView: View {
177182
.navigationSplitViewStyle(.balanced)
178183
} else {
179184
VStack(spacing: 0) {
180-
if !isPhone || showPaletteOnPhone {
185+
if !isPhone {
181186
paletteListPanel
182187
.frame(maxHeight: 300, alignment: .top)
183188
.background(Color(.secondarySystemGroupedBackground))
@@ -187,6 +192,14 @@ struct ContentView: View {
187192
mainEditorNavigationStack
188193
.frame(maxWidth: .infinity, maxHeight: .infinity)
189194
}
195+
.sheet(isPresented: phonePaletteSheetBinding) {
196+
paletteListPanel
197+
.environment(\.appLanguage, lang)
198+
.presentationDetents([.medium, .large])
199+
// Diagramm bleibt bedienbar, z. B. um eine Einfügestelle zu wählen.
200+
.presentationBackgroundInteraction(.enabled(upThrough: .medium))
201+
.presentationDragIndicator(.visible)
202+
}
190203
}
191204
}
192205
#if canImport(UIKit)
@@ -346,6 +359,11 @@ struct ContentView: View {
346359
refreshLineEditor()
347360
DiagramCommandCenter.shared.update(file: diagramFileActions, edit: diagramEditActions)
348361
}
362+
.onChange(of: insertTarget) { _, newTarget in
363+
// iPhone: Einfügestelle gewählt → Palette öffnen, damit gleich ein Element gewählt werden kann.
364+
guard isPhone, newTarget != nil else { return }
365+
showPaletteOnPhone = true
366+
}
349367
.onChange(of: focusedField) { _, newVal in
350368
// iPhone: Sobald Textbearbeitung startet, Palette einklappen für mehr Platz.
351369
guard isPhone else { return }
@@ -881,21 +899,25 @@ struct ContentView: View {
881899
let blockCorner: CGFloat = horizontalSizeClass == .regular ? 14 : 12
882900

883901
return VStack(alignment: .leading, spacing: 0) {
884-
VStack(alignment: .leading, spacing: 8) {
885-
Text(lang.tr("diagram_title_label"))
886-
.font(horizontalSizeClass == .regular ? .caption : .subheadline)
887-
.foregroundStyle(.secondary)
888-
TextField(lang.tr("diagram_title_placeholder"), text: captionBinding)
889-
.font(horizontalSizeClass == .regular ? .subheadline : .body)
890-
.textFieldStyle(.roundedBorder)
891-
.textInputAutocapitalization(.never)
892-
.autocorrectionDisabled(true)
893-
.focused($focusedField, equals: .caption)
902+
if isPhone {
903+
phoneCaptionRow
904+
} else {
905+
VStack(alignment: .leading, spacing: 8) {
906+
Text(lang.tr("diagram_title_label"))
907+
.font(horizontalSizeClass == .regular ? .caption : .subheadline)
908+
.foregroundStyle(.secondary)
909+
TextField(lang.tr("diagram_title_placeholder"), text: captionBinding)
910+
.font(horizontalSizeClass == .regular ? .subheadline : .body)
911+
.textFieldStyle(.roundedBorder)
912+
.textInputAutocapitalization(.never)
913+
.autocorrectionDisabled(true)
914+
.focused($focusedField, equals: .caption)
915+
}
916+
.frame(maxWidth: .infinity, alignment: .leading)
917+
.padding(.horizontal, 14)
918+
.padding(.top, 8)
919+
.padding(.bottom, 6)
894920
}
895-
.frame(maxWidth: .infinity, alignment: .leading)
896-
.padding(.horizontal, 14)
897-
.padding(.top, 8)
898-
.padding(.bottom, 6)
899921
// Innerer Trennstrich — Titel zählt zum Diagramm-„Blatt“, nicht als losgelöster Bereich oberhalb.
900922
Rectangle()
901923
.fill(Color.primary.opacity(0.1))
@@ -935,6 +957,47 @@ struct ContentView: View {
935957
}
936958
}
937959

960+
/// iPhone: Titel als eine kompakte Zeile; Tippen öffnet einen Dialog zum Bearbeiten.
961+
private var phoneCaptionRow: some View {
962+
Button {
963+
phoneTitleDraft = document.caption
964+
showPhoneTitleEditor = true
965+
} label: {
966+
HStack(spacing: 6) {
967+
Text(document.caption.isEmpty ? lang.tr("diagram_title_placeholder") : document.caption)
968+
.font(.subheadline.weight(.semibold))
969+
.foregroundStyle(document.caption.isEmpty ? .secondary : .primary)
970+
.lineLimit(1)
971+
Image(systemName: "pencil")
972+
.font(.caption)
973+
.foregroundStyle(.secondary)
974+
}
975+
.frame(maxWidth: .infinity, alignment: .leading)
976+
.contentShape(Rectangle())
977+
}
978+
.buttonStyle(.plain)
979+
.accessibilityLabel(lang.tr("diagram_title_label"))
980+
.padding(.horizontal, 14)
981+
.padding(.vertical, 8)
982+
.alert(lang.tr("diagram_title_label"), isPresented: $showPhoneTitleEditor) {
983+
TextField(lang.tr("diagram_title_placeholder"), text: $phoneTitleDraft)
984+
.textInputAutocapitalization(.never)
985+
.autocorrectionDisabled(true)
986+
Button(lang.tr("phone_title_done")) {
987+
if phoneTitleDraft != document.caption {
988+
captionBinding.wrappedValue = phoneTitleDraft
989+
}
990+
}
991+
}
992+
}
993+
994+
private var phonePaletteSheetBinding: Binding<Bool> {
995+
Binding(
996+
get: { isPhone && showPaletteOnPhone },
997+
set: { showPaletteOnPhone = $0 }
998+
)
999+
}
1000+
9381001
// MARK: - Bindings (Dokument, Caption)
9391002

9401003
private var documentBinding: Binding<VisuStructDocument> {
@@ -1494,7 +1557,11 @@ struct ContentView: View {
14941557
insertTargetActive: insertTarget != nil,
14951558
hideInspectorOnPhone: hideInspectorOnPhone,
14961559
onCancelInsert: { insertTarget = nil },
1497-
onShowInspector: { hideInspectorOnPhone = false }
1560+
onShowInspector: { hideInspectorOnPhone = false },
1561+
onHideInspector: {
1562+
focusedField = nil
1563+
hideInspectorOnPhone = true
1564+
}
14981565
) {
14991566
inspectorPanel
15001567
}
@@ -1845,6 +1912,7 @@ struct ContentView: View {
18451912
private func insertPaletteItem(_ item: ElementPalette.PaletteItem) {
18461913
if isPhone {
18471914
hideInspectorOnPhone = true
1915+
showPaletteOnPhone = false
18481916
}
18491917
let el = ElementPalette.element(for: item, lang: lang)
18501918
let doc = document

‎VisuStruct-iOS/VisuStructXcodeOpen/VisuStruct/Inspector/InspectorPanelViews.swift‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ struct InspectorPhoneChrome<Inspector: View>: View {
190190
let hideInspectorOnPhone: Bool
191191
let onCancelInsert: () -> Void
192192
let onShowInspector: () -> Void
193+
let onHideInspector: () -> Void
193194
@ViewBuilder let inspector: () -> Inspector
194195

195196
var body: some View {
@@ -211,7 +212,16 @@ struct InspectorPhoneChrome<Inspector: View>: View {
211212
}
212213
.buttonStyle(.bordered)
213214
} else {
214-
inspector()
215+
VStack(alignment: .trailing, spacing: 4) {
216+
Button {
217+
onHideInspector()
218+
} label: {
219+
Label(lang.tr("phone_hide_text_editor"), systemImage: "chevron.down")
220+
.font(.subheadline)
221+
}
222+
.buttonStyle(.borderless)
223+
inspector()
224+
}
215225
}
216226
}
217227
}

‎VisuStruct-iOS/VisuStructXcodeOpen/VisuStruct/ar.lproj/Localizable.strings‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@
194194
/* iPhone: tap-to-insert */
195195
"phone_insert_cancel" = "إلغاء الإدراج";
196196
"phone_show_text_editor" = "تحرير النص";
197+
"phone_hide_text_editor" = "طيّ";
198+
"phone_title_done" = "تم";
197199

198200
/* Alerts */
199201
"err_save_failed_fmt" = "تعذر الحفظ: %@";

‎VisuStruct-iOS/VisuStructXcodeOpen/VisuStruct/da.lproj/Localizable.strings‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,5 @@
140140
"sim_notice_division_by_zero" = "Division med nul.";
141141
"sim_notice_whole_number_expected" = "Indtast et heltal.";
142142
"sim_notice_number_expected" = "Indtast et tal.";
143+
"phone_hide_text_editor" = "Skjul";
144+
"phone_title_done" = "Færdig";

‎VisuStruct-iOS/VisuStructXcodeOpen/VisuStruct/de.lproj/Localizable.strings‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,8 @@
211211
/* iPhone: Einfügen per Tap */
212212
"phone_insert_cancel" = "Einfügen abbrechen";
213213
"phone_show_text_editor" = "Text bearbeiten";
214+
"phone_hide_text_editor" = "Einklappen";
215+
"phone_title_done" = "Fertig";
214216

215217
/* Meldungen */
216218
"err_save_failed_fmt" = "Speichern fehlgeschlagen: %@";

‎VisuStruct-iOS/VisuStructXcodeOpen/VisuStruct/el.lproj/Localizable.strings‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@
191191
/* iPhone: tap-to-insert */
192192
"phone_insert_cancel" = "Ακύρωση εισαγωγής";
193193
"phone_show_text_editor" = "Επεξεργασία κειμένου";
194+
"phone_hide_text_editor" = "Σύμπτυξη";
195+
"phone_title_done" = "Τέλος";
194196

195197
/* Alerts */
196198
"err_save_failed_fmt" = "Δεν ήταν δυνατή η αποθήκευση: %@";

‎VisuStruct-iOS/VisuStructXcodeOpen/VisuStruct/en.lproj/Localizable.strings‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,8 @@
211211
/* iPhone: tap-to-insert */
212212
"phone_insert_cancel" = "Cancel insert";
213213
"phone_show_text_editor" = "Edit text";
214+
"phone_hide_text_editor" = "Collapse";
215+
"phone_title_done" = "Done";
214216

215217
/* Alerts */
216218
"err_save_failed_fmt" = "Could not save: %@";

‎VisuStruct-iOS/VisuStructXcodeOpen/VisuStruct/es.lproj/Localizable.strings‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@
194194
/* iPhone: tap-to-insert */
195195
"phone_insert_cancel" = "Cancelar inserción";
196196
"phone_show_text_editor" = "Editar texto";
197+
"phone_hide_text_editor" = "Contraer";
198+
"phone_title_done" = "Listo";
197199

198200
/* Alerts */
199201
"err_save_failed_fmt" = "No se pudo guardar: %@";

‎VisuStruct-iOS/VisuStructXcodeOpen/VisuStruct/fr.lproj/Localizable.strings‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@
194194
/* iPhone: tap-to-insert */
195195
"phone_insert_cancel" = "Annuler l’insertion";
196196
"phone_show_text_editor" = "Modifier le texte";
197+
"phone_hide_text_editor" = "Réduire";
198+
"phone_title_done" = "OK";
197199

198200
/* Alerts */
199201
"err_save_failed_fmt" = "Impossible d’enregistrer : %@";

0 commit comments

Comments
 (0)