Skip to content

Commit 3747f21

Browse files
committed
Major and minor version collapsable lists implemented
1 parent 6a0e521 commit 3747f21

7 files changed

Lines changed: 490 additions & 9 deletions

File tree

Xcodes.xcodeproj/project.pbxproj

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
05AB7F172E76C96E007C5CFE /* XcodeMajorVersionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05AB7F162E76C96E007C5CFE /* XcodeMajorVersionRow.swift */; };
11+
05AB7F192E76CD8C007C5CFE /* XcodeMinorVersionRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05AB7F182E76CD8C007C5CFE /* XcodeMinorVersionRow.swift */; };
1012
15F5B8902CCF09B900705E2F /* CryptoKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 15F5B88F2CCF09B900705E2F /* CryptoKit.framework */; };
1113
33027E342CA8C18800CB387C /* LibFido2Swift in Frameworks */ = {isa = PBXBuildFile; productRef = 334A932B2CA885A400A5E079 /* LibFido2Swift */; };
1214
3328073F2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3328073E2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift */; };
@@ -192,6 +194,8 @@
192194
/* End PBXCopyFilesBuildPhase section */
193195

194196
/* Begin PBXFileReference section */
197+
05AB7F162E76C96E007C5CFE /* XcodeMajorVersionRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeMajorVersionRow.swift; sourceTree = "<group>"; };
198+
05AB7F182E76CD8C007C5CFE /* XcodeMinorVersionRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XcodeMinorVersionRow.swift; sourceTree = "<group>"; };
195199
15F5B88F2CCF09B900705E2F /* CryptoKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CryptoKit.framework; path = System/Library/Frameworks/CryptoKit.framework; sourceTree = SDKROOT; };
196200
3328073E2CA5E2C80036F691 /* SignInSecurityKeyPinView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInSecurityKeyPinView.swift; sourceTree = "<group>"; };
197201
332807402CA5EA820036F691 /* SignInSecurityKeyTouchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInSecurityKeyTouchView.swift; sourceTree = "<group>"; };
@@ -476,6 +480,8 @@
476480
CA44901E2463AD34003D8213 /* Tag.swift */,
477481
CAE42486259A68A300B8B246 /* XcodeListCategory.swift */,
478482
CAD2E7A32449574E00113D76 /* XcodeListView.swift */,
483+
05AB7F162E76C96E007C5CFE /* XcodeMajorVersionRow.swift */,
484+
05AB7F182E76CD8C007C5CFE /* XcodeMinorVersionRow.swift */,
479485
CAFFFED7259CDA5000903F81 /* XcodeListViewRow.swift */,
480486
E8D0296E284B029800647641 /* BottomStatusBar.swift */,
481487
);
@@ -856,6 +862,7 @@
856862
" -p \"${SRCROOT}/Xcodes.xcodeproj\" \\",
857863
" -o \"${SRCROOT}/Xcodes/Resources/Licenses.rtf\"",
858864
"",
865+
"",
859866
);
860867
};
861868
/* End PBXShellScriptBuildPhase section */
@@ -880,6 +887,7 @@
880887
CAFBDC4E2599B33D003DCC5A /* MainToolbar.swift in Sources */,
881888
CA11E7BA2598476C00D2EE1C /* XcodeCommands.swift in Sources */,
882889
CAA8589B25A2B83000ACF8C0 /* Aria2CError.swift in Sources */,
890+
05AB7F192E76CD8C007C5CFE /* XcodeMinorVersionRow.swift in Sources */,
883891
536CFDD2263C94DE00026CE0 /* SignedInView.swift in Sources */,
884892
CABFAA492593162500380FEE /* Bundle+InfoPlistValues.swift in Sources */,
885893
CA9FF8662595130600E47BAF /* View+IsHidden.swift in Sources */,
@@ -918,6 +926,7 @@
918926
CA452BB0259FD9770072DFA4 /* ProgressIndicator.swift in Sources */,
919927
B0403CF02AD92D7B00137C09 /* ReleaseNotesView.swift in Sources */,
920928
CAFE4AB425B7D3AF0064FE51 /* AdvancedPreferencePane.swift in Sources */,
929+
05AB7F172E76C96E007C5CFE /* XcodeMajorVersionRow.swift in Sources */,
921930
CA9FF84E2595079F00E47BAF /* ScrollingTextView.swift in Sources */,
922931
E832EAF82B0FBCF4001B570D /* RuntimeInstallationStepDetailView.swift in Sources */,
923932
CABFA9C12592EEEA00380FEE /* Version+.swift in Sources */,
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict/>
5+
</plist>

