diff --git a/nym-vpn-apple/Daemon/Info.plist b/nym-vpn-apple/Daemon/Info.plist
index f70964994b..76f58f9f7f 100644
--- a/nym-vpn-apple/Daemon/Info.plist
+++ b/nym-vpn-apple/Daemon/Info.plist
@@ -9,9 +9,9 @@
CFBundleName
NymVPNHelper
CFBundleShortVersionString
- 1.0.0
+ 1.1.0
CFBundleVersion
- 24
+ 27
SMAuthorizedClients
anchor apple generic and identifier "net.nymtech.vpn" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = VW5DZLFHM5)
diff --git a/nym-vpn-apple/Daemon/net.nymtech.vpn.helper b/nym-vpn-apple/Daemon/net.nymtech.vpn.helper
index 7fbea20781..cfab223363 100644
Binary files a/nym-vpn-apple/Daemon/net.nymtech.vpn.helper and b/nym-vpn-apple/Daemon/net.nymtech.vpn.helper differ
diff --git a/nym-vpn-apple/MixnetLibrary/Package.swift b/nym-vpn-apple/MixnetLibrary/Package.swift
index 204277e35a..0963566bb1 100644
--- a/nym-vpn-apple/MixnetLibrary/Package.swift
+++ b/nym-vpn-apple/MixnetLibrary/Package.swift
@@ -23,8 +23,8 @@ let package = Package(
),
.binaryTarget(
name: "NymVpnLib",
- url: "https://github.com/nymtech/nym-vpn-client/releases/download/nym-vpn-core-v1.1.0-dev-1/nym-vpn-core-v1.1.0-dev_ios_universal.zip",
- checksum: "a763ae785ce77aeb5d3fee590d733900ee62c813ba4f48b3aa34ce05df7c0e48"
+ url: "https://github.com/nymtech/nym-vpn-client/releases/download/nym-vpn-core-v1.1.0-beta.1/nym-vpn-core-v1.1.0-beta.1_ios_universal.zip",
+ checksum: "8561affeea5042f3f2bac926987919976b5cce67bb5f89e7fe022d8c3164faaf"
),
// .binaryTarget(
// name: "NymVpnLib",
diff --git a/nym-vpn-apple/NymVPN.xcodeproj/project.pbxproj b/nym-vpn-apple/NymVPN.xcodeproj/project.pbxproj
index 8714fc8f84..7abbe6d823 100644
--- a/nym-vpn-apple/NymVPN.xcodeproj/project.pbxproj
+++ b/nym-vpn-apple/NymVPN.xcodeproj/project.pbxproj
@@ -784,7 +784,7 @@
CODE_SIGN_ENTITLEMENTS = NymMixnetTunnel/NymMixnetTunnel.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 323;
+ CURRENT_PROJECT_VERSION = 325;
DEVELOPMENT_TEAM = VW5DZLFHM5;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GENERATE_INFOPLIST_FILE = NO;
@@ -819,7 +819,7 @@
CODE_SIGN_ENTITLEMENTS = NymMixnetTunnel/NymMixnetTunnel.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 323;
+ CURRENT_PROJECT_VERSION = 325;
DEVELOPMENT_TEAM = VW5DZLFHM5;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GENERATE_INFOPLIST_FILE = NO;
@@ -977,7 +977,7 @@
CODE_SIGN_ENTITLEMENTS = NymVPN/NymVPN.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 323;
+ CURRENT_PROJECT_VERSION = 325;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = VW5DZLFHM5;
ENABLE_PREVIEWS = YES;
@@ -1017,7 +1017,7 @@
CODE_SIGN_ENTITLEMENTS = NymVPN/NymVPN.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 323;
+ CURRENT_PROJECT_VERSION = 325;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = VW5DZLFHM5;
ENABLE_PREVIEWS = YES;
@@ -1056,7 +1056,7 @@
CODE_SIGN_IDENTITY = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
- CURRENT_PROJECT_VERSION = 35;
+ CURRENT_PROJECT_VERSION = 37;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"NymVPNDaemon/Preview Content\"";
DEVELOPMENT_TEAM = VW5DZLFHM5;
@@ -1092,7 +1092,7 @@
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Developer ID Application";
CODE_SIGN_STYLE = Manual;
COMBINE_HIDPI_IMAGES = YES;
- CURRENT_PROJECT_VERSION = 35;
+ CURRENT_PROJECT_VERSION = 37;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_ASSET_PATHS = "\"NymVPNDaemon/Preview Content\"";
DEVELOPMENT_TEAM = "";
diff --git a/nym-vpn-apple/Scripts/UpdateCore.sh b/nym-vpn-apple/Scripts/UpdateCore.sh
index 9e649d8e1c..2ad4880547 100755
--- a/nym-vpn-apple/Scripts/UpdateCore.sh
+++ b/nym-vpn-apple/Scripts/UpdateCore.sh
@@ -190,19 +190,7 @@ echo "✅ Files copied successfully to $destination_folder."
# Go back to the previous directory
cd -
-# Update the requiredVersion in HelperManager.swift to match the provided version
-
-helper_manager_file="../ServicesMacOS/Sources/HelperManager/HelperManager.swift"
-
-if [[ -f "$helper_manager_file" ]]; then
- # Use sed to update the requiredVersion line with the new version
- sed -i '' "s/public let requiredVersion = \".*\"/public let requiredVersion = \"$VERSION\"/g" "$helper_manager_file"
- echo "✅ HelperManager.swift has been successfully updated with the new required version: $VERSION."
-else
- echo "❌ Error: HelperManager.swift file not found at $helper_manager_file"
- exit 1
-fi
-
+# Update daemon Info.plist
sh UpdateDaemonInfoPlist.sh ${VERSION}
# Remove the downloaded source zip file and extracted folder
diff --git a/nym-vpn-apple/Services/Sources/Services/ConnectionManager/ConnectionManager.swift b/nym-vpn-apple/Services/Sources/Services/ConnectionManager/ConnectionManager.swift
index fcdc0d0e4b..877192a903 100644
--- a/nym-vpn-apple/Services/Sources/Services/ConnectionManager/ConnectionManager.swift
+++ b/nym-vpn-apple/Services/Sources/Services/ConnectionManager/ConnectionManager.swift
@@ -190,18 +190,19 @@ public final class ConnectionManager: ObservableObject {
}
}
#endif
-
+
/// Disconnects tunnel if connected.
/// iOS removes tunnel profile.
public func disconnectBeforeLogout() async {
guard currentTunnelStatus != .disconnected else { return }
#if os(iOS)
disconnectActiveTunnel()
+ await waitForTunnelStatus(with: .disconnected)
resetVpnProfile()
#elseif os(macOS)
grpcManager.disconnect()
-#endif
await waitForTunnelStatus(with: .disconnected)
+#endif
}
}
diff --git a/nym-vpn-apple/Services/Sources/Services/CredentialsManager/CredentialsManager.swift b/nym-vpn-apple/Services/Sources/Services/CredentialsManager/CredentialsManager.swift
index b88629b011..f95d24175a 100644
--- a/nym-vpn-apple/Services/Sources/Services/CredentialsManager/CredentialsManager.swift
+++ b/nym-vpn-apple/Services/Sources/Services/CredentialsManager/CredentialsManager.swift
@@ -23,6 +23,8 @@ public final class CredentialsManager {
public static let shared = CredentialsManager()
+ public var deviceIdentifier: String?
+
public var isValidCredentialImported: Bool {
appSettings.isCredentialImported
}
@@ -136,6 +138,7 @@ private extension CredentialsManager {
logger.error("Failed to check credential import: \(error.localizedDescription)")
updateIsCredentialImported(with: false)
}
+ updateDeviceIdentifier()
}
}
@@ -145,3 +148,16 @@ private extension CredentialsManager {
}
}
}
+
+private extension CredentialsManager {
+ func updateDeviceIdentifier() {
+ Task(priority: .background) {
+#if os(iOS)
+ let dataFolderURL = try dataFolderURL()
+ deviceIdentifier = try? getDeviceIdentity(path: dataFolderURL.path())
+#elseif os(macOS)
+ deviceIdentifier = try? await grpcManager.deviceIdentifier()
+#endif
+ }
+ }
+}
diff --git a/nym-vpn-apple/ServicesMacOS/Sources/GRPCManager/GRPCManager+Devices.swift b/nym-vpn-apple/ServicesMacOS/Sources/GRPCManager/GRPCManager+Devices.swift
new file mode 100644
index 0000000000..7eac3f7ac8
--- /dev/null
+++ b/nym-vpn-apple/ServicesMacOS/Sources/GRPCManager/GRPCManager+Devices.swift
@@ -0,0 +1,19 @@
+import GRPC
+
+extension GRPCManager {
+ public func deviceIdentifier() async throws -> String {
+ try await withCheckedThrowingContinuation { continuation in
+ let call = client.getDeviceIdentity(Nym_Vpn_GetDeviceIdentityRequest())
+
+ call.response.whenComplete { result in
+ switch result {
+ case .success(let response):
+ print(response)
+ continuation.resume(returning: response.deviceIdentity)
+ case .failure(let error):
+ continuation.resume(throwing: error)
+ }
+ }
+ }
+ }
+}
diff --git a/nym-vpn-apple/ServicesMacOS/Sources/HelperManager/HelperManager.swift b/nym-vpn-apple/ServicesMacOS/Sources/HelperManager/HelperManager.swift
index be91a5895d..2f3007b50f 100644
--- a/nym-vpn-apple/ServicesMacOS/Sources/HelperManager/HelperManager.swift
+++ b/nym-vpn-apple/ServicesMacOS/Sources/HelperManager/HelperManager.swift
@@ -6,7 +6,6 @@ import Shell
public final class HelperManager {
public static let shared = HelperManager()
- public let requiredVersion = "1.1.0-dev-1"
private var helperName = ""
diff --git a/nym-vpn-apple/ServicesMutual/Sources/AppVersionProvider/AppVersionProvider.swift b/nym-vpn-apple/ServicesMutual/Sources/AppVersionProvider/AppVersionProvider.swift
index 6026ef9838..570f28ea1f 100644
--- a/nym-vpn-apple/ServicesMutual/Sources/AppVersionProvider/AppVersionProvider.swift
+++ b/nym-vpn-apple/ServicesMutual/Sources/AppVersionProvider/AppVersionProvider.swift
@@ -1,7 +1,7 @@
import Foundation
public enum AppVersionProvider {
- public static let libVersion = "1.1.0-dev"
+ public static let libVersion = "1.1.0-beta.1"
public static var app: String {
"nym-vpn-app"
diff --git a/nym-vpn-apple/Settings/Sources/Settings/SettingsViewModel.swift b/nym-vpn-apple/Settings/Sources/Settings/SettingsViewModel.swift
index 9f14133ace..5bc3c79793 100644
--- a/nym-vpn-apple/Settings/Sources/Settings/SettingsViewModel.swift
+++ b/nym-vpn-apple/Settings/Sources/Settings/SettingsViewModel.swift
@@ -16,6 +16,10 @@ public class SettingsViewModel: SettingsFlowState {
private let externalLinkManager: ExternalLinkManager
private var cancellables = Set()
+ private var deviceIdentifier: String? {
+ guard let deviceIdentifier = credentialsManager.deviceIdentifier else { return nil }
+ return "settings.deviceId".localizedString + deviceIdentifier
+ }
let settingsTitle = "settings".localizedString
@@ -147,6 +151,7 @@ private extension SettingsViewModel {
SettingsListItemViewModel(
accessory: .externalLink,
title: "settings.account".localizedString,
+ subtitle: deviceIdentifier,
imageName: "person",
action: { [weak self] in
self?.navigateToAccount()
diff --git a/nym-vpn-apple/Theme/Sources/Theme/Resources/Localizable.xcstrings b/nym-vpn-apple/Theme/Sources/Theme/Resources/Localizable.xcstrings
index 36a2929677..1ef6dfd421 100644
--- a/nym-vpn-apple/Theme/Sources/Theme/Resources/Localizable.xcstrings
+++ b/nym-vpn-apple/Theme/Sources/Theme/Resources/Localizable.xcstrings
@@ -1321,6 +1321,17 @@
}
}
},
+ "settings.deviceId" : {
+ "extractionState" : "manual",
+ "localizations" : {
+ "en" : {
+ "stringUnit" : {
+ "state" : "translated",
+ "value" : "Device ID: "
+ }
+ }
+ }
+ },
"settings.logIn" : {
"extractionState" : "manual",
"localizations" : {
diff --git a/nym-vpn-apple/UIComponents/Sources/UIComponents/Marquee/BouncingMarqueeTextView.swift b/nym-vpn-apple/UIComponents/Sources/UIComponents/Marquee/BouncingMarqueeTextView.swift
new file mode 100644
index 0000000000..14dcbff264
--- /dev/null
+++ b/nym-vpn-apple/UIComponents/Sources/UIComponents/Marquee/BouncingMarqueeTextView.swift
@@ -0,0 +1,80 @@
+import SwiftUI
+import Theme
+
+public struct BouncingMarqueeTextView: View {
+ let text: String
+ let textStyle: NymTextStyle
+ let fontColor: Color
+ let speed: Double // Speed in points per second
+ let pauseDuration: Double // Pause duration at the start and end
+
+ @State private var textWidth: CGFloat = 0
+ @State private var containerWidth: CGFloat = 0
+ @State private var offset: CGFloat = 0
+ @State private var isReversing = false
+
+ public var body: some View {
+ GeometryReader { geo in
+ HStack {
+ Text(text)
+ .foregroundStyle(fontColor)
+ .textStyle(textStyle)
+ .fixedSize()
+ .background(
+ GeometryReader { textGeo in
+ Color.clear
+ .onAppear {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
+ textWidth = textGeo.size.width
+ containerWidth = geo.size.width
+ startAnimationIfNeeded()
+ }
+ }
+ }
+ )
+ .offset(x: offset)
+ }
+ .clipped()
+ }
+ .onChange(of: text) { _ in
+ resetAnimationIfNeeded()
+ }
+ }
+}
+
+private extension BouncingMarqueeTextView {
+ func resetAnimationIfNeeded() {
+ textWidth = 0
+ offset = 0
+ isReversing = false
+ startAnimationIfNeeded()
+ }
+
+ func startAnimationIfNeeded() {
+ guard textWidth > containerWidth
+ else {
+ offset = 0
+ return
+ }
+
+ startAnimation()
+ }
+
+ func startAnimation() {
+ Task(priority: .background) {
+ let maxOffset = containerWidth - textWidth
+ let targetOffset = isReversing ? 0 : maxOffset
+ let distance = abs(offset - targetOffset)
+ let duration = distance / speed
+
+ withAnimation(.linear(duration: duration)) {
+ offset = targetOffset
+ }
+
+ DispatchQueue.main.asyncAfter(deadline: .now() + duration + pauseDuration) {
+ isReversing.toggle()
+ startAnimationIfNeeded()
+ }
+ }
+ }
+}
diff --git a/nym-vpn-apple/UIComponents/Sources/UIComponents/SettingsList/SettingsListItem.swift b/nym-vpn-apple/UIComponents/Sources/UIComponents/SettingsList/SettingsListItem.swift
index 6bf890ea6d..52fbc0bd4b 100644
--- a/nym-vpn-apple/UIComponents/Sources/UIComponents/SettingsList/SettingsListItem.swift
+++ b/nym-vpn-apple/UIComponents/Sources/UIComponents/SettingsList/SettingsListItem.swift
@@ -68,11 +68,16 @@ private extension SettingsListItem {
.foregroundStyle(NymColor.sysOnSurface)
.textStyle(.Body.Large.semibold)
if let subtitle = viewModel.subtitle {
- Text(subtitle)
- .foregroundStyle(NymColor.sysOutline)
- .textStyle(.Body.Medium.regular)
+ BouncingMarqueeTextView(
+ text: subtitle,
+ textStyle: .Body.Medium.regular,
+ fontColor: NymColor.sysOutline,
+ speed: 70,
+ pauseDuration: 1.0
+ )
}
}
+ .clipped()
.padding(.leading, 16)
}