diff --git a/SimDirs.xcodeproj/project.pbxproj b/SimDirs.xcodeproj/project.pbxproj index e8d04b2..36fa051 100644 --- a/SimDirs.xcodeproj/project.pbxproj +++ b/SimDirs.xcodeproj/project.pbxproj @@ -7,15 +7,16 @@ objects = { /* Begin PBXBuildFile section */ + C90BCE442861D3C500C2EF35 /* DeviceContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE432861D3C500C2EF35 /* DeviceContent.swift */; }; + C90BCE462861D57100C2EF35 /* DeviceHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE452861D57100C2EF35 /* DeviceHeader.swift */; }; + C90BCE482861D70500C2EF35 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE472861D70500C2EF35 /* ErrorView.swift */; }; + C90BCE4A2861DA6700C2EF35 /* RuntimeHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE492861DA6700C2EF35 /* RuntimeHeader.swift */; }; + C90BCE4C2861E37900C2EF35 /* AppHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE4B2861E37900C2EF35 /* AppHeader.swift */; }; + C90BCE4E2861E4E400C2EF35 /* AppContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE4D2861E4E400C2EF35 /* AppContent.swift */; }; + C90BCE502861E9D000C2EF35 /* SourceState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE4F2861E9D000C2EF35 /* SourceState.swift */; }; + C90BCE522861EDBF00C2EF35 /* SourceFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C90BCE512861EDBF00C2EF35 /* SourceFilter.swift */; }; C927A0D92846414900533D66 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = C927A0D82846414900533D66 /* Helpers.swift */; }; C927A0DB2846502300533D66 /* PathActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C927A0DA2846502300533D66 /* PathActions.swift */; }; - C94C52C72844E80A00E2129E /* SimItemRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94C52C62844E80A00E2129E /* SimItemRow.swift */; }; - C94C52C92844E99B00E2129E /* SimItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94C52C82844E99B00E2129E /* SimItemContent.swift */; }; - C94C52CB2844EAAC00E2129E /* RuntimeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C94C52CA2844EAAC00E2129E /* RuntimeView.swift */; }; - C95E5AB6284B6DDE00A2124E /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C95E5AB5284B6DDE00A2124E /* AppView.swift */; }; - C977973C284F58A900706DFB /* PresentationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = C977973B284F58A900706DFB /* PresentationState.swift */; }; - C977973E284F5AE100706DFB /* SimItemNavLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C977973D284F5AE100706DFB /* SimItemNavLink.swift */; }; - C9779740284F5CBB00706DFB /* SimItemGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = C977973F284F5CBB00706DFB /* SimItemGroup.swift */; }; C9779742284F6DE000706DFB /* ToolbarMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9779741284F6DE000706DFB /* ToolbarMenu.swift */; }; C982F859283B9F9000D491F4 /* SimDirsApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F858283B9F9000D491F4 /* SimDirsApp.swift */; }; C982F85B283B9F9000D491F4 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F85A283B9F9000D491F4 /* ContentView.swift */; }; @@ -28,23 +29,31 @@ 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 */; }; - C982F880283E57E600D491F4 /* PresentationItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F87F283E57E600D491F4 /* PresentationItem.swift */; }; - C982F883283E813F00D491F4 /* DeviceTypeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C982F882283E813F00D491F4 /* DeviceTypeView.swift */; }; - C9D729F128478AB00064152D /* DeviceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9D729F028478AB00064152D /* DeviceView.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 */; }; + C9DD54C52860938C00D46AB3 /* SourceItemLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54C42860938C00D46AB3 /* SourceItemLink.swift */; }; + C9DD54C7286093A500D46AB3 /* SourceItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54C6286093A500D46AB3 /* SourceItemContent.swift */; }; + C9DD54C9286093C100D46AB3 /* SourceItemImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54C8286093C100D46AB3 /* SourceItemImage.swift */; }; + C9DD54CB2860948600D46AB3 /* SourceItemLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54CA2860948600D46AB3 /* SourceItemLabel.swift */; }; + C9DD54CE2860A0AF00D46AB3 /* DeviceTypeContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54CD2860A0AF00D46AB3 /* DeviceTypeContent.swift */; }; + C9DD54D02860A1A500D46AB3 /* DeviceTypeHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54CF2860A1A500D46AB3 /* DeviceTypeHeader.swift */; }; + C9DD54D22860A24B00D46AB3 /* RuntimeContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9DD54D12860A24B00D46AB3 /* RuntimeContent.swift */; }; C9EE0CD228478FDB00E9B97A /* PathRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9EE0CD128478FDB00E9B97A /* PathRow.swift */; }; C9EE0CD42847B79E00E9B97A /* SimApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9EE0CD32847B79E00E9B97A /* SimApp.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + C90BCE432861D3C500C2EF35 /* DeviceContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceContent.swift; sourceTree = ""; }; + C90BCE452861D57100C2EF35 /* DeviceHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceHeader.swift; sourceTree = ""; }; + C90BCE472861D70500C2EF35 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; + C90BCE492861DA6700C2EF35 /* RuntimeHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeHeader.swift; sourceTree = ""; }; + C90BCE4B2861E37900C2EF35 /* AppHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppHeader.swift; sourceTree = ""; }; + C90BCE4D2861E4E400C2EF35 /* AppContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContent.swift; sourceTree = ""; }; + C90BCE4F2861E9D000C2EF35 /* SourceState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceState.swift; sourceTree = ""; }; + C90BCE512861EDBF00C2EF35 /* SourceFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceFilter.swift; sourceTree = ""; }; C927A0D82846414900533D66 /* Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; C927A0DA2846502300533D66 /* PathActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathActions.swift; sourceTree = ""; }; - C94C52C62844E80A00E2129E /* SimItemRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimItemRow.swift; sourceTree = ""; }; - C94C52C82844E99B00E2129E /* SimItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimItemContent.swift; sourceTree = ""; }; - C94C52CA2844EAAC00E2129E /* RuntimeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeView.swift; sourceTree = ""; }; - C95E5AB5284B6DDE00A2124E /* AppView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppView.swift; sourceTree = ""; }; - C977973B284F58A900706DFB /* PresentationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationState.swift; sourceTree = ""; }; - C977973D284F5AE100706DFB /* SimItemNavLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimItemNavLink.swift; sourceTree = ""; }; - C977973F284F5CBB00706DFB /* SimItemGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimItemGroup.swift; sourceTree = ""; }; C9779741284F6DE000706DFB /* ToolbarMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarMenu.swift; sourceTree = ""; }; C982F855283B9F9000D491F4 /* SimDirs.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SimDirs.app; sourceTree = BUILT_PRODUCTS_DIR; }; C982F858283B9F9000D491F4 /* SimDirsApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimDirsApp.swift; sourceTree = ""; }; @@ -59,9 +68,16 @@ C982F876283D020C00D491F4 /* SimProductFamily.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimProductFamily.swift; sourceTree = ""; }; C982F878283D042E00D491F4 /* SimModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimModel.swift; sourceTree = ""; }; C982F87A283E40C800D491F4 /* SimCtl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimCtl.swift; sourceTree = ""; }; - C982F87F283E57E600D491F4 /* PresentationItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresentationItem.swift; sourceTree = ""; }; - C982F882283E813F00D491F4 /* DeviceTypeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTypeView.swift; sourceTree = ""; }; - C9D729F028478AB00064152D /* DeviceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceView.swift; sourceTree = ""; }; + C9D73C24285C8C0C0044A279 /* SourceItemData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemData.swift; sourceTree = ""; }; + C9D73C28285C8C4B0044A279 /* SourceItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItem.swift; sourceTree = ""; }; + C9DD54C22860936D00D46AB3 /* SourceItemGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemGroup.swift; sourceTree = ""; }; + C9DD54C42860938C00D46AB3 /* SourceItemLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemLink.swift; sourceTree = ""; }; + C9DD54C6286093A500D46AB3 /* SourceItemContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemContent.swift; sourceTree = ""; }; + C9DD54C8286093C100D46AB3 /* SourceItemImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemImage.swift; sourceTree = ""; }; + C9DD54CA2860948600D46AB3 /* SourceItemLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SourceItemLabel.swift; sourceTree = ""; }; + C9DD54CD2860A0AF00D46AB3 /* DeviceTypeContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTypeContent.swift; sourceTree = ""; }; + C9DD54CF2860A1A500D46AB3 /* DeviceTypeHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTypeHeader.swift; sourceTree = ""; }; + C9DD54D12860A24B00D46AB3 /* RuntimeContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeContent.swift; sourceTree = ""; }; C9EE0CD128478FDB00E9B97A /* PathRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PathRow.swift; sourceTree = ""; }; C9EE0CD32847B79E00E9B97A /* SimApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimApp.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -100,6 +116,7 @@ C982F85A283B9F9000D491F4 /* ContentView.swift */, C927A0D82846414900533D66 /* Helpers.swift */, C982F867283BA09B00D491F4 /* Model */, + C9D73C23285C8B3B0044A279 /* Presentation */, C982F881283E7F0400D491F4 /* Views */, C982F85C283B9F9200D491F4 /* Assets.xcassets */, C982F861283B9F9200D491F4 /* SimDirs.entitlements */, @@ -119,7 +136,6 @@ C982F867283BA09B00D491F4 /* Model */ = { isa = PBXGroup; children = ( - C982F87C283E579900D491F4 /* Presentation */, C982F87A283E40C800D491F4 /* SimCtl.swift */, C982F878283D042E00D491F4 /* SimModel.swift */, C9EE0CD32847B79E00E9B97A /* SimApp.swift */, @@ -132,31 +148,55 @@ path = Model; sourceTree = ""; }; - C982F87C283E579900D491F4 /* Presentation */ = { + C982F881283E7F0400D491F4 /* Views */ = { isa = PBXGroup; children = ( - C982F87F283E57E600D491F4 /* PresentationItem.swift */, - C977973B284F58A900706DFB /* PresentationState.swift */, + C9DD54C12860935300D46AB3 /* SourceItem Views */, + C9DD54CC2860992200D46AB3 /* Model Views */, + C90BCE472861D70500C2EF35 /* ErrorView.swift */, + C927A0DA2846502300533D66 /* PathActions.swift */, + C9EE0CD128478FDB00E9B97A /* PathRow.swift */, + C9779741284F6DE000706DFB /* ToolbarMenu.swift */, + ); + path = Views; + sourceTree = ""; + }; + C9D73C23285C8B3B0044A279 /* Presentation */ = { + isa = PBXGroup; + children = ( + C9D73C28285C8C4B0044A279 /* SourceItem.swift */, + C90BCE512861EDBF00C2EF35 /* SourceFilter.swift */, + C9D73C24285C8C0C0044A279 /* SourceItemData.swift */, + C90BCE4F2861E9D000C2EF35 /* SourceState.swift */, ); path = Presentation; sourceTree = ""; }; - C982F881283E7F0400D491F4 /* Views */ = { + C9DD54C12860935300D46AB3 /* SourceItem Views */ = { isa = PBXGroup; children = ( - C95E5AB5284B6DDE00A2124E /* AppView.swift */, - C982F882283E813F00D491F4 /* DeviceTypeView.swift */, - C9D729F028478AB00064152D /* DeviceView.swift */, - C927A0DA2846502300533D66 /* PathActions.swift */, - C9EE0CD128478FDB00E9B97A /* PathRow.swift */, - C94C52CA2844EAAC00E2129E /* RuntimeView.swift */, - C94C52C82844E99B00E2129E /* SimItemContent.swift */, - C977973F284F5CBB00706DFB /* SimItemGroup.swift */, - C977973D284F5AE100706DFB /* SimItemNavLink.swift */, - C94C52C62844E80A00E2129E /* SimItemRow.swift */, - C9779741284F6DE000706DFB /* ToolbarMenu.swift */, + C9DD54C6286093A500D46AB3 /* SourceItemContent.swift */, + C9DD54C22860936D00D46AB3 /* SourceItemGroup.swift */, + C9DD54C8286093C100D46AB3 /* SourceItemImage.swift */, + C9DD54CA2860948600D46AB3 /* SourceItemLabel.swift */, + C9DD54C42860938C00D46AB3 /* SourceItemLink.swift */, ); - path = Views; + path = "SourceItem Views"; + sourceTree = ""; + }; + C9DD54CC2860992200D46AB3 /* Model Views */ = { + isa = PBXGroup; + children = ( + C90BCE4D2861E4E400C2EF35 /* AppContent.swift */, + C90BCE4B2861E37900C2EF35 /* AppHeader.swift */, + C90BCE432861D3C500C2EF35 /* DeviceContent.swift */, + C90BCE452861D57100C2EF35 /* DeviceHeader.swift */, + C9DD54CD2860A0AF00D46AB3 /* DeviceTypeContent.swift */, + C9DD54CF2860A1A500D46AB3 /* DeviceTypeHeader.swift */, + C9DD54D12860A24B00D46AB3 /* RuntimeContent.swift */, + C90BCE492861DA6700C2EF35 /* RuntimeHeader.swift */, + ); + path = "Model Views"; sourceTree = ""; }; /* End PBXGroup section */ @@ -229,29 +269,37 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - C982F883283E813F00D491F4 /* DeviceTypeView.swift in Sources */, - C94C52C92844E99B00E2129E /* SimItemContent.swift in Sources */, + C9DD54C9286093C100D46AB3 /* SourceItemImage.swift in Sources */, + C9D73C25285C8C0C0044A279 /* SourceItemData.swift in Sources */, + C90BCE4E2861E4E400C2EF35 /* AppContent.swift in Sources */, C927A0DB2846502300533D66 /* PathActions.swift in Sources */, + C90BCE442861D3C500C2EF35 /* DeviceContent.swift in Sources */, + C90BCE502861E9D000C2EF35 /* SourceState.swift in Sources */, + C90BCE4A2861DA6700C2EF35 /* RuntimeHeader.swift in Sources */, C9EE0CD42847B79E00E9B97A /* SimApp.swift in Sources */, + C9DD54C7286093A500D46AB3 /* SourceItemContent.swift in Sources */, + C9DD54D22860A24B00D46AB3 /* RuntimeContent.swift in Sources */, C9779742284F6DE000706DFB /* ToolbarMenu.swift in Sources */, - C9779740284F5CBB00706DFB /* SimItemGroup.swift in Sources */, - C95E5AB6284B6DDE00A2124E /* AppView.swift in Sources */, + C9DD54CE2860A0AF00D46AB3 /* DeviceTypeContent.swift in Sources */, + C90BCE4C2861E37900C2EF35 /* AppHeader.swift in Sources */, + C9DD54D02860A1A500D46AB3 /* DeviceTypeHeader.swift in Sources */, + C90BCE522861EDBF00C2EF35 /* SourceFilter.swift in Sources */, C982F85B283B9F9000D491F4 /* ContentView.swift in Sources */, C982F875283CEEBB00D491F4 /* SimDevice.swift in Sources */, - C977973C284F58A900706DFB /* PresentationState.swift in Sources */, + C9DD54C52860938C00D46AB3 /* SourceItemLink.swift in Sources */, C982F873283CE9AD00D491F4 /* SimDeviceType.swift in Sources */, - C977973E284F5AE100706DFB /* SimItemNavLink.swift in Sources */, + C9DD54CB2860948600D46AB3 /* SourceItemLabel.swift in Sources */, + C90BCE482861D70500C2EF35 /* ErrorView.swift in Sources */, C982F877283D020C00D491F4 /* SimProductFamily.swift in Sources */, C982F859283B9F9000D491F4 /* SimDirsApp.swift in Sources */, C982F871283CE7B800D491F4 /* SimRuntime.swift in Sources */, C9EE0CD228478FDB00E9B97A /* PathRow.swift in Sources */, - C94C52C72844E80A00E2129E /* SimItemRow.swift in Sources */, C982F86B283BA22100D491F4 /* SimPlatform.swift in Sources */, C927A0D92846414900533D66 /* Helpers.swift in Sources */, - C94C52CB2844EAAC00E2129E /* RuntimeView.swift in Sources */, - C982F880283E57E600D491F4 /* PresentationItem.swift in Sources */, + C90BCE462861D57100C2EF35 /* DeviceHeader.swift in Sources */, + C9DD54C32860936D00D46AB3 /* SourceItemGroup.swift in Sources */, + C9D73C29285C8C4B0044A279 /* SourceItem.swift in Sources */, C982F879283D042E00D491F4 /* SimModel.swift in Sources */, - C9D729F128478AB00064152D /* DeviceView.swift in Sources */, C982F87B283E40C800D491F4 /* SimCtl.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/SimDirs/Assets.xcassets/HeaderEdge.colorset/Contents.json b/SimDirs/Assets.xcassets/HeaderEdge.colorset/Contents.json new file mode 100644 index 0000000..8ecbf47 --- /dev/null +++ b/SimDirs/Assets.xcassets/HeaderEdge.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.900", + "green" : "0.900", + "red" : "0.900" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.200", + "green" : "0.200", + "red" : "0.200" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/SimDirs/ContentView.swift b/SimDirs/ContentView.swift index e248af8..52fe579 100644 --- a/SimDirs/ContentView.swift +++ b/SimDirs/ContentView.swift @@ -8,33 +8,52 @@ import SwiftUI struct ContentView: View { - @ObservedObject var model : SimModel - @State private var state = PresentationState(filter: []) + @ObservedObject var state : SourceState + + init(model: SimModel) { + state = SourceState(model: model) + } - var rootItems : [PresentationItem] { state.presentationItems(from: model) } - var body: some View { - NavigationView { - List { - ForEach(rootItems) { item in - SimItemGroup(item: item, state: $state) + VStack { + NavigationView { + List { + Divider() + + switch state.filteredRoot { + case .placeholder: + Text("Placeholder") + + case let .device(_, root): + ForEach(root.items) { + SourceItemGroup(selection: $state.selection, item: $0) + } + + case let .runtime(_, root): + ForEach(root.items) { + SourceItemGroup(selection: $state.selection, item: $0) + } + } } - .padding(.leading, 2.0) + .toolbar { + ToolbarItem { ToolbarMenu(state: state) } + } + .frame(minWidth: 200) + + Image("Icon-256") // Initial View } - .frame(minWidth: 200) - .toolbar { - ToolbarItem { ToolbarMenu(state: $state) } - } - Image("Icon-256") + .searchable(text: $state.filter.searchTerm, placement: .sidebar) } - .searchable(text: $state.searchTerm, placement: .sidebar) } } struct ContentView_Previews: PreviewProvider { - static var simModel = SimModel() + static var model = SimModel() static var previews: some View { - ContentView(model: simModel) + ContentView(model: model) + .preferredColorScheme(.dark) + ContentView(model: model) + .preferredColorScheme(.light) } } diff --git a/SimDirs/Helpers.swift b/SimDirs/Helpers.swift index 93c2231..a755efe 100644 --- a/SimDirs/Helpers.swift +++ b/SimDirs/Helpers.swift @@ -5,7 +5,7 @@ // Created by Casey Fleser on 5/31/22. // -import AppKit +import SwiftUI extension NSPasteboard { static func copy(text: String) { @@ -23,12 +23,21 @@ extension NSWorkspace { } extension OptionSet where Self == Self.Element { + func settingBool(_ value: Bool, options: Self) -> Self { + if value { return union(options) } + else { return subtracting (options) } + } + mutating func booleanSet(_ value: Bool, options: Self) { if value { update(with: options) } else { subtract(options) } } } +extension ProcessInfo { + var isPreviewing : Bool { environment["XCODE_RUNNING_FOR_PREVIEWS"] == "1" } +} + extension PropertyListSerialization { class func propertyList(from url: URL) -> [String : AnyObject]? { guard let plistData = try? Data(contentsOf: url) else { return nil } @@ -36,3 +45,15 @@ extension PropertyListSerialization { return try? PropertyListSerialization.propertyList(from: plistData, options: [], format: nil) as? [String : AnyObject] } } + +extension View { + @ViewBuilder + func evalIf(_ test: Bool, then transform: (Self) -> V) -> some View { + if test { + transform(self) + } + else { + self + } + } +} diff --git a/SimDirs/Model/Presentation/PresentationItem.swift b/SimDirs/Model/Presentation/PresentationItem.swift deleted file mode 100644 index 66f9137..0000000 --- a/SimDirs/Model/Presentation/PresentationItem.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// PresentationItem.swift -// SimDirs -// -// Created by Casey Fleser on 5/25/22. -// - -import SwiftUI - -protocol PresentableItem { - var title : String { get } - var id : String { get } - var icon : NSImage? { get } - var imageName : String { get } - var imageColor : Color? { get } - var contentView : AnyView? { get } -} - -extension PresentableItem { - var imageColor : Color? { return nil } - var icon : NSImage? { return nil } - var contentView : AnyView? { return nil } -} - -struct PresentationItem: Identifiable { - let underlying : PresentableItem - var children : [PresentationItem]? - var id : String - var customImage : String? - - var title : String { return underlying.title } - var navTitle : String { return "\(typeName): \(underlying.title)" } - var icon : NSImage? { return underlying.icon } - var imageName : String { return customImage ?? underlying.imageName } - var imageColor : Color { return underlying.imageColor ?? .white } - var contentView : AnyView { return underlying.contentView ?? AnyView(Text(title)) } - var flattened : [PresentationItem] { return [self] + (self.children?.flatMap { $0.flattened } ?? []) } - var typeName : String { - switch underlying { - case is SimPlatform: return "Platform" - case is SimProductFamily: return "Product Family" - case is SimRuntime: return "Runtime" - case is SimDeviceType: return "Device Type" - case is SimDevice: return "Device" - case is SimApp: return "App" - default: return "Item" - } - } - - init(_ presentable: PresentableItem, image: String? = nil, identifier: String? = nil) { - underlying = presentable - id = identifier ?? underlying.id - customImage = image - } - - func titlesContain(_ searchTerm: String) -> Bool { - return title.contains(searchTerm) || children?.contains(where: { $0.titlesContain(searchTerm)}) ?? false - } - - func containsType(_ type: T.Type) -> Bool { - return underlying is T || children?.contains(where: { $0.containsType(type)}) ?? false - } -} - -extension Array where Element == PresentationItem { - var flatItems : [PresentationItem] { self.flatMap { $0.flattened } } - - func itemsOf (type: T.Type) -> [T] { - return flatItems.compactMap { $0.underlying as? T } - } - - func validateItems() { - let allIDs = flatItems.map { $0.id } - var idSet = Set() - - print("Validating \(allIDs.count) items") - for id in allIDs { - if !idSet.insert(id).inserted { - print("Duplicate PresentationItem.id: \(id)") - } - } - } - - func dumpPresentation(level: Int = 0) { - let ident = Array(repeating: "\t", count: level).joined() - - for item in self { - print("\(ident)\(item.title) [\(item.id)]") - - if let children = item.children { - children.dumpPresentation(level: level + 1) - } - } - } -} diff --git a/SimDirs/Model/Presentation/PresentationState.swift b/SimDirs/Model/Presentation/PresentationState.swift deleted file mode 100644 index f9dbd5d..0000000 --- a/SimDirs/Model/Presentation/PresentationState.swift +++ /dev/null @@ -1,118 +0,0 @@ -// -// PresentationState.swift -// SimDirs -// -// Created by Casey Fleser on 6/7/22. -// - -import Foundation - -struct PresentationState { - enum Organization: String, CaseIterable, Identifiable { - case byDevice = "By Device" - case byRuntime = "By Runtime" - - var id: Organization { self } - } - - struct Filter: OptionSet, CaseIterable { - let rawValue: Int - - static let withApps = Filter(rawValue: 1 << 0) - static let runtimeInstalled = Filter(rawValue: 1 << 1) - - static var allCases : [Filter] = [.withApps, .runtimeInstalled] - } - - var organization = Organization.byRuntime - var filter = Filter() - var searchTerm = "" - - static func testItemsOf(type: T.Type) -> [T] { - let flatItems = PresentationState().presentationItems(from: SimModel()).flatItems - - return flatItems.itemsOf(type: type) - } - - func presentationItems(from model: SimModel) -> [PresentationItem] { - switch organization { - case .byDevice: return itemsForDeviceStyle(from: model) - case .byRuntime: return itemsForRuntimeStyle(from: model) - } - } - - func itemsForDeviceStyle(from model: SimModel) -> [PresentationItem] { - return SimProductFamily.presentation.map{ family in - var familyItem = PresentationItem(family) - - familyItem.children = model.deviceTypes.filter({ $0.supports(productFamily: family) }).map { deviceType in - var deviceTypeItem = PresentationItem(deviceType, identifier: deviceType.id) - let deviceTypeChildren : [PresentationItem] = model.runtimes.filter({ $0.supports(deviceType: deviceType) }).map { runtime in - var runtimeItem = PresentationItem(runtime, identifier: "\(deviceType.id) - \(runtime.id)") - let runtimeItemChildren = runtime.devices.filter({ $0.isDeviceOfType(deviceType) }).map { device -> PresentationItem in - var deviceItem = PresentationItem(device, image: family.imageName) - let deviceItemChildren = device.apps.map { PresentationItem($0) } - - if !deviceItemChildren.isEmpty { - deviceItem.children = deviceItemChildren - } - - return deviceItem - } - - if !runtimeItemChildren.isEmpty { - runtimeItem.children = runtimeItemChildren - } - - return runtimeItem - } - - if !deviceTypeChildren.isEmpty { - deviceTypeItem.children = deviceTypeChildren - } - - return deviceTypeItem - } - - return familyItem - } - } - - func itemsForRuntimeStyle(from model: SimModel) -> [PresentationItem] { - return SimPlatform.presentation.map{ platform in - var platformItem = PresentationItem(platform) - - platformItem.children = model.runtimes.filter({ $0.supports(platform: platform) }).map { runtime in - var runtimeItem = PresentationItem(runtime) - let runtimeItemChildren : [PresentationItem] = model.deviceTypes.filter({ $0.supports(runtime: runtime) }).map { deviceType in - var deviceTypeItem = PresentationItem(deviceType, identifier: "\(runtime.id) - \(deviceType.id)") - let deviceTypeChildren = runtime.devices.filter({ $0.isDeviceOfType(deviceType) }).map { device -> PresentationItem in - var deviceItem = PresentationItem(device, image: deviceType.imageName) - let deviceItemChildren = device.apps.map { PresentationItem($0) } - - if !deviceItemChildren.isEmpty { - deviceItem.children = deviceItemChildren - } - - return deviceItem - } - - if !deviceTypeChildren.isEmpty { - deviceTypeItem.children = deviceTypeChildren - } - - return deviceTypeItem - } - - if !runtimeItemChildren.isEmpty { - runtimeItem.children = runtimeItemChildren - } - - return runtimeItem - } - - return platformItem - } - } -} - diff --git a/SimDirs/Model/SimApp.swift b/SimDirs/Model/SimApp.swift index 9a1bc27..06c35ef 100644 --- a/SimDirs/Model/SimApp.swift +++ b/SimDirs/Model/SimApp.swift @@ -71,10 +71,10 @@ struct SimApp: Equatable { } } -extension SimApp: PresentableItem, Identifiable { - var title : String { return displayName } - var id : String { return identifier } +extension SimApp: SourceItemData { + var title : String { return displayName } + var headerTitle : String { "App: \(title)" } + var imageDesc : SourceImageDesc { nsIcon.map { .icon(nsImage: $0) } ?? .symbol(systemName: "questionmark.app.dashed") } - var imageName : String { return "questionmark.app.dashed" } - var icon : NSImage? { return nsIcon } + var optionTrait : SourceFilter.Options { .withApps } } diff --git a/SimDirs/Model/SimDevice.swift b/SimDirs/Model/SimDevice.swift index c5c561c..97a4f25 100644 --- a/SimDirs/Model/SimDevice.swift +++ b/SimDirs/Model/SimDevice.swift @@ -105,11 +105,14 @@ struct SimDevice: Decodable, Equatable { } } -extension SimDevice: PresentableItem, Identifiable { +extension SimDevice: SourceItemData { var title : String { return name } - var id : String { return udid } - - var imageName : String { return "shippingbox" } - var imageColor : Color? { return isAvailable ? .green : .red } + var headerTitle : String { "Device: \(title)" } + var imageDesc : SourceImageDesc { .symbol(systemName: "questionmark.circle", color: isAvailable ? .green : .red) } } +extension Array where Element == SimDevice { + func of(deviceType: SimDeviceType) -> Self { + filter { $0.isDeviceOfType(deviceType) } + } +} diff --git a/SimDirs/Model/SimDeviceType.swift b/SimDirs/Model/SimDeviceType.swift index 9a036c6..58356a5 100644 --- a/SimDirs/Model/SimDeviceType.swift +++ b/SimDirs/Model/SimDeviceType.swift @@ -39,8 +39,19 @@ struct SimDeviceType: Decodable { } } -extension SimDeviceType: PresentableItem, Identifiable { +extension SimDeviceType: SourceItemData { var title : String { return name } - var id : String { return identifier } - var imageName : String { return productFamily.imageName } + var headerTitle : String { "Device Type: \(title)" } + var imageDesc : SourceImageDesc { .symbol(systemName: productFamily.symbolName) } } + +extension Array where Element == SimDeviceType { + func supporting(productFamily: SimProductFamily) -> Self { + filter { $0.supports(productFamily: productFamily) } + } + + func supporting(runtime: SimRuntime) -> Self { + filter { $0.supports(runtime: runtime) } + } +} + diff --git a/SimDirs/Model/SimModel.swift b/SimDirs/Model/SimModel.swift index 9f4d3ad..9e9e77d 100644 --- a/SimDirs/Model/SimModel.swift +++ b/SimDirs/Model/SimModel.swift @@ -15,9 +15,12 @@ enum SimError: Error { class SimModel: ObservableObject { var deviceTypes : [SimDeviceType] @Published var runtimes : [SimRuntime] - let timer = DispatchSource.makeTimerSource() + var monitorSource : DispatchSourceTimer? let updateInterval = 1.0 - + + var devices : [SimDevice] { runtimes.flatMap { $0.devices } } + var apps : [SimApp] { devices.flatMap { $0.apps } } + init() { let simctl = SimCtl() @@ -38,7 +41,9 @@ class SimModel: ObservableObject { } } runtimes.sort() - beginMonitor() + if !ProcessInfo.processInfo.isPreviewing { + beginMonitor() + } } catch { fatalError("Failed to initialize data model:\n\(error)") @@ -46,6 +51,8 @@ class SimModel: ObservableObject { } func beginMonitor() { + let timer = DispatchSource.makeTimerSource() + timer.setEventHandler { guard let runtimeDevs : [String : [SimDevice]] = try? SimCtl().readAllRuntimeDevices() else { return } @@ -78,5 +85,6 @@ class SimModel: ObservableObject { } timer.schedule(deadline: DispatchTime.now(), repeating: updateInterval) timer.resume() + monitorSource = timer } } diff --git a/SimDirs/Model/SimPlatform.swift b/SimDirs/Model/SimPlatform.swift index 3251eb6..eeb68ea 100644 --- a/SimDirs/Model/SimPlatform.swift +++ b/SimDirs/Model/SimPlatform.swift @@ -13,12 +13,8 @@ enum SimPlatform: String, Decodable { case watchOS static let presentation : [SimPlatform] = [.iOS, .watchOS, .tvOS] -} - -extension SimPlatform: PresentableItem { - var title : String { return self.rawValue } - var id : String { return self.rawValue } - var imageName : String { + + var symbolName : String { switch self { case .iOS: return "iphone" case .tvOS: return "appletv" @@ -26,3 +22,9 @@ extension SimPlatform: PresentableItem { } } } + +extension SimPlatform: SourceItemData { + var title : String { self.rawValue } + var headerTitle : String { "Platform: \(title)" } + var imageDesc : SourceImageDesc { .symbol(systemName: symbolName) } +} diff --git a/SimDirs/Model/SimProductFamily.swift b/SimDirs/Model/SimProductFamily.swift index e01e9a1..6732f0b 100644 --- a/SimDirs/Model/SimProductFamily.swift +++ b/SimDirs/Model/SimProductFamily.swift @@ -14,13 +14,8 @@ enum SimProductFamily: String, Decodable { case iPhone static let presentation : [SimProductFamily] = [.iPhone, .iPad, .appleWatch, .appleTV] -} -extension SimProductFamily: PresentableItem { - var title : String { return self.rawValue } - var id : String { return self.rawValue } - - var imageName : String { + var symbolName : String { switch self { case .iPad: return "ipad" case .iPhone: return "iphone" @@ -29,3 +24,9 @@ extension SimProductFamily: PresentableItem { } } } + +extension SimProductFamily: SourceItemData { + var title : String { self.rawValue } + var headerTitle : String { "Product Family: \(title)" } + var imageDesc : SourceImageDesc { .symbol(systemName: symbolName) } +} diff --git a/SimDirs/Model/SimRuntime.swift b/SimDirs/Model/SimRuntime.swift index b0ecba4..d3977e9 100644 --- a/SimDirs/Model/SimRuntime.swift +++ b/SimDirs/Model/SimRuntime.swift @@ -127,11 +127,12 @@ struct SimRuntime: Comparable, Decodable { } } -extension SimRuntime: PresentableItem, Identifiable { - var title : String { return name } - var id : String { return identifier } - var imageName : String { return "v.circle" } - var imageColor : Color? { return isAvailable ? .green : .red } +extension SimRuntime: SourceItemData { + var title : String { return name } + var headerTitle : String { "Runtime: \(title)" } + var imageDesc : SourceImageDesc { .symbol(systemName: "shippingbox", color: isAvailable ? .green : .red) } + + var optionTrait : SourceFilter.Options { isAvailable ? .runtimeInstalled : [] } } extension Array where Element == SimRuntime { @@ -142,4 +143,12 @@ extension Array where Element == SimRuntime { return self.endIndex - 1 }() } + + func supporting(deviceType: SimDeviceType) -> Self { + filter { $0.supports(deviceType: deviceType) } + } + + func supporting(platform: SimPlatform) -> Self { + filter { $0.supports(platform: platform) } + } } diff --git a/SimDirs/Presentation/SourceFilter.swift b/SimDirs/Presentation/SourceFilter.swift new file mode 100644 index 0000000..6bedd30 --- /dev/null +++ b/SimDirs/Presentation/SourceFilter.swift @@ -0,0 +1,89 @@ +// +// SourceFilter.swift +// SimDirs +// +// Created by Casey Fleser on 6/21/22. +// + +import Foundation + +struct SourceFilter { + struct Options: OptionSet, CaseIterable { + let rawValue: Int + + static let withApps = Options(rawValue: 1 << 0) + static let runtimeInstalled = Options(rawValue: 1 << 1) + + static var allCases : [Options] = [.withApps, .runtimeInstalled] + + func search(item: T, progress: Options) -> Self { + var foundOptions = progress.union(item.data.optionTrait) + + if let items = item.children, !subtracting(foundOptions).isEmpty { + for child in items { + foundOptions = search(item: child, progress: foundOptions) + + if isSubset(of: foundOptions) { + break + } + } + } + + return foundOptions + } + } + + var searchTerm = "" + var options = Options() + + func filtered(root: SourceRoot) -> SourceRoot { + if !searchTerm.isEmpty || !options.isEmpty { + var fRoot = root + + fRoot.items = root.items.compactMap { item in + let result = filtered(item: item) + + return result.match ? result.fItem : nil + } + + return fRoot + } + else { + return root + } + } + + func filtered(item: T, inheritedOptions: Options = []) -> (fItem: T, match: Bool) { + var fItem = item + var match = true + var childMatch = false + let optProgress = inheritedOptions.union(fItem.data.optionTrait) // options inherited by children + + // If there are options to match then do that first passing inherited options along + // and consider a match fulfilled if any child contains all the desired options. + + if !options.isEmpty { + var foundOptions = optProgress + + if !options.isSubset(of: foundOptions) { + foundOptions = options.search(item: fItem, progress: foundOptions) + } + match = options.isSubset(of: foundOptions) + } + + if !searchTerm.isEmpty && match { + match = fItem.title.uppercased().contains(searchTerm.uppercased()) + } + + if let srcChildren = fItem.children { + fItem.children = srcChildren.compactMap { child -> T.Child? in + let result = filtered(item: child, inheritedOptions: optProgress) + + return result.match ? result.fItem : nil + } + childMatch = fItem.children?.isEmpty == false + } + + return (fItem, match || childMatch) + } +} diff --git a/SimDirs/Presentation/SourceItem.swift b/SimDirs/Presentation/SourceItem.swift new file mode 100644 index 0000000..63aa33b --- /dev/null +++ b/SimDirs/Presentation/SourceItem.swift @@ -0,0 +1,44 @@ +// +// SourceItem.swift +// SimDirs +// +// Created by Casey Fleser on 6/14/22. +// + +import SwiftUI + +protocol SourceItem: Identifiable { + associatedtype Model : SourceItemData + associatedtype Child : SourceItem + + var id : UUID { get } + var data : Model { get } + var children : [Child]? { get set } + var customImgDesc : SourceImageDesc? { get } +} + +extension SourceItem { + var title : String { data.title } + var headerTitle : String { data.headerTitle } + var header : some View { data.header } + var content : some View { data.content } + var imageDesc : SourceImageDesc { customImgDesc ?? data.imageDesc } + var customImgDesc : SourceImageDesc? { nil } +} + +struct SourceItemVal: SourceItem { + var id = UUID() + var data : Model + var children : [Child]? + var customImgDesc : SourceImageDesc? +} + +struct SourceRoot { + var items : [Item] +} + +extension Never: SourceItem { + public var id : UUID { fatalError() } + public var data : Never { fatalError() } + public var children : [Never]? { get { fatalError() } set { } } +} diff --git a/SimDirs/Presentation/SourceItemData.swift b/SimDirs/Presentation/SourceItemData.swift new file mode 100644 index 0000000..d6d3888 --- /dev/null +++ b/SimDirs/Presentation/SourceItemData.swift @@ -0,0 +1,51 @@ +// +// SourceItemData.swift +// SimDirs +// +// Created by Casey Fleser on 6/15/22. +// + +import SwiftUI + +protocol SourceItemData { + associatedtype Content : View + associatedtype Header : View + + @ViewBuilder var header : Self.Header { get } + @ViewBuilder var content : Self.Content { get } + + var title : String { get } + var headerTitle : String { get } + var imageDesc : SourceImageDesc { get } + + var optionTrait : SourceFilter.Options { get } +} + +extension SourceItemData { + var headerTitle : String { title } + var imageDesc : SourceImageDesc { .symbol() } + + var header : some View { get { EmptyView() } } + var content : some View { get { EmptyView() } } + + var optionTrait : SourceFilter.Options { [] } +} + +enum SourceImageDesc { + case icon(nsImage: NSImage) + case symbol(systemName: String = "questionmark.circle", color: Color = .primary) + + func withColor(_ color: Color) -> Self { + switch self { + case .icon: return self + case let .symbol(name, _): return .symbol(systemName: name, color: color) + } + } +} + +extension Never: SourceItemData { + var headerTitle : String { fatalError () } + var imageDesc : SourceImageDesc { SourceImageDesc.symbol(systemName: "exclamationmark.octagon", color: .red) } + var header : some View { Text("Error: Never SourceItemData type cannot provide header") } + var content : some View { Text("Error: Never SourceItemData type cannot provide content") } +} diff --git a/SimDirs/Presentation/SourceState.swift b/SimDirs/Presentation/SourceState.swift new file mode 100644 index 0000000..d0baadb --- /dev/null +++ b/SimDirs/Presentation/SourceState.swift @@ -0,0 +1,140 @@ +// +// SourceState.swift +// SimDirs +// +// Created by Casey Fleser on 6/21/22. +// + +import Foundation + +class SourceState: ObservableObject { + typealias ProductFamily = SourceItemVal + typealias Platform = SourceItemVal + typealias DeviceType_DS = SourceItemVal + typealias DeviceType_RT = SourceItemVal + typealias Runtime_DS = SourceItemVal + typealias Runtime_RT = SourceItemVal + typealias Device = SourceItemVal + typealias App = SourceItemVal + + enum Root: Identifiable { + case placeholder(id: UUID = UUID()) + case device(id: UUID = UUID(), SourceRoot) + case runtime(id: UUID = UUID(), SourceRoot) + + var id : UUID { + switch self { + case let .placeholder(id): return id + case let .device(id, _): return id + case let .runtime(id, _): return id + } + } + } + + enum Style: Int, CaseIterable, Identifiable { + case placeholder + case byDevice + case byRuntime + + var id : Int { rawValue } + var title : String { + switch self { + case .placeholder: return "Placeholder" + case .byDevice: return "By Device" + case .byRuntime: return "By Runtime" + } + } + var visible : Bool { + switch self { + case .placeholder: return false + default: return true + } + } + } + + @Published var style = Style.placeholder { didSet { baseRoot = rootFor(style: style) } } + @Published var filter = SourceFilter() + @Published var selection : UUID? + + var model : SimModel + var baseRoot = Root.placeholder() + + var filteredRoot : Root { + switch baseRoot { + case .placeholder: return baseRoot + case let .device(_, root): return .device(id: baseRoot.id, filter.filtered(root: root)) + case let .runtime(_, root): return .runtime(id: baseRoot.id, filter.filtered(root: root)) + } + } + + var filterApps : Bool { + get { filter.options.contains(.withApps) } + set { filter.options.booleanSet(newValue, options: .withApps) } + } + + var filterRuntimes : Bool { + get { filter.options.contains(.runtimeInstalled) } + set { filter.options.booleanSet(newValue, options: .runtimeInstalled) } + } + + + func filtering(_ items: [T]) -> [T] { + return items + } + + init(model: SimModel) { + self.model = model + style = Style.byDevice + } + + func rootFor(style: Style) -> Root { + switch style { + case .placeholder: return .placeholder() + case .byDevice: return .device(SourceRoot(items: deviceStyleItems())) + case .byRuntime: return .runtime(SourceRoot(items: runtimeStyleItems())) + } + } + + func deviceStyleItems() -> [ProductFamily] { + SimProductFamily.presentation.map { family in + ProductFamily(data: family, children: model.deviceTypes.supporting(productFamily: family).map { devType in + DeviceType_DS(data: devType, children: model.runtimes.supporting(deviceType: devType).map { runtime in + Runtime_DS(data: runtime, children: runtime.devices.of(deviceType: devType).map { device in + let imageDesc = devType.imageDesc.withColor(device.isAvailable ? .green : .red) + + return Device(data: device, children: device.apps.map { app in App(data: app) }, customImgDesc: imageDesc) + }) + }) + }) + } + } + + func runtimeStyleItems() -> [Platform] { + SimPlatform.presentation.map { platform in + Platform(data: platform, children: model.runtimes.supporting(platform: platform).map { runtime in + Runtime_RT(data: runtime, children: model.deviceTypes.supporting(runtime: runtime).map { devType in + DeviceType_RT(data: devType, children: runtime.devices.of(deviceType: devType).map { device in + let imageDesc = devType.imageDesc.withColor(device.isAvailable ? .green : .red) + + return Device(data: device, children: device.apps.map { app in App(data: app) }, customImgDesc: imageDesc) + }) + }) + }) + } + } + +#if OLDWAY + static func testItemsOf(type: T.Type) -> [T] { + return PresentationState(model: SimModel()).allUnderlyingOf(type: type) + } + + func allItemsOf(type: T.Type) -> [PresentationItem] { + return presentationItems().flatItems.itemsOf(type: type) + } + + func allUnderlyingOf(type: T.Type) -> [T] { + return presentationItems().flatItems.underlyingOf(type: type) + } +#endif +} + diff --git a/SimDirs/SimDirsApp.swift b/SimDirs/SimDirsApp.swift index 67e56c0..4a3b690 100644 --- a/SimDirs/SimDirsApp.swift +++ b/SimDirs/SimDirsApp.swift @@ -15,6 +15,7 @@ struct SimDirsApp: App { WindowGroup { ContentView(model: simModel) } + .windowStyle(.hiddenTitleBar) .commands { SimCommands() } diff --git a/SimDirs/Views/AppView.swift b/SimDirs/Views/AppView.swift deleted file mode 100644 index 3e7f1f0..0000000 --- a/SimDirs/Views/AppView.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// AppView.swift -// SimDirs -// -// Created by Casey Fleser on 6/4/22. -// - -import SwiftUI - -extension SimApp { - var contentView : AnyView? { return AnyView(AppView(app: self)) } -} - -struct AppView: View { - var app : SimApp - var icon : Image { app.nsIcon.map({ Image(nsImage: $0) }) ?? - Image(systemName: "questionmark.app.dashed") - } - - var body: some View { - VStack(alignment: .leading, spacing: 2.0) { - Group { - HStack(alignment: .top) { - VStack(alignment: .leading) { - Text("Display Name: \(app.displayName)") - Text("Bundle Name: \(app.bundleName)") - Text("Bundle ID: \(app.bundleID)") - Text("Version: \(app.version)") - Text("Minimum OS Version: \(app.minOSVersion)") - } - Spacer() - icon - .resizable() - .aspectRatio(contentMode: .fit) - .frame(maxWidth: 72.0, maxHeight: 72.0) - .cornerRadius(4.0) - } - Divider() - .padding([.top, .bottom], 4.0) - PathRow(title: "Bundle Path", path: app.bundlePath) - if let sandboxPath = app.sandboxPath { - PathRow(title: "Sandbox Path", path: sandboxPath) - } - else { - Text("Sandbox Path: ") - } - } - .font(.subheadline) - .textSelection(.enabled) - .lineLimit(1) - } - } -} - -struct AppView_Previews: PreviewProvider { - static var previews: some View { - let apps = PresentationState.testItemsOf(type: SimApp.self) - - if apps.isEmpty { - Text("No SimApp present in model data") - } - else { - ForEach(apps[0...2]) { - AppView(app: $0) - } - } - } -} diff --git a/SimDirs/Views/DeviceTypeView.swift b/SimDirs/Views/DeviceTypeView.swift deleted file mode 100644 index b3784ef..0000000 --- a/SimDirs/Views/DeviceTypeView.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// DeviceTypeView.swift -// SimDirs -// -// Created by Casey Fleser on 5/25/22. -// - -import SwiftUI - -extension SimDeviceType { - var contentView : AnyView? { return AnyView(DeviceTypeView(deviceType: self)) } -} - -struct DeviceTypeView: View { - var deviceType : SimDeviceType - - var body: some View { - VStack(alignment: .leading, spacing: 2.0) { - Group { - Text("Product Family: \(deviceType.productFamily.title)") - Text("Model ID: \(deviceType.modelIdentifier)") - Text("Min Runtime: \(deviceType.minRuntimeVersionString)") - Text("Max Runtime: \(UInt32.max == deviceType.maxRuntimeVersion ? "-" : deviceType.maxRuntimeVersionString)") - Text("Identifier: \(deviceType.identifier)") - PathRow(title: "Bundle Path", path: deviceType.bundlePath) - } - .font(.subheadline) - .textSelection(.enabled) - .lineLimit(1) - } - } -} - -struct DeviceTypeView_Previews: PreviewProvider { - static var previews: some View { - let deviceTypes = PresentationState.testItemsOf(type: SimDeviceType.self) - - if deviceTypes.isEmpty { - Text("No SimDeviceType present in model data") - } - else { - ForEach(deviceTypes[0...2]) { - DeviceTypeView(deviceType: $0) - } - } - } -} diff --git a/SimDirs/Views/DeviceView.swift b/SimDirs/Views/DeviceView.swift deleted file mode 100644 index 2c84fe1..0000000 --- a/SimDirs/Views/DeviceView.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// DeviceView.swift -// SimDirs -// -// Created by Casey Fleser on 6/1/22. -// - -import SwiftUI - -extension SimDevice { - var contentView : AnyView? { return AnyView(DeviceView(device: self)) } -} - -struct DeviceView: View { - var device : SimDevice - - var body: some View { - VStack(alignment: .leading, spacing: 2.0) { - Group { - Text(device.isAvailable ? "Available" : "Unavailable") - .foregroundColor(device.isAvailable ? .green : .red) - if !device.isAvailable { - let errText = device.availabilityError ?? "Unknown Error" - - Text(errText) - .foregroundColor(.red) - .padding(.leading) - } - Text("State: \(device.state.rawValue)") - Text("UDID: \(device.udid)") - PathRow(title: "Data Path", path: device.dataPath) - PathRow(title: "Log Path", path: device.logPath) - } - .font(.subheadline) - .textSelection(.enabled) - .lineLimit(1) - } - } -} - -struct DeviceView_Previews: PreviewProvider { - static var previews: some View { - let devices = PresentationState.testItemsOf(type: SimDevice.self) - - if devices.isEmpty { - Text("No SimDevice present in model data") - } - else { - if let available = devices.first(where: { $0.isAvailable }) { - DeviceView(device: available) - } - if let unavailable = devices.first(where: { !$0.isAvailable }) { - DeviceView(device: unavailable) - } - } - } -} diff --git a/SimDirs/Views/ErrorView.swift b/SimDirs/Views/ErrorView.swift new file mode 100644 index 0000000..2a0e0b0 --- /dev/null +++ b/SimDirs/Views/ErrorView.swift @@ -0,0 +1,35 @@ +// +// ErrorView.swift +// SimDirs +// +// Created by Casey Fleser on 6/21/22. +// + +import SwiftUI + +struct ErrorView: View { + let title : String + let description : String + + var body: some View { + HStack(alignment: .top) { + Image(systemName: "xmark.octagon.fill") + .symbolRenderingMode(.multicolor) + VStack(alignment: .leading) { + Text(title) + .fontWeight(.semibold) + Text(description) + .foregroundColor(.secondary) + } + } + .padding(.bottom, 8.0) + } +} + +struct ErrorView_Previews: PreviewProvider { + static var previews: some View { + ErrorView( + title: "Something bad", + description: "Did you try turning it off and back on again?") + } +} diff --git a/SimDirs/Views/Model Views/AppContent.swift b/SimDirs/Views/Model Views/AppContent.swift new file mode 100644 index 0000000..9da76e4 --- /dev/null +++ b/SimDirs/Views/Model Views/AppContent.swift @@ -0,0 +1,43 @@ +// +// AppContent.swift +// SimDirs +// +// Created by Casey Fleser on 6/21/22. +// + +import SwiftUI + +extension SimApp { + public var content : some View { AppContent(app: self) } +} + +struct AppContent: View { + var app : SimApp + + var body: some View { + VStack(alignment: .leading, spacing: 0.0) { + Text("PATHS") + .fontWeight(.semibold) + .foregroundColor(.secondary) + + PathRow(title: "Bundle Path", path: app.bundlePath) + if let sandboxPath = app.sandboxPath { + PathRow(title: "Sandbox Path", path: sandboxPath) + } + else { + Text("Sandbox Path: ") + } + } + .font(.subheadline) + .lineLimit(1) + } +} + +struct AppContent_Previews: PreviewProvider { + static var apps = SimModel().apps + + static var previews: some View { + AppContent(app: apps[0]) + AppContent(app: apps.randomElement() ?? apps[1]) + } +} diff --git a/SimDirs/Views/Model Views/AppHeader.swift b/SimDirs/Views/Model Views/AppHeader.swift new file mode 100644 index 0000000..89a8cbe --- /dev/null +++ b/SimDirs/Views/Model Views/AppHeader.swift @@ -0,0 +1,36 @@ +// +// AppHeader.swift +// SimDirs +// +// Created by Casey Fleser on 6/21/22. +// + +import SwiftUI + +extension SimApp { + public var header : some View { AppHeader(app: self) } +} + +struct AppHeader: View { + var app : SimApp + + var body: some View { + VStack(alignment: .leading, spacing: 3.0) { + Text("Display Name: \(app.displayName)") + Text("Bundle Name: \(app.bundleName)") + Text("Bundle ID: \(app.bundleID)") + Text("Version: \(app.version)") + Text("Minimum OS Version: \(app.minOSVersion)") + } + .font(.subheadline) + } +} + +struct AppHeader_Previews: PreviewProvider { + static var apps = SimModel().apps + + static var previews: some View { + AppHeader(app: apps[0]) + AppHeader(app: apps.randomElement() ?? apps[1]) + } +} diff --git a/SimDirs/Views/Model Views/DeviceContent.swift b/SimDirs/Views/Model Views/DeviceContent.swift new file mode 100644 index 0000000..2711d92 --- /dev/null +++ b/SimDirs/Views/Model Views/DeviceContent.swift @@ -0,0 +1,46 @@ +// +// DeviceContent.swift +// SimDirs +// +// Created by Casey Fleser on 6/21/22. +// + +import SwiftUI + +extension SimDevice { + public var content : some View { DeviceContent(device: self) } +} + +struct DeviceContent: View { + var device : SimDevice + + var body: some View { + VStack(alignment: .leading, spacing: 3.0) { + Group { + if !device.isAvailable { + ErrorView( + title: "\(device.name) is unavailable", + description: device.availabilityError ?? "Unknown Error") + } + + Text("PATHS") + .fontWeight(.semibold) + .foregroundColor(.secondary) + PathRow(title: "Data Path", path: device.dataPath) + PathRow(title: "Log Path", path: device.logPath) + } + .font(.subheadline) + .textSelection(.enabled) + .lineLimit(1) + } + } +} + +struct DeviceContent_Previews: PreviewProvider { + static var devices = SimModel().devices + + static var previews: some View { + DeviceContent(device: devices[0]) + DeviceContent(device: devices.randomElement() ?? devices[1]) + } +} diff --git a/SimDirs/Views/Model Views/DeviceHeader.swift b/SimDirs/Views/Model Views/DeviceHeader.swift new file mode 100644 index 0000000..5dc1e24 --- /dev/null +++ b/SimDirs/Views/Model Views/DeviceHeader.swift @@ -0,0 +1,34 @@ +// +// DeviceHeader.swift +// SimDirs +// +// Created by Casey Fleser on 6/21/22. +// + +import SwiftUI + +extension SimDevice { + public var header : some View { DeviceHeader(device: self) } +} + +struct DeviceHeader: View { + var device : SimDevice + + var body: some View { + VStack(alignment: .leading, spacing: 3.0) { + Text("State: \(device.state.rawValue)") + Text("UDID: \(device.udid)") + } + .font(.subheadline) + .textSelection(.enabled) + } +} + +struct DeviceHeader_Previews: PreviewProvider { + static var devices = SimModel().devices + + static var previews: some View { + DeviceHeader(device: devices[0]) + DeviceHeader(device: devices.randomElement() ?? devices[1]) + } +} diff --git a/SimDirs/Views/Model Views/DeviceTypeContent.swift b/SimDirs/Views/Model Views/DeviceTypeContent.swift new file mode 100644 index 0000000..bc24769 --- /dev/null +++ b/SimDirs/Views/Model Views/DeviceTypeContent.swift @@ -0,0 +1,32 @@ +// +// DeviceTypeContent.swift +// SimDirs +// +// Created by Casey Fleser on 6/20/22. +// + +import SwiftUI + +extension SimDeviceType { + public var content : some View { DeviceTypeContent(deviceType: self) } +} + +struct DeviceTypeContent: View { + var deviceType : SimDeviceType + + var body: some View { + VStack(alignment: .leading, spacing: 0.0) { + PathRow(title: "Bundle Path", path: deviceType.bundlePath) + } + .font(.subheadline) + .lineLimit(1) + } +} + +struct DeviceTypeContent_Previews: PreviewProvider { + static var deviceTypes = SimModel().deviceTypes + + static var previews: some View { + DeviceTypeContent(deviceType: deviceTypes[0]) + } +} diff --git a/SimDirs/Views/Model Views/DeviceTypeHeader.swift b/SimDirs/Views/Model Views/DeviceTypeHeader.swift new file mode 100644 index 0000000..1057f4a --- /dev/null +++ b/SimDirs/Views/Model Views/DeviceTypeHeader.swift @@ -0,0 +1,36 @@ +// +// DeviceTypeHeader.swift +// SimDirs +// +// Created by Casey Fleser on 6/20/22. +// + +import SwiftUI + +extension SimDeviceType { + public var header : some View { DeviceTypeHeader(deviceType: self) } +} + +struct DeviceTypeHeader: View { + var deviceType : SimDeviceType + + var body: some View { + VStack(alignment: .leading, spacing: 3.0) { + Text("Product Family: \(deviceType.productFamily.title)") + Text("Model ID: \(deviceType.modelIdentifier)") + Text("Min Runtime: \(deviceType.minRuntimeVersionString)") + Text("Max Runtime: \(UInt32.max == deviceType.maxRuntimeVersion ? "-" : deviceType.maxRuntimeVersionString)") + Text("Identifier: \(deviceType.identifier)") + } + .font(.subheadline) + .textSelection(.enabled) + } +} + +struct DeviceTypeHeader_Previews: PreviewProvider { + static var deviceTypes = SimModel().deviceTypes + + static var previews: some View { + DeviceTypeHeader(deviceType: deviceTypes[0]) + } +} diff --git a/SimDirs/Views/Model Views/RuntimeContent.swift b/SimDirs/Views/Model Views/RuntimeContent.swift new file mode 100644 index 0000000..703adfa --- /dev/null +++ b/SimDirs/Views/Model Views/RuntimeContent.swift @@ -0,0 +1,61 @@ +// +// RuntimeContent.swift +// SimDirs +// +// Created by Casey Fleser on 6/20/22. +// + +import SwiftUI + +extension SimRuntime { + public var content : some View { RuntimeContent(runtime: self) } +} + +struct RuntimeContent: View { + struct SupportedItem: Identifiable { + let name : String + var id : String { return name } + } + + var runtime : SimRuntime + + var body: some View { + VStack(alignment: .leading, spacing: 3.0) { + let items = runtime.supportedDeviceTypes.map { SupportedItem(name: $0.name) } + + Group { + if !runtime.isAvailable { + ErrorView( + title: "\(runtime.name) is unavailable", + description: runtime.availabilityError ?? "Unknown Error") + } + + Text("PATHS") + .fontWeight(.semibold) + .foregroundColor(.secondary) + if !runtime.bundlePath.isEmpty { + PathRow(title: "Bundle Path", path: runtime.bundlePath) + } + + Text("SUPPORTED DEVICES \(runtime.isPlaceholder ? "(partial list)" : "")") + .fontWeight(.semibold) + .foregroundColor(.secondary) + .padding(.top, 8.0) + ForEach(items) { item in + Text("• \(item.name)") + } + .padding(.leading) + } + .font(.subheadline) + .textSelection(.enabled) + } + } +} + +struct RuntimeContent_Previews: PreviewProvider { + static var runtimes = SimModel().runtimes + + static var previews: some View { + RuntimeContent(runtime: runtimes[0]) + } +} diff --git a/SimDirs/Views/Model Views/RuntimeHeader.swift b/SimDirs/Views/Model Views/RuntimeHeader.swift new file mode 100644 index 0000000..00cdd33 --- /dev/null +++ b/SimDirs/Views/Model Views/RuntimeHeader.swift @@ -0,0 +1,34 @@ +// +// RuntimeHeader.swift +// SimDirs +// +// Created by Casey Fleser on 6/21/22. +// + +import SwiftUI + +extension SimRuntime { + public var header : some View { RuntimeHeader(runtime: self) } +} + +struct RuntimeHeader: View { + var runtime : SimRuntime + + var body: some View { + VStack(alignment: .leading, spacing: 3.0) { + if !runtime.buildversion.isEmpty { + Text("Build Version: \(runtime.buildversion)") + } + } + .font(.subheadline) + .textSelection(.enabled) + } +} + +struct RuntimeHeader_Previews: PreviewProvider { + static var runtimes = SimModel().runtimes + + static var previews: some View { + RuntimeContent(runtime: runtimes[0]) + } +} diff --git a/SimDirs/Views/PathActions.swift b/SimDirs/Views/PathActions.swift index dd5351a..3b22120 100644 --- a/SimDirs/Views/PathActions.swift +++ b/SimDirs/Views/PathActions.swift @@ -27,8 +27,6 @@ struct PathActions: View { .stroke(.white.opacity(0.4), lineWidth: 1.0)) .background(.black.opacity(0.4)) .cornerRadius(6.0) -// based on scenePhase? -// .shadow(color: .black.opacity(0.4), radius: 8.0, x: 4.0, y: 4.0) } } diff --git a/SimDirs/Views/PathRow.swift b/SimDirs/Views/PathRow.swift index cd58bd2..1097939 100644 --- a/SimDirs/Views/PathRow.swift +++ b/SimDirs/Views/PathRow.swift @@ -15,6 +15,7 @@ struct PathRow: View { HStack { Text("\(title): \(path)") .truncationMode(/*@START_MENU_TOKEN@*/.middle/*@END_MENU_TOKEN@*/) + Spacer() PathActions(path: path) } } diff --git a/SimDirs/Views/RuntimeView.swift b/SimDirs/Views/RuntimeView.swift deleted file mode 100644 index 1b0d2f9..0000000 --- a/SimDirs/Views/RuntimeView.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// RuntimeView.swift -// SimDirs -// -// Created by Casey Fleser on 5/30/22. -// - -import SwiftUI - -extension SimRuntime { - var contentView : AnyView? { return AnyView(RuntimeView(runtime: self)) } -} - -struct RuntimeView: View { - struct SupportedItem: Identifiable { - let name : String - var id : String { return name } - } - - @Environment(\.scenePhase) private var scenePhase - - var runtime : SimRuntime - - var body: some View { - VStack(alignment: .leading, spacing: 2.0) { - let items = runtime.supportedDeviceTypes.map({ SupportedItem(name: $0.name) }) - - Group { - Text(runtime.isAvailable ? "Available" : "Unavailable") - .foregroundColor(runtime.isAvailable ? .green : .red) - if !runtime.isAvailable { - let errText = runtime.availabilityError ?? "Unknown Error" - - Text(errText) - .foregroundColor(.red) - .padding(.leading) - } - - if !runtime.buildversion.isEmpty { - Text("Build Version: \(runtime.buildversion)") - } - - if !runtime.bundlePath.isEmpty { - PathRow(title: "Bundle Path", path: runtime.bundlePath) - } - - Divider() - .padding(.vertical, 4.0) - Text("Supported Devices\(runtime.isPlaceholder ? " (partial list)" : "")") - ForEach(items) { item in - Text("• \(item.name)") - } - .padding(.leading) - } - .font(.subheadline) - .textSelection(.enabled) - } - } -} - -struct RuntimeView_Previews: PreviewProvider { - static var previews: some View { - let runtimes = PresentationState.testItemsOf(type: SimRuntime.self) - - if runtimes.isEmpty { - Text("No SimRuntime present in model data") - } - else { - if let available = runtimes.first(where: { $0.isAvailable }) { - RuntimeView(runtime: available) - .previewLayout(.sizeThatFits) - } - if let unavailable = runtimes.first(where: { !$0.isAvailable }) { - RuntimeView(runtime: unavailable) - } - } - } -} diff --git a/SimDirs/Views/SimItemContent.swift b/SimDirs/Views/SimItemContent.swift deleted file mode 100644 index 9f30b15..0000000 --- a/SimDirs/Views/SimItemContent.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// SimItemContent.swift -// SimDirs -// -// Created by Casey Fleser on 5/30/22. -// - -import SwiftUI - -struct SimItemContent: View { - var item : PresentationItem - - var body: some View { - ScrollView { - VStack { - HStack { - item.contentView - Spacer() - } - Spacer() - } - .padding(.all) - .navigationTitle(item.navTitle) - } - } -} - -struct SimItemContent_Previews: PreviewProvider { - static var previews: some View { - let testItem = PresentationState().presentationItems(from: SimModel())[0] - - SimItemContent(item: testItem.children![0]) - } -} diff --git a/SimDirs/Views/SimItemGroup.swift b/SimDirs/Views/SimItemGroup.swift deleted file mode 100644 index 1831e9b..0000000 --- a/SimDirs/Views/SimItemGroup.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// SimItemGroup.swift -// SimDirs -// -// Created by Casey Fleser on 6/7/22. -// - -import SwiftUI - -struct SimItemGroup: View { - let item : PresentationItem - @Binding var state : PresentationState - @State private var isExpanded = false - - var children : [PresentationItem]? { - guard var items = item.children else { return nil } - - if state.filter.contains(.withApps) { - items = items.filter { $0.containsType(SimApp.self) } - } - if state.filter.contains(.runtimeInstalled) { - items = items.filter { - guard let runtime = $0.underlying as? SimRuntime else { return true } - - return runtime.isAvailable - } - } - if !state.searchTerm.isEmpty { - items = items.filter { $0.titlesContain(state.searchTerm) } - } - - return items.isEmpty ? nil : items - } - - var body: some View { - if let childItems = children { - DisclosureGroup( - isExpanded: $isExpanded, - content: { - ForEach(childItems) { childItem in - SimItemGroup(item: childItem, state: $state) - } - }, - label: { SimItemNavLink(item: item) } - ) - } - else { - SimItemNavLink(item: item) - } - } -} - -struct SimItemGroup_Previews: PreviewProvider { - static var simModel = SimModel() - @State static var state = PresentationState() - - static var previews: some View { - let testItem = state.presentationItems(from: simModel)[0] - - SimItemGroup(item: testItem, state: $state) - } -} diff --git a/SimDirs/Views/SimItemNavLink.swift b/SimDirs/Views/SimItemNavLink.swift deleted file mode 100644 index 46b7869..0000000 --- a/SimDirs/Views/SimItemNavLink.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// SimItemNavLink.swift -// SimDirs -// -// Created by Casey Fleser on 6/7/22. -// - -import SwiftUI - -struct SimItemNavLink: View { - let item : PresentationItem - - var body: some View { - NavigationLink { - SimItemContent(item: item) } label: { - SimItemRow(item: item) - } - } -} - -struct SimItemNavLink_Previews: PreviewProvider { - static var previews: some View { - let testItem = PresentationState().presentationItems(from: SimModel())[0] - - SimItemNavLink(item: testItem) - } -} diff --git a/SimDirs/Views/SimItemRow.swift b/SimDirs/Views/SimItemRow.swift deleted file mode 100644 index 3fdc86d..0000000 --- a/SimDirs/Views/SimItemRow.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// SimItemRow.swift -// SimDirs -// -// Created by Casey Fleser on 5/30/22. -// - -import SwiftUI - -struct SimItemRow: View { - var item : PresentationItem - var body: some View { - Label { - Text(item.title) - } icon: { - if let icon = item.icon { - Image(nsImage: icon) - .resizable() - .frame(maxWidth: 20.0, maxHeight: 20.0) - .cornerRadius(4.0) - } - else { - Image(systemName: item.imageName) - .foregroundColor(item.imageColor) - .symbolRenderingMode(.hierarchical) - } - } - } -} - -struct SimItemRow_Previews: PreviewProvider { - static var previews: some View { - let testItem = PresentationState().presentationItems(from: SimModel())[0] - - SimItemRow(item: testItem) - } -} diff --git a/SimDirs/Views/SourceItem Views/SourceItemContent.swift b/SimDirs/Views/SourceItem Views/SourceItemContent.swift new file mode 100644 index 0000000..a4cc994 --- /dev/null +++ b/SimDirs/Views/SourceItem Views/SourceItemContent.swift @@ -0,0 +1,67 @@ +// +// SourceItemContent.swift +// SimDirs +// +// Created by Casey Fleser on 6/20/22. +// + +import SwiftUI + +import SwiftUI + +struct SourceItemContent: View { + var item : Item + + var body: some View { + GeometryReader { geometry in + VStack(alignment: .leading, spacing: 0.0) { + // --- Header section --- + VStack(alignment: .leading) { + Text(item.headerTitle) + .font(.system(size: 20)) + .padding(.top, 12.0) + .padding(.bottom, 8.0) + item.header + .padding(.trailing, 136.0) + } + .padding([.leading, .trailing]) + .frame(maxWidth: .infinity, maxHeight: 144.0, alignment: .topLeading) + Rectangle().frame(height: 1.0).foregroundColor(Color("HeaderEdge")) + + // --- Content section --- + ScrollView { + HStack { + item.content + .padding(.top, 4.0) + .padding(.trailing) + Spacer() + } + } + .frame(maxWidth: .infinity) + .padding([.leading, .top]) + .background(.background) + } + .overlay( + SourceItemImage(imageDesc: item.imageDesc, isLabelImage: false) + .padding([.top, .trailing], 24.0), + alignment: .topTrailing + ) + .padding(.top, -geometry.frame(in: .global).origin.y) + } + .navigationTitle(item.title) + } +} + +struct SourceItemContent_Previews: PreviewProvider { + static var state = SourceState(model: SimModel()) + static var sampleItems = state.deviceStyleItems()[0...1] + + static var previews: some View { + ForEach(sampleItems) { item in + SourceItemContent(item: item) + .preferredColorScheme(.dark) + SourceItemContent(item: item) + .preferredColorScheme(.light) + } + } +} diff --git a/SimDirs/Views/SourceItem Views/SourceItemGroup.swift b/SimDirs/Views/SourceItem Views/SourceItemGroup.swift new file mode 100644 index 0000000..bf155c0 --- /dev/null +++ b/SimDirs/Views/SourceItem Views/SourceItemGroup.swift @@ -0,0 +1,42 @@ +// +// SourceItemGroup.swift +// SimDirs +// +// Created by Casey Fleser on 6/20/22. +// + +import SwiftUI + +struct SourceItemGroup: View { + @State private var isExpanded = false + @Binding var selection: UUID? + + var item : Item + + var body: some View { + if let childItems = item.children, childItems.count > 0 { + DisclosureGroup( + isExpanded: $isExpanded) { + ForEach(childItems) { childItem in + SourceItemGroup(selection: $selection, item: childItem) + } + } label: { + SourceItemLink(selection: $selection, item: item) + } + + } + else { + SourceItemLink(selection: $selection, item: item) + } + } +} + +struct SourceItemGroup_Previews: PreviewProvider { + @State static var selection : UUID? + static var state = SourceState(model: SimModel()) + static var sampleItem = state.deviceStyleItems()[0] + + static var previews: some View { + SourceItemGroup(selection: $selection, item: sampleItem) + } +} diff --git a/SimDirs/Views/SourceItem Views/SourceItemImage.swift b/SimDirs/Views/SourceItem Views/SourceItemImage.swift new file mode 100644 index 0000000..f325204 --- /dev/null +++ b/SimDirs/Views/SourceItem Views/SourceItemImage.swift @@ -0,0 +1,54 @@ +// +// SourceItemImage.swift +// SimDirs +// +// Created by Casey Fleser on 6/20/22. +// + +import SwiftUI + +struct SourceItemImage: View { + var imageDesc : SourceImageDesc + var isLabelImage = true + var imageSize : CGFloat? + + var body: some View { + let size = imageSize ?? (isLabelImage ? 20.0 : 128.0) + + switch imageDesc { + case let .icon(nsImage): + Image(nsImage: nsImage) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: size, maxHeight: size) + .cornerRadius(size / 5.0) + .shadow(radius: 4.0, x: 2.0, y: 2.0) + + case let .symbol(systemName, color): + if isLabelImage { + Image(systemName: systemName) + .foregroundColor(color) + .symbolRenderingMode(.hierarchical) + } + else { + Image(systemName: systemName) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(maxWidth: size, maxHeight: size) + .shadow(radius: 4.0, x: 2.0, y: 2.0) + } + } + } +} + +struct SourceItemImage_Previews: PreviewProvider { + static var state = SourceState(model: SimModel()) + static var sampleItems = state.deviceStyleItems()[0...1] + + static var previews: some View { + ForEach(sampleItems) { item in + SourceItemImage(imageDesc: item.imageDesc) + SourceItemImage(imageDesc: item.imageDesc, isLabelImage: false) + } + } +} diff --git a/SimDirs/Views/SourceItem Views/SourceItemLabel.swift b/SimDirs/Views/SourceItem Views/SourceItemLabel.swift new file mode 100644 index 0000000..c49c08f --- /dev/null +++ b/SimDirs/Views/SourceItem Views/SourceItemLabel.swift @@ -0,0 +1,31 @@ +// +// SourceItemLabel.swift +// SimDirs +// +// Created by Casey Fleser on 6/20/22. +// + +import SwiftUI + +struct SourceItemLabel: View { + var item : Item + + var body: some View { + Label( + title: { Text(item.title) }, + icon: { SourceItemImage(imageDesc: item.imageDesc) } + ) + } +} + +struct SourceItemLabel_Previews: PreviewProvider { + static var state = SourceState(model: SimModel()) + static var sampleItems = state.deviceStyleItems()[0...1] + + static var previews: some View { + ForEach(sampleItems) { item in + SourceItemLabel(item: item) + SourceItemLabel(item: item) + } + } +} diff --git a/SimDirs/Views/SourceItem Views/SourceItemLink.swift b/SimDirs/Views/SourceItem Views/SourceItemLink.swift new file mode 100644 index 0000000..10cb7be --- /dev/null +++ b/SimDirs/Views/SourceItem Views/SourceItemLink.swift @@ -0,0 +1,37 @@ +// +// SourceItemLink.swift +// SimDirs +// +// Created by Casey Fleser on 6/20/22. +// + +import SwiftUI + +struct SourceItemLink: View { + @Binding var selection: UUID? + + var item : Item + + var body: some View { + NavigationLink(tag: item.id, selection: $selection, + destination: { SourceItemContent(item: item) }, + label: { + SourceItemLabel(item: item) + .padding(.leading, 2.0) + } + ) + } +} + +struct SourceItemLink_Previews: PreviewProvider { + @State static var selection : UUID? + static var state = SourceState(model: SimModel()) + static var sampleItem = state.deviceStyleItems()[0] + + static var previews: some View { + SourceItemLink(selection: $selection, item: sampleItem) + .preferredColorScheme(.dark) + SourceItemLink(selection: $selection, item: sampleItem) + .preferredColorScheme(.light) + } +} diff --git a/SimDirs/Views/ToolbarMenu.swift b/SimDirs/Views/ToolbarMenu.swift index 3086575..201916a 100644 --- a/SimDirs/Views/ToolbarMenu.swift +++ b/SimDirs/Views/ToolbarMenu.swift @@ -8,27 +8,20 @@ import SwiftUI struct ToolbarMenu: View { - @Binding var state : PresentationState - - var withApps : Binding { - Binding(get: { state.filter.contains(.withApps) }, - set: { state.filter.booleanSet($0, options: .withApps) }) - } - var withRuntimes : Binding { - Binding(get: { state.filter.contains(.runtimeInstalled) }, - set: { state.filter.booleanSet($0, options: .runtimeInstalled) }) - } + @ObservedObject var state : SourceState var body: some View { Menu { - Picker("Organization", selection: $state.organization) { - ForEach(PresentationState.Organization.allCases) { style in - Text(style.rawValue).tag(style) + Picker("Style", selection: $state.style) { + ForEach(SourceState.Style.allCases) { style in + if style.visible { + Text(style.title).tag(style) + } } } .pickerStyle(.inline) - Toggle(isOn: withApps) { Label("With Apps", systemImage: "app.fill") } - Toggle(isOn: withRuntimes) { Label("Installed Runtimes", systemImage: "cpu.fill") } + Toggle(isOn: $state.filterApps) { Label("With Apps", systemImage: "app.fill") } + Toggle(isOn: $state.filterRuntimes) { Label("Installed Runtimes", systemImage: "cpu.fill") } } label: { Label("Filter", systemImage: "slider.horizontal.3") } @@ -36,10 +29,10 @@ struct ToolbarMenu: View { } struct ToolbarMenu_Previews: PreviewProvider { - @State static var state = PresentationState(filter: []) + static var state = SourceState(model: SimModel()) static var previews: some View { - ToolbarMenu(state: $state) + ToolbarMenu(state: state) } } diff --git a/screenshot.png b/screenshot.png index 56cddd5..b8b74ea 100644 Binary files a/screenshot.png and b/screenshot.png differ