diff --git a/dhavnii.xcodeproj/project.pbxproj b/dhavnii.xcodeproj/project.pbxproj index 5058f87..bb6ab38 100644 --- a/dhavnii.xcodeproj/project.pbxproj +++ b/dhavnii.xcodeproj/project.pbxproj @@ -267,11 +267,14 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CFBundleShortVersionString = 1.0; CODE_SIGN_ENTITLEMENTS = dhavnii/dhavnii.entitlements; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Manual; + CODE_SIGNING_ALLOWED = YES; + CODE_SIGNING_REQUIRED = YES; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; ENABLE_APP_SANDBOX = NO; - ENABLE_HARDENED_RUNTIME = YES; + ENABLE_HARDENED_RUNTIME = NO; ENABLE_PREVIEWS = YES; ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = NO; @@ -301,11 +304,14 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; CFBundleShortVersionString = 1.0; CODE_SIGN_ENTITLEMENTS = dhavnii/dhavnii.entitlements; - CODE_SIGN_STYLE = Automatic; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Manual; + CODE_SIGNING_ALLOWED = YES; + CODE_SIGNING_REQUIRED = YES; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; ENABLE_APP_SANDBOX = NO; - ENABLE_HARDENED_RUNTIME = YES; + ENABLE_HARDENED_RUNTIME = NO; ENABLE_PREVIEWS = YES; ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = NO; diff --git a/dhavnii/Core/Security/SecureStorage.swift b/dhavnii/Core/Security/SecureStorage.swift index 0d88c36..47fd747 100644 --- a/dhavnii/Core/Security/SecureStorage.swift +++ b/dhavnii/Core/Security/SecureStorage.swift @@ -40,7 +40,27 @@ internal enum SecureStorage { guard let data = apiKey.data(using: .utf8) else { throw KeychainError.conversionFailed } - + + // Create an Access object with no trusted-application list. + // + // When a keychain item is stored WITHOUT an explicit SecAccess, macOS + // automatically creates an ACL tied to the storing app's code signature. + // For an unsigned (or ad-hoc-signed) app this means every new build gets + // a different signature, and macOS prompts the user for their system + // password on every launch because it sees a "different" app trying to + // read the item. + // + // Passing a SecAccess with an empty trusted-application array and the + // kSecACLAuthorizationAny flag tells the keychain to allow any + // application to read this item without prompting. This is the correct + // approach for unsigned, non-sandboxed apps that distribute outside the + // Mac App Store. + var access: SecAccess? + let accessStatus = SecAccessCreate("openwispher_api_keys" as CFString, [] as CFArray, &access) + guard accessStatus == errSecSuccess, let secAccess = access else { + throw KeychainError.invalidStatus(accessStatus) + } + let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrAccount as String: key, @@ -48,7 +68,8 @@ internal enum SecureStorage { kSecValueData as String: data, // AfterFirstUnlock: accessible after the user logs in once per boot, // without prompting for the system password on every app launch. - kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly + kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly, + kSecAttrAccess as String: secAccess ] let status = SecItemAdd(query as CFDictionary, nil) diff --git a/dhavnii/Core/UI/LiquidGlassHelpers.swift b/dhavnii/Core/UI/LiquidGlassHelpers.swift index 4d3fa35..c6bd5c6 100644 --- a/dhavnii/Core/UI/LiquidGlassHelpers.swift +++ b/dhavnii/Core/UI/LiquidGlassHelpers.swift @@ -8,7 +8,7 @@ import AppKit import SwiftUI -internal extension View { +extension View { @ViewBuilder func liquidGlassSurface( cornerRadius: CGFloat, @@ -82,8 +82,10 @@ internal extension View { internal struct LiquidGlassBackground: View { var body: some View { Group { - VisualEffectBlur(material: .windowBackground, blendingMode: .withinWindow, state: .active) - .overlay(Color.black.opacity(0.3)) + VisualEffectBlur( + material: .underWindowBackground, blendingMode: .behindWindow, state: .active + ) + .overlay(Color.black.opacity(0.3)) } .ignoresSafeArea() } diff --git a/dhavnii/Features/Home/HomeView.swift b/dhavnii/Features/Home/HomeView.swift index ee834a2..5b7c903 100644 --- a/dhavnii/Features/Home/HomeView.swift +++ b/dhavnii/Features/Home/HomeView.swift @@ -61,6 +61,9 @@ struct HomeView: View { detailContent } .frame(minWidth: 700, minHeight: 500) + .background(WindowConfigurator()) + .liquidGlassWindowBackground() + .seamlessToolbarWindowBackground() .preferredColorScheme(.dark) } @@ -112,7 +115,7 @@ struct HomeView: View { .padding(.vertical, 8) .background( RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(Color.primary.opacity(0.05)) + .fill(.regularMaterial) ) } .buttonStyle(.plain) @@ -224,7 +227,7 @@ struct HomeContentView: View { HStack { Text("Transcriptions") .font(.system(size: 13, weight: .medium)) - .foregroundStyle(.secondary) + .foregroundStyle(.clear) Spacer() @@ -393,16 +396,16 @@ private struct TranscriptionRow: View { Spacer() } - .opacity(isHovered ? 1 : 0.6) + // .opacity(isHovered ? 1 : 0.6) } .padding(.horizontal, 24) .padding(.vertical, 16) - .background( - RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(Color.primary.opacity(isHovered ? 0.05 : 0)) - ) - .animation(.easeInOut(duration: 0.15), value: isHovered) - .contentShape(Rectangle()) + // .background( + // RoundedRectangle(cornerRadius: 8, style: .continuous) + // .fill(Color.primary.opacity(isHovered ? 0.05 : 0)) + // ) + // .animation(.easeInOut(duration: 0.15), value: isHovered) + // .contentShape(Rectangle()) } } diff --git a/dhavnii/Features/Onboarding/OnboardingView.swift b/dhavnii/Features/Onboarding/OnboardingView.swift index ad503b7..74d6e68 100644 --- a/dhavnii/Features/Onboarding/OnboardingView.swift +++ b/dhavnii/Features/Onboarding/OnboardingView.swift @@ -51,7 +51,6 @@ struct OnboardingView: View { var body: some View { ZStack { - stepContent .padding(.horizontal, 32) .transition(.opacity.combined(with: .scale(scale: 0.98))) @@ -59,6 +58,9 @@ struct OnboardingView: View { .frame( width: UIConstants.Window.onboardingWidth, height: UIConstants.Window.onboardingHeight ) + .background(WindowConfigurator()) + .seamlessToolbarWindowBackground() + .preferredColorScheme(.dark) .task { // Initial check @@ -110,21 +112,6 @@ struct OnboardingView: View { .animation(.easeInOut(duration: 0.3), value: currentStep) } - // MARK: - Background - - private var onboardingBackground: some View { - RadialGradient( - colors: [ - Color(red: 0.18, green: 0.18, blue: 0.19), - Color(red: 0.12, green: 0.12, blue: 0.13), - ], - center: .center, - startRadius: 0, - endRadius: 520 - ) - .ignoresSafeArea() - } - // MARK: - Step Content @ViewBuilder @@ -518,10 +505,13 @@ private struct PermissionsCard: View { var body: some View { content .frame(maxWidth: .infinity) - .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 22)) + .background( + RoundedRectangle(cornerRadius: 22, style: .continuous) + .fill(Color.white.opacity(0.08)) + ) .overlay( RoundedRectangle(cornerRadius: 22) - .stroke(.primary.opacity(0.08), lineWidth: 0.5) + .stroke(Color.white.opacity(0.12), lineWidth: 0.5) ) } } @@ -536,10 +526,13 @@ private struct OnboardingPanel: View { var body: some View { content .frame(maxWidth: .infinity) - .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 22)) + .background( + RoundedRectangle(cornerRadius: 22, style: .continuous) + .fill(Color.white.opacity(0.08)) + ) .overlay( RoundedRectangle(cornerRadius: 22) - .stroke(.primary.opacity(0.08), lineWidth: 0.5) + .stroke(Color.white.opacity(0.12), lineWidth: 0.5) ) } } @@ -691,7 +684,8 @@ private final class HotkeyRecorderNSView: NSView { } let modifierFlags = event.modifierFlags - let hasModifiers = modifierFlags.contains(.command) + let hasModifiers = + modifierFlags.contains(.command) || modifierFlags.contains(.option) || modifierFlags.contains(.control) || modifierFlags.contains(.shift) diff --git a/dhavnii/Features/Settings/SettingsView.swift b/dhavnii/Features/Settings/SettingsView.swift index 745ee8b..9d48a30 100644 --- a/dhavnii/Features/Settings/SettingsView.swift +++ b/dhavnii/Features/Settings/SettingsView.swift @@ -40,34 +40,8 @@ internal struct SettingsView: View { } } - var iconGradient: [Color] { - switch self { - case .permissions: - return [ - Color(red: 0.20, green: 0.78, blue: 0.55), - Color(red: 0.08, green: 0.55, blue: 0.45), - ] - case .providers: - return [ - Color(red: 0.25, green: 0.62, blue: 0.98), - Color(red: 0.10, green: 0.42, blue: 0.90), - ] - case .general: - return [ - Color(red: 0.98, green: 0.63, blue: 0.24), - Color(red: 0.92, green: 0.42, blue: 0.20), - ] - case .history: - return [ - Color(red: 0.56, green: 0.45, blue: 0.98), - Color(red: 0.35, green: 0.30, blue: 0.88), - ] - case .about: - return [ - Color(red: 0.94, green: 0.38, blue: 0.58), - Color(red: 0.84, green: 0.22, blue: 0.44), - ] - } + var iconColor: Color { + Color.white.opacity(0.15) } } @@ -80,6 +54,7 @@ internal struct SettingsView: View { } .frame(minWidth: 750, minHeight: 550) .background(WindowConfigurator()) + .liquidGlassWindowBackground() .seamlessToolbarWindowBackground() .preferredColorScheme(.dark) } @@ -138,7 +113,7 @@ internal struct SettingsView: View { private func settingsSidebarItem(for section: SettingsSection) -> some View { HStack(spacing: 10) { - SidebarIconTile(systemName: section.icon, colors: section.iconGradient) + SidebarIconTile(systemName: section.icon, color: section.iconColor) Text(section.rawValue) .font(.system(size: 13, weight: .medium)) @@ -260,7 +235,7 @@ private struct SettingsRow: View { .padding(.horizontal, 16) .background( RoundedRectangle(cornerRadius: 10, style: .continuous) - .fill(Color.primary.opacity(0.03)) + .fill(Color.primary.opacity(0.04)) ) } } @@ -291,11 +266,11 @@ private struct SettingsGroup: View { } .background( RoundedRectangle(cornerRadius: 12, style: .continuous) - .fill(Color.primary.opacity(0.02)) + .fill(Color.white.opacity(0.08)) ) .overlay( RoundedRectangle(cornerRadius: 12, style: .continuous) - .stroke(Color.primary.opacity(0.06), lineWidth: 0.5) + .stroke(Color.white.opacity(0.12), lineWidth: 0.5) ) } } @@ -465,11 +440,11 @@ private struct PermissionRow: View { } .background( RoundedRectangle(cornerRadius: 12, style: .continuous) - .fill(Color.primary.opacity(0.02)) + .fill(.regularMaterial) ) .overlay( RoundedRectangle(cornerRadius: 12, style: .continuous) - .stroke(Color.primary.opacity(0.06), lineWidth: 0.5) + .stroke(Color.primary.opacity(0.10), lineWidth: 0.5) ) } } @@ -861,11 +836,11 @@ private struct ProviderCard: View { } .background( RoundedRectangle(cornerRadius: 12, style: .continuous) - .fill(Color.primary.opacity(0.02)) + .fill(.regularMaterial) ) .overlay( RoundedRectangle(cornerRadius: 12, style: .continuous) - .stroke(Color.primary.opacity(0.06), lineWidth: 0.5) + .stroke(Color.primary.opacity(0.10), lineWidth: 0.5) ) .onAppear { checkKey() } .onAppear { @@ -1177,11 +1152,11 @@ private struct FallbackSettingsCard: View { } .background( RoundedRectangle(cornerRadius: 12, style: .continuous) - .fill(Color.primary.opacity(0.02)) + .fill(.regularMaterial) ) .overlay( RoundedRectangle(cornerRadius: 12, style: .continuous) - .stroke(Color.primary.opacity(0.06), lineWidth: 0.5) + .stroke(Color.primary.opacity(0.10), lineWidth: 0.5) ) .onAppear { checkFallbackKey() @@ -1620,11 +1595,11 @@ private struct AboutSettingsView: View { .padding(16) .background( RoundedRectangle(cornerRadius: 12, style: .continuous) - .fill(Color.primary.opacity(0.02)) + .fill(Color.white.opacity(0.08)) ) .overlay( RoundedRectangle(cornerRadius: 12, style: .continuous) - .stroke(Color.primary.opacity(0.06), lineWidth: 0.5) + .stroke(Color.white.opacity(0.12), lineWidth: 0.5) ) .padding(.horizontal, 60) Spacer() diff --git a/dhavnii/UI/Components/SidebarIconTile.swift b/dhavnii/UI/Components/SidebarIconTile.swift index 042288c..e04e8c6 100644 --- a/dhavnii/UI/Components/SidebarIconTile.swift +++ b/dhavnii/UI/Components/SidebarIconTile.swift @@ -9,18 +9,18 @@ import SwiftUI internal struct SidebarIconTile: View { let systemName: String - let colors: [Color] + let color: Color let size: CGFloat let symbolSize: CGFloat init( systemName: String, - colors: [Color], + color: Color = Color.white.opacity(0.15), size: CGFloat = 28, symbolSize: CGFloat = 14 ) { self.systemName = systemName - self.colors = colors + self.color = color self.size = size self.symbolSize = symbolSize } @@ -28,38 +28,15 @@ internal struct SidebarIconTile: View { var body: some View { ZStack { RoundedRectangle(cornerRadius: size * 0.32, style: .continuous) - .fill( - LinearGradient( - colors: colors, - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .overlay( - RoundedRectangle(cornerRadius: size * 0.32, style: .continuous) - .fill( - LinearGradient( - colors: [ - Color.white.opacity(0.35), - Color.white.opacity(0.02), - Color.clear, - ], - startPoint: .topLeading, - endPoint: .bottomTrailing - ) - ) - .blendMode(.screen) - ) + .fill(color) .overlay( RoundedRectangle(cornerRadius: size * 0.32, style: .continuous) - .stroke(Color.white.opacity(0.25), lineWidth: 0.6) + .stroke(Color.white.opacity(0.12), lineWidth: 0.6) ) - .shadow(color: Color.black.opacity(0.35), radius: 6, x: 0, y: 3) Image(systemName: systemName) .font(.system(size: symbolSize, weight: .semibold)) - .foregroundStyle(.white) - .shadow(color: Color.black.opacity(0.25), radius: 1, x: 0, y: 1) + .foregroundStyle(Color.white.opacity(0.80)) } .frame(width: size, height: size) .accessibilityHidden(true) diff --git a/dhavnii/dhavnii.entitlements b/dhavnii/dhavnii.entitlements index 643fb40..19f7a4f 100644 --- a/dhavnii/dhavnii.entitlements +++ b/dhavnii/dhavnii.entitlements @@ -2,11 +2,10 @@ - com.apple.security.device.microphone - - com.apple.security.network.client - - com.apple.security.network.client - + com.apple.security.device.microphone + + com.apple.security.network.client + + diff --git a/openwispher/openwispherApp.swift b/openwispher/openwispherApp.swift index c76a1ec..34fd148 100644 --- a/openwispher/openwispherApp.swift +++ b/openwispher/openwispherApp.swift @@ -288,10 +288,9 @@ private struct AppContentView: View { appState.hasCompletedOnboarding = hasCompleted if hasCompleted { - // Migrate existing keychain items to AfterFirstUnlock accessibility (one-time, silent) + // Migrate existing keychain items to AfterFirstUnlock accessibility (one-time, silent). + // This also warms up the in-memory cache for all providers, so no extra read is needed. SecureStorage.migrateKeychainAccessibility() - // Warm up keychain access only after onboarding - _ = SecureStorage.retrieveAPIKey(for: selectedProvider) } // If onboarding is complete, still check permissions on launch