Added Increase Contrast controls to devices.

This commit is contained in:
Casey Fleser 2022-08-07 08:19:36 -05:00
parent 61f82f2115
commit b68c2779fd
10 changed files with 354 additions and 27 deletions

View file

@ -31,6 +31,8 @@
C982F877283D020C00D491F4 /* SimProductFamily.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F876283D020C00D491F4 /* SimProductFamily.swift */; };
C982F879283D042E00D491F4 /* SimModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F878283D042E00D491F4 /* SimModel.swift */; };
C982F87B283E40C800D491F4 /* SimCtl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F87A283E40C800D491F4 /* SimCtl.swift */; };
C9BF5232289FE95D00BDDC91 /* DescriptiveToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BF5231289FE95D00BDDC91 /* DescriptiveToggle.swift */; };
C9BF5234289FE99600BDDC91 /* DescriptiveToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9BF5233289FE99600BDDC91 /* DescriptiveToggleStyle.swift */; };
C9D73C25285C8C0C0044A279 /* SourceItemData.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D73C24285C8C0C0044A279 /* SourceItemData.swift */; };
C9D73C29285C8C4B0044A279 /* SourceItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D73C28285C8C4B0044A279 /* SourceItem.swift */; };
C9DD54C32860936D00D46AB3 /* SourceItemGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54C22860936D00D46AB3 /* SourceItemGroup.swift */; };
@ -72,6 +74,8 @@
C982F876283D020C00D491F4 /* SimProductFamily.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimProductFamily.swift; sourceTree = "<group>"; };
C982F878283D042E00D491F4 /* SimModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimModel.swift; sourceTree = "<group>"; };
C982F87A283E40C800D491F4 /* SimCtl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimCtl.swift; sourceTree = "<group>"; };
C9BF5231289FE95D00BDDC91 /* DescriptiveToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DescriptiveToggle.swift; sourceTree = "<group>"; };
C9BF5233289FE99600BDDC91 /* DescriptiveToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DescriptiveToggleStyle.swift; sourceTree = "<group>"; };
C9D73C24285C8C0C0044A279 /* SourceItemData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemData.swift; sourceTree = "<group>"; };
C9D73C28285C8C4B0044A279 /* SourceItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItem.swift; sourceTree = "<group>"; };
C9DD54C22860936D00D46AB3 /* SourceItemGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemGroup.swift; sourceTree = "<group>"; };
@ -159,6 +163,8 @@
C9DD54CC2860992200D46AB3 /* Model Views */,
C90DCC152896B0370072E403 /* AppearancePicker.swift */,
C90DCC132896AAAA0072E403 /* ContentHeader.swift */,
C9BF5231289FE95D00BDDC91 /* DescriptiveToggle.swift */,
C9BF5233289FE99600BDDC91 /* DescriptiveToggleStyle.swift */,
C90BCE472861D70500C2EF35 /* ErrorView.swift */,
C927A0DA2846502300533D66 /* PathActions.swift */,
C9EE0CD128478FDB00E9B97A /* PathRow.swift */,
@ -281,6 +287,7 @@
C927A0DB2846502300533D66 /* PathActions.swift in Sources */,
C90BCE442861D3C500C2EF35 /* DeviceContent.swift in Sources */,
C90DCC142896AAAA0072E403 /* ContentHeader.swift in Sources */,
C9BF5232289FE95D00BDDC91 /* DescriptiveToggle.swift in Sources */,
C90BCE502861E9D000C2EF35 /* SourceState.swift in Sources */,
C90BCE4A2861DA6700C2EF35 /* RuntimeHeader.swift in Sources */,
C9EE0CD42847B79E00E9B97A /* SimApp.swift in Sources */,
@ -295,6 +302,7 @@
C982F85B283B9F9000D491F4 /* ContentView.swift in Sources */,
C982F875283CEEBB00D491F4 /* SimDevice.swift in Sources */,
C9DD54C52860938C00D46AB3 /* SourceItemLink.swift in Sources */,
C9BF5234289FE99600BDDC91 /* DescriptiveToggleStyle.swift in Sources */,
C982F873283CE9AD00D491F4 /* SimDeviceType.swift in Sources */,
C9DD54CB2860948600D46AB3 /* SourceItemLabel.swift in Sources */,
C90BCE482861D70500C2EF35 /* ErrorView.swift in Sources */,

