mirror of
https://github.com/tw93/Mole.git
synced 2026-02-15 00:00:10 +00:00
feat: Add VS Code launch/task configurations and refactor app icon and resource management.
This commit is contained in:
210
app/PasswordSheetView.swift
Normal file
210
app/PasswordSheetView.swift
Normal file
@@ -0,0 +1,210 @@
|
||||
import AppKit
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - NSVisualEffectView bridge (Liquid Glass / blur)
|
||||
struct VisualEffectBlur: NSViewRepresentable {
|
||||
var material: NSVisualEffectView.Material = .hudWindow
|
||||
var blendingMode: NSVisualEffectView.BlendingMode = .withinWindow
|
||||
var state: NSVisualEffectView.State = .active
|
||||
|
||||
func makeNSView(context: Context) -> NSVisualEffectView {
|
||||
let v = NSVisualEffectView()
|
||||
v.material = material
|
||||
v.blendingMode = blendingMode
|
||||
v.state = state
|
||||
v.wantsLayer = true
|
||||
return v
|
||||
}
|
||||
|
||||
func updateNSView(_ nsView: NSVisualEffectView, context: Context) {
|
||||
nsView.material = material
|
||||
nsView.blendingMode = blendingMode
|
||||
nsView.state = state
|
||||
}
|
||||
}
|
||||
|
||||
struct PasswordSheetView: View {
|
||||
@State private var passwordInput = ""
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
var onUnlock: () -> Void
|
||||
@FocusState private var isFocused: Bool
|
||||
|
||||
private var accent: Color { Color.accentColor }
|
||||
|
||||
var body: some View {
|
||||
dialogCard
|
||||
.frame(width: 280)
|
||||
// Attempt to clear window background for true glass effect
|
||||
.background(ClearBackgroundView())
|
||||
}
|
||||
|
||||
private var dialogCard: some View {
|
||||
VStack(alignment: .leading, spacing: 14) {
|
||||
// Icon
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
if let icon = Bundle.module.image(forResource: "mole") {
|
||||
Image(nsImage: icon)
|
||||
.resizable()
|
||||
.interpolation(.high)
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 48, height: 48)
|
||||
.shadow(radius: 2)
|
||||
} else {
|
||||
Image(nsImage: NSApp.applicationIconImage)
|
||||
.resizable()
|
||||
.interpolation(.high)
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(width: 48, height: 48)
|
||||
.shadow(radius: 2)
|
||||
}
|
||||
}
|
||||
.padding(.leading, 6)
|
||||
|
||||
// Title + message
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
Text("Mole is trying to make changes.")
|
||||
.font(.system(size: 14, weight: .semibold))
|
||||
.foregroundStyle(.primary)
|
||||
|
||||
Text("Enter your password to allow this.")
|
||||
.font(.system(size: 12, weight: .regular))
|
||||
.foregroundStyle(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
|
||||
// Fields
|
||||
VStack(spacing: 12) {
|
||||
// Username field
|
||||
HStack {
|
||||
Text(NSUserName())
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
.foregroundStyle(.primary.opacity(0.85))
|
||||
Spacer()
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.frame(height: 36)
|
||||
.background(fieldBackground)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
||||
.stroke(.white.opacity(0.10), lineWidth: 1)
|
||||
)
|
||||
|
||||
// Password field
|
||||
ZStack(alignment: .leading) {
|
||||
SecureField("", text: $passwordInput)
|
||||
.textFieldStyle(.plain)
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
.focused($isFocused)
|
||||
.padding(.horizontal, 10)
|
||||
.frame(height: 36)
|
||||
.onSubmit(submit)
|
||||
|
||||
if passwordInput.isEmpty {
|
||||
Text("Password")
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
.foregroundStyle(.primary.opacity(0.35))
|
||||
.padding(.leading, 10)
|
||||
.allowsHitTesting(false)
|
||||
}
|
||||
}
|
||||
.background(fieldBackground)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
||||
.stroke(.white.opacity(0.10), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
.padding(.top, 2)
|
||||
|
||||
// Buttons
|
||||
HStack(spacing: 12) {
|
||||
Button("Cancel") {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
.foregroundStyle(.primary)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 32)
|
||||
.background(buttonGlassBackground)
|
||||
.clipShape(Capsule())
|
||||
.keyboardShortcut(.cancelAction)
|
||||
|
||||
Button("Allow") {
|
||||
submit()
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
.foregroundStyle(.white)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 32)
|
||||
.background(Color(nsColor: .controlAccentColor))
|
||||
.clipShape(Capsule())
|
||||
.keyboardShortcut(.defaultAction)
|
||||
.disabled(passwordInput.isEmpty)
|
||||
}
|
||||
.padding(.top, 4)
|
||||
}
|
||||
.padding(20)
|
||||
.background(glassBackground)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 24, style: .continuous))
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 24, style: .continuous)
|
||||
.stroke(.white.opacity(0.18), lineWidth: 1)
|
||||
)
|
||||
.shadow(color: .black.opacity(0.22), radius: 30, x: 0, y: 18)
|
||||
.onAppear {
|
||||
isFocused = true
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
}
|
||||
|
||||
private var glassBackground: some View {
|
||||
ZStack {
|
||||
VisualEffectBlur(material: .hudWindow, blendingMode: .withinWindow, state: .active)
|
||||
LinearGradient(
|
||||
colors: [.white.opacity(0.55), .white.opacity(0.22), .black.opacity(0.08)],
|
||||
startPoint: .top, endPoint: .bottom
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private var fieldBackground: some View {
|
||||
RoundedRectangle(cornerRadius: 12, style: .continuous)
|
||||
.fill(.black.opacity(0.10))
|
||||
.background(
|
||||
VisualEffectBlur(material: .sidebar, blendingMode: .withinWindow, state: .active)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
|
||||
.opacity(0.45)
|
||||
)
|
||||
}
|
||||
|
||||
private var buttonGlassBackground: some View {
|
||||
ZStack {
|
||||
VisualEffectBlur(material: .sidebar, blendingMode: .withinWindow, state: .active)
|
||||
LinearGradient(
|
||||
colors: [.white.opacity(0.25), .black.opacity(0.06)],
|
||||
startPoint: .top, endPoint: .bottom
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func submit() {
|
||||
guard !passwordInput.isEmpty else { return }
|
||||
AuthContext.shared.setPassword(passwordInput)
|
||||
onUnlock()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to reset window background for clean glass effect in sheets
|
||||
struct ClearBackgroundView: NSViewRepresentable {
|
||||
func makeNSView(context: Context) -> NSView {
|
||||
let view = NSView()
|
||||
DispatchQueue.main.async {
|
||||
view.window?.backgroundColor = .clear
|
||||
view.window?.isOpaque = false
|
||||
}
|
||||
return view
|
||||
}
|
||||
func updateNSView(_ nsView: NSView, context: Context) {}
|
||||
}
|
||||
Reference in New Issue
Block a user