ironOS native ios app
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add accessibility features

+57 -3
+36 -1
ios/Tinkcil/ContentView.swift
··· 31 31 ) 32 32 .padding(.horizontal, 20) 33 33 .padding(.vertical, 120) 34 + .accessibilityLabel("Temperature history graph") 35 + .accessibilityHint("Visual representation of temperature over time") 34 36 } 35 37 36 38 // Main content ··· 41 43 } 42 44 } 43 45 .background(Color(.systemBackground)) 46 + .dynamicTypeSize(...DynamicTypeSize.xxxLarge) 44 47 .onChange(of: bleManager.liveData.setpoint) { _, newValue in 45 48 if !isEditingSlider && newValue > 0 { 46 49 targetTemp = Double(newValue) ··· 93 96 } 94 97 .foregroundStyle(.secondary) 95 98 .padding(.top, 8) 99 + .accessibilityElement(children: .ignore) 100 + .accessibilityLabel("Target temperature \(bleManager.liveData.setpoint) degrees") 96 101 } 97 102 98 103 Spacer() ··· 121 126 .font(.subheadline.bold()) 122 127 .lineLimit(1) 123 128 .truncationMode(.tail) 129 + .accessibilityLabel("Device name") 130 + .accessibilityValue(bleManager.deviceName) 131 + .accessibilityAddTraits(.isHeader) 124 132 125 133 Spacer(minLength: 8) 126 134 127 135 // Stats 128 136 HStack(spacing: 12) { 129 137 statItem(value: String(format: "%.1f", bleManager.liveData.watts), unit: "W") 138 + .accessibilityLabel("Power") 139 + .accessibilityValue("\(String(format: "%.1f", bleManager.liveData.watts)) watts") 130 140 statItem(value: String(format: "%.1f", bleManager.liveData.voltage), unit: "V") 141 + .accessibilityLabel("Voltage") 142 + .accessibilityValue("\(String(format: "%.1f", bleManager.liveData.voltage)) volts") 131 143 statItem(value: "\(bleManager.liveData.powerPercent)", unit: "%") 144 + .accessibilityLabel("Power level") 145 + .accessibilityValue("\(bleManager.liveData.powerPercent) percent") 132 146 } 133 147 .layoutPriority(1) 134 148 ··· 137 151 Image(systemName: mode.icon) 138 152 .font(.caption) 139 153 .foregroundStyle(mode.isActive ? .orange : .secondary) 154 + .accessibilityLabel("Operating mode") 155 + .accessibilityValue(mode.displayName) 140 156 } 141 157 142 158 // Expand chevron ··· 151 167 .contentShape(Rectangle()) 152 168 } 153 169 .buttonStyle(.plain) 170 + .accessibilityLabel(isTopBarExpanded ? "Collapse device details" : "Expand device details") 171 + .accessibilityHint("Shows detailed device information and settings") 172 + .accessibilityAddTraits(.isButton) 154 173 155 174 // Expanded info section 156 175 if isTopBarExpanded { ··· 198 217 .buttonStyle(.plain) 199 218 .padding(.horizontal, 20) 200 219 .padding(.bottom, 12) 220 + .accessibilityLabel("Settings and device information") 221 + .accessibilityHint("Opens device configuration and diagnostics") 201 222 } 202 223 .transition(.opacity.combined(with: .move(edge: .top))) 203 224 } ··· 224 245 .foregroundStyle(.secondary) 225 246 } 226 247 .foregroundStyle(colorForTemp(currentTemp, maxTemp: maxTemp)) 248 + .accessibilityElement(children: .ignore) 249 + .accessibilityLabel("Current temperature") 250 + .accessibilityValue("\(bleManager.liveData.liveTemp) degrees Celsius") 251 + .accessibilityHint(isHeating ? "Heating to \(bleManager.liveData.setpoint) degrees" : "") 227 252 } 228 253 229 254 // MARK: - Slider Panel ··· 241 266 } 242 267 .foregroundStyle(colorForTemp(targetTemp, maxTemp: 450)) 243 268 .frame(width: 60) 269 + .accessibilityHidden(true) 244 270 245 271 // Slider 246 272 Slider( ··· 265 291 ) 266 292 .tint(colorForTemp(targetTemp, maxTemp: 450)) 267 293 .accessibilityLabel("Target temperature") 268 - .accessibilityValue("\(Int(targetTemp)) degrees") 294 + .accessibilityValue("\(Int(targetTemp)) degrees Celsius") 295 + .accessibilityHint("Adjust the target soldering temperature") 269 296 } 270 297 .padding(.horizontal, 20) 271 298 .padding(.vertical, 14) 272 299 .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16)) 300 + .accessibilityElement(children: .contain) 273 301 } 274 302 275 303 // MARK: - Helpers ··· 329 357 ZStack { 330 358 Color.black.opacity(0.4) 331 359 .ignoresSafeArea() 360 + .accessibilityHidden(true) 332 361 333 362 VStack(spacing: 20) { 334 363 if bleManager.isScanning || bleManager.connectionState.isConnecting { 335 364 ProgressView() 336 365 .scaleEffect(1.2) 337 366 .padding(.bottom, 4) 367 + .accessibilityLabel(bleManager.connectionState.isConnecting ? "Connecting to device" : "Scanning for device") 338 368 339 369 Text(bleManager.connectionState.isConnecting ? "Connecting..." : "Scanning...") 340 370 .font(.headline) ··· 347 377 .font(.system(size: 36)) 348 378 .foregroundStyle(.secondary) 349 379 .padding(.bottom, 4) 380 + .accessibilityHidden(true) 350 381 351 382 Text("No Device Found") 352 383 .font(.headline) 384 + .accessibilityAddTraits(.isHeader) 353 385 354 386 Button("Scan Again") { 355 387 hapticLight() 356 388 bleManager.startScanning() 357 389 } 358 390 .buttonStyle(.borderedProminent) 391 + .accessibilityLabel("Scan for device") 392 + .accessibilityHint("Searches for nearby soldering iron") 359 393 } 360 394 } 361 395 .padding(32) 362 396 .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 24)) 363 397 .shadow(color: .black.opacity(0.2), radius: 20) 398 + .accessibilityElement(children: .contain) 364 399 } 365 400 } 366 401
+21 -2
ios/Tinkcil/SettingsView.swift
··· 18 18 Label("Settings", systemImage: "slider.horizontal.3") 19 19 } 20 20 .tag(0) 21 - 21 + 22 22 DiagnosticsView(bleManager: bleManager) 23 23 .tabItem { 24 24 Label("Info", systemImage: "info.circle") ··· 27 27 } 28 28 .navigationTitle(selectedTab == 0 ? "Settings" : "Device Info") 29 29 .navigationBarTitleDisplayMode(.inline) 30 + .dynamicTypeSize(...DynamicTypeSize.xxxLarge) 30 31 .toolbar { 31 32 ToolbarItem(placement: .topBarTrailing) { 32 33 Button("Done") { ··· 277 278 } 278 279 } 279 280 .disabled(saveInProgress) 281 + .accessibilityLabel(saveInProgress ? "Saving settings" : "Save settings to device") 282 + .accessibilityHint("Saves all settings to persist across device restarts") 280 283 } footer: { 281 284 Text("Changes are written immediately but must be saved to persist across restarts.") 282 285 } ··· 286 289 ProgressView("Loading settings...") 287 290 .padding() 288 291 .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 12)) 292 + .accessibilityLabel("Loading device settings") 289 293 } 290 294 } 291 295 .task { ··· 414 418 Spacer() 415 419 } 416 420 } 421 + .accessibilityLabel("Disconnect from device") 422 + .accessibilityHint("Closes the Bluetooth connection to your soldering iron") 417 423 } 418 424 } 419 425 } ··· 472 478 .foregroundStyle(.secondary) 473 479 .monospacedDigit() 474 480 } 481 + .accessibilityHidden(true) 475 482 476 483 Slider( 477 484 value: Binding( ··· 489 496 } 490 497 } 491 498 ) 499 + .accessibilityLabel(label) 500 + .accessibilityValue("\(value) \(unit)") 501 + .accessibilityHint("Adjust using the slider. Range is \(range.lowerBound) to \(range.upperBound) \(unit)") 492 502 } 503 + .accessibilityElement(children: .contain) 493 504 } 494 505 495 506 private func hapticLight() { ··· 517 528 onChange(newValue) 518 529 } 519 530 )) 531 + .accessibilityLabel(label) 532 + .accessibilityValue(value ? "On" : "Off") 533 + .accessibilityHint("Double tap to toggle") 520 534 } 521 535 522 536 private func hapticLight() { ··· 544 558 Text(option.1).tag(option.0) 545 559 } 546 560 } 561 + .accessibilityLabel(label) 562 + .accessibilityValue(options.first(where: { $0.0 == value })?.1 ?? "") 563 + .accessibilityHint("Select an option from the list") 547 564 } 548 565 549 566 private func hapticSelection() { ··· 555 572 struct InfoRow: View { 556 573 let label: String 557 574 let value: String 558 - 575 + 559 576 var body: some View { 560 577 HStack { 561 578 Text(label) ··· 565 582 .foregroundStyle(.primary) 566 583 .multilineTextAlignment(.trailing) 567 584 } 585 + .accessibilityElement(children: .combine) 586 + .accessibilityLabel("\(label): \(value)") 568 587 } 569 588 }