From 9022def6fe0a44fb54e2ec5dfb7b2b7e76974518 Mon Sep 17 00:00:00 2001 From: Sabrina Tardio <44158575+SabrinaTardio@users.noreply.github.com> Date: Thu, 31 Oct 2024 12:49:26 +0100 Subject: [PATCH 1/5] update UI test (#3469) Task/Issue URL: https://app.asana.com/0/72649045549333/1208077416568735/f **Description**: Updates the UI test --- .../MainWindow/MainWindowController.swift | 4 + UITests/Common/UITests.swift | 2 + UITests/OnboardingUITests.swift | 105 ++++++++---------- 3 files changed, 54 insertions(+), 57 deletions(-) diff --git a/DuckDuckGo/MainWindow/MainWindowController.swift b/DuckDuckGo/MainWindow/MainWindowController.swift index 5911d9c289..a21f293ae6 100644 --- a/DuckDuckGo/MainWindow/MainWindowController.swift +++ b/DuckDuckGo/MainWindow/MainWindowController.swift @@ -74,8 +74,12 @@ final class MainWindowController: NSWindowController { return false #elseif REVIEW if Application.runType == .uiTests { + Application.appDelegate.onboardingStateMachine.state = .onboardingCompleted return false } else { + if Application.runType == .uiTestsOnboarding { + Application.appDelegate.onboardingStateMachine.state = .onboardingCompleted + } let onboardingIsComplete = OnboardingViewModel.isOnboardingFinished || LocalStatisticsStore().waitlistUnlocked return !onboardingIsComplete } diff --git a/UITests/Common/UITests.swift b/UITests/Common/UITests.swift index a92f64ce90..ff590a386e 100644 --- a/UITests/Common/UITests.swift +++ b/UITests/Common/UITests.swift @@ -56,6 +56,7 @@ enum UITests { /// - Parameter requestedToggleState: How the autocomplete checkbox state should be set static func setAutocompleteToggleBeforeTestcaseRuns(_ requestedToggleState: Bool) { let app = XCUIApplication() + app.launchEnvironment["UITEST_MODE"] = "1" app.launch() app.typeKey(",", modifierFlags: [.command]) // Open settings @@ -96,6 +97,7 @@ enum UITests { notificationCenter.typeKey(.escape, modifierFlags: []) } let app = XCUIApplication() + app.launchEnvironment["UITEST_MODE"] = "1" app.launch() app.typeKey("n", modifierFlags: .command) app.typeKey("w", modifierFlags: [.command, .option]) diff --git a/UITests/OnboardingUITests.swift b/UITests/OnboardingUITests.swift index d83b751520..32af9112d0 100644 --- a/UITests/OnboardingUITests.swift +++ b/UITests/OnboardingUITests.swift @@ -20,6 +20,10 @@ import XCTest final class OnboardingUITests: XCTestCase { + override func tearDownWithError() throws { + try resetApplicationData() + } + func testOnboardingToBrowsing() throws { try resetApplicationData() continueAfterFailure = false @@ -35,84 +39,71 @@ final class OnboardingUITests: XCTestCase { XCTAssertFalse(optionsButton.isEnabled) // Get Started - XCTAssertTrue(welcomeWindow.webViews["Welcome"].staticTexts["Tired of being tracked online? We can help!"].waitForExistence(timeout: UITests.Timeouts.elementExistence)) - let getStartedButton = welcomeWindow.webViews["Welcome"].buttons["Get Started"] + XCTAssertTrue(welcomeWindow.webViews["Welcome"].staticTexts["Ready for a faster browser that keeps you protected?"].waitForExistence(timeout: UITests.Timeouts.elementExistence)) + + let getStartedButton = welcomeWindow.webViews["Welcome"].buttons["Let’s Do It!"] XCTAssertTrue(getStartedButton.waitForExistence(timeout: UITests.Timeouts.elementExistence)) getStartedButton.click() + // When it clicks on the button the y it's not alligned + let centerCoordinate = getStartedButton.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.2)) + centerCoordinate.tap() + + // Protections activated + XCTAssertTrue(welcomeWindow.webViews["Welcome"].staticTexts["Protections activated!"].waitForExistence(timeout: UITests.Timeouts.elementExistence)) - // Default Privacy - XCTAssertTrue(welcomeWindow.webViews["Welcome"].staticTexts["Unlike other browsers, DuckDuckGo comes with privacy by default"].waitForExistence(timeout: UITests.Timeouts.elementExistence)) - XCTAssertTrue(welcomeWindow.webViews["Welcome"].staticTexts["Private Search"].waitForExistence(timeout: UITests.Timeouts.elementExistence)) - XCTAssertTrue(welcomeWindow.webViews["Welcome"].staticTexts["Advanced Tracking Protection"].waitForExistence(timeout: UITests.Timeouts.elementExistence)) - XCTAssertTrue(welcomeWindow.webViews["Welcome"].staticTexts["Automatic Cookie Pop-Up Blocking"].waitForExistence(timeout: UITests.Timeouts.elementExistence)) - let gotItButton = welcomeWindow.webViews["Welcome"].buttons["Got It"] - XCTAssertTrue(gotItButton.waitForExistence(timeout: UITests.Timeouts.elementExistence)) - gotItButton.click() - - // Fewer ads and popups - XCTAssertTrue(welcomeWindow.webViews["Welcome"].staticTexts["Private also means fewer ads and pop-ups"].waitForExistence(timeout: UITests.Timeouts.elementExistence)) - XCTAssertTrue(welcomeWindow.webViews["Welcome"].staticTexts["While browsing the web"].waitForExistence(timeout: UITests.Timeouts.elementExistence)) - let seeWithTrackerBlockingButton = welcomeWindow.webViews["Welcome"].buttons["See With Tracker Blocking"] - XCTAssertTrue(seeWithTrackerBlockingButton.waitForExistence(timeout: UITests.Timeouts.elementExistence)) - seeWithTrackerBlockingButton.click() - XCTAssertTrue(gotItButton.waitForExistence(timeout: UITests.Timeouts.elementExistence)) - gotItButton.click() - XCTAssertTrue(welcomeWindow.webViews["Welcome"].staticTexts["While watching YouTube"].waitForExistence(timeout: UITests.Timeouts.elementExistence)) - let seeWithDuckPlayerButton = welcomeWindow.webViews["Welcome"].buttons["See With Duck Player"] - XCTAssertTrue(seeWithDuckPlayerButton.waitForExistence(timeout: UITests.Timeouts.elementExistence)) - seeWithDuckPlayerButton.click() - let nextGotItButton = welcomeWindow.webViews["Welcome"].buttons["Got It"] - XCTAssertTrue(nextGotItButton.waitForExistence(timeout: UITests.Timeouts.elementExistence)) - nextGotItButton.click() - welcomeWindow.webViews["Welcome"].click() - let nextButton = welcomeWindow.webViews["Welcome"].buttons["Next"] - XCTAssertTrue(nextButton.waitForExistence(timeout: UITests.Timeouts.elementExistence)) - nextButton.click() - - // Make Privacy your go-to - XCTAssertTrue(welcomeWindow.webViews["Welcome"].staticTexts["Make privacy your go-to"].waitForExistence(timeout: UITests.Timeouts.elementExistence)) let skipButton = welcomeWindow.webViews["Welcome"].buttons["Skip"] XCTAssertTrue(skipButton.waitForExistence(timeout: UITests.Timeouts.elementExistence)) skipButton.click() - welcomeWindow.webViews["Welcome"].click() - let importButton = welcomeWindow.webViews["Welcome"].buttons["Import"] - XCTAssertTrue(importButton.waitForExistence(timeout: UITests.Timeouts.elementExistence)) - importButton.click() + + // Let’s get you set up + XCTAssertTrue(welcomeWindow.webViews["Welcome"].staticTexts["Let’s get you set up!"].waitForExistence(timeout: UITests.Timeouts.elementExistence)) + + XCTAssertTrue(skipButton.waitForExistence(timeout: UITests.Timeouts.elementExistence)) + skipButton.click() + + let importNowButton = welcomeWindow.webViews["Welcome"].buttons["Import Now"] + XCTAssertTrue(importNowButton.waitForExistence(timeout: UITests.Timeouts.elementExistence)) + importNowButton.click() + let cancelButton = welcomeWindow.sheets.buttons["Cancel"] XCTAssertTrue(cancelButton.waitForExistence(timeout: UITests.Timeouts.elementExistence)) cancelButton.click() + + let nextButtonSetUp = welcomeWindow.webViews["Welcome"].buttons["Next"] + XCTAssertTrue(nextButtonSetUp.waitForExistence(timeout: UITests.Timeouts.elementExistence)) + nextButtonSetUp.click() + + // Duck Player + XCTAssertTrue(welcomeWindow.webViews["Welcome"].staticTexts["Drowning in ads on YouTube? Not with Duck Player!"].waitForExistence(timeout: UITests.Timeouts.elementExistence)) + + let nextButtonDuckPlayer = welcomeWindow.webViews["Welcome"].buttons["Next"] + XCTAssertTrue(nextButtonDuckPlayer.waitForExistence(timeout: UITests.Timeouts.elementExistence)) + nextButtonDuckPlayer.click() + + // Customize Experience + XCTAssertTrue(welcomeWindow.webViews["Welcome"].staticTexts["Let’s customize a few things…"].waitForExistence(timeout: UITests.Timeouts.elementExistence)) + + // Session Restore XCTAssertTrue(skipButton.waitForExistence(timeout: UITests.Timeouts.elementExistence)) skipButton.click() - welcomeWindow.webViews["Welcome"].click() - XCTAssertTrue(nextButton.waitForExistence(timeout: UITests.Timeouts.elementExistence)) - nextButton.click() - - // Customize your experience - XCTAssertTrue(welcomeWindow.webViews["Welcome"].staticTexts["Customize your experience"].waitForExistence(timeout: UITests.Timeouts.elementExistence)) - XCTAssertTrue(welcomeWindow.webViews["Welcome"].staticTexts["Make DuckDuckGo work just the way you want."].waitForExistence(timeout: UITests.Timeouts.elementExistence)) - let showBookmarksBarButton = welcomeWindow.webViews["Welcome"].buttons["Show Bookmarks Bar"] - XCTAssertTrue(showBookmarksBarButton.waitForExistence(timeout: UITests.Timeouts.elementExistence)) - showBookmarksBarButton.click() - XCTAssertTrue(welcomeWindow.collectionViews["BookmarksBarViewController.bookmarksBarCollectionView"].waitForExistence(timeout: UITests.Timeouts.elementExistence)) + let enableSessionRestoreButton = welcomeWindow.webViews["Welcome"].buttons["Enable Session Restore"] XCTAssertTrue(enableSessionRestoreButton.waitForExistence(timeout: UITests.Timeouts.elementExistence)) enableSessionRestoreButton.click() - welcomeWindow.webViews["Welcome"].click() + let showHomeButton = welcomeWindow.webViews["Welcome"].buttons["Show Home Button"] XCTAssertTrue(showHomeButton.waitForExistence(timeout: UITests.Timeouts.elementExistence)) showHomeButton.click() - XCTAssertTrue(welcomeWindow.children(matching: .button).element(boundBy: 3).waitForExistence(timeout: UITests.Timeouts.elementExistence)) - welcomeWindow.webViews["Welcome"].click() - XCTAssertTrue(nextButton.waitForExistence(timeout: UITests.Timeouts.elementExistence)) - nextButton.click() + + // Start Browsing let startBrowsingButton = welcomeWindow.webViews["Welcome"].buttons["Start Browsing"] XCTAssertTrue(startBrowsingButton.waitForExistence(timeout: UITests.Timeouts.elementExistence)) startBrowsingButton.click() - // AfterOnboarding - let duckduckgoPrivacySimplifiedWindow = app.windows["DuckDuckGo — Privacy, simplified."] - XCTAssertTrue(duckduckgoPrivacySimplifiedWindow.webViews["DuckDuckGo — Privacy, simplified."].waitForExistence(timeout: UITests.Timeouts.elementExistence)) - XCTAssertTrue(duckduckgoPrivacySimplifiedWindow.buttons["NavigationBarViewController.optionsButton"].isEnabled) + // After Onboarding + let duckduckgoPage = app.windows["DuckDuckGo — Privacy, simplified."] + XCTAssertTrue(duckduckgoPage.webViews["DuckDuckGo — Privacy, simplified."].waitForExistence(timeout: UITests.Timeouts.elementExistence)) + XCTAssertTrue(duckduckgoPage.buttons["NavigationBarViewController.optionsButton"].isEnabled) } func resetApplicationData() throws { From fbdb8a5f155c6907aa862260520433621ee664a1 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Fri, 1 Nov 2024 01:34:08 +0500 Subject: [PATCH 2/5] Fix crash when opening permission popover for NewTab page address bar (#3484) Task/Issue URL: https://app.asana.com/0/1202406491309510/1208667754274335/f Description: Fixes crash when permission requested with NewTab address bar experiment caused by permission popover tried to be presented on the NewTab page address bar --- DuckDuckGo/Common/Extensions/NSViewExtension.swift | 6 ++++++ .../Extensions/WKWebViewConfigurationExtensions.swift | 2 +- .../View/AddressBarButtonsViewController.swift | 6 +++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo/Common/Extensions/NSViewExtension.swift b/DuckDuckGo/Common/Extensions/NSViewExtension.swift index 2e81660afc..c17edf489f 100644 --- a/DuckDuckGo/Common/Extensions/NSViewExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSViewExtension.swift @@ -85,6 +85,12 @@ extension NSView { set { isHidden = !newValue } } + var isVisible: Bool { + guard !isHiddenOrHasHiddenAncestor, + let window, window.isVisible else { return false } + return true + } + func makeMeFirstResponder() { guard let window = window else { Logger.general.error("\(self.className): Window not available") diff --git a/DuckDuckGo/Common/Extensions/WKWebViewConfigurationExtensions.swift b/DuckDuckGo/Common/Extensions/WKWebViewConfigurationExtensions.swift index 090e9c3121..9a89e51e4f 100644 --- a/DuckDuckGo/Common/Extensions/WKWebViewConfigurationExtensions.swift +++ b/DuckDuckGo/Common/Extensions/WKWebViewConfigurationExtensions.swift @@ -119,7 +119,7 @@ extension NSPopover { // https://app.asana.com/0/1201037661562251/1206407295280737/f @objc(swizzled_showRelativeToRect:ofView:preferredEdge:) private dynamic func swizzled_show(relativeTo positioningRect: NSRect, of positioningView: NSView, preferredEdge: NSRectEdge) { - if positioningView.superview == nil { + if positioningView.window == nil { var observer: Cancellable? observer = positioningView.observe(\.window) { positioningView, _ in if positioningView.window != nil { diff --git a/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift b/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift index 44e6cf951e..63766e5838 100644 --- a/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift +++ b/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift @@ -61,7 +61,7 @@ final class AddressBarButtonsViewController: NSViewController { @IBOutlet weak var imageButtonWrapper: NSView! @IBOutlet weak var imageButton: NSButton! @IBOutlet weak var clearButton: NSButton! - @IBOutlet weak var buttonsContainer: NSStackView! + @IBOutlet private weak var buttonsContainer: NSStackView! @IBOutlet weak var animationWrapperView: NSView! var trackerAnimationView1: LottieAnimationView! @@ -72,7 +72,7 @@ final class AddressBarButtonsViewController: NSViewController { @IBOutlet weak var notificationAnimationView: NavigationBarBadgeAnimationView! - @IBOutlet weak var permissionButtons: NSView! + @IBOutlet private weak var permissionButtons: NSView! @IBOutlet weak var cameraButton: PermissionButton! { didSet { cameraButton.isHidden = true @@ -368,7 +368,7 @@ final class AddressBarButtonsViewController: NSViewController { return } } - guard button.isShown, permissionButtons.isShown else { return } + guard button.isVisible else { return } (popover.contentViewController as? PermissionAuthorizationViewController)?.query = query popover.show(relativeTo: button.bounds, of: button, preferredEdge: .maxY) From 8c54db2a24316ede8f3be7c1f796a2c059477b97 Mon Sep 17 00:00:00 2001 From: Dax the Duck Date: Fri, 1 Nov 2024 05:22:49 +0000 Subject: [PATCH 3/5] Bump version to 1.112.0 (294) --- Configuration/BuildNumber.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/BuildNumber.xcconfig b/Configuration/BuildNumber.xcconfig index 9ba329c56c..458552a5ee 100644 --- a/Configuration/BuildNumber.xcconfig +++ b/Configuration/BuildNumber.xcconfig @@ -1 +1 @@ -CURRENT_PROJECT_VERSION = 293 +CURRENT_PROJECT_VERSION = 294 From ef1ca984f6e62453da8e62c66ee9ae2046609b17 Mon Sep 17 00:00:00 2001 From: Michal Smaga Date: Fri, 1 Nov 2024 13:36:49 +0100 Subject: [PATCH 4/5] Update to subscription cookie (#3489) Task/Issue URL: https://app.asana.com/0/1108686900785972/1208264562025859/f **Description**: Initial PR -> https://github.com/duckduckgo/macos-browser/pull/3458 Update to how the subscription cookie operates: - constraint the cookie to `subscriptions.duckduckgo.com` - on sign out do not fully remove the cookie - just clear the value - gate the feature behind the `setAccessTokenCookieForSubscriptionDomains` privacy config feature flag --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +-- DuckDuckGo/Application/AppDelegate.swift | 28 ++++++++++++++++++- ...riptionCookieManageEventPixelMapping.swift | 25 +++++++---------- .../Tab/Services/WebsiteDataStore.swift | 3 +- .../DataBrokerProtection/Package.swift | 2 +- .../NetworkProtectionMac/Package.swift | 2 +- LocalPackages/SubscriptionUI/Package.swift | 2 +- 8 files changed, 45 insertions(+), 23 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index a3acccb424..a7737cf420 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -14510,7 +14510,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 201.0.0; + version = "201.0.0-1"; }; }; 9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 51a522e596..3d5c0df68d 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "e5946eee6af859690cc1cc5e51daef3c8368981b", - "version" : "201.0.0" + "revision" : "9506581ae99273681073f9993fc6d881d3edaa7f", + "version" : "201.0.0-1" } }, { diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index a3ef983fee..8546ec81b1 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -96,7 +96,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate { public let subscriptionManager: SubscriptionManager public let subscriptionUIHandler: SubscriptionUIHandling - public let subscriptionCookieManager: SubscriptionCookieManaging + private let subscriptionCookieManager: SubscriptionCookieManaging + private var subscriptionCookieManagerFeatureFlagCancellable: AnyCancellable? public let vpnSettings = VPNSettings(defaults: .netP) @@ -302,6 +303,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate { vpnUninstaller: vpnUninstaller) } + // swiftlint:disable:next cyclomatic_complexity func applicationDidFinishLaunching(_ notification: Notification) { guard NSApp.runType.requiresEnvironment else { return } defer { @@ -344,6 +346,30 @@ final class AppDelegate: NSObject, NSApplicationDelegate { subscriptionManager.loadInitialData() + let privacyConfigurationManager = ContentBlocking.shared.privacyConfigurationManager + + // Enable subscriptionCookieManager if feature flag is present + if privacyConfigurationManager.privacyConfig.isSubfeatureEnabled(PrivacyProSubfeature.setAccessTokenCookieForSubscriptionDomains) { + subscriptionCookieManager.enableSettingSubscriptionCookie() + } + + // Keep track of feature flag changes + subscriptionCookieManagerFeatureFlagCancellable = privacyConfigurationManager.updatesPublisher + .sink { [weak self, weak privacyConfigurationManager] in + guard let self, let privacyConfigurationManager else { return } + + let isEnabled = privacyConfigurationManager.privacyConfig.isSubfeatureEnabled(PrivacyProSubfeature.setAccessTokenCookieForSubscriptionDomains) + + Task { [weak self] in + if isEnabled { + self?.subscriptionCookieManager.enableSettingSubscriptionCookie() + await self?.subscriptionCookieManager.refreshSubscriptionCookie() + } else { + await self?.subscriptionCookieManager.disableSettingSubscriptionCookie() + } + } + } + if [.normal, .uiTests].contains(NSApp.runType) { stateRestorationManager.applicationDidFinishLaunching() } diff --git a/DuckDuckGo/Subscription/SubscriptionCookieManageEventPixelMapping.swift b/DuckDuckGo/Subscription/SubscriptionCookieManageEventPixelMapping.swift index c18cf2e6d4..fc5128756d 100644 --- a/DuckDuckGo/Subscription/SubscriptionCookieManageEventPixelMapping.swift +++ b/DuckDuckGo/Subscription/SubscriptionCookieManageEventPixelMapping.swift @@ -24,21 +24,18 @@ import Subscription enum SubscriptionCookieManagerPixel: PixelKitEventV2 { case missingTokenOnSignIn - case missingCookieOnSignOut - case cookieRefreshedWithUpdate - case cookieRefreshedWithDelete + case cookieRefreshedWithAccessToken + case cookieRefreshedWithEmptyValue case failedToSetSubscriptionCookie var name: String { switch self { case .missingTokenOnSignIn: return "m_mac_privacy-pro_subscription-cookie-missing_token_on_sign_in" - case .missingCookieOnSignOut: - return "m_mac_privacy-pro_subscription-cookie-missing_cookie_on_sign_out" - case .cookieRefreshedWithUpdate: - return "m_mac_privacy-pro_subscription-cookie-refreshed_with_update" - case .cookieRefreshedWithDelete: - return "m_mac_privacy-pro_subscription-cookie-refreshed_with_delete" + case .cookieRefreshedWithAccessToken: + return "m_mac_privacy-pro_subscription-cookie-refreshed_with_access_token" + case .cookieRefreshedWithEmptyValue: + return "m_mac_privacy-pro_subscription-cookie-refreshed_with_empty_value" case .failedToSetSubscriptionCookie: return "m_mac_privacy-pro_subscription-cookie-failed_to_set_subscription_cookie" } @@ -61,12 +58,10 @@ public final class SubscriptionCookieManageEventPixelMapping: EventMapping Date: Fri, 1 Nov 2024 13:03:00 +0000 Subject: [PATCH 5/5] Bump version to 1.112.0 (295) --- Configuration/BuildNumber.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/BuildNumber.xcconfig b/Configuration/BuildNumber.xcconfig index 458552a5ee..58a5e5422c 100644 --- a/Configuration/BuildNumber.xcconfig +++ b/Configuration/BuildNumber.xcconfig @@ -1 +1 @@ -CURRENT_PROJECT_VERSION = 294 +CURRENT_PROJECT_VERSION = 295