diff --git a/Xcodes/AppStoreButtonStyle.swift b/Xcodes/AppStoreButtonStyle.swift index 172063e..c22f16d 100644 --- a/Xcodes/AppStoreButtonStyle.swift +++ b/Xcodes/AppStoreButtonStyle.swift @@ -4,59 +4,70 @@ struct AppStoreButtonStyle: ButtonStyle { var installed: Bool var highlighted: Bool - var textColor: Color { - if installed { - if highlighted { - return Color.white - } - else { - return Color.secondary - } - } - else { - if highlighted { - return Color.accentColor - } - else { - return Color.white - } - } - } - - func background(isPressed: Bool) -> some View { - Group { + private struct AppStoreButton: View { + var configuration: ButtonStyle.Configuration + var installed: Bool + var highlighted: Bool + // This seems to magically help the highlight colors update on time + @SwiftUI.Environment(\.isFocused) var isFocused + + var textColor: Color { if installed { - EmptyView() - } else { - Capsule() - .fill( - highlighted ? - Color.white : - Color.accentColor - ) - .brightness(isPressed ? -0.25 : 0) + if highlighted { + return Color.white + } + else { + return Color.secondary + } } + else { + if highlighted { + return Color.accentColor + } + else { + return Color.white + } + } + } + + func background(isPressed: Bool) -> some View { + Group { + if installed { + EmptyView() + } else { + Capsule() + .fill( + highlighted ? + Color.white : + Color.accentColor + ) + .brightness(isPressed ? -0.25 : 0) + } + } + } + var body: some View { + configuration.label + .font(Font.caption.weight(.medium)) + .foregroundColor(textColor) + .padding(EdgeInsets(top: 2, leading: 8, bottom: 2, trailing: 8)) + .frame(minWidth: 80) + .background(background(isPressed: configuration.isPressed)) + .padding(1) } } - func makeBody(configuration: Configuration) -> some View { - configuration.label - .font(Font.caption.weight(.medium)) - .foregroundColor(textColor) - .padding(EdgeInsets(top: 2, leading: 8, bottom: 2, trailing: 8)) - .frame(minWidth: 80) - .background(background(isPressed: configuration.isPressed)) - .padding(1) + func makeBody(configuration: ButtonStyle.Configuration) -> some View { + AppStoreButton(configuration: configuration, installed: installed, highlighted: highlighted) } } struct AppStoreButtonStyle_Previews: PreviewProvider { static var previews: some View { Group { - Button("INSTALL", action: {}) + Button("INSTALLED", action: {}) .buttonStyle(AppStoreButtonStyle(installed: true, highlighted: false)) .padding() - Button("UNINSTALLED", action: {}) + Button("INSTALL", action: {}) .buttonStyle(AppStoreButtonStyle(installed: false, highlighted: false)) .padding() } diff --git a/Xcodes/ContentView.swift b/Xcodes/ContentView.swift index 0f96606..ff4048b 100644 --- a/Xcodes/ContentView.swift +++ b/Xcodes/ContentView.swift @@ -4,12 +4,34 @@ import Version import PromiseKit struct ContentView: View { - @ObservedObject var appState = AppState() + @EnvironmentObject var appState: AppState @State private var selection = Set() @State private var rowBeingConfirmedForUninstallation: AppState.XcodeVersion? - + @State private var category: Category = .all + @State private var searchText: String = "" + + var visibleVersions: [AppState.XcodeVersion] { + var versions: [AppState.XcodeVersion] + switch category { + case .all: + versions = appState.allVersions + case .installed: + versions = appState.allVersions.filter { $0.installed } + } + + if !searchText.isEmpty { + versions = versions.filter { $0.title.contains(searchText) } + } + + return versions + } + + enum Category { + case all, installed + } + var body: some View { - List(appState.allVersions, selection: $selection) { row in + List(visibleVersions, selection: $selection) { row in VStack(alignment: .leading) { HStack { Text(row.title) @@ -22,32 +44,13 @@ struct ContentView: View { Button(row.installed ? "INSTALLED" : "INSTALL") { print("Installing...") } - .buttonStyle(AppStoreButtonStyle(installed: row.installed, + .buttonStyle(AppStoreButtonStyle(installed: row.installed, highlighted: self.selection.contains(row.id))) .disabled(row.installed) } Text(verbatim: row.path ?? "") .font(.caption) .foregroundColor(self.selection.contains(row.id) ? Color(NSColor.selectedMenuItemTextColor) : Color(NSColor.secondaryLabelColor)) - // if row.installed { - // HStack { - // Button(action: { row.installed ? self.rowBeingConfirmedForUninstallation = row : self.appState.install(id: row.id) }) { - // Text("Uninstall") - // } - // Button(action: { self.appState.reveal(id: row.id) }) { - // Text("Reveal in Finder") - // } - // Button(action: { self.appState.select(id: row.id) }) { - // Text("Select") - // } - // } - // .buttonStyle(PlainButtonStyle()) - // .foregroundColor( - // self.selection.contains(row.id) ? - // Color(NSColor.selectedMenuItemTextColor) : - // .accentColor - // ) - // } } .contextMenu { Button(action: { row.installed ? self.rowBeingConfirmedForUninstallation = row : self.appState.install(id: row.id) }) { @@ -63,16 +66,31 @@ struct ContentView: View { } } } - .frame(minWidth: 200, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity) - .onAppear(perform: appState.load) .toolbar { - ToolbarItem { - Button(action: { appState.update().cauterize() }) { + ToolbarItem(placement: .primaryAction) { + Button(action: { self.appState.update() }) { Image(systemName: "arrow.clockwise") } - .keyboardShortcut("r") + .keyboardShortcut(KeyEquivalent("r")) + } + ToolbarItem(placement: .principal) { + Picker("", selection: $category) { + Text("All") + .tag(Category.all) + Text("Installed") + .tag(Category.installed) + } + .pickerStyle(SegmentedPickerStyle()) + } + ToolbarItem { + TextField("Search...", text: $searchText) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .frame(width: 200) } } + .navigationSubtitle(Text("Updated \(Date().addingTimeInterval(-600), style: .relative) ago")) + .frame(minWidth: 200, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity) + .onAppear(perform: appState.load) .alert(item: $appState.error) { error in Alert(title: Text(error.title), message: Text(verbatim: error.message), @@ -84,15 +102,28 @@ struct ContentView: View { primaryButton: .destructive(Text("Uninstall"), action: { self.appState.uninstall(id: row.id) }), secondaryButton: .cancel(Text("Cancel"))) } - .sheet(isPresented: $appState.presentingSignInAlert, content: { + .sheet(isPresented: $appState.presentingSignInAlert) { SignInCredentialsView(isPresented: $appState.presentingSignInAlert) .environmentObject(appState) - }) + } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { - ContentView() + Group { + ContentView() + .environmentObject({ () -> AppState in + let a = AppState() + a.allVersions = [ + AppState.XcodeVersion(title: "12.3", installState: .installed, selected: true, path: nil), + AppState.XcodeVersion(title: "12.2", installState: .notInstalled, selected: false, path: nil), + AppState.XcodeVersion(title: "12.1", installState: .notInstalled, selected: false, path: nil), + AppState.XcodeVersion(title: "12.0", installState: .installed, selected: false, path: nil), + ] + return a + }()) + } + .previewLayout(.sizeThatFits) } } diff --git a/Xcodes/SignIn/SignInCredentialsView.swift b/Xcodes/SignIn/SignInCredentialsView.swift index 2d2f004..f1de68b 100644 --- a/Xcodes/SignIn/SignInCredentialsView.swift +++ b/Xcodes/SignIn/SignInCredentialsView.swift @@ -7,27 +7,30 @@ struct SignInCredentialsView: View { @State private var password: String = "" var body: some View { - VStack { + VStack(alignment: .leading) { + Text("Sign in with your Apple ID.") + .bold() + .padding(.vertical) HStack { - Text("Apple ID") - TextField("Apple ID", text: $username) + Text("Apple ID:") + .frame(minWidth: 100, alignment: .trailing) + TextField("example@icloud.com", text: $username) + .frame(width: 250) + } + HStack { + Text("Password:") + .frame(minWidth: 100, alignment: .trailing) + SecureField("Required", text: $password) + .frame(width: 250) } HStack { - Text("Password") - SecureField("Password", text: $password) - } - - HStack { - Button("Cancel") { - isPresented = false - } - .keyboardShortcut(.cancelAction) Spacer() - Button("Sign In") { - appState.continueLogin(username: username, password: password) - } - .keyboardShortcut(.defaultAction) + Button("Cancel") { isPresented = false } + .keyboardShortcut(.cancelAction) + Button("Next") { appState.continueLogin(username: username, password: password) } + .disabled(username.isEmpty) + .keyboardShortcut(.defaultAction) } } .padding() diff --git a/Xcodes/XcodesApp.swift b/Xcodes/XcodesApp.swift index ca2d78c..5a913a7 100644 --- a/Xcodes/XcodesApp.swift +++ b/Xcodes/XcodesApp.swift @@ -1,11 +1,15 @@ -import Cocoa import SwiftUI @main struct XcodesApp: App { + @StateObject private var appState = AppState() + var body: some Scene { - WindowGroup { - ContentView() + Group { + WindowGroup("Xcodes") { + ContentView() + .environmentObject(appState) + } } } }