Skip to content

Commit

Permalink
update page overhaul and fix ui inconsistencies
Browse files Browse the repository at this point in the history
  • Loading branch information
ejbills committed Jul 4, 2024
1 parent 1613f9a commit 21f9815
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 70 deletions.
Binary file not shown.
67 changes: 34 additions & 33 deletions DockDoor/Views/HoverWindow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -337,12 +337,14 @@ struct HoverView: View {
Image(nsImage: appIcon)
.resizable()
.scaledToFit()
.zIndex(1)
.frame(width: 24, height: 24)
} else {
ProgressView()
.frame(width: 24, height: 24)
}
Text(appName)
.lineLimit(1)
.padding(3)
.fontWeight(.medium)
.font(.system(size: 14))
Expand Down Expand Up @@ -462,7 +464,7 @@ struct WindowPreview: View {
return CGSize(width: targetWidth, height: targetHeight)
}

private func windowContent(isMinimized: Bool) -> some View {
private func windowContent(isMinimized: Bool, isSelected: Bool) -> some View {
Group {
if isMinimized {
let width = maxWindowDimension.x > 300 ? maxWindowDimension.x : 300
Expand Down Expand Up @@ -496,6 +498,11 @@ struct WindowPreview: View {
.aspectRatio(contentMode: .fill)
}
}
.overlay { if isSelected { FluidGradient(blobs: [.purple, .blue, .green, .yellow, .red, .purple].shuffled(),
highlights: [.red, .orange, .pink, .blue, .purple].shuffled(),
speed: 0.45,
blur: 0.75).opacity(0.125)
}}
.frame(width: isMinimized ? nil : calculatedSize.width, height: isMinimized ? nil : calculatedSize.height, alignment: .center)
.frame(maxWidth: calculatedMaxDimensions?.width, maxHeight: calculatedMaxDimensions?.height)
}
Expand All @@ -505,19 +512,32 @@ struct WindowPreview: View {
let selected = isHovering || isHighlighted

ZStack(alignment: .topTrailing) {
windowContent(isMinimized: windowInfo.isMinimized)
.overlay { if selected { FluidGradient(blobs: [.purple, .blue, .green, .yellow, .red, .purple].shuffled(),
highlights: [.red, .orange, .pink, .blue, .purple].shuffled(),
speed: 0.45,
blur: 0.75).opacity(0.125)
}}
.overlay { Color.white.opacity(isHoveringOverTabMenu ? 0.1 : 0) }
.clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous))
.shadow(radius: selected || isHoveringOverTabMenu ? 0 : 3)
.background {
RoundedRectangle(cornerRadius: 6, style: .continuous)
.fill(Color.clear.shadow(.drop(color: .black.opacity(selected ? 0.35 : 0.25), radius: selected ? 12 : 8, y: selected ? 6 : 4)))
VStack(spacing: 0) {
windowContent(isMinimized: windowInfo.isMinimized, isSelected: selected)
.overlay { Color.white.opacity(isHoveringOverTabMenu ? 0.1 : 0) }
.clipShape(RoundedRectangle(cornerRadius: 6, style: .continuous))
.shadow(radius: selected || isHoveringOverTabMenu ? 0 : 3)
.background {
RoundedRectangle(cornerRadius: 6, style: .continuous)
.fill(Color.clear.shadow(.drop(color: .black.opacity(selected ? 0.35 : 0.25), radius: selected ? 12 : 8, y: selected ? 6 : 4)))
}
}
.overlay(alignment: .bottomLeading) {
if selected, let windowTitle = windowInfo.window?.title, !windowTitle.isEmpty, windowTitle != windowInfo.appName {
let maxLabelWidth = calculatedSize.width - 50
let stringMeasurementWidth = measureString(windowTitle, fontSize: 12).width + 5
let width = maxLabelWidth > stringMeasurementWidth ? stringMeasurementWidth : maxLabelWidth

TheMarquee(width: width, secsBeforeLooping: 3, speedPtsPerSec: 20, nonMovingAlignment: .leading) {
Text(windowInfo.windowName ?? "Hidden window")
.font(.system(size: 12, weight: .medium))
.foregroundStyle(.primary)
}
.padding(4)
.dockStyle(cornerRadius: 6)
.padding(4)
}
}

if !windowInfo.isMinimized, let closeButton = windowInfo.closeButton {
VStack(alignment: .leading) {
Expand Down Expand Up @@ -586,26 +606,7 @@ struct WindowPreview: View {

Spacer()
}
.padding(6)

Spacer()

HStack {
if selected, let windowTitle = windowInfo.window?.title, !windowTitle.isEmpty, windowTitle != windowInfo.appName {
let maxLabelWidth = calculatedSize.width - 150
let stringMeasurementWidth = measureString(windowTitle, fontSize: 12).width + 5
let width = maxLabelWidth > stringMeasurementWidth ? stringMeasurementWidth : maxLabelWidth

TheMarquee(width: width, secsBeforeLooping: 3, speedPtsPerSec: 20, nonMovingAlignment: .leading) {
Text(windowInfo.windowName ?? "Hidden window")
.font(.system(size: 12, weight: .medium))
.foregroundStyle(.primary)
}
.padding(4)
.dockStyle(cornerRadius: 6)
}
}
.padding(6)
.padding(4)
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions DockDoor/Views/Settings/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,6 @@ struct SizePickerView: View {

var body: some View {
VStack(spacing: 20) {
Slider(value: $windowPadding, in: -200...200, step: 20) {
Text("Window Buffer (adjust if hover window is misaligned with dock)")
}.buttonStyle(PlainButtonStyle())

Picker("Window Size", selection: $sizingMultiplier) {
ForEach(2...10, id: \.self) { size in
Text(getLabel(for: CGFloat(size))).tag(CGFloat(size))
Expand All @@ -84,6 +80,10 @@ struct SizePickerView: View {
.onChange(of: sizingMultiplier) { _, newValue in
HoverWindow.shared.windowSize = getWindowSize()
}

Slider(value: $windowPadding, in: -200...200, step: 20) {
Text("Window Buffer (if misaligned with dock)")
}.buttonStyle(PlainButtonStyle())
}
}

Expand Down
121 changes: 89 additions & 32 deletions DockDoor/Views/Settings/UpdateView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,54 +8,111 @@
import SwiftUI
import Sparkle

// This view model class publishes when new updates can be checked by the user
final class CheckForUpdatesViewModel: ObservableObject {
final class UpdaterViewModel: ObservableObject {
@Published var canCheckForUpdates = false

@Published var lastUpdateCheckDate: Date?
@Published var currentVersion: String
@Published var isAutomaticChecksEnabled: Bool
@Published var updateStatus: UpdateStatus = .noUpdates

private let updater: SPUUpdater

enum UpdateStatus {
case noUpdates
case checking
case available(version: String)
case error(String)
}

init(updater: SPUUpdater) {
self.updater = updater
self.currentVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "Unknown"
self.isAutomaticChecksEnabled = updater.automaticallyChecksForUpdates

updater.publisher(for: \.canCheckForUpdates)
.assign(to: &$canCheckForUpdates)

updater.publisher(for: \.lastUpdateCheckDate)
.assign(to: &$lastUpdateCheckDate)
}

func checkForUpdates() {
updateStatus = .checking
updater.checkForUpdates()
}

func toggleAutomaticChecks() {
isAutomaticChecksEnabled.toggle()
updater.automaticallyChecksForUpdates = isAutomaticChecksEnabled
}
}

// This is the view for the Check for Updates menu item
// Note this intermediate view is necessary for the disabled state on the menu item to work properly before Monterey.
// See https://stackoverflow.com/questions/68553092/menu-not-updating-swiftui-bug for more info
struct CheckForUpdatesView: View {
@ObservedObject private var checkForUpdatesViewModel: CheckForUpdatesViewModel
private let updater: SPUUpdater
struct UpdateView: View {
@StateObject private var viewModel: UpdaterViewModel

init(updater: SPUUpdater) {
self.updater = updater

// Create our view model for our CheckForUpdatesView
self.checkForUpdatesViewModel = CheckForUpdatesViewModel(updater: updater)
_viewModel = StateObject(wrappedValue: UpdaterViewModel(updater: updater))
}

var body: some View {
VStack(alignment: .leading, spacing: 20) {
HStack {
Image(systemName: checkForUpdatesViewModel.canCheckForUpdates ? "checkmark.circle.fill" : "xmark.circle.fill")
.foregroundColor(checkForUpdatesViewModel.canCheckForUpdates ? .green : .red)
.scaleEffect(checkForUpdatesViewModel.canCheckForUpdates ? 1.2 : 1.0)
.padding(10)
HStack {
updateStatusView
Spacer()

Divider()

Spacer()
VStack(alignment: .leading) {
VStack(alignment: .leading) {
Text("Current Version: \(viewModel.currentVersion)")
if let lastCheck = viewModel.lastUpdateCheckDate {
Text("Last checked: \(lastCheck, formatter: dateFormatter)")
}
}

Divider()
Button(action: viewModel.checkForUpdates) {
Label("Check for Updates", systemImage: "arrow.triangle.2.circlepath")
}
.disabled(!viewModel.canCheckForUpdates)

Text("Can check for updates")
.font(.headline)
Toggle("Automatically check for updates", isOn: $viewModel.isAutomaticChecksEnabled)
.onChange(of: viewModel.isAutomaticChecksEnabled) { _, _ in
viewModel.toggleAutomaticChecks()
}
}

Button(action: updater.checkForUpdates) {
HStack {
Image(systemName: "arrow.triangle.2.circlepath")
Text("Check for Updates…")
}
.padding()
.frame(width: 600)
}

private var updateStatusView: some View {
Group {
switch viewModel.updateStatus {
case .noUpdates:
Label("Up to date", systemImage: "checkmark.circle.fill")
.foregroundColor(.green)
case .checking:
ProgressView()
.scaleEffect(0.7)
case .available(let version):
VStack {
Label("Update available", systemImage: "arrow.down.circle.fill")
.foregroundColor(.blue)
Text("Version \(version)")
.font(.caption)
}
case .error(let message):
Label(message, systemImage: "exclamationmark.triangle.fill")
.foregroundColor(.red)
}
.buttonStyle(.bordered)
.disabled(!checkForUpdatesViewModel.canCheckForUpdates)

Spacer()
}
.padding(20)
.frame(minWidth: 600)
}

private let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
return formatter
}()
}
2 changes: 1 addition & 1 deletion DockDoor/Views/Settings/settings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func UpdatesSettingsViewController(updater: SPUUpdater) -> SettingsPane {
title: "Updates",
toolbarIcon: NSImage(systemSymbolName: "arrow.triangle.2.circlepath", accessibilityDescription: "Update settings")!
) {
CheckForUpdatesView(updater: updater)
UpdateView(updater: updater)
}

return Settings.PaneHostingController(pane: paneView)
Expand Down

0 comments on commit 21f9815

Please sign in to comment.