|
|
@@ -10,6 +10,7 @@ struct LauncherRootView: View {
|
|
|
}
|
|
|
|
|
|
@EnvironmentObject private var premiumStore: PremiumStore
|
|
|
+ @Environment(\.colorScheme) private var colorScheme
|
|
|
@State private var query = ""
|
|
|
@State private var selectedApp: LauncherApp?
|
|
|
@State private var showingPremiumScreen = false
|
|
|
@@ -98,11 +99,14 @@ struct LauncherRootView: View {
|
|
|
|
|
|
var body: some View {
|
|
|
ZStack {
|
|
|
- VisualEffectBlur(material: .hudWindow, blendingMode: .behindWindow)
|
|
|
+ VisualEffectBlur(
|
|
|
+ material: colorScheme == .dark ? .hudWindow : .underWindowBackground,
|
|
|
+ blendingMode: .behindWindow
|
|
|
+ )
|
|
|
.ignoresSafeArea()
|
|
|
|
|
|
- Color.black.opacity(0.22)
|
|
|
- .ignoresSafeArea()
|
|
|
+ (colorScheme == .dark ? Color.black.opacity(0.22) : Color.white.opacity(0.28))
|
|
|
+ .ignoresSafeArea()
|
|
|
|
|
|
VStack(spacing: 16) {
|
|
|
SearchHeader(
|
|
|
@@ -237,13 +241,13 @@ struct LauncherRootView: View {
|
|
|
VStack(spacing: 10) {
|
|
|
Image(systemName: "magnifyingglass")
|
|
|
.font(.system(size: 28, weight: .semibold))
|
|
|
- .foregroundStyle(.white.opacity(0.75))
|
|
|
+ .foregroundStyle(.secondary)
|
|
|
Text("No apps found")
|
|
|
.font(.title3.weight(.semibold))
|
|
|
- .foregroundStyle(.white.opacity(0.9))
|
|
|
+ .foregroundStyle(.primary)
|
|
|
Text("Try a different search term.")
|
|
|
.font(.subheadline)
|
|
|
- .foregroundStyle(.white.opacity(0.7))
|
|
|
+ .foregroundStyle(.secondary)
|
|
|
}
|
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
|
} else {
|
|
|
@@ -543,26 +547,27 @@ private struct SearchHeader: View {
|
|
|
let shouldHighlightWidgets: Bool
|
|
|
let onRestoreApp: (UUID) -> Void
|
|
|
let onRestoreAll: () -> Void
|
|
|
+ @Environment(\.colorScheme) private var colorScheme
|
|
|
@State private var pulse = false
|
|
|
|
|
|
var body: some View {
|
|
|
HStack {
|
|
|
HStack(spacing: 10) {
|
|
|
Image(systemName: "magnifyingglass")
|
|
|
- .foregroundStyle(.white.opacity(0.82))
|
|
|
+ .foregroundStyle(secondaryForeground)
|
|
|
TextField("Search", text: $query)
|
|
|
.textFieldStyle(.plain)
|
|
|
- .foregroundStyle(.white.opacity(0.94))
|
|
|
+ .foregroundStyle(primaryForeground)
|
|
|
}
|
|
|
.padding(.horizontal, 12)
|
|
|
.padding(.vertical, 8)
|
|
|
.background(
|
|
|
RoundedRectangle(cornerRadius: 15, style: .continuous)
|
|
|
- .fill(Color.black.opacity(0.28))
|
|
|
+ .fill(searchBackground)
|
|
|
)
|
|
|
.overlay(
|
|
|
RoundedRectangle(cornerRadius: 15, style: .continuous)
|
|
|
- .stroke(Color.white.opacity(0.08), lineWidth: 1)
|
|
|
+ .stroke(searchStroke, lineWidth: 1)
|
|
|
)
|
|
|
|
|
|
Spacer(minLength: 8)
|
|
|
@@ -585,19 +590,19 @@ private struct SearchHeader: View {
|
|
|
.foregroundStyle(Color.blue.opacity(0.95))
|
|
|
}
|
|
|
}
|
|
|
- .foregroundStyle(.white.opacity(0.95))
|
|
|
+ .foregroundStyle(widgetButtonForeground)
|
|
|
.padding(.horizontal, 10)
|
|
|
.padding(.vertical, 6)
|
|
|
.background(
|
|
|
Capsule(style: .continuous)
|
|
|
- .fill(shouldHighlightWidgets ? Color.blue.opacity(0.82) : Color.white.opacity(0.12))
|
|
|
+ .fill(shouldHighlightWidgets ? Color.blue.opacity(0.82) : neutralButtonFill)
|
|
|
)
|
|
|
.overlay(
|
|
|
Capsule(style: .continuous)
|
|
|
.stroke(
|
|
|
shouldHighlightWidgets
|
|
|
? Color.white.opacity(0.95)
|
|
|
- : Color.white.opacity(0.15),
|
|
|
+ : neutralButtonStroke,
|
|
|
lineWidth: shouldHighlightWidgets ? 1.4 : 1
|
|
|
)
|
|
|
)
|
|
|
@@ -649,7 +654,7 @@ private struct SearchHeader: View {
|
|
|
.menuStyle(.borderlessButton)
|
|
|
}
|
|
|
.font(.system(size: 15, weight: .semibold))
|
|
|
- .foregroundStyle(.white.opacity(0.82))
|
|
|
+ .foregroundStyle(secondaryForeground)
|
|
|
.padding(.trailing, 4)
|
|
|
}
|
|
|
.onAppear {
|
|
|
@@ -659,6 +664,37 @@ private struct SearchHeader: View {
|
|
|
pulse = shouldHighlight
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ private var primaryForeground: Color {
|
|
|
+ colorScheme == .dark ? .white.opacity(0.94) : .primary.opacity(0.95)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var secondaryForeground: Color {
|
|
|
+ colorScheme == .dark ? .white.opacity(0.82) : .primary.opacity(0.72)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var searchBackground: Color {
|
|
|
+ colorScheme == .dark ? .black.opacity(0.28) : .white.opacity(0.72)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var searchStroke: Color {
|
|
|
+ colorScheme == .dark ? .white.opacity(0.08) : .black.opacity(0.07)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var neutralButtonFill: Color {
|
|
|
+ colorScheme == .dark ? .white.opacity(0.12) : .black.opacity(0.05)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var neutralButtonStroke: Color {
|
|
|
+ colorScheme == .dark ? .white.opacity(0.15) : .black.opacity(0.09)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var widgetButtonForeground: Color {
|
|
|
+ if shouldHighlightWidgets {
|
|
|
+ return .white.opacity(0.95)
|
|
|
+ }
|
|
|
+ return colorScheme == .dark ? .white.opacity(0.95) : .primary.opacity(0.88)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private struct IconPanelView: View {
|
|
|
@@ -699,6 +735,7 @@ private struct PanelAppRowView: View {
|
|
|
let onHideApp: (UUID) -> Void
|
|
|
|
|
|
@ObservedObject private var premiumStore = PremiumStore.shared
|
|
|
+ @Environment(\.colorScheme) private var colorScheme
|
|
|
@AppStorage("pinnedTileIDsData") private var pinnedTileIDsData = ""
|
|
|
@AppStorage("statusBarAppIDsData") private var statusBarAppIDsData = ""
|
|
|
@AppStorage("widgetAppIDsData") private var widgetAppIDsData = ""
|
|
|
@@ -749,7 +786,7 @@ private struct PanelAppRowView: View {
|
|
|
.animation(.spring(response: 0.22, dampingFraction: 0.82), value: isHovering)
|
|
|
Text(app.name)
|
|
|
.font(.system(size: 18, weight: .medium))
|
|
|
- .foregroundStyle(.white.opacity(0.94))
|
|
|
+ .foregroundStyle(titleColor)
|
|
|
.lineLimit(1)
|
|
|
.truncationMode(.tail)
|
|
|
Spacer(minLength: 0)
|
|
|
@@ -761,17 +798,17 @@ private struct PanelAppRowView: View {
|
|
|
if isPinned && !app.isCreateNew {
|
|
|
Image(systemName: "pin.fill")
|
|
|
.font(.system(size: 12, weight: .semibold))
|
|
|
- .foregroundStyle(.white.opacity(0.92))
|
|
|
+ .foregroundStyle(pinForegroundColor)
|
|
|
.frame(width: 26, height: 26)
|
|
|
.background(
|
|
|
Circle()
|
|
|
- .fill(Color.black.opacity(isHovering ? 0.35 : 0.2))
|
|
|
+ .fill(controlBackgroundColor)
|
|
|
)
|
|
|
.overlay(
|
|
|
Circle()
|
|
|
- .stroke(Color.white.opacity(isHovering ? 0.18 : 0.12), lineWidth: 1)
|
|
|
+ .stroke(controlStrokeColor, lineWidth: 1)
|
|
|
)
|
|
|
- .shadow(color: .black.opacity(0.22), radius: 10, x: 0, y: 6)
|
|
|
+ .shadow(color: controlShadowColor, radius: 10, x: 0, y: 6)
|
|
|
.padding(8)
|
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
|
.allowsHitTesting(false)
|
|
|
@@ -809,17 +846,17 @@ private struct PanelAppRowView: View {
|
|
|
Image(systemName: "ellipsis")
|
|
|
.font(.system(size: 13, weight: .semibold))
|
|
|
.rotationEffect(.degrees(90))
|
|
|
- .foregroundStyle(.white.opacity(isHovering ? 0.92 : 0.75))
|
|
|
+ .foregroundStyle(controlForegroundColor)
|
|
|
.frame(width: 26, height: 26)
|
|
|
.background(
|
|
|
Circle()
|
|
|
- .fill(Color.black.opacity(isHovering ? 0.35 : 0.2))
|
|
|
+ .fill(controlBackgroundColor)
|
|
|
)
|
|
|
.overlay(
|
|
|
Circle()
|
|
|
- .stroke(Color.white.opacity(isHovering ? 0.18 : 0.12), lineWidth: 1)
|
|
|
+ .stroke(controlStrokeColor, lineWidth: 1)
|
|
|
)
|
|
|
- .shadow(color: .black.opacity(0.25), radius: 10, x: 0, y: 6)
|
|
|
+ .shadow(color: controlShadowColor, radius: 10, x: 0, y: 6)
|
|
|
}
|
|
|
.menuStyle(.borderlessButton)
|
|
|
.modifier(HideMenuIndicatorIfAvailable())
|
|
|
@@ -834,14 +871,14 @@ private struct PanelAppRowView: View {
|
|
|
.frame(maxWidth: .infinity, minHeight: 60, alignment: .leading)
|
|
|
.background {
|
|
|
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
|
- .fill(Color.white.opacity(isHovering ? 0.18 : 0))
|
|
|
+ .fill(rowFillColor)
|
|
|
.overlay(
|
|
|
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
|
.stroke(
|
|
|
LinearGradient(
|
|
|
colors: [
|
|
|
- Color.white.opacity(isHovering ? 0.38 : 0),
|
|
|
- Color.white.opacity(isHovering ? 0.14 : 0),
|
|
|
+ rowGradientColorTop,
|
|
|
+ rowGradientColorBottom,
|
|
|
],
|
|
|
startPoint: .topLeading,
|
|
|
endPoint: .bottomTrailing
|
|
|
@@ -849,7 +886,7 @@ private struct PanelAppRowView: View {
|
|
|
lineWidth: 1
|
|
|
)
|
|
|
)
|
|
|
- .shadow(color: Color.black.opacity(isHovering ? 0.35 : 0), radius: isHovering ? 14 : 0, x: 0, y: 4)
|
|
|
+ .shadow(color: rowShadowColor, radius: isHovering ? 14 : 0, x: 0, y: 4)
|
|
|
.animation(.easeOut(duration: 0.14), value: isHovering)
|
|
|
}
|
|
|
.contentShape(Rectangle())
|
|
|
@@ -967,6 +1004,58 @@ private struct PanelAppRowView: View {
|
|
|
alert.alertStyle = .informational
|
|
|
alert.runModal()
|
|
|
}
|
|
|
+
|
|
|
+ private var titleColor: Color {
|
|
|
+ colorScheme == .dark ? .white.opacity(0.94) : .primary.opacity(0.94)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var pinForegroundColor: Color {
|
|
|
+ colorScheme == .dark ? .white.opacity(0.92) : .primary.opacity(0.88)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var controlForegroundColor: Color {
|
|
|
+ colorScheme == .dark
|
|
|
+ ? .white.opacity(isHovering ? 0.92 : 0.75)
|
|
|
+ : .primary.opacity(isHovering ? 0.90 : 0.68)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var controlBackgroundColor: Color {
|
|
|
+ colorScheme == .dark
|
|
|
+ ? .black.opacity(isHovering ? 0.35 : 0.2)
|
|
|
+ : .black.opacity(isHovering ? 0.08 : 0.04)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var controlStrokeColor: Color {
|
|
|
+ colorScheme == .dark
|
|
|
+ ? .white.opacity(isHovering ? 0.18 : 0.12)
|
|
|
+ : .black.opacity(isHovering ? 0.12 : 0.07)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var controlShadowColor: Color {
|
|
|
+ .black.opacity(colorScheme == .dark ? 0.22 : 0.05)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var rowFillColor: Color {
|
|
|
+ colorScheme == .dark
|
|
|
+ ? .white.opacity(isHovering ? 0.18 : 0)
|
|
|
+ : .white.opacity(isHovering ? 0.60 : 0)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var rowGradientColorTop: Color {
|
|
|
+ colorScheme == .dark
|
|
|
+ ? .white.opacity(isHovering ? 0.38 : 0)
|
|
|
+ : .black.opacity(isHovering ? 0.10 : 0)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var rowGradientColorBottom: Color {
|
|
|
+ colorScheme == .dark
|
|
|
+ ? .white.opacity(isHovering ? 0.14 : 0)
|
|
|
+ : .black.opacity(isHovering ? 0.05 : 0)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var rowShadowColor: Color {
|
|
|
+ .black.opacity(colorScheme == .dark ? (isHovering ? 0.35 : 0) : (isHovering ? 0.06 : 0))
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private struct PanelQuickActionButton: View {
|
|
|
@@ -976,6 +1065,7 @@ private struct PanelQuickActionButton: View {
|
|
|
let action: WidgetAction
|
|
|
let onTap: () -> Void
|
|
|
|
|
|
+ @Environment(\.colorScheme) private var colorScheme
|
|
|
@State private var isHovering = false
|
|
|
|
|
|
private var highlight: Bool { isHovering }
|
|
|
@@ -984,37 +1074,37 @@ private struct PanelQuickActionButton: View {
|
|
|
Button(action: onTap) {
|
|
|
Image(systemName: action.systemImage)
|
|
|
.font(.system(size: Self.iconPointSize, weight: .semibold))
|
|
|
- .foregroundStyle(.white.opacity(0.82))
|
|
|
+ .foregroundStyle(buttonIconColor)
|
|
|
.frame(width: Self.circleDiameter, height: Self.circleDiameter)
|
|
|
.background(
|
|
|
Circle()
|
|
|
- .fill(Color.black.opacity(highlight ? 0.48 : 0.22))
|
|
|
+ .fill(buttonBackgroundColor)
|
|
|
)
|
|
|
.overlay(
|
|
|
Circle()
|
|
|
- .stroke(Color.white.opacity(highlight ? 0.18 : 0.10), lineWidth: 1)
|
|
|
+ .stroke(buttonStrokeColor, lineWidth: 1)
|
|
|
)
|
|
|
- .shadow(color: .black.opacity(highlight ? 0.28 : 0.20), radius: highlight ? 12 : 8, x: 0, y: 4)
|
|
|
+ .shadow(color: buttonShadowColor, radius: highlight ? 12 : 8, x: 0, y: 4)
|
|
|
}
|
|
|
.buttonStyle(.plain)
|
|
|
.animation(.spring(response: 0.28, dampingFraction: 0.85), value: highlight)
|
|
|
.overlay(alignment: .top) {
|
|
|
Text(action.title)
|
|
|
.font(.system(size: 10, weight: .semibold))
|
|
|
- .foregroundStyle(.white.opacity(0.96))
|
|
|
+ .foregroundStyle(tooltipForeground)
|
|
|
.lineLimit(1)
|
|
|
.fixedSize(horizontal: true, vertical: false)
|
|
|
.padding(.horizontal, 7)
|
|
|
.padding(.vertical, 3)
|
|
|
.background(
|
|
|
Capsule(style: .continuous)
|
|
|
- .fill(Color.black.opacity(0.78))
|
|
|
+ .fill(tooltipBackground)
|
|
|
)
|
|
|
.overlay(
|
|
|
Capsule(style: .continuous)
|
|
|
- .stroke(Color.white.opacity(0.12), lineWidth: 1)
|
|
|
+ .stroke(tooltipStroke, lineWidth: 1)
|
|
|
)
|
|
|
- .shadow(color: .black.opacity(0.32), radius: 7, x: 0, y: 3)
|
|
|
+ .shadow(color: tooltipShadow, radius: 7, x: 0, y: 3)
|
|
|
.offset(y: -(Self.circleDiameter / 2 + 14))
|
|
|
.opacity(isHovering ? 1 : 0)
|
|
|
.scaleEffect(isHovering ? 1 : 0.88, anchor: .bottom)
|
|
|
@@ -1025,15 +1115,52 @@ private struct PanelQuickActionButton: View {
|
|
|
.onHover { isHovering = $0 }
|
|
|
.accessibilityLabel(action.title)
|
|
|
}
|
|
|
+
|
|
|
+ private var buttonIconColor: Color {
|
|
|
+ colorScheme == .dark ? .white.opacity(0.82) : .primary.opacity(0.82)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var buttonBackgroundColor: Color {
|
|
|
+ colorScheme == .dark
|
|
|
+ ? .black.opacity(highlight ? 0.48 : 0.22)
|
|
|
+ : .black.opacity(highlight ? 0.11 : 0.05)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var buttonStrokeColor: Color {
|
|
|
+ colorScheme == .dark
|
|
|
+ ? .white.opacity(highlight ? 0.18 : 0.10)
|
|
|
+ : .black.opacity(highlight ? 0.14 : 0.08)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var buttonShadowColor: Color {
|
|
|
+ .black.opacity(colorScheme == .dark ? (highlight ? 0.28 : 0.20) : (highlight ? 0.10 : 0.04))
|
|
|
+ }
|
|
|
+
|
|
|
+ private var tooltipForeground: Color {
|
|
|
+ colorScheme == .dark ? .white.opacity(0.96) : .primary.opacity(0.92)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var tooltipBackground: Color {
|
|
|
+ colorScheme == .dark ? .black.opacity(0.78) : .white.opacity(0.97)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var tooltipStroke: Color {
|
|
|
+ colorScheme == .dark ? .white.opacity(0.12) : .black.opacity(0.08)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var tooltipShadow: Color {
|
|
|
+ .black.opacity(colorScheme == .dark ? 0.32 : 0.06)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private struct AppIconGlyph: View {
|
|
|
let app: LauncherApp
|
|
|
+ @Environment(\.colorScheme) private var colorScheme
|
|
|
|
|
|
var body: some View {
|
|
|
ZStack {
|
|
|
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
|
- .fill(Color.white.opacity(0.08))
|
|
|
+ .fill(colorScheme == .dark ? Color.white.opacity(0.08) : Color.black.opacity(0.06))
|
|
|
.frame(width: 48, height: 48)
|
|
|
|
|
|
if let webURL = app.webURL {
|
|
|
@@ -1052,7 +1179,7 @@ private struct AppIconGlyph: View {
|
|
|
} else {
|
|
|
Image(systemName: app.fallbackSymbolName)
|
|
|
.font(.system(size: 20, weight: .semibold))
|
|
|
- .foregroundStyle(.white)
|
|
|
+ .foregroundStyle(colorScheme == .dark ? .white : .primary)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -1085,6 +1212,7 @@ private struct PromoBanner: View {
|
|
|
let onCloseTap: () -> Void
|
|
|
let onUpgradeTap: () -> Void
|
|
|
let onManageSubscriptionTap: () -> Void
|
|
|
+ @Environment(\.colorScheme) private var colorScheme
|
|
|
|
|
|
var body: some View {
|
|
|
HStack(spacing: 10) {
|
|
|
@@ -1149,11 +1277,11 @@ private struct PromoBanner: View {
|
|
|
.padding(.vertical, 8)
|
|
|
.background(
|
|
|
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
|
- .fill(Color.white.opacity(0.03))
|
|
|
+ .fill(colorScheme == .dark ? Color.white.opacity(0.03) : Color.white.opacity(0.72))
|
|
|
)
|
|
|
.overlay(
|
|
|
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
|
|
- .stroke(Color.white.opacity(0.05), lineWidth: 1)
|
|
|
+ .stroke(colorScheme == .dark ? Color.white.opacity(0.05) : Color.black.opacity(0.06), lineWidth: 1)
|
|
|
)
|
|
|
}
|
|
|
}
|