Xcodes/Backend/Xcode.swift

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,5 +85,97 @@ struct Xcode: Identifiable, CustomStringConvertible {
8585
return nil
8686
}
8787
}
88-
88+
89+
}
90+
91+
struct XcodeMinorVersionGroup: Identifiable {
92+
let majorVersion: Int
93+
let minorVersion: Int
94+
let versions: [Xcode]
95+
var isExpanded: Bool = false
96+
97+
var id: String {
98+
"\(majorVersion).\(minorVersion)"
99+
}
100+
101+
var latestRelease: Xcode? {
102+
versions
103+
.filter { $0.version.isNotPrerelease }
104+
.sorted { $0.version < $1.version }
105+
.last
106+
}
107+
108+
var displayName: String {
109+
"\(majorVersion).\(minorVersion)"
110+
}
111+
112+
var hasInstalled: Bool {
113+
versions.contains { $0.installState.installed }
114+
}
115+
116+
var hasInstalling: Bool {
117+
versions.contains { $0.installState.installing }
118+
}
119+
120+
var selectedVersion: Xcode? {
121+
versions.first { $0.selected }
122+
}
123+
}
124+
125+
struct XcodeMajorVersionGroup: Identifiable {
126+
let majorVersion: Int
127+
let minorVersionGroups: [XcodeMinorVersionGroup]
128+
var isExpanded: Bool = false
129+
130+
var id: Int {
131+
majorVersion
132+
}
133+
134+
var versions: [Xcode] {
135+
minorVersionGroups.flatMap { $0.versions }
136+
}
137+
138+
var latestRelease: Xcode? {
139+
versions
140+
.filter { $0.version.isNotPrerelease }
141+
.sorted { $0.version < $1.version }
142+
.last
143+
}
144+
145+
var displayName: String {
146+
"\(majorVersion)"
147+
}
148+
149+
var hasInstalled: Bool {
150+
minorVersionGroups.contains { $0.hasInstalled }
151+
}
152+
153+
var hasInstalling: Bool {
154+
minorVersionGroups.contains { $0.hasInstalling }
155+
}
156+
157+
var selectedVersion: Xcode? {
158+
minorVersionGroups.compactMap { $0.selectedVersion }.first
159+
}
160+
}
161+
162+
extension Array where Element == Xcode {
163+
func groupedByMajorVersion() -> [XcodeMajorVersionGroup] {
164+
let majorGroups = Dictionary(grouping: self) { $0.version.major }
165+
return majorGroups.map { majorVersion, xcodes in
166+
let minorGroups = Dictionary(grouping: xcodes) { $0.version.minor }
167+
let minorVersionGroups = minorGroups.map { minorVersion, minorXcodes in
168+
XcodeMinorVersionGroup(
169+
majorVersion: majorVersion,
170+
minorVersion: minorVersion,
171+
versions: minorXcodes.sorted { $0.version > $1.version }
172+
)
173+
}.sorted { $0.minorVersion > $1.minorVersion }
174+
175+
return XcodeMajorVersionGroup(
176+
majorVersion: majorVersion,
177+
minorVersionGroups: minorVersionGroups
178+
)
179+
}.sorted { $0.majorVersion > $1.majorVersion }
180+
}
89181
}

