Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,12 @@ public final class FeatureConfigRepository: FeatureConfigRepositoryProtocol {
isEnabled: config.status == .enabled
)

case let .simplifiedUserConnectionRequestQRCode(config):
return FeatureState(
name: .simplifiedUserConnectionRequestQRCode,
isEnabled: config.status == .enabled
)

case let .cells(cellsConfig):
return FeatureState(
name: .cells,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ extension FeatureConfigLocalStoreProtocol {
isEnabled: config.status == .enabled,
config: nil
)
case let .simplifiedUserConnectionRequestQRCode(config):
await storeFeature(
name: .simplifiedUserConnectionRequestQRCode,
isEnabled: config.status == .enabled,
config: nil
)
case let .cells(config):
await storeFeature(
name: .cells,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ public extension JournalKey where Value == Bool {
defaultValue: false
)

static let isSimplifiedUserConnectionRequestQRCode = Self(
"isSimplifiedUserConnectionRequestQRCode",
defaultValue: false
)

/// Whether new sync mechanism (initial sync, incremental
/// sync, live sync) is used.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,14 @@ struct StorableFeatureConfigUpdateEvent: Equatable, Codable, Sendable {
)
)
)
case let .simplifiedUserConnectionRequestQRCode(config):
.consumableNotifications(
StorableBasicFeatureConfig(
status: StorableFeatureConfigStatus(
config.status
)
)
)
case let .cells(config):
.cells(
StorableBasicFeatureConfig(
Expand Down Expand Up @@ -268,6 +276,12 @@ struct StorableFeatureConfigUpdateEvent: Equatable, Codable, Sendable {
status: config.status.toAPIModel()
)
)
case let .simplifiedUserConnectionRequestQRCode(config):
.simplifiedUserConnectionRequestQRCode(
SimplifiedUserConnectionRequestQRCodeConfig(
status: config.status.toAPIModel()
)
)
case let .cells(config):
.cells(
.init(status: config.status.toAPIModel())
Expand Down Expand Up @@ -310,6 +324,7 @@ enum StorableFeatureConfig: Equatable, Codable, Sendable {
case channels(StorableChannelsFeatureConfig)
case allowedGlobalOperations(StorableAllowedGlobalOperationsFeatureConfig)
case consumableNotifications(StorableBasicFeatureConfig)
case simplifiedUserConnectionRequestQRCode(StorableBasicFeatureConfig)
case cells(StorableBasicFeatureConfig)
case cellsInternal(StorableCellsInternalFeatureConfig)
case unknown(featureName: String)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,16 @@ private enum StorableUserClientCapability: String, Codable, Sendable {

case legalholdConsent
case consumableNotifications
case simplifiedUserConnectionRequestQRCode

init(_ value: WireNetwork.UserClientCapability) {
switch value {
case .legalholdConsent:
self = .legalholdConsent
case .consumableNotifications:
self = .consumableNotifications
case .simplifiedUserConnectionRequestQRCode:
self = .simplifiedUserConnectionRequestQRCode
}
}

Expand All @@ -155,6 +158,8 @@ private enum StorableUserClientCapability: String, Codable, Sendable {
.legalholdConsent
case .consumableNotifications:
.consumableNotifications
case .simplifiedUserConnectionRequestQRCode:
.simplifiedUserConnectionRequestQRCode
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,11 @@ final class FeatureConfigRepositoryTests: XCTestCase {
status: .enabled
)
),
.simplifiedUserConnectionRequestQRCode(
SimplifiedUserConnectionRequestQRCodeConfig(
status: .disabled
)
),
.cells(
.init(status: .enabled)
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ final class PullAllFeatureConfigsSyncTests: XCTestCase {
XCTAssertEqual(api.getFeatureConfigs_Invocations.count, 1)

let storeInvocations = store.storeFeatureNameIsEnabledConfig_Invocations
try XCTAssertCount(storeInvocations, count: 14)
try XCTAssertCount(storeInvocations, count: 15)

XCTAssertEqual(storeInvocations[0].name, .appLock)
XCTAssertEqual(storeInvocations[0].isEnabled, true)
Expand Down Expand Up @@ -131,6 +131,9 @@ final class PullAllFeatureConfigsSyncTests: XCTestCase {
storeInvocations[13].config as? Feature.SelfDeletingMessages.Config,
Scaffolding.selfDeletingMessagesFeatureConfig.toDomainModel()
)
XCTAssertEqual(storeInvocations[14].name, .simplifiedUserConnectionRequestQRCode)
XCTAssertFalse(storeInvocations[14].isEnabled)
XCTAssertNil(storeInvocations[14].config)
}

}
Expand All @@ -151,7 +154,8 @@ private enum Scaffolding {
.fileSharing(fileSharingFeatureConfig),
.mls(mlsFeatureConfig),
.mlsMigration(mlsMigrationFeatureConfig),
.selfDeletingMessages(selfDeletingMessagesFeatureConfig)
.selfDeletingMessages(selfDeletingMessagesFeatureConfig),
.simplifiedUserConnectionRequestQRCode(simplifiedUserConnectionRequestQRCodeConfig)
]

static let appLockFeatureConfig = AppLockFeatureConfig(
Expand Down Expand Up @@ -222,6 +226,10 @@ private enum Scaffolding {
status: .enabled
)

static let simplifiedUserConnectionRequestQRCodeConfig = SimplifiedUserConnectionRequestQRCodeConfig(
status: .disabled
)

static let cellsFeatureConfig = CellsFeatureConfig(
status: .enabled
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ struct FeatureConfigsResponseAPIV14: Decodable, ToAPIModelConvertible {
let allowedGlobalOperations: FeatureWithConfig<FeatureConfigResponse.AllowedGlobalOperationsV10>
let consumableNotifications: FeatureWithoutConfig
let assetAuditLog: FeatureWithoutConfig
let simplifiedUserConnectionRequestQRCode: FeatureWithoutConfig

// added in v14
let cellsInternal: FeatureWithConfig<FeatureConfigResponse.CellsInternalV14>
Expand Down Expand Up @@ -146,6 +147,12 @@ struct FeatureConfigsResponseAPIV14: Decodable, ToAPIModelConvertible {
)
featureConfigs.append(.consumableNotifications(consumableNotifications))

let simplifiedUserConnectionRequestQRCode = SimplifiedUserConnectionRequestQRCodeConfig(
status: simplifiedUserConnectionRequestQRCode.status
.toAPIModel()
)
featureConfigs.append(.simplifiedUserConnectionRequestQRCode(simplifiedUserConnectionRequestQRCode))

featureConfigs.append(.assetAuditLog(AssetAuditLogFeatureConfig(
status: assetAuditLog.status.toAPIModel()
)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ public enum FeatureConfig: Equatable, Sendable {

case consumableNotifications(ConsumableNotificationsFeatureConfig)

case simplifiedUserConnectionRequestQRCode(SimplifiedUserConnectionRequestQRCodeConfig)

/// Config for the *Conversation Guest Links* feature.`
///
/// *Conversation Guest Links* enable a group admin to create
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// Wire
// Copyright (C) 2026 Wire Swiss GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.
//

import Foundation

/// A configuration for the *File Sharing* feature.

public struct SimplifiedUserConnectionRequestQRCodeConfig: Equatable, Sendable {

/// The feature's status.

public let status: FeatureConfigStatus

public init(
status: FeatureConfigStatus
) {
self.status = status
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,22 @@ public enum UserClientCapability: Sendable {

case consumableNotifications

case simplifiedUserConnectionRequestQRCode
}

enum UserClientCapabilityV0: String, Codable, Sendable, ToAPIModelConvertible {
case legalholdConsent = "legalhold-implicit-consent"
case consumableNotifications = "consumable-notifications"
case simplifiedUserConnectionRequestQRCode = "simplified-user-connection-request-qr-code"

func toAPIModel() -> UserClientCapability {
switch self {
case .legalholdConsent:
.legalholdConsent
case .consumableNotifications:
.consumableNotifications
case .simplifiedUserConnectionRequestQRCode:
.simplifiedUserConnectionRequestQRCode
}
}
}
Expand All @@ -52,6 +56,8 @@ extension UserClientCapability: ToNetworkConvertible {
.legalholdConsent
case .consumableNotifications:
.consumableNotifications
case .simplifiedUserConnectionRequestQRCode:
.simplifiedUserConnectionRequestQRCode
}
}
}
9 changes: 9 additions & 0 deletions wire-ios-data-model/Source/Model/FeatureConfig/Feature.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class Feature: ZMManagedObject {
case classifiedDomains
case conferenceCalling
case consumableNotifications
case simplifiedUserConnectionRequestQRCode
case conversationGuestLinks
case digitalSignature
case e2ei = "mlsE2EId"
Expand All @@ -57,6 +58,13 @@ public class Feature: ZMManagedObject {

}

public enum LockStatus: String, Codable {

case locked
case unlocked

}

// MARK: - Properties

@NSManaged private var nameValue: String
Expand Down Expand Up @@ -236,6 +244,7 @@ public class Feature: ZMManagedObject {
.classifiedDomains,
.conferenceCalling,
.consumableNotifications,
.simplifiedUserConnectionRequestQRCode,
.conversationGuestLinks,
.digitalSignature,
.e2ei,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// Wire
// Copyright (C) 2026 Wire Swiss GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see http://www.gnu.org/licenses/.
//

import Foundation

public extension Feature {

struct SimplifiedUserConnectionRequestQRCode: Codable {

// MARK: - Properties

public let status: Status
public let config: Config

// MARK: - Life cycle

public init(status: Feature.Status = .disabled, config: Config = .init()) {
self.status = status
self.config = config
}

// MARK: - Types

public struct Config: Codable, Equatable {

/// If `true` then app lock is mandatory and can not
/// be disabled by by the user.

public let lockStatus: LockStatus

/// The number of seconds in the background before the
/// app should relock.
public let status: Status

/// The number of seconds in the background before the
/// app should relock.
public let ttl: UInt

public init(status: Feature.Status = .disabled, lockStatus: Feature.LockStatus = .locked, ttl: UInt = 60) {
self.lockStatus = lockStatus
self.status = status
self.ttl = ttl
}
}
}

}
3 changes: 3 additions & 0 deletions wire-ios-data-model/Source/Model/UserClient/UserClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@
/// Client has the capability to use new `consumable-notifications` synchronization system (v8+)
@NSManaged public var isConsumableNotificationsCapable: Bool

/// Client has the capability to use new `consumable-notifications` synchronization system (v8+)
@NSManaged public var isSimplifiedUserConnectionRequestQRCode: Bool

/// Clients that are trusted by self client.
@NSManaged public var trustedClients: Set<UserClient>

Expand Down Expand Up @@ -234,7 +237,7 @@
try await deleteSession()
} catch {
WireLogger.userClient.error("error deleting session: \(String(reflecting: error))")
}

Check warning on line 240 in wire-ios-data-model/Source/Model/UserClient/UserClient.swift

View workflow job for this annotation

GitHub Actions / Test Results

Capture of 'self' with non-Sendable type 'UserClient' in a '@Sendable' closure

Capture of 'self' with non-Sendable type 'UserClient' in a '@sendable' closure

Check warning on line 240 in wire-ios-data-model/Source/Model/UserClient/UserClient.swift

View workflow job for this annotation

GitHub Actions / Test Results

Capture of 'self' with non-Sendable type 'UserClient' in a '@Sendable' closure

Capture of 'self' with non-Sendable type 'UserClient' in a '@sendable' closure
await managedObjectContext?.perform { self.deleteClient() }
}

Expand Down Expand Up @@ -277,7 +280,7 @@

public var hasSessionWithSelfClient: Bool {
get async {
guard

Check warning on line 283 in wire-ios-data-model/Source/Model/UserClient/UserClient.swift

View workflow job for this annotation

GitHub Actions / Test Results

Capture of 'self' with non-Sendable type 'UserClient' in a '@Sendable' closure

Capture of 'self' with non-Sendable type 'UserClient' in a '@sendable' closure

Check warning on line 283 in wire-ios-data-model/Source/Model/UserClient/UserClient.swift

View workflow job for this annotation

GitHub Actions / Test Results

Capture of 'self' with non-Sendable type 'UserClient' in a '@Sendable' closure

Capture of 'self' with non-Sendable type 'UserClient' in a '@sendable' closure
let sessionID = await managedObjectContext?.perform({ self.proteusSessionID }),
let proteusService = await managedObjectContext?
.perform({ [managedObjectContext] in managedObjectContext?.proteusService })
Expand All @@ -301,7 +304,7 @@
}

WaitingGroupTask(context: syncMOC) {
guard let syncClient = await syncMOC.perform({

Check warning on line 307 in wire-ios-data-model/Source/Model/UserClient/UserClient.swift

View workflow job for this annotation

GitHub Actions / Test Results

Capture of 'self' with non-Sendable type 'UserClient' in a '@Sendable' closure

Capture of 'self' with non-Sendable type 'UserClient' in a '@sendable' closure

Check warning on line 307 in wire-ios-data-model/Source/Model/UserClient/UserClient.swift

View workflow job for this annotation

GitHub Actions / Test Results

Capture of 'self' with non-Sendable type 'UserClient' in a '@Sendable' closure

Capture of 'self' with non-Sendable type 'UserClient' in a '@sendable' closure
(try? syncMOC.existingObject(with: self.objectID)) as? UserClient
}) else {
return
Expand All @@ -311,7 +314,7 @@
try? await syncClient.deleteSession()

await syncMOC.perform {
// Mark that we need notify the other party about the session reset

Check warning on line 317 in wire-ios-data-model/Source/Model/UserClient/UserClient.swift

View workflow job for this annotation

GitHub Actions / Test Results

Capture of 'syncClient' with non-Sendable type 'UserClient' in a '@Sendable' closure

Capture of 'syncClient' with non-Sendable type 'UserClient' in a '@sendable' closure

Check warning on line 317 in wire-ios-data-model/Source/Model/UserClient/UserClient.swift

View workflow job for this annotation

GitHub Actions / Test Results

Capture of 'syncClient' with non-Sendable type 'UserClient' in a '@Sendable' closure

Capture of 'syncClient' with non-Sendable type 'UserClient' in a '@sendable' closure
syncClient.needsToNotifyOtherUserAboutSessionReset = true

syncMOC.saveOrRollback()
Expand Down Expand Up @@ -553,8 +556,8 @@
/// If there is no session it does nothing
func deleteSession() async throws {
guard
let context = managedObjectContext,

Check warning on line 559 in wire-ios-data-model/Source/Model/UserClient/UserClient.swift

View workflow job for this annotation

GitHub Actions / Test Results

Capture of 'self' with non-Sendable type 'UserClient' in a '@Sendable' closure

Capture of 'self' with non-Sendable type 'UserClient' in a '@sendable' closure

Check warning on line 559 in wire-ios-data-model/Source/Model/UserClient/UserClient.swift

View workflow job for this annotation

GitHub Actions / Test Results

Capture of 'self' with non-Sendable type 'UserClient' in a '@Sendable' closure

Capture of 'self' with non-Sendable type 'UserClient' in a '@sendable' closure
await context.perform({ !self.isSelfClient() }),

Check warning on line 560 in wire-ios-data-model/Source/Model/UserClient/UserClient.swift

View workflow job for this annotation

GitHub Actions / Test Results

Capture of 'self' with non-Sendable type 'UserClient' in a '@Sendable' closure

Capture of 'self' with non-Sendable type 'UserClient' in a '@sendable' closure

Check warning on line 560 in wire-ios-data-model/Source/Model/UserClient/UserClient.swift

View workflow job for this annotation

GitHub Actions / Test Results

Capture of 'self' with non-Sendable type 'UserClient' in a '@Sendable' closure

Capture of 'self' with non-Sendable type 'UserClient' in a '@sendable' closure
let sessionID = await context.perform({ self.proteusSessionID }),
let proteusService = await context.perform({ context.proteusService })
else {
Expand All @@ -569,7 +572,7 @@
) async -> Bool {
guard
let context = managedObjectContext,
let proteusService = await context.perform({ context.proteusService }),

Check warning on line 575 in wire-ios-data-model/Source/Model/UserClient/UserClient.swift

View workflow job for this annotation

GitHub Actions / Test Results

Capture of 'client' with non-Sendable type 'UserClient' in a '@Sendable' closure

Capture of 'client' with non-Sendable type 'UserClient' in a '@sendable' closure

Check warning on line 575 in wire-ios-data-model/Source/Model/UserClient/UserClient.swift

View workflow job for this annotation

GitHub Actions / Test Results

Capture of 'client' with non-Sendable type 'UserClient' in a '@Sendable' closure

Capture of 'client' with non-Sendable type 'UserClient' in a '@sendable' closure
let sessionId = await context.perform({ client.proteusSessionID })
else {
return false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ public protocol LegacyFeatureRepositoryInterface {
func storeChannels(_ channels: Feature.Channels)
func fetchConsumableNotifications() -> Feature.ConsumableNotifications
func storeConsumableNotifications(_ consumableNotifications: Feature.ConsumableNotifications)
func fetchUserProfileQRCode() -> Feature.SimplifiedUserConnectionRequestQRCode
func storeUserProfileQRCode(_ userProfileQRCode: Feature.SimplifiedUserConnectionRequestQRCode)
func fetchCells() -> Feature.Cells
func storeCells(_ cells: Feature.Cells)
func fetchAssetAuditLog() -> Feature.AssetAuditLog
Expand Down Expand Up @@ -541,6 +543,19 @@ public class LegacyFeatureRepository: LegacyFeatureRepositoryInterface {
}
}

public func fetchUserProfileQRCode() -> Feature.SimplifiedUserConnectionRequestQRCode {
guard let feature = Feature.fetch(name: .simplifiedUserConnectionRequestQRCode, context: context) else {
return .init()
}
return .init(status: feature.status)
}

public func storeUserProfileQRCode(_ userProfileQRCode: Feature.SimplifiedUserConnectionRequestQRCode) {
Feature.updateOrCreate(havingName: .simplifiedUserConnectionRequestQRCode, in: context) {
$0.status = userProfileQRCode.status
}
}

// MARK: Cells

public func fetchCells() -> Feature.Cells {
Expand Down Expand Up @@ -633,6 +648,9 @@ public class LegacyFeatureRepository: LegacyFeatureRepositoryInterface {
case .consumableNotifications:
storeConsumableNotifications(.init())

case .simplifiedUserConnectionRequestQRCode:
storeUserProfileQRCode(.init())

case .cells:
storeCells(.init())

Expand Down
Loading
Loading