View file

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.804",
"green" : "0.804",
"red" : "0.804"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.310",
"green" : "0.310",
"red" : "0.310"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.306",
"green" : "0.306",
"red" : "0.306"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.796",
"green" : "0.796",
"red" : "0.796"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.902",
"green" : "0.902",
"red" : "0.902"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.173",
"green" : "0.173",
"red" : "0.173"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.902",
"green" : "0.902",
"red" : "0.902"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0.294",
"green" : "0.294",
"red" : "0.294"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View file

@ -105,4 +105,14 @@ struct SimCtl {
func setDeviceContentSize(_ device: SimDevice, contentSize: SimDevice.ContentSize) throws {
try runAsync(args: ["ui", device.udid, "content_size", contentSize.rawValue])
}
func getDeviceIncreaseContrast(_ device: SimDevice) async throws -> SimDevice.IncreaseContrast {
let increaseContrast : String = try await runAsync(args: ["ui", device.udid, "increase_contrast"]).trimmingCharacters(in: .whitespacesAndNewlines)
return SimDevice.IncreaseContrast(rawValue: increaseContrast) ?? .unknown
}
func setDeviceIncreaseContrast(_ device: SimDevice, increaseContrast: SimDevice.IncreaseContrast) throws {
try runAsync(args: ["ui", device.udid, "increase_contrast", increaseContrast.rawValue])
}
}

View file

@ -26,6 +26,7 @@ class SimDevice: ObservableObject, Decodable {
@Published var availabilityError : String?
@Published var appearance = Appearance.unknown
@Published var contentSize = ContentSize.unknown
@Published var increaseContrast = IncreaseContrast.unknown
var isTransitioning : Bool { state == .booting || state == .shuttingDown }
var isBooted : Bool {
get { state.showBooted == true }
@ -143,6 +144,13 @@ class SimDevice: ObservableObject, Decodable {
await MainActor.run { contentSize = result }
}
}
if increaseContrast == .unknown {
Task {
let result = try await SimCtl().getDeviceIncreaseContrast(self)
await MainActor.run { increaseContrast = result }
}
}
}
func setAppearance(_ appearance: Appearance) {
@ -164,6 +172,16 @@ class SimDevice: ObservableObject, Decodable {
print("Failed to set device content size: \(error)")
}
}
func setIncreaseContrast(_ increaseContrast: IncreaseContrast) {
self.increaseContrast = increaseContrast // optimistic
do {
try SimCtl().setDeviceIncreaseContrast(self, increaseContrast: increaseContrast)
} catch {
print("Failed to set device increase contrast: \(error)")
}
}
}
extension SimDevice {
@ -244,11 +262,21 @@ extension SimDevice {
}
}
}
enum IncreaseContrast: String {
case enabled = "enabled"
case disabled = "disabled"
case unsupported = "unsupported"
case unknown = "unknown"
var isOn : Bool { self == .enabled }
}
}
extension SimDevice: SourceItemData {
var title : String { return name }
var headerTitle : String { "Device: \(title)" }
var isEnabled : Bool { isBooted }
var imageDesc : SourceImageDesc { .symbol(systemName: "questionmark.circle", color: isAvailable ? .green : .red) }
}

View file

@ -0,0 +1,50 @@
//
// DescriptiveToggle.swift
// SimDirs
//
// Created by Casey Fleser on 8/7/22.
//
import SwiftUI
protocol ToggleDescriptor {
var isOn : Bool { get }
var titleKey : LocalizedStringKey { get }
var text : String { get }
var image : Image { get }
}
extension ToggleDescriptor {
var circleColor : Color { isOn ? .accentColor : Color("CircleSymbolBkgOff") }
}
struct DescriptiveToggle<T: ToggleDescriptor>: View {
@Binding var isOn : Bool
var descriptor : T
init(_ descriptor: T, isOn: Binding<Bool>) {
self._isOn = isOn
self.descriptor = descriptor
}
var body: some View {
Toggle(descriptor.titleKey, isOn: _isOn)
.toggleStyle(DescriptiveToggleStyle(descriptor))
}
}
struct DescriptiveToggle_Previews: PreviewProvider {
struct DarkMode: ToggleDescriptor {
var isOn : Bool = true
var titleKey : LocalizedStringKey { "Dark Mode" }
var text : String { isOn ? "On" : "Off" }
var image : Image { Image(systemName: "circle.circle") }
}
@State static var toggle = DarkMode()
static var previews: some View {
DescriptiveToggle(DarkMode(), isOn: $toggle.isOn)
.disabled(true)
}
}

View file