Xcodes/Frontend/XcodeList/XcodeListView.swift

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ struct XcodeListView: View {
1010
private let architecture: XcodeListArchitecture
1111
private let isInstalledOnly: Bool
1212
@AppStorage(PreferenceKey.allowedMajorVersions.rawValue) private var allowedMajorVersions = Int.max
13+
@State private var expandedMajorVersions = Set<Int>()
14+
@State private var expandedMinorVersions = Set<String>()
1315

1416
init(selectedXcodeID: Binding<Xcode.ID?>, searchText: String, category: XcodeListCategory, isInstalledOnly: Bool, architecture: XcodeListArchitecture) {
1517
self._selectedXcodeID = selectedXcodeID
@@ -29,12 +31,12 @@ struct XcodeListView: View {
2931
case .beta:
3032
xcodes = appState.allXcodes.filter { $0.version.isPrerelease }
3133
}
32-
34+
3335
if architecture == .appleSilicon {
3436
xcodes = xcodes.filter { $0.architectures == [.arm64] }
3537
}
36-
37-
38+
39+
3840
let latestMajor = xcodes.sorted(\.version)
3941
.filter { $0.version.isNotPrerelease }
4042
.last?
@@ -54,17 +56,69 @@ struct XcodeListView: View {
5456
if !searchText.isEmpty {
5557
xcodes = xcodes.filter { $0.description.contains(searchText) }
5658
}
57-
59+
5860
if isInstalledOnly {
5961
xcodes = xcodes.filter { $0.installState.installed }
6062
}
61-
63+
6264
return xcodes
6365
}
66+
67+
var majorVersionGroups: [XcodeMajorVersionGroup] {
68+
visibleXcodes.groupedByMajorVersion()
69+
}
6470

6571
var body: some View {
66-
List(visibleXcodes, selection: $selectedXcodeID) { xcode in
67-
XcodeListViewRow(xcode: xcode, selected: selectedXcodeID == xcode.id, appState: appState)
72+
List(selection: $selectedXcodeID) {
73+
ForEach(majorVersionGroups) { majorVersionGroup in
74+
let isMajorExpanded = expandedMajorVersions.contains(majorVersionGroup.majorVersion)
75+
76+
XcodeMajorVersionRow(
77+
majorVersionGroup: majorVersionGroup,
78+
isExpanded: isMajorExpanded,
79+
onToggleExpanded: {
80+
if isMajorExpanded {
81+
expandedMajorVersions.remove(majorVersionGroup.majorVersion)
82+
// Collapse all minor versions when major version is collapsed
83+
for minorGroup in majorVersionGroup.minorVersionGroups {
84+
expandedMinorVersions.remove(minorGroup.id)
85+
}
86+
} else {
87+
expandedMajorVersions.insert(majorVersionGroup.majorVersion)
88+
}
89+
},
90+
appState: appState
91+
)
92+
.tag(majorVersionGroup.selectedVersion?.id)
93+
94+
if isMajorExpanded {
95+
ForEach(majorVersionGroup.minorVersionGroups) { minorVersionGroup in
96+
let isMinorExpanded = expandedMinorVersions.contains(minorVersionGroup.id)
97+
98+
XcodeMinorVersionRow(
99+
minorVersionGroup: minorVersionGroup,
100+
isExpanded: isMinorExpanded,
101+
onToggleExpanded: {
102+
if isMinorExpanded {
103+
expandedMinorVersions.remove(minorVersionGroup.id)
104+
} else {
105+
expandedMinorVersions.insert(minorVersionGroup.id)
106+
}
107+
},
108+
appState: appState
109+
)
110+
.tag(minorVersionGroup.selectedVersion?.id)
111+
112+
if isMinorExpanded {
113+
ForEach(minorVersionGroup.versions) { xcode in
114+
XcodeListViewRow(xcode: xcode, selected: selectedXcodeID == xcode.id, appState: appState)
115+
.padding(.leading, 40)
116+
.tag(xcode.id)
117+
}
118+
}
119+
}
120+
}
121+
}
68122
}
69123
.listStyle(.sidebar)
70124
.safeAreaInset(edge: .bottom, spacing: 0) {

0 commit comments

Comments
 (0)