From ab9d6ad8303bc43e8431fe7cc3b91d8808872759 Mon Sep 17 00:00:00 2001 From: Beingpax Date: Sat, 6 Dec 2025 08:18:35 +0545 Subject: [PATCH] Add option to dismiss Affiliate promotion --- VoiceInk.xcodeproj/project.pbxproj | 8 +- VoiceInk/Services/UserDefaultsManager.swift | 9 +- .../Metrics/DashboardPromotionsSection.swift | 86 +++++++++++-------- 3 files changed, 61 insertions(+), 42 deletions(-) diff --git a/VoiceInk.xcodeproj/project.pbxproj b/VoiceInk.xcodeproj/project.pbxproj index 3cf1cd5..d17ed92 100644 --- a/VoiceInk.xcodeproj/project.pbxproj +++ b/VoiceInk.xcodeproj/project.pbxproj @@ -469,7 +469,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 163; + CURRENT_PROJECT_VERSION = 164; DEVELOPMENT_ASSET_PATHS = "\"VoiceInk/Preview Content\""; DEVELOPMENT_TEAM = V6J6A3VWY2; ENABLE_HARDENED_RUNTIME = YES; @@ -484,7 +484,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 1.63; + MARKETING_VERSION = 1.64; PRODUCT_BUNDLE_IDENTIFIER = com.prakashjoshipax.VoiceInk; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG ENABLE_NATIVE_SPEECH_ANALYZER $(inherited)"; @@ -503,7 +503,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 163; + CURRENT_PROJECT_VERSION = 164; DEVELOPMENT_ASSET_PATHS = "\"VoiceInk/Preview Content\""; DEVELOPMENT_TEAM = V6J6A3VWY2; ENABLE_HARDENED_RUNTIME = YES; @@ -518,7 +518,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 1.63; + MARKETING_VERSION = 1.64; PRODUCT_BUNDLE_IDENTIFIER = com.prakashjoshipax.VoiceInk; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "ENABLE_NATIVE_SPEECH_ANALYZER $(inherited)"; diff --git a/VoiceInk/Services/UserDefaultsManager.swift b/VoiceInk/Services/UserDefaultsManager.swift index 4315a11..88a9895 100644 --- a/VoiceInk/Services/UserDefaultsManager.swift +++ b/VoiceInk/Services/UserDefaultsManager.swift @@ -8,7 +8,8 @@ extension UserDefaults { static let audioInputMode = "audioInputMode" static let selectedAudioDeviceUID = "selectedAudioDeviceUID" static let prioritizedDevices = "prioritizedDevices" - + static let affiliatePromotionDismissed = "VoiceInkAffiliatePromotionDismissed" + // Obfuscated keys for license-related data enum License { static let trialStartDate = "VoiceInkTrialStartDate" @@ -72,4 +73,10 @@ extension UserDefaults { get { data(forKey: Keys.prioritizedDevices) } set { setValue(newValue, forKey: Keys.prioritizedDevices) } } + + // MARK: - Affiliate Promotion Dismissal + var affiliatePromotionDismissed: Bool { + get { bool(forKey: Keys.affiliatePromotionDismissed) } + set { setValue(newValue, forKey: Keys.affiliatePromotionDismissed) } + } } \ No newline at end of file diff --git a/VoiceInk/Views/Metrics/DashboardPromotionsSection.swift b/VoiceInk/Views/Metrics/DashboardPromotionsSection.swift index afe0cd4..739f9e3 100644 --- a/VoiceInk/Views/Metrics/DashboardPromotionsSection.swift +++ b/VoiceInk/Views/Metrics/DashboardPromotionsSection.swift @@ -3,21 +3,22 @@ import AppKit struct DashboardPromotionsSection: View { let licenseState: LicenseViewModel.LicenseState - + @State private var isAffiliatePromotionDismissed: Bool = UserDefaults.standard.affiliatePromotionDismissed + private var shouldShowUpgradePromotion: Bool { switch licenseState { case .trial(let daysRemaining): - return daysRemaining <= 2 + return daysRemaining <= 3 case .trialExpired: return true case .licensed: return false } } - + private var shouldShowAffiliatePromotion: Bool { if case .licensed = licenseState { - return true + return !isAffiliatePromotionDismissed } return false } @@ -52,7 +53,8 @@ struct DashboardPromotionsSection: View { glowColor: Color(red: 0.08, green: 0.48, blue: 0.85), actionTitle: "Explore Affiliate", actionIcon: "arrow.up.right", - action: openAffiliateProgram + action: openAffiliateProgram, + onDismiss: dismissAffiliatePromotion ) .frame(maxWidth: .infinity) } @@ -74,6 +76,13 @@ struct DashboardPromotionsSection: View { NSWorkspace.shared.open(url) } } + + private func dismissAffiliatePromotion() { + withAnimation(.easeInOut(duration: 0.3)) { + isAffiliatePromotionDismissed = true + } + UserDefaults.standard.affiliatePromotionDismissed = true + } } private struct DashboardPromotionCard: View { @@ -85,6 +94,7 @@ private struct DashboardPromotionCard: View { let actionTitle: String let actionIcon: String let action: () -> Void + var onDismiss: (() -> Void)? = nil private static let defaultGradient: LinearGradient = LinearGradient( colors: [ @@ -96,8 +106,8 @@ private struct DashboardPromotionCard: View { ) var body: some View { - VStack(alignment: .leading, spacing: 14) { - HStack(alignment: .top) { + ZStack(alignment: .topTrailing) { + VStack(alignment: .leading, spacing: 14) { Text(badge.uppercased()) .font(.system(size: 11, weight: .heavy)) .tracking(0.8) @@ -107,42 +117,44 @@ private struct DashboardPromotionCard: View { .clipShape(Capsule()) .foregroundColor(.white) - Spacer() + Text(title) + .font(.system(size: 20, weight: .heavy, design: .rounded)) + .foregroundColor(.white) + .fixedSize(horizontal: false, vertical: true) - Image(systemName: accentSymbol) - .font(.system(size: 20, weight: .bold)) + Text(message) + .font(.system(size: 13, weight: .medium)) .foregroundColor(.white.opacity(0.85)) - .padding(10) - .background(.white.opacity(0.18)) - .clipShape(RoundedRectangle(cornerRadius: 14, style: .continuous)) - } + .fixedSize(horizontal: false, vertical: true) - Text(title) - .font(.system(size: 20, weight: .heavy, design: .rounded)) - .foregroundColor(.white) - .fixedSize(horizontal: false, vertical: true) - - Text(message) - .font(.system(size: 13, weight: .medium)) - .foregroundColor(.white.opacity(0.85)) - .fixedSize(horizontal: false, vertical: true) - - Button(action: action) { - HStack(spacing: 6) { - Text(actionTitle) - Image(systemName: actionIcon) + Button(action: action) { + HStack(spacing: 6) { + Text(actionTitle) + Image(systemName: actionIcon) + } + .font(.system(size: 13, weight: .semibold)) + .padding(.horizontal, 16) + .padding(.vertical, 9) + .background(.white.opacity(0.22)) + .clipShape(Capsule()) + .foregroundColor(.white) } - .font(.system(size: 13, weight: .semibold)) - .padding(.horizontal, 16) - .padding(.vertical, 9) - .background(.white.opacity(0.22)) - .clipShape(Capsule()) - .foregroundColor(.white) + .buttonStyle(.plain) + } + .padding(18) + .frame(maxWidth: .infinity, alignment: .topLeading) + + if let onDismiss = onDismiss { + Button(action: onDismiss) { + Image(systemName: "xmark.circle.fill") + .font(.system(size: 18, weight: .medium)) + .foregroundColor(.white.opacity(0.7)) + } + .buttonStyle(.plain) + .padding(12) + .help("Dismiss this promotion") } - .buttonStyle(.plain) } - .padding(18) - .frame(maxWidth: .infinity, alignment: .topLeading) .background( RoundedRectangle(cornerRadius: 28, style: .continuous) .fill(Self.defaultGradient)