@ -0,0 +1,46 @@
//
// DescriptiveToggleStyle.swift
// SimDirs
//
// Created by Casey Fleser on 8/7/22.
//
import SwiftUI
struct DescriptiveToggleStyle<T: ToggleDescriptor>: ToggleStyle {
var descriptor : T
init(_ descriptor: T) {
self.descriptor = descriptor
}
func makeBody(configuration: Configuration) -> some View {
Button(action: { configuration.isOn.toggle() }) {
VStack(spacing: 0) {
ZStack {
Circle()
.foregroundColor(descriptor.circleColor)
descriptor.image
.resizable()
.foregroundColor(descriptor.isOn ? .white : Color("CircleSymbolOff"))
.aspectRatio(contentMode: .fit)
.padding(9)
}
.frame(width: 36, height: 36)
.padding(.bottom, 4)
Group {
Text(descriptor.titleKey)
.fontWeight(.semibold)
Text(descriptor.text)
.foregroundColor(.secondary)
}
.font(.system(size: 11))
.allowsTightening(true)
.minimumScaleFactor(0.5)
.multilineTextAlignment(.center)
}
}
.buttonStyle(.plain)
}
}

View file

@ -8,8 +8,9 @@
import SwiftUI
extension SimDevice {
public var content : some View { DeviceContent(device: self) }
var scheme : ColorScheme? {
public var content : some View { DeviceContent(self) }
var scheme : ColorScheme? {
get {
switch appearance {
case .light: return .light
@ -25,17 +26,36 @@ extension SimDevice {
}
}
}
var contentSizeVal : Double {
var contentSizeVal : Double {
get { Double(contentSize.intValue) }
set { setContenSize(ContentSize(intValue: Int(newValue))) }
}
var isIncreaseContrast : Bool {
get { increaseContrast.isOn }
set { setIncreaseContrast(newValue ? .enabled : .disabled) }
}
}
extension SimDevice.IncreaseContrast: ToggleDescriptor {
var titleKey : LocalizedStringKey { "Increase Contrast" }
var text : String { rawValue.capitalized }
var image : Image { Image(systemName: "circle.lefthalf.filled") }
}
struct DeviceContent: View {
@ObservedObject var device : SimDevice
@State var isBooted : Bool
init(_ device: SimDevice) {
self.device = device
self.isBooted = device.isBooted
}
var body: some View {
VStack(alignment: .leading, spacing: 3.0) {
ContentHeader("Paths")
Group {
if !device.isAvailable {
ErrorView(
@ -43,37 +63,50 @@ struct DeviceContent: View {
description: device.availabilityError ?? "Unknown Error")
}
ContentHeader("Paths")
PathRow(title: "Data Path", path: device.dataPath)
PathRow(title: "Log Path", path: device.logPath)
ContentHeader("UI")
HStack(spacing: 32) {
if device.appearance != .unsupported {
AppearancePicker(scheme: $device.scheme)
}
if device.contentSize != .unsupported {
VStack {
HStack {
Image(systemName: "textformat.size")
.imageScale(.small)
Slider(value: $device.contentSizeVal, in: SimDevice.ContentSize.range, step: 1)
Image(systemName: "textformat.size")
.imageScale(.large)
}
Text("Content Size")
}
}
}
.disabled(!device.isBooted)
}
.font(.subheadline)
.textSelection(.enabled)
.lineLimit(1)
ContentHeader("UI")
HStack(spacing: 16) {
if device.appearance != .unsupported {
AppearancePicker(scheme: $device.scheme)
}
if device.appearance != .unsupported {
DescriptiveToggle(device.increaseContrast, isOn: $device.isIncreaseContrast)
}
if device.contentSize != .unsupported {
VStack {
HStack {
Image(systemName: "textformat.size")
.imageScale(.small)
Slider(value: $device.contentSizeVal, in: SimDevice.ContentSize.range, step: 1)
Image(systemName: "textformat.size")
.imageScale(.large)
}
Text("Content Size")
}
.opacity(isBooted ? 1.0 : 0.5)
}
}
}
.environment(\.isEnabled, isBooted)
.onAppear {
device.discoverUI()
}
.onChange(of: device.state) { state in
let trulyBooted = state == .booted
if isBooted != trulyBooted {
isBooted = trulyBooted
if isBooted {
device.discoverUI()
}
}
}
}
}
@ -82,9 +115,9 @@ struct DeviceContent_Previews: PreviewProvider {
static var previews: some View {
if !devices.isEmpty {
DeviceContent(device: devices[0])
DeviceContent(devices[0])
.preferredColorScheme(.light)
DeviceContent(device: devices.randomElement() ?? devices[0])
DeviceContent(devices.randomElement() ?? devices[0])
.preferredColorScheme(.dark)
}
else {