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) }