From 8a58ae12f6fd5e4c32da794cd4cb1f104845773d Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 3 Dec 2025 15:17:46 +0100 Subject: [PATCH 01/18] fix: potential race condition - WPB-21441 --- .../UserSession/Search/SearchTask.swift | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/wire-ios-sync-engine/Source/UserSession/Search/SearchTask.swift b/wire-ios-sync-engine/Source/UserSession/Search/SearchTask.swift index 7934a99539a..728421fde4a 100644 --- a/wire-ios-sync-engine/Source/UserSession/Search/SearchTask.swift +++ b/wire-ios-sync-engine/Source/UserSession/Search/SearchTask.swift @@ -50,11 +50,23 @@ public class SearchTask { searchUsersCache: nil ) - private var tasksRemaining = 0 { - didSet { + private let tasksRemainingLock = NSRecursiveLock() + private var _tasksRemaining = 0 + private var tasksRemaining: Int { + get { + tasksRemainingLock.withLock { + _tasksRemaining + } + } + set { + let oldValue = tasksRemainingLock.withLock { + let oldValue = _tasksRemaining + _tasksRemaining = newValue + return oldValue + } // only trigger handles if decrement to 0 - if oldValue > tasksRemaining { - let isCompleted = tasksRemaining == 0 + if oldValue > newValue { + let isCompleted = newValue == 0 resultHandlers.forEach { $0(result, isCompleted) } if isCompleted { @@ -124,11 +136,11 @@ public class SearchTask { public func cancel() { resultHandlers.removeAll() - teamMembershipTaskIdentifier.flatMap(transportSession.cancelTask) - userLookupTaskIdentifier.flatMap(transportSession.cancelTask) - directoryTaskIdentifier.flatMap(transportSession.cancelTask) - servicesTaskIdentifier.flatMap(transportSession.cancelTask) - handleTaskIdentifier.flatMap(transportSession.cancelTask) + teamMembershipTaskIdentifier.map(transportSession.cancelTask) + userLookupTaskIdentifier.map(transportSession.cancelTask) + directoryTaskIdentifier.map(transportSession.cancelTask) + servicesTaskIdentifier.map(transportSession.cancelTask) + handleTaskIdentifier.map(transportSession.cancelTask) tasksRemaining = 0 } From 7c658d78a4a2ee03ea46b1e3b1fe387437c7b242 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 3 Dec 2025 15:23:02 +0100 Subject: [PATCH 02/18] create isApp, isBot, isAppOrBot --- .../Model/Conversation/ConversationLike.swift | 4 ++-- .../ZMConversation+ExternalParticipant.swift | 6 +++--- .../ZMConversation+SystemMessages.swift | 2 +- .../Source/Model/User/Set+ServiceUser.swift | 8 +++----- .../Source/Model/User/UserType.swift | 10 ++++++++-- .../Source/Model/User/ZMSearchUser.swift | 14 +++++++++++--- .../Source/Model/User/ZMUser+Permissions.swift | 2 +- wire-ios-data-model/Source/Model/User/ZMUser.m | 12 +++++++++++- wire-ios-data-model/Source/Model/User/ZMUser.swift | 12 ++++++++++-- .../ObjectObserverTokens/UserChangeInfo.swift | 3 ++- .../Tests/Source/Model/TeamTests.swift | 2 +- .../User/ZMSearchUserPayloadParsingTests.swift | 4 ++-- .../Tests/Source/Model/User/ZMUserTests.m | 2 +- 13 files changed, 56 insertions(+), 25 deletions(-) diff --git a/wire-ios-data-model/Source/Model/Conversation/ConversationLike.swift b/wire-ios-data-model/Source/Model/Conversation/ConversationLike.swift index 9b64c79ea1b..be1edbae348 100644 --- a/wire-ios-data-model/Source/Model/Conversation/ConversationLike.swift +++ b/wire-ios-data-model/Source/Model/Conversation/ConversationLike.swift @@ -102,13 +102,13 @@ extension ZMConversation: ConversationLike { public var sortedOtherParticipants: [UserType] { localParticipants - .filter { !$0.isApp } + .filter { !$0.isAppOrBot } .sortedAscendingPrependingNil(by: \.name) } public var sortedApps: [UserType] { localParticipants - .filter(\.isApp) + .filter(\.isAppOrBot) .sortedAscendingPrependingNil(by: \.name) } diff --git a/wire-ios-data-model/Source/Model/Conversation/ZMConversation+ExternalParticipant.swift b/wire-ios-data-model/Source/Model/Conversation/ZMConversation+ExternalParticipant.swift index d649af03add..0d4fcff5f1c 100644 --- a/wire-ios-data-model/Source/Model/Conversation/ZMConversation+ExternalParticipant.swift +++ b/wire-ios-data-model/Source/Model/Conversation/ZMConversation+ExternalParticipant.swift @@ -63,7 +63,7 @@ public extension ZMConversation { let selfUser = ZMUser.selfUser(in: managedObjectContext!) let otherUsers = participants.subtracting([selfUser]) - if otherUsers.count == 1, otherUsers.first!.isApp { + if otherUsers.count == 1, otherUsers.first!.isApp_ { return [] } @@ -75,7 +75,7 @@ public extension ZMConversation { for user in otherUsers { if canDisplayGuests, user.isFederated { state.insert(.visibleRemotes) - } else if user.isApp { + } else if user.isAppOrBot { state.insert(.visibleApps) } else if canDisplayExternals, user.isExternalPartner { state.insert(.visibleExternals) @@ -98,7 +98,7 @@ public extension ZMConversation { /// Returns whether apps are present, regardless of the display rules. var areAppsPresent: Bool { - localParticipants.any(\.isApp) + localParticipants.any(\.isAppOrBot) } /// Returns whether guests are present, regardless of the display rules. diff --git a/wire-ios-data-model/Source/Model/Conversation/ZMConversation+SystemMessages.swift b/wire-ios-data-model/Source/Model/Conversation/ZMConversation+SystemMessages.swift index e461696d403..94ae0cbb209 100644 --- a/wire-ios-data-model/Source/Model/Conversation/ZMConversation+SystemMessages.swift +++ b/wire-ios-data-model/Source/Model/Conversation/ZMConversation+SystemMessages.swift @@ -97,7 +97,7 @@ public extension ZMConversation { team == selfUserTeam { let members = selfUserTeam.members.compactMap(\.user) - let guests = users.filter { !$0.isApp && $0.membership == nil } + let guests = users.filter { !$0.isAppOrBot && $0.membership == nil } systemMessage.allTeamUsersAdded = users.isSuperset(of: members) systemMessage.numberOfGuestsAdded = Int16(guests.count) diff --git a/wire-ios-data-model/Source/Model/User/Set+ServiceUser.swift b/wire-ios-data-model/Source/Model/User/Set+ServiceUser.swift index 607396611bf..d165da84ab7 100644 --- a/wire-ios-data-model/Source/Model/User/Set+ServiceUser.swift +++ b/wire-ios-data-model/Source/Model/User/Set+ServiceUser.swift @@ -16,16 +16,14 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation - extension Set { - var apps: Set { - filter(\.isApp) + var appsOrBots: Set { + filter(\.isAppOrBot) } func categorizeServicesAndUser() -> (services: Set, users: Set) { - let services = apps + let services = appsOrBots let users = subtracting(services) return (services, users) } diff --git a/wire-ios-data-model/Source/Model/User/UserType.swift b/wire-ios-data-model/Source/Model/User/UserType.swift index 74b56cc69d7..7a97ba88b6c 100644 --- a/wire-ios-data-model/Source/Model/User/UserType.swift +++ b/wire-ios-data-model/Source/Model/User/UserType.swift @@ -72,9 +72,15 @@ public protocol UserType: NSObjectProtocol, UserConnections { /// The role (and permissions) e.g. partner, member, admin, owner var teamRole: TeamRole { get } - /// Whether this is an app/bot/service user. + /// Whether this is an app (new-style MLS service). - var isApp: Bool { get } + var isApp_: Bool { get } + + /// Whether this is an bot (old-style service). + + var isBot: Bool { get } + + var isAppOrBot: Bool { get } /// Whether this uses uses SSO. var usesCompanyLogin: Bool { get } diff --git a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift index 39998db600b..447ee7db26d 100644 --- a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift +++ b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift @@ -208,8 +208,16 @@ public class ZMSearchUser: NSObject, UserType { return user.teamRole } - public var isApp: Bool { - providerIdentifier != nil + public var isApp_: Bool { + fatalError("not implemented") + } + + public var isBot: Bool { + fatalError("not implemented") + } + + public var isAppOrBot: Bool { + fatalError("not implemented") } public var usesCompanyLogin: Bool { @@ -650,7 +658,7 @@ public class ZMSearchUser: NSObject, UserType { } @objc public var canBeConnected: Bool { - guard !isApp else { return false } + guard !isApp_ else { return false } if let user { return user.canBeConnected diff --git a/wire-ios-data-model/Source/Model/User/ZMUser+Permissions.swift b/wire-ios-data-model/Source/Model/User/ZMUser+Permissions.swift index 627f1721cd2..d30153a10ce 100644 --- a/wire-ios-data-model/Source/Model/User/ZMUser+Permissions.swift +++ b/wire-ios-data-model/Source/Model/User/ZMUser+Permissions.swift @@ -257,7 +257,7 @@ public extension ZMUser { return false } - return !isApp // Bots are never guests + return !isAppOrBot // Apps or bots are never guests && !isFederated // Federated users are never guests && ZMUser.selfUser(in: context).hasTeam // There can't be guests in a team that doesn't exist && conversation.localParticipantsContain(user: self) diff --git a/wire-ios-data-model/Source/Model/User/ZMUser.m b/wire-ios-data-model/Source/Model/User/ZMUser.m index 701ac7cdd66..f6c8c0a864a 100644 --- a/wire-ios-data-model/Source/Model/User/ZMUser.m +++ b/wire-ios-data-model/Source/Model/User/ZMUser.m @@ -163,6 +163,16 @@ @implementation ZMUser return [NSSet setWithObjects:TypeKey, nil]; } ++ (NSSet *)keyPathsForValuesAffectingIsBot +{ + return [NSSet setWithObjects:TypeKey, nil]; +} + ++ (NSSet *)keyPathsForValuesAffectingIsAppOrBot +{ + return [NSSet setWithObjects:TypeKey, nil]; +} + - (BOOL)isSelfUser { if ([self isZombieObject]) { @@ -239,7 +249,7 @@ - (BOOL) managedByWire { - (BOOL)canBeConnected; { - if (self.isApp || self.isWirelessUser) { + if (self.isApp_ || self.isWirelessUser) { return NO; } return ! self.isConnected && ! self.isPendingApprovalByOtherUser; diff --git a/wire-ios-data-model/Source/Model/User/ZMUser.swift b/wire-ios-data-model/Source/Model/User/ZMUser.swift index 7a5310505be..3968a1cd63e 100644 --- a/wire-ios-data-model/Source/Model/User/ZMUser.swift +++ b/wire-ios-data-model/Source/Model/User/ZMUser.swift @@ -63,8 +63,16 @@ extension ZMUser: UserType { _isGuest(in: conversation) } - @objc public var isApp: Bool { - type == .app || type == .bot + @objc public var isApp_: Bool { + type == .app + } + + @objc public var isBot: Bool { + type == .bot + } + + @objc public var isAppOrBot: Bool { + isApp_ || isBot } public var teamName: String? { diff --git a/wire-ios-data-model/Source/Notifications/ObjectObserverTokens/UserChangeInfo.swift b/wire-ios-data-model/Source/Notifications/ObjectObserverTokens/UserChangeInfo.swift index 193c222cb71..2b1c692f6c3 100644 --- a/wire-ios-data-model/Source/Notifications/ObjectObserverTokens/UserChangeInfo.swift +++ b/wire-ios-data-model/Source/Notifications/ObjectObserverTokens/UserChangeInfo.swift @@ -46,7 +46,8 @@ extension ZMUser: ObjectInSnapshot { #keyPath(ZMUser.readReceiptsEnabled), #keyPath(ZMUser.readReceiptsEnabledChangedRemotely), ZMUserKeys.RichProfile, - #keyPath(ZMUser.isApp), + #keyPath(ZMUser.isApp_), + #keyPath(ZMUser.isBot), #keyPath(ZMUser.serviceIdentifier), #keyPath(ZMUser.providerIdentifier), ZMUserKeys.legalHoldRequest, diff --git a/wire-ios-data-model/Tests/Source/Model/TeamTests.swift b/wire-ios-data-model/Tests/Source/Model/TeamTests.swift index ce87efa36f9..eab4a21db25 100644 --- a/wire-ios-data-model/Tests/Source/Model/TeamTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/TeamTests.swift @@ -83,7 +83,7 @@ final class TeamTests: ZMConversationTestsBase { let guest = ZMUser.insertNewObject(in: uiMOC) let bot = ZMUser.insertNewObject(in: uiMOC) bot.type = .bot - XCTAssert(bot.isApp) + XCTAssert(bot.isAppOrBot) guard let conversation = ZMConversation.insertGroupConversation( moc: uiMOC, participants: [guest, bot], diff --git a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserPayloadParsingTests.swift b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserPayloadParsingTests.swift index 94d8d25608d..bd5f3b00ec7 100644 --- a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserPayloadParsingTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserPayloadParsingTests.swift @@ -63,7 +63,7 @@ final class ZMSearchUserPayloadParsingTests: ZMBaseManagedObjectTest { XCTAssertEqual(user.domain, domain) XCTAssertEqual(user.remoteIdentifier, uuid) XCTAssertEqual(user.zmAccentColor?.rawValue, 5) - XCTAssertFalse(user.isApp) + XCTAssertFalse(user.isAppOrBot) XCTAssertTrue(user.canBeConnected) } @@ -88,7 +88,7 @@ final class ZMSearchUserPayloadParsingTests: ZMBaseManagedObjectTest { )! // then - XCTAssertTrue(user.isApp) + XCTAssertTrue(user.isAppOrBot) XCTAssertEqual(user.summary, "Short summary") XCTAssertEqual(user.providerIdentifier, provider.transportString()) XCTAssertEqual(user.serviceIdentifier, uuid.transportString()) diff --git a/wire-ios-data-model/Tests/Source/Model/User/ZMUserTests.m b/wire-ios-data-model/Tests/Source/Model/User/ZMUserTests.m index 56a7391b80a..67b34820ccd 100644 --- a/wire-ios-data-model/Tests/Source/Model/User/ZMUserTests.m +++ b/wire-ios-data-model/Tests/Source/Model/User/ZMUserTests.m @@ -1927,7 +1927,7 @@ - (void)testThatItReturnsCorrectUserNameForService user.name = @"User Name"; [user setValue:@(ZMTypeOfUserBot) forKey:@"typeValue"]; - XCTAssertTrue(user.isApp); + XCTAssertTrue(user.isAppOrBot); XCTAssertEqualObjects(user.name, @"User Name"); } From a32c61ca8bcc9ac5908e1b8133d1745f3f994091 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 3 Dec 2025 15:54:07 +0100 Subject: [PATCH 03/18] fix build errors in Wire-iOS --- .../Repositories/Message/MessageLocalStore.swift | 2 +- wire-ios-data-model/Source/Model/User/ZMUser.swift | 2 +- wire-ios/Tests/Mocks/MockServiceUserType.swift | 8 +++++--- wire-ios/Tests/Mocks/MockUserType.swift | 11 +++++++---- .../ConversationAvatarViewModeTests.swift | 3 ++- .../Wire-iOS Tests/ConversationAvatarViewTests.swift | 2 +- ...rsationSenderMessageDetailsCellSnapshotTests.swift | 2 +- .../UserInterface/Components/UserImageView.swift | 10 +++++----- .../Components/Views/ConversationAvatarView.swift | 2 +- .../Views/ConversationConnectAvatarView.swift | 6 +++--- .../Views/IconImageView/UserTypeIconStyle.swift | 2 +- .../UserInterface/Components/Views/UserCell.swift | 10 +++++----- .../ConversationSenderMessageDetailsCell.swift | 10 +++++----- .../Content/Cells/ParticipantsCellViewModel.swift | 4 ++-- .../Create/ConversationCreationValues.swift | 4 ++-- .../Mentions/Collection+MentionSearch.swift | 2 +- .../Helpers/UserDetailViewControllerFactory.swift | 2 +- .../StartUI/StartUI/SearchResultsViewController.swift | 2 +- 18 files changed, 45 insertions(+), 39 deletions(-) diff --git a/WireDomain/Sources/WireDomain/Repositories/Message/MessageLocalStore.swift b/WireDomain/Sources/WireDomain/Repositories/Message/MessageLocalStore.swift index 37e344d76ea..f94f9fb6a82 100644 --- a/WireDomain/Sources/WireDomain/Repositories/Message/MessageLocalStore.swift +++ b/WireDomain/Sources/WireDomain/Repositories/Message/MessageLocalStore.swift @@ -680,7 +680,7 @@ public final class MessageLocalStore: MessageLocalStoreProtocol { let members = selfUserTeam.members.compactMap(\.user) let guests = localParticipants.filter { - !$0.isApp && $0.membership == nil + !$0.isAppOrBot && $0.membership == nil } newConversationMessage.allTeamUsersAdded = localParticipants.isSuperset(of: members) diff --git a/wire-ios-data-model/Source/Model/User/ZMUser.swift b/wire-ios-data-model/Source/Model/User/ZMUser.swift index 3968a1cd63e..53f46e50801 100644 --- a/wire-ios-data-model/Source/Model/User/ZMUser.swift +++ b/wire-ios-data-model/Source/Model/User/ZMUser.swift @@ -63,7 +63,7 @@ extension ZMUser: UserType { _isGuest(in: conversation) } - @objc public var isApp_: Bool { + @objc public var isApp_: Bool { // TODO: remove underscore type == .app } diff --git a/wire-ios/Tests/Mocks/MockServiceUserType.swift b/wire-ios/Tests/Mocks/MockServiceUserType.swift index a8c7d3c6700..bd5008691c3 100644 --- a/wire-ios/Tests/Mocks/MockServiceUserType.swift +++ b/wire-ios/Tests/Mocks/MockServiceUserType.swift @@ -16,16 +16,18 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation - final class MockServiceUserType: MockUserType, ServiceUser { var providerIdentifier: String? var serviceIdentifier: String? - override var isApp: Bool { + override var isApp_: Bool { true } + override var isBot: Bool { + false + } + } diff --git a/wire-ios/Tests/Mocks/MockUserType.swift b/wire-ios/Tests/Mocks/MockUserType.swift index fe2e0453d04..8b8b1b4a61f 100644 --- a/wire-ios/Tests/Mocks/MockUserType.swift +++ b/wire-ios/Tests/Mocks/MockUserType.swift @@ -126,10 +126,13 @@ class MockUserType: NSObject, UserType, Decodable, EditableUserType { var isSelfUser: Bool = false - var mockedIsApp: Bool = false - var isApp: Bool { - mockedIsApp - } + var mockedIsApp = false + var isApp_: Bool { mockedIsApp } + + var mockedIsBot = false + var isBot: Bool { mockedIsBot } + + var isAppOrBot: Bool { isApp_ || isBot } var isVerified: Bool = false diff --git a/wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift b/wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift index 3cfdfcd5aa9..a1dfce7390b 100644 --- a/wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift +++ b/wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift @@ -17,6 +17,7 @@ // import XCTest + @testable import Wire final class ConversationConnectAvatarViewModeTests: XCTestCase { @@ -46,7 +47,7 @@ final class ConversationConnectAvatarViewModeTests: XCTestCase { let mockServiceUser = MockServiceUserType() mockServiceUser.serviceIdentifier = "serviceIdentifier" mockServiceUser.providerIdentifier = "providerIdentifier" - XCTAssert(mockServiceUser.isApp) + XCTAssert(mockServiceUser.isApp_) users = [mockServiceUser] diff --git a/wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift b/wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift index 41727c75be2..1326c33a5d9 100644 --- a/wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift +++ b/wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift @@ -85,7 +85,7 @@ final class ConversationAvatarViewTests: XCTestCase { otherUser.serviceIdentifier = "serviceIdentifier" otherUser.providerIdentifier = "providerIdentifier" otherUser.isConnected = true - XCTAssert(otherUser.isApp) + XCTAssert(otherUser.isApp_) otherUser.zmAccentColor = .green let otherUserConversation = MockStableRandomParticipantsConversation() diff --git a/wire-ios/Wire-iOS Tests/ConversationMessageCell/ConversationSenderMessageDetailsCellSnapshotTests.swift b/wire-ios/Wire-iOS Tests/ConversationMessageCell/ConversationSenderMessageDetailsCellSnapshotTests.swift index feff8d4cdd5..49a3e2ab2d7 100644 --- a/wire-ios/Wire-iOS Tests/ConversationMessageCell/ConversationSenderMessageDetailsCellSnapshotTests.swift +++ b/wire-ios/Wire-iOS Tests/ConversationMessageCell/ConversationSenderMessageDetailsCellSnapshotTests.swift @@ -111,7 +111,7 @@ final class ConversationSenderMessageDetailsCellSnapshotTests: XCTestCase { let configuration = ConversationSenderMessageDetailsCell.Configuration( sender: mockUser, indicator: .none, - teamRoleIndicator: .app + teamRoleIndicator: .appOrBot ) // WHEN diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Components/UserImageView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Components/UserImageView.swift index 3b57aec5d0e..7d9391d7839 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Components/UserImageView.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Components/UserImageView.swift @@ -106,19 +106,19 @@ class UserImageView: AvatarImageView, UserObserving { /// Returns the appropriate border width for the user. private func borderWidth(for user: UserType) -> CGFloat { - user.isApp ? 0.5 : 0 + user.isAppOrBot ? 0.5 : 0 } /// Returns the appropriate border color for the user. private func borderColor(for user: UserType) -> CGColor? { - user.isApp ? UIColor.black.withAlphaComponent(0.08).cgColor : nil + user.isAppOrBot ? UIColor.black.withAlphaComponent(0.08).cgColor : nil } /// Returns the placeholder background color for the user. private func containerBackgroundColor(for user: UserType) -> UIColor { switch avatar { case .image: - user.isApp ? .white : .clear + user.isAppOrBot ? .white : .clear case .text: if user.isConnected || user.isSelfUser || user.isTeamMember || user.isWirelessUser { user.accentColor @@ -130,7 +130,7 @@ class UserImageView: AvatarImageView, UserObserving { /// Returns the appropriate avatar shape for the user. private func shape(for user: UserType) -> AvatarImageView.Shape { - user.isApp ? .relative : .circle + user.isAppOrBot ? .relative : .circle } // MARK: - Changing the Content @@ -169,7 +169,7 @@ class UserImageView: AvatarImageView, UserObserving { var desaturate = false if shouldDesaturate { - desaturate = !user.isConnected && !user.isSelfUser && !user.isTeamMember && !user.isApp + desaturate = !user.isConnected && !user.isSelfUser && !user.isTeamMember && !user.isAppOrBot } user.fetchProfileImage( diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationAvatarView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationAvatarView.swift index e9027ca0b79..21b4acc5e0b 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationAvatarView.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationAvatarView.swift @@ -55,7 +55,7 @@ final class ConversationAvatarView: UIView { configureForOneOnOne(users) case let .conversation(conversation: conversation) where conversation.conversationType == .group: let users = conversation.stableRandomParticipants.filter { !$0.isSelfUser } - if let user = users.first, user.isApp { + if let user = users.first, user.isAppOrBot { configureForOneOnOne(users) break } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationConnectAvatarView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationConnectAvatarView.swift index 505522176e7..0a05ae3100e 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationConnectAvatarView.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationConnectAvatarView.swift @@ -111,10 +111,10 @@ extension Mode { case (0, _): self = .none case (1, .group?): - let isApp = users[0].isApp - self = isApp ? .one(serviceUser: isApp) : .four + let isAppOrBot = users[0].isAppOrBot + self = isAppOrBot ? .one(serviceUser: isAppOrBot) : .four case (1, _): - self = .one(serviceUser: users[0].isApp) + self = .one(serviceUser: users[0].isAppOrBot) default: self = .four } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/IconImageView/UserTypeIconStyle.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/IconImageView/UserTypeIconStyle.swift index 1bf936ed4b4..02fa2dd6505 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/IconImageView/UserTypeIconStyle.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/IconImageView/UserTypeIconStyle.swift @@ -73,7 +73,7 @@ extension UserTypeIconStyle { } else if let conversation { self = !user.isGuest(in: conversation) || user.isSelfUser ? .member : .guest } else { - self = !selfUserHasTeam || user.isTeamMember || user.isApp + self = !selfUserHasTeam || user.isTeamMember || user.isAppOrBot ? .member : .guest } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/UserCell.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/UserCell.swift index e58f83b9c58..20d0a87909d 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/UserCell.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/UserCell.swift @@ -35,7 +35,7 @@ final class UserCell: SeparatorCollectionViewCell, SectionListCellType { private var userIsSelfUser = false private var isSelfUserPartOfATeam = false - private var userIsApp = false + private var userIsAppOrBot = false typealias IconColors = SemanticColors.Icon typealias LabelColors = SemanticColors.Label @@ -352,7 +352,7 @@ final class UserCell: SeparatorCollectionViewCell, SectionListCellType { if !checkmarkIconView.isHidden { accessibilityHint = isSelected ? CreateConversation.SelectedUser.hint : CreateConversation.UnselectedUser .hint - } else if userIsApp { + } else if userIsAppOrBot { accessibilityHint = ServicesList.ServiceCell.hint } else { accessibilityHint = ContactsList.UserCell.hint @@ -389,7 +389,7 @@ extension UserCell { self.userStatus = userStatus self.userIsSelfUser = userIsSelfUser self.isSelfUserPartOfATeam = isSelfUserPartOfATeam - userIsApp = user.isApp + userIsAppOrBot = user.isAppOrBot let subtitle: NSAttributedString? = if overrideSubtitle == nil { self.subtitle(for: user) @@ -451,8 +451,8 @@ extension UserCell: UserCellSubtitleProtocol {} extension UserCell { private func subtitle(for user: UserType) -> NSAttributedString? { - if user.isApp, let service = user as? SearchServiceUser { - subtitle(forServiceUser: service) + if user.isAppOrBot, let appOrBot = user as? SearchServiceUser { + subtitle(forServiceUser: appOrBot) } else { subtitle(forRegularUser: user) } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ConfigurationMessageCell/Components/ConversationSenderMessageDetailsCell.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ConfigurationMessageCell/Components/ConversationSenderMessageDetailsCell.swift index 047195ac3d9..f31f6966ab1 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ConfigurationMessageCell/Components/ConversationSenderMessageDetailsCell.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ConfigurationMessageCell/Components/ConversationSenderMessageDetailsCell.swift @@ -33,7 +33,7 @@ enum TeamRoleIndicator { case guest case externalPartner case federated - case app + case appOrBot } // MARK: - ConversationSenderMessageDetailsCell @@ -193,7 +193,7 @@ final class ConversationSenderMessageDetailsCell: UIView, ConversationMessageCel private func configureAuthorLabel(object: Configuration) { let sender = object.sender - let textColor: UIColor = sender.isApp ? SemanticColors.Label.textDefault : sender.accentColor + let textColor: UIColor = sender.isAppOrBot ? SemanticColors.Label.textDefault : sender.accentColor let attributedString = NSMutableAttributedString( string: sender.name ?? L10n.Localizable.Profile.Details.Title.unavailable, attributes: [ @@ -233,7 +233,7 @@ final class ConversationSenderMessageDetailsCell: UIView, ConversationMessageCel attributedString.append(attachment) } - case .app: + case .appOrBot: accessibilityIdentifier = "img.serviceUser" if let attachment = attachment(from: .bot, size: 14) { attributedString.append(attachment) @@ -378,8 +378,8 @@ final class ConversationSenderMessageCellDescription: ConversationMessageCellDes private extension UserType { func teamRoleIndicator(selfUser: any UserType) -> TeamRoleIndicator? { - if isApp { - .app + if isAppOrBot { + .appOrBot } else if isExternalPartner { .externalPartner diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ParticipantsCellViewModel.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ParticipantsCellViewModel.swift index 3abb2bbbb85..d887b12e812 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ParticipantsCellViewModel.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ParticipantsCellViewModel.swift @@ -105,8 +105,8 @@ final class ParticipantsCellViewModel { guard let users = Array(messageData.userTypes) as? [UserType] else { return false } let selfAddedToServiceConversation = users.any(\.isSelfUser) && conversation.areAppsPresent - let appAdded = users.any(\.isApp) - return selfAddedToServiceConversation || appAdded + let appOrBotAdded = users.any(\.isAppOrBot) + return selfAddedToServiceConversation || appOrBotAdded } /// Users displayed in the system message, up to 17 when not collapsed diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Create/ConversationCreationValues.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Create/ConversationCreationValues.swift index 953ebb83175..285c9028dff 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Create/ConversationCreationValues.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Create/ConversationCreationValues.swift @@ -52,8 +52,8 @@ final class ConversationCreationValues { } if !allowApps { - let noApps = result.filter { !$0.isApp } - result = UserSet(noApps) + let noAppsOrBots = result.filter { !$0.isAppOrBot } + result = UserSet(noAppsOrBots) } return result diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Mentions/Collection+MentionSearch.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Mentions/Collection+MentionSearch.swift index 56b4eefcbac..a5836dadc46 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Mentions/Collection+MentionSearch.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Mentions/Collection+MentionSearch.swift @@ -23,7 +23,7 @@ import WireUtilities extension Collection where Iterator.Element: UserType { func searchForMentions(withQuery query: String) -> [UserType] { - let usersToSearch = filter { !$0.isSelfUser && !$0.isApp } + let usersToSearch = filter { !$0.isSelfUser && !$0.isAppOrBot } guard !query.isEmpty else { return usersToSearch } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Helpers/UserDetailViewControllerFactory.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Helpers/UserDetailViewControllerFactory.swift index 35f0ed6c82b..764701635a4 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Helpers/UserDetailViewControllerFactory.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Helpers/UserDetailViewControllerFactory.swift @@ -40,7 +40,7 @@ enum UserDetailViewControllerFactory { selfProfileUIBuilder: some SelfProfileViewControllerBuilderProtocol ) -> UIViewController { - if user.isApp, let serviceUser = user as? ServiceUser { + if user.isAppOrBot, let serviceUser = user as? ServiceUser { return ServiceDetailViewController( serviceUser: serviceUser, actionType: .removeService(conversation), diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/SearchResultsViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/SearchResultsViewController.swift index 6cebba394b3..4fd120a9e48 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/SearchResultsViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/SearchResultsViewController.swift @@ -449,7 +449,7 @@ extension SearchResultsViewController: SearchSectionControllerDelegate { indexPath: indexPath, section: sectionFor(controller: searchSectionController) ) - } else if let service = user as? ServiceUser, service.isApp { + } else if let service = user as? ServiceUser, service.isAppOrBot { delegate?.searchResultsViewController(self, didTapOnSeviceUser: service) } else if let searchUser = user as? ZMSearchUser { delegate?.searchResultsViewController( From 33ab6b6bd9b2efe672ca32e09f94505602df49a6 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 3 Dec 2025 17:01:20 +0100 Subject: [PATCH 04/18] fix error --- .../Conversation/ZMConversation+ExternalParticipant.swift | 3 ++- wire-ios-data-model/Source/Model/User/ZMSearchUser.swift | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/wire-ios-data-model/Source/Model/Conversation/ZMConversation+ExternalParticipant.swift b/wire-ios-data-model/Source/Model/Conversation/ZMConversation+ExternalParticipant.swift index 0d4fcff5f1c..b74c333e0ee 100644 --- a/wire-ios-data-model/Source/Model/Conversation/ZMConversation+ExternalParticipant.swift +++ b/wire-ios-data-model/Source/Model/Conversation/ZMConversation+ExternalParticipant.swift @@ -48,6 +48,7 @@ public extension ZMConversation { internal class func keyPathsForValuesAffectingExternalParticipantsState() -> Set { [ "participantRoles.user.isApp", + "participantRoles.user.isBot", "participantRoles.user.hasTeam", "participantRoles.user.isExternalPartner" ] @@ -63,7 +64,7 @@ public extension ZMConversation { let selfUser = ZMUser.selfUser(in: managedObjectContext!) let otherUsers = participants.subtracting([selfUser]) - if otherUsers.count == 1, otherUsers.first!.isApp_ { + if otherUsers.count == 1, otherUsers.first!.isAppOrBot { return [] } diff --git a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift index 447ee7db26d..e41e0759045 100644 --- a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift +++ b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift @@ -213,11 +213,11 @@ public class ZMSearchUser: NSObject, UserType { } public var isBot: Bool { - fatalError("not implemented") + providerIdentifier != nil } public var isAppOrBot: Bool { - fatalError("not implemented") + isBot || isApp_ } public var usesCompanyLogin: Bool { From 86e95a56d5c28d167e34750c7c1903ea6b3b4ac1 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 3 Dec 2025 15:55:45 +0100 Subject: [PATCH 05/18] revert renaming # Conflicts: # wire-ios-data-model/Source/Model/Conversation/ZMConversation+ExternalParticipant.swift --- wire-ios-data-model/Source/Model/User/UserType.swift | 2 +- wire-ios-data-model/Source/Model/User/ZMSearchUser.swift | 4 ++-- wire-ios-data-model/Source/Model/User/ZMUser.m | 2 +- wire-ios-data-model/Source/Model/User/ZMUser.swift | 4 ++-- .../Notifications/ObjectObserverTokens/UserChangeInfo.swift | 2 +- wire-ios/Tests/Mocks/MockServiceUserType.swift | 2 +- wire-ios/Tests/Mocks/MockUserType.swift | 4 ++-- wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift | 2 +- wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/wire-ios-data-model/Source/Model/User/UserType.swift b/wire-ios-data-model/Source/Model/User/UserType.swift index 7a97ba88b6c..08bab29a01a 100644 --- a/wire-ios-data-model/Source/Model/User/UserType.swift +++ b/wire-ios-data-model/Source/Model/User/UserType.swift @@ -74,7 +74,7 @@ public protocol UserType: NSObjectProtocol, UserConnections { /// Whether this is an app (new-style MLS service). - var isApp_: Bool { get } + var isApp: Bool { get } /// Whether this is an bot (old-style service). diff --git a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift index e41e0759045..19ff6a488dc 100644 --- a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift +++ b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift @@ -208,7 +208,7 @@ public class ZMSearchUser: NSObject, UserType { return user.teamRole } - public var isApp_: Bool { + public var isApp: Bool { fatalError("not implemented") } @@ -658,7 +658,7 @@ public class ZMSearchUser: NSObject, UserType { } @objc public var canBeConnected: Bool { - guard !isApp_ else { return false } + guard !isApp else { return false } if let user { return user.canBeConnected diff --git a/wire-ios-data-model/Source/Model/User/ZMUser.m b/wire-ios-data-model/Source/Model/User/ZMUser.m index f6c8c0a864a..e6a84542185 100644 --- a/wire-ios-data-model/Source/Model/User/ZMUser.m +++ b/wire-ios-data-model/Source/Model/User/ZMUser.m @@ -249,7 +249,7 @@ - (BOOL) managedByWire { - (BOOL)canBeConnected; { - if (self.isApp_ || self.isWirelessUser) { + if (self.isApp || self.isWirelessUser) { return NO; } return ! self.isConnected && ! self.isPendingApprovalByOtherUser; diff --git a/wire-ios-data-model/Source/Model/User/ZMUser.swift b/wire-ios-data-model/Source/Model/User/ZMUser.swift index 53f46e50801..61a0b3b23e2 100644 --- a/wire-ios-data-model/Source/Model/User/ZMUser.swift +++ b/wire-ios-data-model/Source/Model/User/ZMUser.swift @@ -63,7 +63,7 @@ extension ZMUser: UserType { _isGuest(in: conversation) } - @objc public var isApp_: Bool { // TODO: remove underscore + @objc public var isApp: Bool { type == .app } @@ -72,7 +72,7 @@ extension ZMUser: UserType { } @objc public var isAppOrBot: Bool { - isApp_ || isBot + isApp || isBot } public var teamName: String? { diff --git a/wire-ios-data-model/Source/Notifications/ObjectObserverTokens/UserChangeInfo.swift b/wire-ios-data-model/Source/Notifications/ObjectObserverTokens/UserChangeInfo.swift index 2b1c692f6c3..e4abd74b6a4 100644 --- a/wire-ios-data-model/Source/Notifications/ObjectObserverTokens/UserChangeInfo.swift +++ b/wire-ios-data-model/Source/Notifications/ObjectObserverTokens/UserChangeInfo.swift @@ -46,7 +46,7 @@ extension ZMUser: ObjectInSnapshot { #keyPath(ZMUser.readReceiptsEnabled), #keyPath(ZMUser.readReceiptsEnabledChangedRemotely), ZMUserKeys.RichProfile, - #keyPath(ZMUser.isApp_), + #keyPath(ZMUser.isApp), #keyPath(ZMUser.isBot), #keyPath(ZMUser.serviceIdentifier), #keyPath(ZMUser.providerIdentifier), diff --git a/wire-ios/Tests/Mocks/MockServiceUserType.swift b/wire-ios/Tests/Mocks/MockServiceUserType.swift index bd5008691c3..ceb86d27587 100644 --- a/wire-ios/Tests/Mocks/MockServiceUserType.swift +++ b/wire-ios/Tests/Mocks/MockServiceUserType.swift @@ -22,7 +22,7 @@ final class MockServiceUserType: MockUserType, ServiceUser { var serviceIdentifier: String? - override var isApp_: Bool { + override var isApp: Bool { true } diff --git a/wire-ios/Tests/Mocks/MockUserType.swift b/wire-ios/Tests/Mocks/MockUserType.swift index 8b8b1b4a61f..3b09848f441 100644 --- a/wire-ios/Tests/Mocks/MockUserType.swift +++ b/wire-ios/Tests/Mocks/MockUserType.swift @@ -127,12 +127,12 @@ class MockUserType: NSObject, UserType, Decodable, EditableUserType { var isSelfUser: Bool = false var mockedIsApp = false - var isApp_: Bool { mockedIsApp } + var isApp: Bool { mockedIsApp } var mockedIsBot = false var isBot: Bool { mockedIsBot } - var isAppOrBot: Bool { isApp_ || isBot } + var isAppOrBot: Bool { isApp || isBot } var isVerified: Bool = false diff --git a/wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift b/wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift index a1dfce7390b..ab2bb020f4d 100644 --- a/wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift +++ b/wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift @@ -47,7 +47,7 @@ final class ConversationConnectAvatarViewModeTests: XCTestCase { let mockServiceUser = MockServiceUserType() mockServiceUser.serviceIdentifier = "serviceIdentifier" mockServiceUser.providerIdentifier = "providerIdentifier" - XCTAssert(mockServiceUser.isApp_) + XCTAssert(mockServiceUser.isApp) users = [mockServiceUser] diff --git a/wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift b/wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift index 1326c33a5d9..41727c75be2 100644 --- a/wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift +++ b/wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift @@ -85,7 +85,7 @@ final class ConversationAvatarViewTests: XCTestCase { otherUser.serviceIdentifier = "serviceIdentifier" otherUser.providerIdentifier = "providerIdentifier" otherUser.isConnected = true - XCTAssert(otherUser.isApp_) + XCTAssert(otherUser.isApp) otherUser.zmAccentColor = .green let otherUserConversation = MockStableRandomParticipantsConversation() From 291ff43e702eb18b0da6dbd4a3d95cdfca9f0b7b Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Thu, 4 Dec 2025 09:56:05 +0100 Subject: [PATCH 06/18] fix property names --- wire-ios-data-model/Source/Model/User/ZMSearchUser.swift | 6 +++--- wire-ios-data-model/Source/Model/User/ZMUser.m | 2 +- wire-ios/Tests/Mocks/MockUser.h | 2 ++ wire-ios/Tests/Mocks/MockUser.m | 1 + .../Wire-iOS Tests/ConversationAvatarViewModeTests.swift | 2 +- wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift index 19ff6a488dc..a2923ccbcfa 100644 --- a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift +++ b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift @@ -209,7 +209,7 @@ public class ZMSearchUser: NSObject, UserType { } public var isApp: Bool { - fatalError("not implemented") + false } public var isBot: Bool { @@ -217,7 +217,7 @@ public class ZMSearchUser: NSObject, UserType { } public var isAppOrBot: Bool { - isBot || isApp_ + isBot || isApp } public var usesCompanyLogin: Bool { @@ -658,7 +658,7 @@ public class ZMSearchUser: NSObject, UserType { } @objc public var canBeConnected: Bool { - guard !isApp else { return false } + guard !isAppOrBot else { return false } if let user { return user.canBeConnected diff --git a/wire-ios-data-model/Source/Model/User/ZMUser.m b/wire-ios-data-model/Source/Model/User/ZMUser.m index e6a84542185..49c372f0706 100644 --- a/wire-ios-data-model/Source/Model/User/ZMUser.m +++ b/wire-ios-data-model/Source/Model/User/ZMUser.m @@ -249,7 +249,7 @@ - (BOOL) managedByWire { - (BOOL)canBeConnected; { - if (self.isApp || self.isWirelessUser) { + if (self.isAppOrBot || self.isWirelessUser) { return NO; } return ! self.isConnected && ! self.isPendingApprovalByOtherUser; diff --git a/wire-ios/Tests/Mocks/MockUser.h b/wire-ios/Tests/Mocks/MockUser.h index 4d68bcafbe0..9f21d0d63ce 100644 --- a/wire-ios/Tests/Mocks/MockUser.h +++ b/wire-ios/Tests/Mocks/MockUser.h @@ -66,6 +66,8 @@ @property (nonatomic, readwrite) NSTimeInterval expiresAfter; @property (nonatomic, assign) BOOL isSelfUser; @property (nonatomic, assign) BOOL isApp; +@property (nonatomic, assign) BOOL isBot; +@property (nonatomic, assign) BOOL isAppOrBot; @property (nonatomic, readwrite) BOOL isTeamMember; @property (nonatomic, readwrite) TeamRole teamRole; @property (nonatomic, assign) BOOL isGuestInConversation; diff --git a/wire-ios/Tests/Mocks/MockUser.m b/wire-ios/Tests/Mocks/MockUser.m index 139cfe5258c..1b2ff9321e8 100644 --- a/wire-ios/Tests/Mocks/MockUser.m +++ b/wire-ios/Tests/Mocks/MockUser.m @@ -94,6 +94,7 @@ + (MockUser *)mockServiceUser @"displayName": @"GitHub", @"isSelfUser": @false, @"isApp": @true, + @"isBot": @false, @"isConnected": @true, @"accentColorValue": @1}]; } diff --git a/wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift b/wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift index ab2bb020f4d..9071393e563 100644 --- a/wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift +++ b/wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift @@ -47,7 +47,7 @@ final class ConversationConnectAvatarViewModeTests: XCTestCase { let mockServiceUser = MockServiceUserType() mockServiceUser.serviceIdentifier = "serviceIdentifier" mockServiceUser.providerIdentifier = "providerIdentifier" - XCTAssert(mockServiceUser.isApp) + XCTAssert(mockServiceUser.isAppOrBot) users = [mockServiceUser] diff --git a/wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift b/wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift index 41727c75be2..5d0c8a55a43 100644 --- a/wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift +++ b/wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift @@ -85,7 +85,7 @@ final class ConversationAvatarViewTests: XCTestCase { otherUser.serviceIdentifier = "serviceIdentifier" otherUser.providerIdentifier = "providerIdentifier" otherUser.isConnected = true - XCTAssert(otherUser.isApp) + XCTAssert(otherUser.isAppOrBot) otherUser.zmAccentColor = .green let otherUserConversation = MockStableRandomParticipantsConversation() From 6234722396fff84fa6cd8120ed97d7748375907f Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Thu, 4 Dec 2025 10:22:22 +0100 Subject: [PATCH 07/18] add missing argument --- .../Source/Model/User/ZMSearchUser.swift | 19 +++++++++++++------ .../SearchUserObserverCenterTests.swift | 3 ++- .../Observer/SearchUserObserverTests.swift | 3 ++- .../Observer/SearchUserSnapshotTests.swift | 3 ++- .../User/ZMSearchUserTests+Connections.swift | 3 ++- .../ZMSearchUserTests+ProfileImages.swift | 3 ++- .../User/ZMSearchUserTests+TeamUser.swift | 3 ++- .../Model/UserTypeTests+Materialize.swift | 3 ++- .../ConnectToBotURLActionProcessor.swift | 3 ++- 9 files changed, 29 insertions(+), 14 deletions(-) diff --git a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift index a2923ccbcfa..3637b1274bf 100644 --- a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift +++ b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift @@ -93,7 +93,8 @@ extension ZMSearchUser: SearchServiceUser { @objc public class ZMSearchUser: NSObject, UserType { - public var providerIdentifier: String? + public var type: TypeOfUser + public var providerIdentifier: String? // TODO: consider deleting public var summary: String? public var assetKeys: SearchUserAssetKeys? public var remoteIdentifier: UUID! @@ -209,11 +210,11 @@ public class ZMSearchUser: NSObject, UserType { } public var isApp: Bool { - false + type == .app } public var isBot: Bool { - providerIdentifier != nil + type == .bot } public var isAppOrBot: Bool { @@ -491,7 +492,8 @@ public class ZMSearchUser: NSObject, UserType { domain: String? = nil, teamIdentifier: UUID? = nil, user existingUser: ZMUser? = nil, - searchUsersCache: SearchUsersCache? + searchUsersCache: SearchUsersCache?, + type: TypeOfUser ) { let personName = PersonName.person(withName: name, schemeTagger: nil) @@ -510,6 +512,7 @@ public class ZMSearchUser: NSObject, UserType { let selfUser = ZMUser.selfUser(inUserSession: contextProvider) self.internalIsTeamMember = teamIdentifier != nil && selfUser.teamIdentifier == teamIdentifier self.internalIsConnected = internalIsTeamMember + self.type = existingUser?.type ?? type super.init() @@ -533,7 +536,8 @@ public class ZMSearchUser: NSObject, UserType { domain: user.domain, teamIdentifier: user.teamIdentifier, user: user, - searchUsersCache: searchUsersCache + searchUsersCache: searchUsersCache, + type: user.type ) } @@ -556,6 +560,8 @@ public class ZMSearchUser: NSObject, UserType { let domain = qualifiedID?["domain"] as? String let accentColorRawValue = (payload["accent_id"] as? NSNumber)?.int16Value ?? 0 + print(payload) // TODO: delete + self.init( contextProvider: contextProvider, name: name, @@ -565,7 +571,8 @@ public class ZMSearchUser: NSObject, UserType { domain: domain, teamIdentifier: teamIdentifier, user: user, - searchUsersCache: searchUsersCache + searchUsersCache: searchUsersCache, + type: .bot // TODO: this is wrong, check if the actual value can be retrieved ) self.providerIdentifier = payload["provider"] as? String diff --git a/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserObserverCenterTests.swift b/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserObserverCenterTests.swift index 7e25bc176d7..ab1c2f0b4f3 100644 --- a/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserObserverCenterTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserObserverCenterTests.swift @@ -176,7 +176,8 @@ final class SearchUserObserverCenterTests: ModelObjectsTests { accentColor: accentColor, remoteIdentifier: remoteIdentifier, user: user, - searchUsersCache: nil + searchUsersCache: nil, + type: .regular ) } } diff --git a/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserObserverTests.swift b/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserObserverTests.swift index af681198077..e4309e1e988 100644 --- a/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserObserverTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserObserverTests.swift @@ -143,7 +143,8 @@ final class SearchUserObserverTests: NotificationDispatcherTestBase { accentColor: .amber, remoteIdentifier: remoteIdentifier, user: user, - searchUsersCache: nil + searchUsersCache: nil, + type: .regular ) } } diff --git a/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserSnapshotTests.swift b/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserSnapshotTests.swift index ee41571e14b..3c3c2715a0d 100644 --- a/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserSnapshotTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserSnapshotTests.swift @@ -231,7 +231,8 @@ final class SearchUserSnapshotTests: ZMBaseManagedObjectTest { accentColor: accentColor, remoteIdentifier: remoteIdentifier, user: user, - searchUsersCache: nil + searchUsersCache: nil, + type: .regular ) } } diff --git a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+Connections.swift b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+Connections.swift index be23c1111aa..39e53190737 100644 --- a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+Connections.swift +++ b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+Connections.swift @@ -29,7 +29,8 @@ final class ZMSearchUserTests_Connections: ModelObjectsTests { handle: "johndoe", accentColor: .turquoise, remoteIdentifier: UUID(), - searchUsersCache: nil + searchUsersCache: nil, + type: .regular ) // expect diff --git a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+ProfileImages.swift b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+ProfileImages.swift index 1789b99fc8d..e4e0cbc03e0 100644 --- a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+ProfileImages.swift +++ b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+ProfileImages.swift @@ -183,7 +183,8 @@ final class ZMSearchUserTests_ProfileImages: ZMBaseManagedObjectTest { accentColor: .amber, remoteIdentifier: UUID(), user: user, - searchUsersCache: nil + searchUsersCache: nil, + type: .regular ) } diff --git a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+TeamUser.swift b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+TeamUser.swift index 2bdae56ed82..956e8fa4ee7 100644 --- a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+TeamUser.swift +++ b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+TeamUser.swift @@ -79,7 +79,8 @@ final class ZMSearchUserTests_TeamUser: ModelObjectsTests { accentColor: .amber, remoteIdentifier: teamIdentifier, teamIdentifier: teamIdentifier, - searchUsersCache: nil + searchUsersCache: nil, + type: .regular ) } } diff --git a/wire-ios-data-model/Tests/Source/Model/UserTypeTests+Materialize.swift b/wire-ios-data-model/Tests/Source/Model/UserTypeTests+Materialize.swift index 9d64d4ddea8..dee61054858 100644 --- a/wire-ios-data-model/Tests/Source/Model/UserTypeTests+Materialize.swift +++ b/wire-ios-data-model/Tests/Source/Model/UserTypeTests+Materialize.swift @@ -103,7 +103,8 @@ final class UserTypeTests_Materialize: ModelObjectsTests { accentColor: .amber, remoteIdentifier: remoteIdentifier, teamIdentifier: teamIdentifier, - searchUsersCache: nil + searchUsersCache: nil, + type: .regular ) } diff --git a/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift b/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift index 30b0c6a5195..c2eab8bb725 100644 --- a/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift +++ b/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift @@ -50,7 +50,8 @@ final class ConnectToBotURLActionProcessor: NSObject, URLActionProcessor { remoteIdentifier: serviceUserData.service, teamIdentifier: nil, user: nil, - searchUsersCache: searchUsersCache + searchUsersCache: searchUsersCache, + type: .bot // TODO: this is wrong! ) serviceUser.providerIdentifier = serviceUserData.provider.transportString() serviceUser.createConversation( From 9b55828be503d260e88aafd3d87b02e45c782807 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Thu, 4 Dec 2025 11:01:53 +0100 Subject: [PATCH 08/18] rename protocol --- .../ManagedObjectContext/CoreDataStack.swift | 6 +++--- .../Model/Conversation/ZMConversation.m | 2 +- .../Source/Model/User/ZMSearchUser.swift | 6 ++++-- .../Source/Model/User/ZMUser.m | 2 +- .../Source/Model/ZMManagedObject.m | 4 ++-- .../Source/Public/ZMConversation.h | 2 +- .../Source/Public/ZMManagedObject.h | 6 +++--- wire-ios-data-model/Source/Public/ZMUser.h | 2 +- .../Source/Model/User/ZMSearchUserTests.m | 21 ++++++++++++------- .../ConnectToBotURLActionProcessor.swift | 5 +++-- 10 files changed, 33 insertions(+), 23 deletions(-) diff --git a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack.swift b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack.swift index a0218f0e2cd..76d60b605fe 100644 --- a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack.swift +++ b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack.swift @@ -40,7 +40,7 @@ extension CoreDataStackError: LocalizedError { } } -@objc +@objc(ZMContextProvider) public protocol ContextProvider { var account: Account { get } @@ -114,8 +114,8 @@ public protocol CoreDataStackProtocol: ContextProvider { } -@objcMembers -public class CoreDataStack: NSObject, CoreDataStackProtocol { +@objc @objcMembers +public class CoreDataStack: NSObject, CoreDataStackProtocol, ContextProvider { public let account: Account diff --git a/wire-ios-data-model/Source/Model/Conversation/ZMConversation.m b/wire-ios-data-model/Source/Model/Conversation/ZMConversation.m index 6f4d40047f6..79a73353c3d 100644 --- a/wire-ios-data-model/Source/Model/Conversation/ZMConversation.m +++ b/wire-ios-data-model/Source/Model/Conversation/ZMConversation.m @@ -393,7 +393,7 @@ + (NSSet *)keyPathsForValuesAffectingIsReadOnly; return [NSSet setWithObjects:ZMConversationConversationTypeKey, ZMConversationParticipantRolesKey, ZMConversationIsForcedReadOnlyKey, nil]; } -+ (instancetype)existingOneOnOneConversationWithUser:(ZMUser *)otherUser inUserSession:(id)session; ++ (instancetype)existingOneOnOneConversationWithUser:(ZMUser *)otherUser inUserSession:(id)session; { NOT_USED(session); return otherUser.oneOnOneConversation; diff --git a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift index 3637b1274bf..a1bcc0f2ca8 100644 --- a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift +++ b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift @@ -562,6 +562,7 @@ public class ZMSearchUser: NSObject, UserType { print(payload) // TODO: delete + let providerIdentifier = payload["provider"] as? String self.init( contextProvider: contextProvider, name: name, @@ -572,10 +573,11 @@ public class ZMSearchUser: NSObject, UserType { teamIdentifier: teamIdentifier, user: user, searchUsersCache: searchUsersCache, - type: .bot // TODO: this is wrong, check if the actual value can be retrieved + type: providerIdentifier?.isEmpty != false ? .regular : .bot ) - self.providerIdentifier = payload["provider"] as? String + self.providerIdentifier = providerIdentifier + print("providerIdentifier: \(providerIdentifier, default: "nil")") // TODO: delete self.summary = payload["summary"] as? String self.assetKeys = SearchUserAssetKeys(payload: payload) self.internalIsAccountDeleted = payload["deleted"] as? Bool diff --git a/wire-ios-data-model/Source/Model/User/ZMUser.m b/wire-ios-data-model/Source/Model/User/ZMUser.m index 49c372f0706..162099df1cd 100644 --- a/wire-ios-data-model/Source/Model/User/ZMUser.m +++ b/wire-ios-data-model/Source/Model/User/ZMUser.m @@ -720,7 +720,7 @@ + (instancetype)selfUserInContext:(NSManagedObjectContext *)moc; @implementation ZMUser (Utilities) -+ (ZMUser *)selfUserInUserSession:(id)session ++ (ZMUser *)selfUserInUserSession:(id)session { VerifyReturnNil(session != nil); return [self selfUserInContext:session.viewContext]; diff --git a/wire-ios-data-model/Source/Model/ZMManagedObject.m b/wire-ios-data-model/Source/Model/ZMManagedObject.m index 09451070212..9cf96c97ed1 100644 --- a/wire-ios-data-model/Source/Model/ZMManagedObject.m +++ b/wire-ios-data-model/Source/Model/ZMManagedObject.m @@ -39,7 +39,7 @@ @interface ZMManagedObject () @implementation ZMManagedObject -+ (NSManagedObjectID *)objectIDForURIRepresentation:(NSURL *)url inUserSession:(id)userSession ++ (NSManagedObjectID *)objectIDForURIRepresentation:(NSURL *)url inUserSession:(id)userSession { VerifyReturnNil(url != nil); VerifyReturnNil(userSession != nil); @@ -56,7 +56,7 @@ + (NSManagedObjectID *)objectIDForURIRepresentation:(NSURL *)url inManagedObject return [psc managedObjectIDForURIRepresentation:url]; } -+ (instancetype)existingObjectWithID:(NSManagedObjectID *)identifier inUserSession:(id)userSession; ++ (instancetype)existingObjectWithID:(NSManagedObjectID *)identifier inUserSession:(id)userSession; { VerifyReturnNil(identifier); VerifyReturnNil(userSession); diff --git a/wire-ios-data-model/Source/Public/ZMConversation.h b/wire-ios-data-model/Source/Public/ZMConversation.h index d502e68ecb1..0979a32b485 100644 --- a/wire-ios-data-model/Source/Public/ZMConversation.h +++ b/wire-ios-data-model/Source/Public/ZMConversation.h @@ -97,7 +97,7 @@ typedef NS_ENUM(int16_t, ZMConversationListIndicator) { - (void)markAsUnread; /// If that conversation exists, it is returned, @c nil otherwise. -+ (nullable instancetype)existingOneOnOneConversationWithUser:(nonnull ZMUser *)otherUser inUserSession:(nonnull id )session; ++ (nullable instancetype)existingOneOnOneConversationWithUser:(nonnull ZMUser *)otherUser inUserSession:(nonnull id )session; @end diff --git a/wire-ios-data-model/Source/Public/ZMManagedObject.h b/wire-ios-data-model/Source/Public/ZMManagedObject.h index aadeabc2944..6072ce26613 100644 --- a/wire-ios-data-model/Source/Public/ZMManagedObject.h +++ b/wire-ios-data-model/Source/Public/ZMManagedObject.h @@ -20,15 +20,15 @@ extern NSString * _Nonnull const ZMDataPropertySuffix; -@protocol ContextProvider; +@protocol ZMContextProvider; @interface ZMManagedObject : NSManagedObject @property (nonatomic, readonly) BOOL isZombieObject; -+ (nullable NSManagedObjectID *)objectIDForURIRepresentation:(nullable NSURL *)url inUserSession:(nullable id)userSession; ++ (nullable NSManagedObjectID *)objectIDForURIRepresentation:(nullable NSURL *)url inUserSession:(nullable id)userSession; + (nullable NSManagedObjectID *)objectIDForURIRepresentation:(nullable NSURL *)url inManagedObjectContext:(nullable NSManagedObjectContext *)context; -+ (nullable instancetype)existingObjectWithID:(nullable NSManagedObjectID *)identifier inUserSession:(nullable id)userSession; ++ (nullable instancetype)existingObjectWithID:(nullable NSManagedObjectID *)identifier inUserSession:(nullable id)userSession; + (nullable instancetype)existingObjectWithObjectIdentifier:(nullable NSString *)identifier inManagedObjectContext:(nullable NSManagedObjectContext *)context; - (nullable NSString *)objectIDURLString; diff --git a/wire-ios-data-model/Source/Public/ZMUser.h b/wire-ios-data-model/Source/Public/ZMUser.h index 4fc0340562d..62d951a7f66 100644 --- a/wire-ios-data-model/Source/Public/ZMUser.h +++ b/wire-ios-data-model/Source/Public/ZMUser.h @@ -87,7 +87,7 @@ typedef NS_ENUM(int16_t, ZMBlockState) { @interface ZMUser (Utilities) -+ (ZMUser *_Nonnull)selfUserInUserSession:(id _Nonnull)session; ++ (ZMUser *_Nonnull)selfUserInUserSession:(id _Nonnull)session; @end diff --git a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests.m b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests.m index 6352859649f..c6f4fca80d4 100644 --- a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests.m +++ b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests.m @@ -57,7 +57,8 @@ - (void)testThatItComparesEqualBasedOnRemoteID; domain:nil teamIdentifier:nil user:nil - searchUsersCache:nil]; + searchUsersCache:nil + type:ZMTypeOfUserRegular]; // (1) ZMSearchUser *user2 = [[ZMSearchUser alloc] initWithContextProvider:self.coreDataStack @@ -68,7 +69,8 @@ - (void)testThatItComparesEqualBasedOnRemoteID; domain:nil teamIdentifier:nil user:nil - searchUsersCache:nil]; + searchUsersCache:nil + type:ZMTypeOfUserRegular]; XCTAssertEqualObjects(user1, user2); XCTAssertEqual(user1.hash, user2.hash); @@ -82,7 +84,8 @@ - (void)testThatItComparesEqualBasedOnRemoteID; domain:nil teamIdentifier:nil user:nil - searchUsersCache:nil]; + searchUsersCache:nil + type:ZMTypeOfUserRegular]; XCTAssertNotEqualObjects(user1, user3); } @@ -103,7 +106,8 @@ - (void)testThatItHasAllDataItWasInitializedWith domain:nil teamIdentifier:nil user:nil - searchUsersCache:nil]; + searchUsersCache:nil + type:ZMTypeOfUserRegular]; // then @@ -142,7 +146,8 @@ - (void)testThatItUsesDataFromAUserIfItHasOne domain:nil teamIdentifier:nil user:user - searchUsersCache:nil]; + searchUsersCache:nil + type:ZMTypeOfUserRegular]; // then XCTAssertEqualObjects(searchUser.name, user.name); @@ -170,7 +175,8 @@ - (void)testThatItCanBeConnectedIfItIsNotAlreadyConnected domain:nil teamIdentifier:nil user:nil - searchUsersCache:nil]; + searchUsersCache:nil + type:ZMTypeOfUserRegular]; // then @@ -189,7 +195,8 @@ - (void)testThatItCanNotBeConnectedIfItHasNoRemoteIdentifier domain:nil teamIdentifier:nil user:nil - searchUsersCache:nil]; + searchUsersCache:nil + type:ZMTypeOfUserRegular]; // then XCTAssertFalse(searchUser.canBeConnected); diff --git a/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift b/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift index c2eab8bb725..181f936177f 100644 --- a/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift +++ b/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift @@ -42,6 +42,7 @@ final class ConnectToBotURLActionProcessor: NSObject, URLActionProcessor { func process(urlAction: URLAction, delegate: PresentationDelegate?) { guard case let .connectBot(serviceUserData) = urlAction else { return } + let providerIdentifier = serviceUserData.provider.transportString() let serviceUser = ZMSearchUser( contextProvider: contextProvider, name: "", @@ -51,9 +52,9 @@ final class ConnectToBotURLActionProcessor: NSObject, URLActionProcessor { teamIdentifier: nil, user: nil, searchUsersCache: searchUsersCache, - type: .bot // TODO: this is wrong! + type: providerIdentifier.isEmpty ? .regular : .bot // TODO: apps? different stack? ) - serviceUser.providerIdentifier = serviceUserData.provider.transportString() + serviceUser.providerIdentifier = providerIdentifier serviceUser.createConversation( transportSession: transportSession, eventProcessor: eventProcessor, From deabb1e0a1becc0fcb8d07220c002d5e75604b6c Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Thu, 4 Dec 2025 11:31:04 +0100 Subject: [PATCH 09/18] revert changes --- .../Source/Model/User/ServiceUser.swift | 2 -- .../Source/Model/User/ZMSearchUser.swift | 28 ++++++------------- .../SearchUserObserverCenterTests.swift | 3 +- .../Observer/SearchUserObserverTests.swift | 3 +- .../Observer/SearchUserSnapshotTests.swift | 3 +- .../User/ZMSearchUserTests+Connections.swift | 3 +- .../ZMSearchUserTests+ProfileImages.swift | 3 +- .../User/ZMSearchUserTests+TeamUser.swift | 3 +- .../Source/Model/User/ZMSearchUserTests.m | 21 +++++--------- .../Model/UserTypeTests+Materialize.swift | 3 +- 10 files changed, 23 insertions(+), 49 deletions(-) diff --git a/wire-ios-data-model/Source/Model/User/ServiceUser.swift b/wire-ios-data-model/Source/Model/User/ServiceUser.swift index e0c4f529b4e..19bce9f4df5 100644 --- a/wire-ios-data-model/Source/Model/User/ServiceUser.swift +++ b/wire-ios-data-model/Source/Model/User/ServiceUser.swift @@ -16,8 +16,6 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation - @objc public protocol ServiceUser: AnyObject, UserType { var providerIdentifier: String? { get } diff --git a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift index a1bcc0f2ca8..75f56b3f8a9 100644 --- a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift +++ b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift @@ -93,8 +93,8 @@ extension ZMSearchUser: SearchServiceUser { @objc public class ZMSearchUser: NSObject, UserType { - public var type: TypeOfUser - public var providerIdentifier: String? // TODO: consider deleting + + public var providerIdentifier: String? public var summary: String? public var assetKeys: SearchUserAssetKeys? public var remoteIdentifier: UUID! @@ -210,16 +210,14 @@ public class ZMSearchUser: NSObject, UserType { } public var isApp: Bool { - type == .app + false // Apps (new-style services) don't use ZMSearchUser } public var isBot: Bool { - type == .bot + providerIdentifier?.isEmpty != false } - public var isAppOrBot: Bool { - isBot || isApp - } + public var isAppOrBot: Bool { isBot } public var usesCompanyLogin: Bool { user?.usesCompanyLogin == true @@ -492,8 +490,7 @@ public class ZMSearchUser: NSObject, UserType { domain: String? = nil, teamIdentifier: UUID? = nil, user existingUser: ZMUser? = nil, - searchUsersCache: SearchUsersCache?, - type: TypeOfUser + searchUsersCache: SearchUsersCache? ) { let personName = PersonName.person(withName: name, schemeTagger: nil) @@ -512,7 +509,6 @@ public class ZMSearchUser: NSObject, UserType { let selfUser = ZMUser.selfUser(inUserSession: contextProvider) self.internalIsTeamMember = teamIdentifier != nil && selfUser.teamIdentifier == teamIdentifier self.internalIsConnected = internalIsTeamMember - self.type = existingUser?.type ?? type super.init() @@ -536,8 +532,7 @@ public class ZMSearchUser: NSObject, UserType { domain: user.domain, teamIdentifier: user.teamIdentifier, user: user, - searchUsersCache: searchUsersCache, - type: user.type + searchUsersCache: searchUsersCache ) } @@ -560,9 +555,6 @@ public class ZMSearchUser: NSObject, UserType { let domain = qualifiedID?["domain"] as? String let accentColorRawValue = (payload["accent_id"] as? NSNumber)?.int16Value ?? 0 - print(payload) // TODO: delete - - let providerIdentifier = payload["provider"] as? String self.init( contextProvider: contextProvider, name: name, @@ -572,12 +564,10 @@ public class ZMSearchUser: NSObject, UserType { domain: domain, teamIdentifier: teamIdentifier, user: user, - searchUsersCache: searchUsersCache, - type: providerIdentifier?.isEmpty != false ? .regular : .bot + searchUsersCache: searchUsersCache ) - self.providerIdentifier = providerIdentifier - print("providerIdentifier: \(providerIdentifier, default: "nil")") // TODO: delete + self.providerIdentifier = payload["provider"] as? String self.summary = payload["summary"] as? String self.assetKeys = SearchUserAssetKeys(payload: payload) self.internalIsAccountDeleted = payload["deleted"] as? Bool diff --git a/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserObserverCenterTests.swift b/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserObserverCenterTests.swift index ab1c2f0b4f3..7e25bc176d7 100644 --- a/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserObserverCenterTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserObserverCenterTests.swift @@ -176,8 +176,7 @@ final class SearchUserObserverCenterTests: ModelObjectsTests { accentColor: accentColor, remoteIdentifier: remoteIdentifier, user: user, - searchUsersCache: nil, - type: .regular + searchUsersCache: nil ) } } diff --git a/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserObserverTests.swift b/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserObserverTests.swift index e4309e1e988..af681198077 100644 --- a/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserObserverTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserObserverTests.swift @@ -143,8 +143,7 @@ final class SearchUserObserverTests: NotificationDispatcherTestBase { accentColor: .amber, remoteIdentifier: remoteIdentifier, user: user, - searchUsersCache: nil, - type: .regular + searchUsersCache: nil ) } } diff --git a/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserSnapshotTests.swift b/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserSnapshotTests.swift index 3c3c2715a0d..ee41571e14b 100644 --- a/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserSnapshotTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/Observer/SearchUserSnapshotTests.swift @@ -231,8 +231,7 @@ final class SearchUserSnapshotTests: ZMBaseManagedObjectTest { accentColor: accentColor, remoteIdentifier: remoteIdentifier, user: user, - searchUsersCache: nil, - type: .regular + searchUsersCache: nil ) } } diff --git a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+Connections.swift b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+Connections.swift index 39e53190737..be23c1111aa 100644 --- a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+Connections.swift +++ b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+Connections.swift @@ -29,8 +29,7 @@ final class ZMSearchUserTests_Connections: ModelObjectsTests { handle: "johndoe", accentColor: .turquoise, remoteIdentifier: UUID(), - searchUsersCache: nil, - type: .regular + searchUsersCache: nil ) // expect diff --git a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+ProfileImages.swift b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+ProfileImages.swift index e4e0cbc03e0..1789b99fc8d 100644 --- a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+ProfileImages.swift +++ b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+ProfileImages.swift @@ -183,8 +183,7 @@ final class ZMSearchUserTests_ProfileImages: ZMBaseManagedObjectTest { accentColor: .amber, remoteIdentifier: UUID(), user: user, - searchUsersCache: nil, - type: .regular + searchUsersCache: nil ) } diff --git a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+TeamUser.swift b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+TeamUser.swift index 956e8fa4ee7..2bdae56ed82 100644 --- a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+TeamUser.swift +++ b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests+TeamUser.swift @@ -79,8 +79,7 @@ final class ZMSearchUserTests_TeamUser: ModelObjectsTests { accentColor: .amber, remoteIdentifier: teamIdentifier, teamIdentifier: teamIdentifier, - searchUsersCache: nil, - type: .regular + searchUsersCache: nil ) } } diff --git a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests.m b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests.m index c6f4fca80d4..6352859649f 100644 --- a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests.m +++ b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserTests.m @@ -57,8 +57,7 @@ - (void)testThatItComparesEqualBasedOnRemoteID; domain:nil teamIdentifier:nil user:nil - searchUsersCache:nil - type:ZMTypeOfUserRegular]; + searchUsersCache:nil]; // (1) ZMSearchUser *user2 = [[ZMSearchUser alloc] initWithContextProvider:self.coreDataStack @@ -69,8 +68,7 @@ - (void)testThatItComparesEqualBasedOnRemoteID; domain:nil teamIdentifier:nil user:nil - searchUsersCache:nil - type:ZMTypeOfUserRegular]; + searchUsersCache:nil]; XCTAssertEqualObjects(user1, user2); XCTAssertEqual(user1.hash, user2.hash); @@ -84,8 +82,7 @@ - (void)testThatItComparesEqualBasedOnRemoteID; domain:nil teamIdentifier:nil user:nil - searchUsersCache:nil - type:ZMTypeOfUserRegular]; + searchUsersCache:nil]; XCTAssertNotEqualObjects(user1, user3); } @@ -106,8 +103,7 @@ - (void)testThatItHasAllDataItWasInitializedWith domain:nil teamIdentifier:nil user:nil - searchUsersCache:nil - type:ZMTypeOfUserRegular]; + searchUsersCache:nil]; // then @@ -146,8 +142,7 @@ - (void)testThatItUsesDataFromAUserIfItHasOne domain:nil teamIdentifier:nil user:user - searchUsersCache:nil - type:ZMTypeOfUserRegular]; + searchUsersCache:nil]; // then XCTAssertEqualObjects(searchUser.name, user.name); @@ -175,8 +170,7 @@ - (void)testThatItCanBeConnectedIfItIsNotAlreadyConnected domain:nil teamIdentifier:nil user:nil - searchUsersCache:nil - type:ZMTypeOfUserRegular]; + searchUsersCache:nil]; // then @@ -195,8 +189,7 @@ - (void)testThatItCanNotBeConnectedIfItHasNoRemoteIdentifier domain:nil teamIdentifier:nil user:nil - searchUsersCache:nil - type:ZMTypeOfUserRegular]; + searchUsersCache:nil]; // then XCTAssertFalse(searchUser.canBeConnected); diff --git a/wire-ios-data-model/Tests/Source/Model/UserTypeTests+Materialize.swift b/wire-ios-data-model/Tests/Source/Model/UserTypeTests+Materialize.swift index dee61054858..9d64d4ddea8 100644 --- a/wire-ios-data-model/Tests/Source/Model/UserTypeTests+Materialize.swift +++ b/wire-ios-data-model/Tests/Source/Model/UserTypeTests+Materialize.swift @@ -103,8 +103,7 @@ final class UserTypeTests_Materialize: ModelObjectsTests { accentColor: .amber, remoteIdentifier: remoteIdentifier, teamIdentifier: teamIdentifier, - searchUsersCache: nil, - type: .regular + searchUsersCache: nil ) } From 59edd7c914e2a5a29a81d862e2249b9d6125aa91 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Thu, 4 Dec 2025 11:53:04 +0100 Subject: [PATCH 10/18] fixes --- wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.h | 2 +- wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.m | 2 +- .../URLActionProcessors/ConnectToBotURLActionProcessor.swift | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.h b/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.h index 86c9b7f25fe..4ea8ebd52f3 100644 --- a/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.h +++ b/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.h @@ -35,7 +35,7 @@ @interface ZMSyncStrategy : NSObject -- (instancetype _Nonnull )initWithContextProvider:(id _Nonnull)contextProvider +- (instancetype _Nonnull )initWithContextProvider:(id _Nonnull)contextProvider notificationsDispatcher:(NotificationDispatcher * _Nonnull)notificationsDispatcher operationStatus:(OperationStatus * _Nonnull)operationStatus application:(id _Nonnull)application diff --git a/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.m b/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.m index 0d65d10fd38..b05e037ddab 100644 --- a/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.m +++ b/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.m @@ -58,7 +58,7 @@ @implementation ZMSyncStrategy ZM_EMPTY_ASSERTING_INIT() -- (instancetype)initWithContextProvider:(id)contextProvider +- (instancetype)initWithContextProvider:(id)contextProvider notificationsDispatcher:(NotificationDispatcher *)notificationsDispatcher operationStatus:(OperationStatus *)operationStatus application:(id)application diff --git a/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift b/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift index 181f936177f..f9a2776a738 100644 --- a/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift +++ b/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift @@ -51,8 +51,7 @@ final class ConnectToBotURLActionProcessor: NSObject, URLActionProcessor { remoteIdentifier: serviceUserData.service, teamIdentifier: nil, user: nil, - searchUsersCache: searchUsersCache, - type: providerIdentifier.isEmpty ? .regular : .bot // TODO: apps? different stack? + searchUsersCache: searchUsersCache ) serviceUser.providerIdentifier = providerIdentifier serviceUser.createConversation( From bdb435e400833db0024e15347be627f43a6724dc Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Thu, 4 Dec 2025 12:03:57 +0100 Subject: [PATCH 11/18] minor change --- wire-ios-data-model/Source/Model/User/ZMSearchUser.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift index 75f56b3f8a9..3f26a20e516 100644 --- a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift +++ b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift @@ -481,7 +481,7 @@ public class ZMSearchUser: NSObject, UserType { } @objc - public init( + public required init( contextProvider: ContextProvider, name: String, handle: String?, From bab57b65e126f6ab39c269dda844b8b546b1ec3d Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Thu, 4 Dec 2025 12:06:09 +0100 Subject: [PATCH 12/18] rename protocol --- wire-ios-data-model/Source/Model/User/ServiceUser.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/wire-ios-data-model/Source/Model/User/ServiceUser.swift b/wire-ios-data-model/Source/Model/User/ServiceUser.swift index 19bce9f4df5..bcdf01260cb 100644 --- a/wire-ios-data-model/Source/Model/User/ServiceUser.swift +++ b/wire-ios-data-model/Source/Model/User/ServiceUser.swift @@ -17,16 +17,19 @@ // @objc -public protocol ServiceUser: AnyObject, UserType { +public protocol Bot: AnyObject, UserType { var providerIdentifier: String? { get } var serviceIdentifier: String? { get } } @objc -public protocol SearchServiceUser: ServiceUser { +public protocol BotSearchResult: Bot { var summary: String? { get } } +public typealias ServiceUser = Bot +public typealias SearchServiceUser = BotSearchResult + extension ZMUser { static let servicesMustBeMentioned = false static let serviceMentionKeyword = "@bots" From a67301a35741663e942c58593e52865c536e3d62 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Thu, 4 Dec 2025 12:51:01 +0100 Subject: [PATCH 13/18] fix condition --- wire-ios-data-model/Source/Model/User/ZMSearchUser.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift index 3f26a20e516..95221c9d041 100644 --- a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift +++ b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift @@ -214,7 +214,7 @@ public class ZMSearchUser: NSObject, UserType { } public var isBot: Bool { - providerIdentifier?.isEmpty != false + providerIdentifier?.isEmpty == false } public var isAppOrBot: Bool { isBot } From edb5c090caf85d1f901cae167e72e6d2a8bf2d5d Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Mon, 8 Dec 2025 10:18:59 +0100 Subject: [PATCH 14/18] Trigger CI From f92bcd55e8bbfdd1a9db29df3c14cdc0bda69baa Mon Sep 17 00:00:00 2001 From: Sam Wyndham Date: Mon, 8 Dec 2025 11:30:53 +0100 Subject: [PATCH 15/18] fix: add missing developer flag check - WPB-22238 (#3987) --- .../WireMessagingUI/WireCells/Components/Files/FilesView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WireMessaging/Sources/WireMessagingUI/WireCells/Components/Files/FilesView.swift b/WireMessaging/Sources/WireMessagingUI/WireCells/Components/Files/FilesView.swift index 23fed99a6b9..7cab19ea6d4 100644 --- a/WireMessaging/Sources/WireMessagingUI/WireCells/Components/Files/FilesView.swift +++ b/WireMessaging/Sources/WireMessagingUI/WireCells/Components/Files/FilesView.swift @@ -126,7 +126,7 @@ private extension FilesView { } } - if !viewModel.isRecycleBin { + if !viewModel.isRecycleBin, viewModel.isFoldersEnabled { ToolbarItem(placement: .navigationBarTrailing) { moreActionsButton } From 82076c5a11ca54fda7bc51728cdb44e6a311ad2d Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Mon, 8 Dec 2025 11:55:40 +0100 Subject: [PATCH 16/18] fix typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: François Benaiteau --- wire-ios-data-model/Source/Model/User/UserType.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wire-ios-data-model/Source/Model/User/UserType.swift b/wire-ios-data-model/Source/Model/User/UserType.swift index 08bab29a01a..f5c28d7343b 100644 --- a/wire-ios-data-model/Source/Model/User/UserType.swift +++ b/wire-ios-data-model/Source/Model/User/UserType.swift @@ -76,7 +76,7 @@ public protocol UserType: NSObjectProtocol, UserConnections { var isApp: Bool { get } - /// Whether this is an bot (old-style service). + /// Whether this is a bot (old-style service). var isBot: Bool { get } From cf7e2416598cb98f51a606b6c7437c56fff41ef3 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 10 Dec 2025 10:53:05 +0100 Subject: [PATCH 17/18] chore: separate user properties isApp and isBot - WPB-21441 --- .../Repositories/Message/MessageLocalStore.swift | 2 +- .../ManagedObjectContext/CoreDataStack.swift | 6 +++--- .../Model/Conversation/ConversationLike.swift | 4 ++-- .../ZMConversation+ExternalParticipant.swift | 7 ++++--- .../ZMConversation+SystemMessages.swift | 2 +- .../Source/Model/Conversation/ZMConversation.m | 2 +- .../Source/Model/User/ServiceUser.swift | 9 +++++---- .../Source/Model/User/Set+ServiceUser.swift | 8 +++----- .../Source/Model/User/UserType.swift | 8 +++++++- .../Source/Model/User/ZMSearchUser.swift | 13 ++++++++++--- .../Source/Model/User/ZMUser+Permissions.swift | 2 +- wire-ios-data-model/Source/Model/User/ZMUser.m | 14 ++++++++++++-- wire-ios-data-model/Source/Model/User/ZMUser.swift | 10 +++++++++- wire-ios-data-model/Source/Model/ZMManagedObject.m | 4 ++-- .../ObjectObserverTokens/UserChangeInfo.swift | 1 + wire-ios-data-model/Source/Public/ZMConversation.h | 2 +- .../Source/Public/ZMManagedObject.h | 6 +++--- wire-ios-data-model/Source/Public/ZMUser.h | 2 +- .../Tests/Source/Model/TeamTests.swift | 2 +- .../User/ZMSearchUserPayloadParsingTests.swift | 4 ++-- .../Tests/Source/Model/User/ZMUserTests.m | 2 +- .../Source/Synchronization/ZMSyncStrategy.h | 2 +- .../Source/Synchronization/ZMSyncStrategy.m | 2 +- .../ConnectToBotURLActionProcessor.swift | 3 ++- wire-ios/Tests/Mocks/MockServiceUserType.swift | 6 ++++-- wire-ios/Tests/Mocks/MockUser.h | 2 ++ wire-ios/Tests/Mocks/MockUser.m | 1 + wire-ios/Tests/Mocks/MockUserType.swift | 11 +++++++---- .../ConversationAvatarViewModeTests.swift | 3 ++- .../ConversationAvatarViewTests.swift | 2 +- ...tionSenderMessageDetailsCellSnapshotTests.swift | 2 +- .../UserInterface/Components/UserImageView.swift | 10 +++++----- .../Components/Views/ConversationAvatarView.swift | 2 +- .../Views/ConversationConnectAvatarView.swift | 6 +++--- .../Views/IconImageView/UserTypeIconStyle.swift | 2 +- .../UserInterface/Components/Views/UserCell.swift | 10 +++++----- .../ConversationSenderMessageDetailsCell.swift | 10 +++++----- .../Content/Cells/ParticipantsCellViewModel.swift | 4 ++-- .../Create/ConversationCreationValues.swift | 4 ++-- .../Mentions/Collection+MentionSearch.swift | 2 +- .../Helpers/UserDetailViewControllerFactory.swift | 2 +- .../StartUI/SearchResultsViewController.swift | 2 +- 42 files changed, 120 insertions(+), 78 deletions(-) diff --git a/WireDomain/Sources/WireDomain/Repositories/Message/MessageLocalStore.swift b/WireDomain/Sources/WireDomain/Repositories/Message/MessageLocalStore.swift index 37e344d76ea..f94f9fb6a82 100644 --- a/WireDomain/Sources/WireDomain/Repositories/Message/MessageLocalStore.swift +++ b/WireDomain/Sources/WireDomain/Repositories/Message/MessageLocalStore.swift @@ -680,7 +680,7 @@ public final class MessageLocalStore: MessageLocalStoreProtocol { let members = selfUserTeam.members.compactMap(\.user) let guests = localParticipants.filter { - !$0.isApp && $0.membership == nil + !$0.isAppOrBot && $0.membership == nil } newConversationMessage.allTeamUsersAdded = localParticipants.isSuperset(of: members) diff --git a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack.swift b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack.swift index a0218f0e2cd..76d60b605fe 100644 --- a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack.swift +++ b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack.swift @@ -40,7 +40,7 @@ extension CoreDataStackError: LocalizedError { } } -@objc +@objc(ZMContextProvider) public protocol ContextProvider { var account: Account { get } @@ -114,8 +114,8 @@ public protocol CoreDataStackProtocol: ContextProvider { } -@objcMembers -public class CoreDataStack: NSObject, CoreDataStackProtocol { +@objc @objcMembers +public class CoreDataStack: NSObject, CoreDataStackProtocol, ContextProvider { public let account: Account diff --git a/wire-ios-data-model/Source/Model/Conversation/ConversationLike.swift b/wire-ios-data-model/Source/Model/Conversation/ConversationLike.swift index 9b64c79ea1b..be1edbae348 100644 --- a/wire-ios-data-model/Source/Model/Conversation/ConversationLike.swift +++ b/wire-ios-data-model/Source/Model/Conversation/ConversationLike.swift @@ -102,13 +102,13 @@ extension ZMConversation: ConversationLike { public var sortedOtherParticipants: [UserType] { localParticipants - .filter { !$0.isApp } + .filter { !$0.isAppOrBot } .sortedAscendingPrependingNil(by: \.name) } public var sortedApps: [UserType] { localParticipants - .filter(\.isApp) + .filter(\.isAppOrBot) .sortedAscendingPrependingNil(by: \.name) } diff --git a/wire-ios-data-model/Source/Model/Conversation/ZMConversation+ExternalParticipant.swift b/wire-ios-data-model/Source/Model/Conversation/ZMConversation+ExternalParticipant.swift index d649af03add..b74c333e0ee 100644 --- a/wire-ios-data-model/Source/Model/Conversation/ZMConversation+ExternalParticipant.swift +++ b/wire-ios-data-model/Source/Model/Conversation/ZMConversation+ExternalParticipant.swift @@ -48,6 +48,7 @@ public extension ZMConversation { internal class func keyPathsForValuesAffectingExternalParticipantsState() -> Set { [ "participantRoles.user.isApp", + "participantRoles.user.isBot", "participantRoles.user.hasTeam", "participantRoles.user.isExternalPartner" ] @@ -63,7 +64,7 @@ public extension ZMConversation { let selfUser = ZMUser.selfUser(in: managedObjectContext!) let otherUsers = participants.subtracting([selfUser]) - if otherUsers.count == 1, otherUsers.first!.isApp { + if otherUsers.count == 1, otherUsers.first!.isAppOrBot { return [] } @@ -75,7 +76,7 @@ public extension ZMConversation { for user in otherUsers { if canDisplayGuests, user.isFederated { state.insert(.visibleRemotes) - } else if user.isApp { + } else if user.isAppOrBot { state.insert(.visibleApps) } else if canDisplayExternals, user.isExternalPartner { state.insert(.visibleExternals) @@ -98,7 +99,7 @@ public extension ZMConversation { /// Returns whether apps are present, regardless of the display rules. var areAppsPresent: Bool { - localParticipants.any(\.isApp) + localParticipants.any(\.isAppOrBot) } /// Returns whether guests are present, regardless of the display rules. diff --git a/wire-ios-data-model/Source/Model/Conversation/ZMConversation+SystemMessages.swift b/wire-ios-data-model/Source/Model/Conversation/ZMConversation+SystemMessages.swift index e461696d403..94ae0cbb209 100644 --- a/wire-ios-data-model/Source/Model/Conversation/ZMConversation+SystemMessages.swift +++ b/wire-ios-data-model/Source/Model/Conversation/ZMConversation+SystemMessages.swift @@ -97,7 +97,7 @@ public extension ZMConversation { team == selfUserTeam { let members = selfUserTeam.members.compactMap(\.user) - let guests = users.filter { !$0.isApp && $0.membership == nil } + let guests = users.filter { !$0.isAppOrBot && $0.membership == nil } systemMessage.allTeamUsersAdded = users.isSuperset(of: members) systemMessage.numberOfGuestsAdded = Int16(guests.count) diff --git a/wire-ios-data-model/Source/Model/Conversation/ZMConversation.m b/wire-ios-data-model/Source/Model/Conversation/ZMConversation.m index 6f4d40047f6..79a73353c3d 100644 --- a/wire-ios-data-model/Source/Model/Conversation/ZMConversation.m +++ b/wire-ios-data-model/Source/Model/Conversation/ZMConversation.m @@ -393,7 +393,7 @@ + (NSSet *)keyPathsForValuesAffectingIsReadOnly; return [NSSet setWithObjects:ZMConversationConversationTypeKey, ZMConversationParticipantRolesKey, ZMConversationIsForcedReadOnlyKey, nil]; } -+ (instancetype)existingOneOnOneConversationWithUser:(ZMUser *)otherUser inUserSession:(id)session; ++ (instancetype)existingOneOnOneConversationWithUser:(ZMUser *)otherUser inUserSession:(id)session; { NOT_USED(session); return otherUser.oneOnOneConversation; diff --git a/wire-ios-data-model/Source/Model/User/ServiceUser.swift b/wire-ios-data-model/Source/Model/User/ServiceUser.swift index e0c4f529b4e..bcdf01260cb 100644 --- a/wire-ios-data-model/Source/Model/User/ServiceUser.swift +++ b/wire-ios-data-model/Source/Model/User/ServiceUser.swift @@ -16,19 +16,20 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation - @objc -public protocol ServiceUser: AnyObject, UserType { +public protocol Bot: AnyObject, UserType { var providerIdentifier: String? { get } var serviceIdentifier: String? { get } } @objc -public protocol SearchServiceUser: ServiceUser { +public protocol BotSearchResult: Bot { var summary: String? { get } } +public typealias ServiceUser = Bot +public typealias SearchServiceUser = BotSearchResult + extension ZMUser { static let servicesMustBeMentioned = false static let serviceMentionKeyword = "@bots" diff --git a/wire-ios-data-model/Source/Model/User/Set+ServiceUser.swift b/wire-ios-data-model/Source/Model/User/Set+ServiceUser.swift index 607396611bf..d165da84ab7 100644 --- a/wire-ios-data-model/Source/Model/User/Set+ServiceUser.swift +++ b/wire-ios-data-model/Source/Model/User/Set+ServiceUser.swift @@ -16,16 +16,14 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation - extension Set { - var apps: Set { - filter(\.isApp) + var appsOrBots: Set { + filter(\.isAppOrBot) } func categorizeServicesAndUser() -> (services: Set, users: Set) { - let services = apps + let services = appsOrBots let users = subtracting(services) return (services, users) } diff --git a/wire-ios-data-model/Source/Model/User/UserType.swift b/wire-ios-data-model/Source/Model/User/UserType.swift index 74b56cc69d7..f5c28d7343b 100644 --- a/wire-ios-data-model/Source/Model/User/UserType.swift +++ b/wire-ios-data-model/Source/Model/User/UserType.swift @@ -72,10 +72,16 @@ public protocol UserType: NSObjectProtocol, UserConnections { /// The role (and permissions) e.g. partner, member, admin, owner var teamRole: TeamRole { get } - /// Whether this is an app/bot/service user. + /// Whether this is an app (new-style MLS service). var isApp: Bool { get } + /// Whether this is a bot (old-style service). + + var isBot: Bool { get } + + var isAppOrBot: Bool { get } + /// Whether this uses uses SSO. var usesCompanyLogin: Bool { get } diff --git a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift index 39998db600b..95221c9d041 100644 --- a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift +++ b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift @@ -93,6 +93,7 @@ extension ZMSearchUser: SearchServiceUser { @objc public class ZMSearchUser: NSObject, UserType { + public var providerIdentifier: String? public var summary: String? public var assetKeys: SearchUserAssetKeys? @@ -209,9 +210,15 @@ public class ZMSearchUser: NSObject, UserType { } public var isApp: Bool { - providerIdentifier != nil + false // Apps (new-style services) don't use ZMSearchUser + } + + public var isBot: Bool { + providerIdentifier?.isEmpty == false } + public var isAppOrBot: Bool { isBot } + public var usesCompanyLogin: Bool { user?.usesCompanyLogin == true } @@ -474,7 +481,7 @@ public class ZMSearchUser: NSObject, UserType { } @objc - public init( + public required init( contextProvider: ContextProvider, name: String, handle: String?, @@ -650,7 +657,7 @@ public class ZMSearchUser: NSObject, UserType { } @objc public var canBeConnected: Bool { - guard !isApp else { return false } + guard !isAppOrBot else { return false } if let user { return user.canBeConnected diff --git a/wire-ios-data-model/Source/Model/User/ZMUser+Permissions.swift b/wire-ios-data-model/Source/Model/User/ZMUser+Permissions.swift index 627f1721cd2..d30153a10ce 100644 --- a/wire-ios-data-model/Source/Model/User/ZMUser+Permissions.swift +++ b/wire-ios-data-model/Source/Model/User/ZMUser+Permissions.swift @@ -257,7 +257,7 @@ public extension ZMUser { return false } - return !isApp // Bots are never guests + return !isAppOrBot // Apps or bots are never guests && !isFederated // Federated users are never guests && ZMUser.selfUser(in: context).hasTeam // There can't be guests in a team that doesn't exist && conversation.localParticipantsContain(user: self) diff --git a/wire-ios-data-model/Source/Model/User/ZMUser.m b/wire-ios-data-model/Source/Model/User/ZMUser.m index 701ac7cdd66..162099df1cd 100644 --- a/wire-ios-data-model/Source/Model/User/ZMUser.m +++ b/wire-ios-data-model/Source/Model/User/ZMUser.m @@ -163,6 +163,16 @@ @implementation ZMUser return [NSSet setWithObjects:TypeKey, nil]; } ++ (NSSet *)keyPathsForValuesAffectingIsBot +{ + return [NSSet setWithObjects:TypeKey, nil]; +} + ++ (NSSet *)keyPathsForValuesAffectingIsAppOrBot +{ + return [NSSet setWithObjects:TypeKey, nil]; +} + - (BOOL)isSelfUser { if ([self isZombieObject]) { @@ -239,7 +249,7 @@ - (BOOL) managedByWire { - (BOOL)canBeConnected; { - if (self.isApp || self.isWirelessUser) { + if (self.isAppOrBot || self.isWirelessUser) { return NO; } return ! self.isConnected && ! self.isPendingApprovalByOtherUser; @@ -710,7 +720,7 @@ + (instancetype)selfUserInContext:(NSManagedObjectContext *)moc; @implementation ZMUser (Utilities) -+ (ZMUser *)selfUserInUserSession:(id)session ++ (ZMUser *)selfUserInUserSession:(id)session { VerifyReturnNil(session != nil); return [self selfUserInContext:session.viewContext]; diff --git a/wire-ios-data-model/Source/Model/User/ZMUser.swift b/wire-ios-data-model/Source/Model/User/ZMUser.swift index 7a5310505be..61a0b3b23e2 100644 --- a/wire-ios-data-model/Source/Model/User/ZMUser.swift +++ b/wire-ios-data-model/Source/Model/User/ZMUser.swift @@ -64,7 +64,15 @@ extension ZMUser: UserType { } @objc public var isApp: Bool { - type == .app || type == .bot + type == .app + } + + @objc public var isBot: Bool { + type == .bot + } + + @objc public var isAppOrBot: Bool { + isApp || isBot } public var teamName: String? { diff --git a/wire-ios-data-model/Source/Model/ZMManagedObject.m b/wire-ios-data-model/Source/Model/ZMManagedObject.m index 09451070212..9cf96c97ed1 100644 --- a/wire-ios-data-model/Source/Model/ZMManagedObject.m +++ b/wire-ios-data-model/Source/Model/ZMManagedObject.m @@ -39,7 +39,7 @@ @interface ZMManagedObject () @implementation ZMManagedObject -+ (NSManagedObjectID *)objectIDForURIRepresentation:(NSURL *)url inUserSession:(id)userSession ++ (NSManagedObjectID *)objectIDForURIRepresentation:(NSURL *)url inUserSession:(id)userSession { VerifyReturnNil(url != nil); VerifyReturnNil(userSession != nil); @@ -56,7 +56,7 @@ + (NSManagedObjectID *)objectIDForURIRepresentation:(NSURL *)url inManagedObject return [psc managedObjectIDForURIRepresentation:url]; } -+ (instancetype)existingObjectWithID:(NSManagedObjectID *)identifier inUserSession:(id)userSession; ++ (instancetype)existingObjectWithID:(NSManagedObjectID *)identifier inUserSession:(id)userSession; { VerifyReturnNil(identifier); VerifyReturnNil(userSession); diff --git a/wire-ios-data-model/Source/Notifications/ObjectObserverTokens/UserChangeInfo.swift b/wire-ios-data-model/Source/Notifications/ObjectObserverTokens/UserChangeInfo.swift index 193c222cb71..e4abd74b6a4 100644 --- a/wire-ios-data-model/Source/Notifications/ObjectObserverTokens/UserChangeInfo.swift +++ b/wire-ios-data-model/Source/Notifications/ObjectObserverTokens/UserChangeInfo.swift @@ -47,6 +47,7 @@ extension ZMUser: ObjectInSnapshot { #keyPath(ZMUser.readReceiptsEnabledChangedRemotely), ZMUserKeys.RichProfile, #keyPath(ZMUser.isApp), + #keyPath(ZMUser.isBot), #keyPath(ZMUser.serviceIdentifier), #keyPath(ZMUser.providerIdentifier), ZMUserKeys.legalHoldRequest, diff --git a/wire-ios-data-model/Source/Public/ZMConversation.h b/wire-ios-data-model/Source/Public/ZMConversation.h index d502e68ecb1..0979a32b485 100644 --- a/wire-ios-data-model/Source/Public/ZMConversation.h +++ b/wire-ios-data-model/Source/Public/ZMConversation.h @@ -97,7 +97,7 @@ typedef NS_ENUM(int16_t, ZMConversationListIndicator) { - (void)markAsUnread; /// If that conversation exists, it is returned, @c nil otherwise. -+ (nullable instancetype)existingOneOnOneConversationWithUser:(nonnull ZMUser *)otherUser inUserSession:(nonnull id )session; ++ (nullable instancetype)existingOneOnOneConversationWithUser:(nonnull ZMUser *)otherUser inUserSession:(nonnull id )session; @end diff --git a/wire-ios-data-model/Source/Public/ZMManagedObject.h b/wire-ios-data-model/Source/Public/ZMManagedObject.h index aadeabc2944..6072ce26613 100644 --- a/wire-ios-data-model/Source/Public/ZMManagedObject.h +++ b/wire-ios-data-model/Source/Public/ZMManagedObject.h @@ -20,15 +20,15 @@ extern NSString * _Nonnull const ZMDataPropertySuffix; -@protocol ContextProvider; +@protocol ZMContextProvider; @interface ZMManagedObject : NSManagedObject @property (nonatomic, readonly) BOOL isZombieObject; -+ (nullable NSManagedObjectID *)objectIDForURIRepresentation:(nullable NSURL *)url inUserSession:(nullable id)userSession; ++ (nullable NSManagedObjectID *)objectIDForURIRepresentation:(nullable NSURL *)url inUserSession:(nullable id)userSession; + (nullable NSManagedObjectID *)objectIDForURIRepresentation:(nullable NSURL *)url inManagedObjectContext:(nullable NSManagedObjectContext *)context; -+ (nullable instancetype)existingObjectWithID:(nullable NSManagedObjectID *)identifier inUserSession:(nullable id)userSession; ++ (nullable instancetype)existingObjectWithID:(nullable NSManagedObjectID *)identifier inUserSession:(nullable id)userSession; + (nullable instancetype)existingObjectWithObjectIdentifier:(nullable NSString *)identifier inManagedObjectContext:(nullable NSManagedObjectContext *)context; - (nullable NSString *)objectIDURLString; diff --git a/wire-ios-data-model/Source/Public/ZMUser.h b/wire-ios-data-model/Source/Public/ZMUser.h index 4fc0340562d..62d951a7f66 100644 --- a/wire-ios-data-model/Source/Public/ZMUser.h +++ b/wire-ios-data-model/Source/Public/ZMUser.h @@ -87,7 +87,7 @@ typedef NS_ENUM(int16_t, ZMBlockState) { @interface ZMUser (Utilities) -+ (ZMUser *_Nonnull)selfUserInUserSession:(id _Nonnull)session; ++ (ZMUser *_Nonnull)selfUserInUserSession:(id _Nonnull)session; @end diff --git a/wire-ios-data-model/Tests/Source/Model/TeamTests.swift b/wire-ios-data-model/Tests/Source/Model/TeamTests.swift index ce87efa36f9..eab4a21db25 100644 --- a/wire-ios-data-model/Tests/Source/Model/TeamTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/TeamTests.swift @@ -83,7 +83,7 @@ final class TeamTests: ZMConversationTestsBase { let guest = ZMUser.insertNewObject(in: uiMOC) let bot = ZMUser.insertNewObject(in: uiMOC) bot.type = .bot - XCTAssert(bot.isApp) + XCTAssert(bot.isAppOrBot) guard let conversation = ZMConversation.insertGroupConversation( moc: uiMOC, participants: [guest, bot], diff --git a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserPayloadParsingTests.swift b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserPayloadParsingTests.swift index 94d8d25608d..bd5f3b00ec7 100644 --- a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserPayloadParsingTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserPayloadParsingTests.swift @@ -63,7 +63,7 @@ final class ZMSearchUserPayloadParsingTests: ZMBaseManagedObjectTest { XCTAssertEqual(user.domain, domain) XCTAssertEqual(user.remoteIdentifier, uuid) XCTAssertEqual(user.zmAccentColor?.rawValue, 5) - XCTAssertFalse(user.isApp) + XCTAssertFalse(user.isAppOrBot) XCTAssertTrue(user.canBeConnected) } @@ -88,7 +88,7 @@ final class ZMSearchUserPayloadParsingTests: ZMBaseManagedObjectTest { )! // then - XCTAssertTrue(user.isApp) + XCTAssertTrue(user.isAppOrBot) XCTAssertEqual(user.summary, "Short summary") XCTAssertEqual(user.providerIdentifier, provider.transportString()) XCTAssertEqual(user.serviceIdentifier, uuid.transportString()) diff --git a/wire-ios-data-model/Tests/Source/Model/User/ZMUserTests.m b/wire-ios-data-model/Tests/Source/Model/User/ZMUserTests.m index cbcd00f8590..4c54722efac 100644 --- a/wire-ios-data-model/Tests/Source/Model/User/ZMUserTests.m +++ b/wire-ios-data-model/Tests/Source/Model/User/ZMUserTests.m @@ -1927,7 +1927,7 @@ - (void)testThatItReturnsCorrectUserNameForService user.name = @"User Name"; [user setValue:@(ZMTypeOfUserBot) forKey:@"typeValue"]; - XCTAssertTrue(user.isApp); + XCTAssertTrue(user.isAppOrBot); XCTAssertEqualObjects(user.name, @"User Name"); } diff --git a/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.h b/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.h index 86c9b7f25fe..4ea8ebd52f3 100644 --- a/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.h +++ b/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.h @@ -35,7 +35,7 @@ @interface ZMSyncStrategy : NSObject -- (instancetype _Nonnull )initWithContextProvider:(id _Nonnull)contextProvider +- (instancetype _Nonnull )initWithContextProvider:(id _Nonnull)contextProvider notificationsDispatcher:(NotificationDispatcher * _Nonnull)notificationsDispatcher operationStatus:(OperationStatus * _Nonnull)operationStatus application:(id _Nonnull)application diff --git a/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.m b/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.m index 0d65d10fd38..b05e037ddab 100644 --- a/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.m +++ b/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.m @@ -58,7 +58,7 @@ @implementation ZMSyncStrategy ZM_EMPTY_ASSERTING_INIT() -- (instancetype)initWithContextProvider:(id)contextProvider +- (instancetype)initWithContextProvider:(id)contextProvider notificationsDispatcher:(NotificationDispatcher *)notificationsDispatcher operationStatus:(OperationStatus *)operationStatus application:(id)application diff --git a/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift b/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift index 30b0c6a5195..f9a2776a738 100644 --- a/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift +++ b/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift @@ -42,6 +42,7 @@ final class ConnectToBotURLActionProcessor: NSObject, URLActionProcessor { func process(urlAction: URLAction, delegate: PresentationDelegate?) { guard case let .connectBot(serviceUserData) = urlAction else { return } + let providerIdentifier = serviceUserData.provider.transportString() let serviceUser = ZMSearchUser( contextProvider: contextProvider, name: "", @@ -52,7 +53,7 @@ final class ConnectToBotURLActionProcessor: NSObject, URLActionProcessor { user: nil, searchUsersCache: searchUsersCache ) - serviceUser.providerIdentifier = serviceUserData.provider.transportString() + serviceUser.providerIdentifier = providerIdentifier serviceUser.createConversation( transportSession: transportSession, eventProcessor: eventProcessor, diff --git a/wire-ios/Tests/Mocks/MockServiceUserType.swift b/wire-ios/Tests/Mocks/MockServiceUserType.swift index a8c7d3c6700..ceb86d27587 100644 --- a/wire-ios/Tests/Mocks/MockServiceUserType.swift +++ b/wire-ios/Tests/Mocks/MockServiceUserType.swift @@ -16,8 +16,6 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation - final class MockServiceUserType: MockUserType, ServiceUser { var providerIdentifier: String? @@ -28,4 +26,8 @@ final class MockServiceUserType: MockUserType, ServiceUser { true } + override var isBot: Bool { + false + } + } diff --git a/wire-ios/Tests/Mocks/MockUser.h b/wire-ios/Tests/Mocks/MockUser.h index 4d68bcafbe0..9f21d0d63ce 100644 --- a/wire-ios/Tests/Mocks/MockUser.h +++ b/wire-ios/Tests/Mocks/MockUser.h @@ -66,6 +66,8 @@ @property (nonatomic, readwrite) NSTimeInterval expiresAfter; @property (nonatomic, assign) BOOL isSelfUser; @property (nonatomic, assign) BOOL isApp; +@property (nonatomic, assign) BOOL isBot; +@property (nonatomic, assign) BOOL isAppOrBot; @property (nonatomic, readwrite) BOOL isTeamMember; @property (nonatomic, readwrite) TeamRole teamRole; @property (nonatomic, assign) BOOL isGuestInConversation; diff --git a/wire-ios/Tests/Mocks/MockUser.m b/wire-ios/Tests/Mocks/MockUser.m index 139cfe5258c..1b2ff9321e8 100644 --- a/wire-ios/Tests/Mocks/MockUser.m +++ b/wire-ios/Tests/Mocks/MockUser.m @@ -94,6 +94,7 @@ + (MockUser *)mockServiceUser @"displayName": @"GitHub", @"isSelfUser": @false, @"isApp": @true, + @"isBot": @false, @"isConnected": @true, @"accentColorValue": @1}]; } diff --git a/wire-ios/Tests/Mocks/MockUserType.swift b/wire-ios/Tests/Mocks/MockUserType.swift index fe2e0453d04..3b09848f441 100644 --- a/wire-ios/Tests/Mocks/MockUserType.swift +++ b/wire-ios/Tests/Mocks/MockUserType.swift @@ -126,10 +126,13 @@ class MockUserType: NSObject, UserType, Decodable, EditableUserType { var isSelfUser: Bool = false - var mockedIsApp: Bool = false - var isApp: Bool { - mockedIsApp - } + var mockedIsApp = false + var isApp: Bool { mockedIsApp } + + var mockedIsBot = false + var isBot: Bool { mockedIsBot } + + var isAppOrBot: Bool { isApp || isBot } var isVerified: Bool = false diff --git a/wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift b/wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift index 3cfdfcd5aa9..9071393e563 100644 --- a/wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift +++ b/wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift @@ -17,6 +17,7 @@ // import XCTest + @testable import Wire final class ConversationConnectAvatarViewModeTests: XCTestCase { @@ -46,7 +47,7 @@ final class ConversationConnectAvatarViewModeTests: XCTestCase { let mockServiceUser = MockServiceUserType() mockServiceUser.serviceIdentifier = "serviceIdentifier" mockServiceUser.providerIdentifier = "providerIdentifier" - XCTAssert(mockServiceUser.isApp) + XCTAssert(mockServiceUser.isAppOrBot) users = [mockServiceUser] diff --git a/wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift b/wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift index 41727c75be2..5d0c8a55a43 100644 --- a/wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift +++ b/wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift @@ -85,7 +85,7 @@ final class ConversationAvatarViewTests: XCTestCase { otherUser.serviceIdentifier = "serviceIdentifier" otherUser.providerIdentifier = "providerIdentifier" otherUser.isConnected = true - XCTAssert(otherUser.isApp) + XCTAssert(otherUser.isAppOrBot) otherUser.zmAccentColor = .green let otherUserConversation = MockStableRandomParticipantsConversation() diff --git a/wire-ios/Wire-iOS Tests/ConversationMessageCell/ConversationSenderMessageDetailsCellSnapshotTests.swift b/wire-ios/Wire-iOS Tests/ConversationMessageCell/ConversationSenderMessageDetailsCellSnapshotTests.swift index feff8d4cdd5..49a3e2ab2d7 100644 --- a/wire-ios/Wire-iOS Tests/ConversationMessageCell/ConversationSenderMessageDetailsCellSnapshotTests.swift +++ b/wire-ios/Wire-iOS Tests/ConversationMessageCell/ConversationSenderMessageDetailsCellSnapshotTests.swift @@ -111,7 +111,7 @@ final class ConversationSenderMessageDetailsCellSnapshotTests: XCTestCase { let configuration = ConversationSenderMessageDetailsCell.Configuration( sender: mockUser, indicator: .none, - teamRoleIndicator: .app + teamRoleIndicator: .appOrBot ) // WHEN diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Components/UserImageView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Components/UserImageView.swift index 3b57aec5d0e..7d9391d7839 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Components/UserImageView.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Components/UserImageView.swift @@ -106,19 +106,19 @@ class UserImageView: AvatarImageView, UserObserving { /// Returns the appropriate border width for the user. private func borderWidth(for user: UserType) -> CGFloat { - user.isApp ? 0.5 : 0 + user.isAppOrBot ? 0.5 : 0 } /// Returns the appropriate border color for the user. private func borderColor(for user: UserType) -> CGColor? { - user.isApp ? UIColor.black.withAlphaComponent(0.08).cgColor : nil + user.isAppOrBot ? UIColor.black.withAlphaComponent(0.08).cgColor : nil } /// Returns the placeholder background color for the user. private func containerBackgroundColor(for user: UserType) -> UIColor { switch avatar { case .image: - user.isApp ? .white : .clear + user.isAppOrBot ? .white : .clear case .text: if user.isConnected || user.isSelfUser || user.isTeamMember || user.isWirelessUser { user.accentColor @@ -130,7 +130,7 @@ class UserImageView: AvatarImageView, UserObserving { /// Returns the appropriate avatar shape for the user. private func shape(for user: UserType) -> AvatarImageView.Shape { - user.isApp ? .relative : .circle + user.isAppOrBot ? .relative : .circle } // MARK: - Changing the Content @@ -169,7 +169,7 @@ class UserImageView: AvatarImageView, UserObserving { var desaturate = false if shouldDesaturate { - desaturate = !user.isConnected && !user.isSelfUser && !user.isTeamMember && !user.isApp + desaturate = !user.isConnected && !user.isSelfUser && !user.isTeamMember && !user.isAppOrBot } user.fetchProfileImage( diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationAvatarView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationAvatarView.swift index e9027ca0b79..21b4acc5e0b 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationAvatarView.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationAvatarView.swift @@ -55,7 +55,7 @@ final class ConversationAvatarView: UIView { configureForOneOnOne(users) case let .conversation(conversation: conversation) where conversation.conversationType == .group: let users = conversation.stableRandomParticipants.filter { !$0.isSelfUser } - if let user = users.first, user.isApp { + if let user = users.first, user.isAppOrBot { configureForOneOnOne(users) break } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationConnectAvatarView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationConnectAvatarView.swift index 505522176e7..0a05ae3100e 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationConnectAvatarView.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationConnectAvatarView.swift @@ -111,10 +111,10 @@ extension Mode { case (0, _): self = .none case (1, .group?): - let isApp = users[0].isApp - self = isApp ? .one(serviceUser: isApp) : .four + let isAppOrBot = users[0].isAppOrBot + self = isAppOrBot ? .one(serviceUser: isAppOrBot) : .four case (1, _): - self = .one(serviceUser: users[0].isApp) + self = .one(serviceUser: users[0].isAppOrBot) default: self = .four } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/IconImageView/UserTypeIconStyle.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/IconImageView/UserTypeIconStyle.swift index 1bf936ed4b4..02fa2dd6505 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/IconImageView/UserTypeIconStyle.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/IconImageView/UserTypeIconStyle.swift @@ -73,7 +73,7 @@ extension UserTypeIconStyle { } else if let conversation { self = !user.isGuest(in: conversation) || user.isSelfUser ? .member : .guest } else { - self = !selfUserHasTeam || user.isTeamMember || user.isApp + self = !selfUserHasTeam || user.isTeamMember || user.isAppOrBot ? .member : .guest } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/UserCell.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/UserCell.swift index e58f83b9c58..20d0a87909d 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/UserCell.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/UserCell.swift @@ -35,7 +35,7 @@ final class UserCell: SeparatorCollectionViewCell, SectionListCellType { private var userIsSelfUser = false private var isSelfUserPartOfATeam = false - private var userIsApp = false + private var userIsAppOrBot = false typealias IconColors = SemanticColors.Icon typealias LabelColors = SemanticColors.Label @@ -352,7 +352,7 @@ final class UserCell: SeparatorCollectionViewCell, SectionListCellType { if !checkmarkIconView.isHidden { accessibilityHint = isSelected ? CreateConversation.SelectedUser.hint : CreateConversation.UnselectedUser .hint - } else if userIsApp { + } else if userIsAppOrBot { accessibilityHint = ServicesList.ServiceCell.hint } else { accessibilityHint = ContactsList.UserCell.hint @@ -389,7 +389,7 @@ extension UserCell { self.userStatus = userStatus self.userIsSelfUser = userIsSelfUser self.isSelfUserPartOfATeam = isSelfUserPartOfATeam - userIsApp = user.isApp + userIsAppOrBot = user.isAppOrBot let subtitle: NSAttributedString? = if overrideSubtitle == nil { self.subtitle(for: user) @@ -451,8 +451,8 @@ extension UserCell: UserCellSubtitleProtocol {} extension UserCell { private func subtitle(for user: UserType) -> NSAttributedString? { - if user.isApp, let service = user as? SearchServiceUser { - subtitle(forServiceUser: service) + if user.isAppOrBot, let appOrBot = user as? SearchServiceUser { + subtitle(forServiceUser: appOrBot) } else { subtitle(forRegularUser: user) } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ConfigurationMessageCell/Components/ConversationSenderMessageDetailsCell.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ConfigurationMessageCell/Components/ConversationSenderMessageDetailsCell.swift index b43d9bbe1fe..2bce677122e 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ConfigurationMessageCell/Components/ConversationSenderMessageDetailsCell.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ConfigurationMessageCell/Components/ConversationSenderMessageDetailsCell.swift @@ -33,7 +33,7 @@ enum TeamRoleIndicator { case guest case externalPartner case federated - case app + case appOrBot } // MARK: - ConversationSenderMessageDetailsCell @@ -180,7 +180,7 @@ final class ConversationSenderMessageDetailsCell: UIView, ConversationMessageCel private func configureAuthorLabel(object: Configuration) { let sender = object.sender - let textColor: UIColor = sender.isApp ? SemanticColors.Label.textDefault : sender.accentColor + let textColor: UIColor = sender.isAppOrBot ? SemanticColors.Label.textDefault : sender.accentColor let attributedString = NSMutableAttributedString( string: sender.name ?? L10n.Localizable.Profile.Details.Title.unavailable, attributes: [ @@ -220,7 +220,7 @@ final class ConversationSenderMessageDetailsCell: UIView, ConversationMessageCel attributedString.append(attachment) } - case .app: + case .appOrBot: accessibilityIdentifier = "img.serviceUser" if let attachment = attachment(from: .bot, size: 14) { attributedString.append(attachment) @@ -361,8 +361,8 @@ final class ConversationSenderMessageCellDescription: ConversationMessageCellDes private extension UserType { func teamRoleIndicator(selfUser: any UserType) -> TeamRoleIndicator? { - if isApp { - .app + if isAppOrBot { + .appOrBot } else if isExternalPartner { .externalPartner diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ParticipantsCellViewModel.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ParticipantsCellViewModel.swift index 3abb2bbbb85..d887b12e812 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ParticipantsCellViewModel.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ParticipantsCellViewModel.swift @@ -105,8 +105,8 @@ final class ParticipantsCellViewModel { guard let users = Array(messageData.userTypes) as? [UserType] else { return false } let selfAddedToServiceConversation = users.any(\.isSelfUser) && conversation.areAppsPresent - let appAdded = users.any(\.isApp) - return selfAddedToServiceConversation || appAdded + let appOrBotAdded = users.any(\.isAppOrBot) + return selfAddedToServiceConversation || appOrBotAdded } /// Users displayed in the system message, up to 17 when not collapsed diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Create/ConversationCreationValues.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Create/ConversationCreationValues.swift index 953ebb83175..285c9028dff 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Create/ConversationCreationValues.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Create/ConversationCreationValues.swift @@ -52,8 +52,8 @@ final class ConversationCreationValues { } if !allowApps { - let noApps = result.filter { !$0.isApp } - result = UserSet(noApps) + let noAppsOrBots = result.filter { !$0.isAppOrBot } + result = UserSet(noAppsOrBots) } return result diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Mentions/Collection+MentionSearch.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Mentions/Collection+MentionSearch.swift index 56b4eefcbac..a5836dadc46 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Mentions/Collection+MentionSearch.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Mentions/Collection+MentionSearch.swift @@ -23,7 +23,7 @@ import WireUtilities extension Collection where Iterator.Element: UserType { func searchForMentions(withQuery query: String) -> [UserType] { - let usersToSearch = filter { !$0.isSelfUser && !$0.isApp } + let usersToSearch = filter { !$0.isSelfUser && !$0.isAppOrBot } guard !query.isEmpty else { return usersToSearch } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Helpers/UserDetailViewControllerFactory.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Helpers/UserDetailViewControllerFactory.swift index 35f0ed6c82b..764701635a4 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Helpers/UserDetailViewControllerFactory.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Helpers/UserDetailViewControllerFactory.swift @@ -40,7 +40,7 @@ enum UserDetailViewControllerFactory { selfProfileUIBuilder: some SelfProfileViewControllerBuilderProtocol ) -> UIViewController { - if user.isApp, let serviceUser = user as? ServiceUser { + if user.isAppOrBot, let serviceUser = user as? ServiceUser { return ServiceDetailViewController( serviceUser: serviceUser, actionType: .removeService(conversation), diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/SearchResultsViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/SearchResultsViewController.swift index 6cebba394b3..4fd120a9e48 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/SearchResultsViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/SearchResultsViewController.swift @@ -449,7 +449,7 @@ extension SearchResultsViewController: SearchSectionControllerDelegate { indexPath: indexPath, section: sectionFor(controller: searchSectionController) ) - } else if let service = user as? ServiceUser, service.isApp { + } else if let service = user as? ServiceUser, service.isAppOrBot { delegate?.searchResultsViewController(self, didTapOnSeviceUser: service) } else if let searchUser = user as? ZMSearchUser { delegate?.searchResultsViewController( From 375a3666151399511ca7b0bbe1e6ff57d27501bc Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 10 Dec 2025 10:53:05 +0100 Subject: [PATCH 18/18] chore: separate user properties isApp and isBot - WPB-21441 --- .../Repositories/Message/MessageLocalStore.swift | 2 +- .../ManagedObjectContext/CoreDataStack.swift | 6 +++--- .../Model/Conversation/ConversationLike.swift | 4 ++-- .../ZMConversation+ExternalParticipant.swift | 7 ++++--- .../ZMConversation+SystemMessages.swift | 2 +- .../Source/Model/Conversation/ZMConversation.m | 2 +- .../Source/Model/User/ServiceUser.swift | 9 +++++---- .../Source/Model/User/Set+ServiceUser.swift | 8 +++----- .../Source/Model/User/UserType.swift | 8 +++++++- .../Source/Model/User/ZMSearchUser.swift | 13 ++++++++++--- .../Source/Model/User/ZMUser+Permissions.swift | 2 +- wire-ios-data-model/Source/Model/User/ZMUser.m | 14 ++++++++++++-- wire-ios-data-model/Source/Model/User/ZMUser.swift | 10 +++++++++- wire-ios-data-model/Source/Model/ZMManagedObject.m | 4 ++-- .../ObjectObserverTokens/UserChangeInfo.swift | 1 + wire-ios-data-model/Source/Public/ZMConversation.h | 2 +- .../Source/Public/ZMManagedObject.h | 6 +++--- wire-ios-data-model/Source/Public/ZMUser.h | 2 +- .../Tests/Source/Model/TeamTests.swift | 2 +- .../User/ZMSearchUserPayloadParsingTests.swift | 4 ++-- .../Tests/Source/Model/User/ZMUserTests.m | 2 +- .../Source/Synchronization/ZMSyncStrategy.h | 2 +- .../Source/Synchronization/ZMSyncStrategy.m | 2 +- .../ConnectToBotURLActionProcessor.swift | 3 ++- wire-ios/Tests/Mocks/MockServiceUserType.swift | 6 ++++-- wire-ios/Tests/Mocks/MockUser.h | 2 ++ wire-ios/Tests/Mocks/MockUser.m | 1 + wire-ios/Tests/Mocks/MockUserType.swift | 11 +++++++---- .../ConversationAvatarViewModeTests.swift | 3 ++- .../ConversationAvatarViewTests.swift | 2 +- ...tionSenderMessageDetailsCellSnapshotTests.swift | 2 +- .../UserInterface/Components/UserImageView.swift | 10 +++++----- .../Components/Views/ConversationAvatarView.swift | 2 +- .../Views/ConversationConnectAvatarView.swift | 6 +++--- .../Views/IconImageView/UserTypeIconStyle.swift | 2 +- .../UserInterface/Components/Views/UserCell.swift | 10 +++++----- .../ConversationSenderMessageDetailsCell.swift | 10 +++++----- .../Content/Cells/ParticipantsCellViewModel.swift | 4 ++-- .../Create/ConversationCreationValues.swift | 4 ++-- .../Mentions/Collection+MentionSearch.swift | 2 +- .../Helpers/UserDetailViewControllerFactory.swift | 2 +- .../StartUI/SearchResultsViewController.swift | 2 +- 42 files changed, 120 insertions(+), 78 deletions(-) diff --git a/WireDomain/Sources/WireDomain/Repositories/Message/MessageLocalStore.swift b/WireDomain/Sources/WireDomain/Repositories/Message/MessageLocalStore.swift index 37e344d76ea..f94f9fb6a82 100644 --- a/WireDomain/Sources/WireDomain/Repositories/Message/MessageLocalStore.swift +++ b/WireDomain/Sources/WireDomain/Repositories/Message/MessageLocalStore.swift @@ -680,7 +680,7 @@ public final class MessageLocalStore: MessageLocalStoreProtocol { let members = selfUserTeam.members.compactMap(\.user) let guests = localParticipants.filter { - !$0.isApp && $0.membership == nil + !$0.isAppOrBot && $0.membership == nil } newConversationMessage.allTeamUsersAdded = localParticipants.isSuperset(of: members) diff --git a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack.swift b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack.swift index a0218f0e2cd..76d60b605fe 100644 --- a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack.swift +++ b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack.swift @@ -40,7 +40,7 @@ extension CoreDataStackError: LocalizedError { } } -@objc +@objc(ZMContextProvider) public protocol ContextProvider { var account: Account { get } @@ -114,8 +114,8 @@ public protocol CoreDataStackProtocol: ContextProvider { } -@objcMembers -public class CoreDataStack: NSObject, CoreDataStackProtocol { +@objc @objcMembers +public class CoreDataStack: NSObject, CoreDataStackProtocol, ContextProvider { public let account: Account diff --git a/wire-ios-data-model/Source/Model/Conversation/ConversationLike.swift b/wire-ios-data-model/Source/Model/Conversation/ConversationLike.swift index 9b64c79ea1b..be1edbae348 100644 --- a/wire-ios-data-model/Source/Model/Conversation/ConversationLike.swift +++ b/wire-ios-data-model/Source/Model/Conversation/ConversationLike.swift @@ -102,13 +102,13 @@ extension ZMConversation: ConversationLike { public var sortedOtherParticipants: [UserType] { localParticipants - .filter { !$0.isApp } + .filter { !$0.isAppOrBot } .sortedAscendingPrependingNil(by: \.name) } public var sortedApps: [UserType] { localParticipants - .filter(\.isApp) + .filter(\.isAppOrBot) .sortedAscendingPrependingNil(by: \.name) } diff --git a/wire-ios-data-model/Source/Model/Conversation/ZMConversation+ExternalParticipant.swift b/wire-ios-data-model/Source/Model/Conversation/ZMConversation+ExternalParticipant.swift index d649af03add..b74c333e0ee 100644 --- a/wire-ios-data-model/Source/Model/Conversation/ZMConversation+ExternalParticipant.swift +++ b/wire-ios-data-model/Source/Model/Conversation/ZMConversation+ExternalParticipant.swift @@ -48,6 +48,7 @@ public extension ZMConversation { internal class func keyPathsForValuesAffectingExternalParticipantsState() -> Set { [ "participantRoles.user.isApp", + "participantRoles.user.isBot", "participantRoles.user.hasTeam", "participantRoles.user.isExternalPartner" ] @@ -63,7 +64,7 @@ public extension ZMConversation { let selfUser = ZMUser.selfUser(in: managedObjectContext!) let otherUsers = participants.subtracting([selfUser]) - if otherUsers.count == 1, otherUsers.first!.isApp { + if otherUsers.count == 1, otherUsers.first!.isAppOrBot { return [] } @@ -75,7 +76,7 @@ public extension ZMConversation { for user in otherUsers { if canDisplayGuests, user.isFederated { state.insert(.visibleRemotes) - } else if user.isApp { + } else if user.isAppOrBot { state.insert(.visibleApps) } else if canDisplayExternals, user.isExternalPartner { state.insert(.visibleExternals) @@ -98,7 +99,7 @@ public extension ZMConversation { /// Returns whether apps are present, regardless of the display rules. var areAppsPresent: Bool { - localParticipants.any(\.isApp) + localParticipants.any(\.isAppOrBot) } /// Returns whether guests are present, regardless of the display rules. diff --git a/wire-ios-data-model/Source/Model/Conversation/ZMConversation+SystemMessages.swift b/wire-ios-data-model/Source/Model/Conversation/ZMConversation+SystemMessages.swift index e461696d403..94ae0cbb209 100644 --- a/wire-ios-data-model/Source/Model/Conversation/ZMConversation+SystemMessages.swift +++ b/wire-ios-data-model/Source/Model/Conversation/ZMConversation+SystemMessages.swift @@ -97,7 +97,7 @@ public extension ZMConversation { team == selfUserTeam { let members = selfUserTeam.members.compactMap(\.user) - let guests = users.filter { !$0.isApp && $0.membership == nil } + let guests = users.filter { !$0.isAppOrBot && $0.membership == nil } systemMessage.allTeamUsersAdded = users.isSuperset(of: members) systemMessage.numberOfGuestsAdded = Int16(guests.count) diff --git a/wire-ios-data-model/Source/Model/Conversation/ZMConversation.m b/wire-ios-data-model/Source/Model/Conversation/ZMConversation.m index 6f4d40047f6..79a73353c3d 100644 --- a/wire-ios-data-model/Source/Model/Conversation/ZMConversation.m +++ b/wire-ios-data-model/Source/Model/Conversation/ZMConversation.m @@ -393,7 +393,7 @@ + (NSSet *)keyPathsForValuesAffectingIsReadOnly; return [NSSet setWithObjects:ZMConversationConversationTypeKey, ZMConversationParticipantRolesKey, ZMConversationIsForcedReadOnlyKey, nil]; } -+ (instancetype)existingOneOnOneConversationWithUser:(ZMUser *)otherUser inUserSession:(id)session; ++ (instancetype)existingOneOnOneConversationWithUser:(ZMUser *)otherUser inUserSession:(id)session; { NOT_USED(session); return otherUser.oneOnOneConversation; diff --git a/wire-ios-data-model/Source/Model/User/ServiceUser.swift b/wire-ios-data-model/Source/Model/User/ServiceUser.swift index e0c4f529b4e..bcdf01260cb 100644 --- a/wire-ios-data-model/Source/Model/User/ServiceUser.swift +++ b/wire-ios-data-model/Source/Model/User/ServiceUser.swift @@ -16,19 +16,20 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation - @objc -public protocol ServiceUser: AnyObject, UserType { +public protocol Bot: AnyObject, UserType { var providerIdentifier: String? { get } var serviceIdentifier: String? { get } } @objc -public protocol SearchServiceUser: ServiceUser { +public protocol BotSearchResult: Bot { var summary: String? { get } } +public typealias ServiceUser = Bot +public typealias SearchServiceUser = BotSearchResult + extension ZMUser { static let servicesMustBeMentioned = false static let serviceMentionKeyword = "@bots" diff --git a/wire-ios-data-model/Source/Model/User/Set+ServiceUser.swift b/wire-ios-data-model/Source/Model/User/Set+ServiceUser.swift index 607396611bf..d165da84ab7 100644 --- a/wire-ios-data-model/Source/Model/User/Set+ServiceUser.swift +++ b/wire-ios-data-model/Source/Model/User/Set+ServiceUser.swift @@ -16,16 +16,14 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation - extension Set { - var apps: Set { - filter(\.isApp) + var appsOrBots: Set { + filter(\.isAppOrBot) } func categorizeServicesAndUser() -> (services: Set, users: Set) { - let services = apps + let services = appsOrBots let users = subtracting(services) return (services, users) } diff --git a/wire-ios-data-model/Source/Model/User/UserType.swift b/wire-ios-data-model/Source/Model/User/UserType.swift index 74b56cc69d7..f5c28d7343b 100644 --- a/wire-ios-data-model/Source/Model/User/UserType.swift +++ b/wire-ios-data-model/Source/Model/User/UserType.swift @@ -72,10 +72,16 @@ public protocol UserType: NSObjectProtocol, UserConnections { /// The role (and permissions) e.g. partner, member, admin, owner var teamRole: TeamRole { get } - /// Whether this is an app/bot/service user. + /// Whether this is an app (new-style MLS service). var isApp: Bool { get } + /// Whether this is a bot (old-style service). + + var isBot: Bool { get } + + var isAppOrBot: Bool { get } + /// Whether this uses uses SSO. var usesCompanyLogin: Bool { get } diff --git a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift index 39998db600b..95221c9d041 100644 --- a/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift +++ b/wire-ios-data-model/Source/Model/User/ZMSearchUser.swift @@ -93,6 +93,7 @@ extension ZMSearchUser: SearchServiceUser { @objc public class ZMSearchUser: NSObject, UserType { + public var providerIdentifier: String? public var summary: String? public var assetKeys: SearchUserAssetKeys? @@ -209,9 +210,15 @@ public class ZMSearchUser: NSObject, UserType { } public var isApp: Bool { - providerIdentifier != nil + false // Apps (new-style services) don't use ZMSearchUser + } + + public var isBot: Bool { + providerIdentifier?.isEmpty == false } + public var isAppOrBot: Bool { isBot } + public var usesCompanyLogin: Bool { user?.usesCompanyLogin == true } @@ -474,7 +481,7 @@ public class ZMSearchUser: NSObject, UserType { } @objc - public init( + public required init( contextProvider: ContextProvider, name: String, handle: String?, @@ -650,7 +657,7 @@ public class ZMSearchUser: NSObject, UserType { } @objc public var canBeConnected: Bool { - guard !isApp else { return false } + guard !isAppOrBot else { return false } if let user { return user.canBeConnected diff --git a/wire-ios-data-model/Source/Model/User/ZMUser+Permissions.swift b/wire-ios-data-model/Source/Model/User/ZMUser+Permissions.swift index 627f1721cd2..d30153a10ce 100644 --- a/wire-ios-data-model/Source/Model/User/ZMUser+Permissions.swift +++ b/wire-ios-data-model/Source/Model/User/ZMUser+Permissions.swift @@ -257,7 +257,7 @@ public extension ZMUser { return false } - return !isApp // Bots are never guests + return !isAppOrBot // Apps or bots are never guests && !isFederated // Federated users are never guests && ZMUser.selfUser(in: context).hasTeam // There can't be guests in a team that doesn't exist && conversation.localParticipantsContain(user: self) diff --git a/wire-ios-data-model/Source/Model/User/ZMUser.m b/wire-ios-data-model/Source/Model/User/ZMUser.m index 701ac7cdd66..162099df1cd 100644 --- a/wire-ios-data-model/Source/Model/User/ZMUser.m +++ b/wire-ios-data-model/Source/Model/User/ZMUser.m @@ -163,6 +163,16 @@ @implementation ZMUser return [NSSet setWithObjects:TypeKey, nil]; } ++ (NSSet *)keyPathsForValuesAffectingIsBot +{ + return [NSSet setWithObjects:TypeKey, nil]; +} + ++ (NSSet *)keyPathsForValuesAffectingIsAppOrBot +{ + return [NSSet setWithObjects:TypeKey, nil]; +} + - (BOOL)isSelfUser { if ([self isZombieObject]) { @@ -239,7 +249,7 @@ - (BOOL) managedByWire { - (BOOL)canBeConnected; { - if (self.isApp || self.isWirelessUser) { + if (self.isAppOrBot || self.isWirelessUser) { return NO; } return ! self.isConnected && ! self.isPendingApprovalByOtherUser; @@ -710,7 +720,7 @@ + (instancetype)selfUserInContext:(NSManagedObjectContext *)moc; @implementation ZMUser (Utilities) -+ (ZMUser *)selfUserInUserSession:(id)session ++ (ZMUser *)selfUserInUserSession:(id)session { VerifyReturnNil(session != nil); return [self selfUserInContext:session.viewContext]; diff --git a/wire-ios-data-model/Source/Model/User/ZMUser.swift b/wire-ios-data-model/Source/Model/User/ZMUser.swift index 7a5310505be..61a0b3b23e2 100644 --- a/wire-ios-data-model/Source/Model/User/ZMUser.swift +++ b/wire-ios-data-model/Source/Model/User/ZMUser.swift @@ -64,7 +64,15 @@ extension ZMUser: UserType { } @objc public var isApp: Bool { - type == .app || type == .bot + type == .app + } + + @objc public var isBot: Bool { + type == .bot + } + + @objc public var isAppOrBot: Bool { + isApp || isBot } public var teamName: String? { diff --git a/wire-ios-data-model/Source/Model/ZMManagedObject.m b/wire-ios-data-model/Source/Model/ZMManagedObject.m index 09451070212..9cf96c97ed1 100644 --- a/wire-ios-data-model/Source/Model/ZMManagedObject.m +++ b/wire-ios-data-model/Source/Model/ZMManagedObject.m @@ -39,7 +39,7 @@ @interface ZMManagedObject () @implementation ZMManagedObject -+ (NSManagedObjectID *)objectIDForURIRepresentation:(NSURL *)url inUserSession:(id)userSession ++ (NSManagedObjectID *)objectIDForURIRepresentation:(NSURL *)url inUserSession:(id)userSession { VerifyReturnNil(url != nil); VerifyReturnNil(userSession != nil); @@ -56,7 +56,7 @@ + (NSManagedObjectID *)objectIDForURIRepresentation:(NSURL *)url inManagedObject return [psc managedObjectIDForURIRepresentation:url]; } -+ (instancetype)existingObjectWithID:(NSManagedObjectID *)identifier inUserSession:(id)userSession; ++ (instancetype)existingObjectWithID:(NSManagedObjectID *)identifier inUserSession:(id)userSession; { VerifyReturnNil(identifier); VerifyReturnNil(userSession); diff --git a/wire-ios-data-model/Source/Notifications/ObjectObserverTokens/UserChangeInfo.swift b/wire-ios-data-model/Source/Notifications/ObjectObserverTokens/UserChangeInfo.swift index 193c222cb71..e4abd74b6a4 100644 --- a/wire-ios-data-model/Source/Notifications/ObjectObserverTokens/UserChangeInfo.swift +++ b/wire-ios-data-model/Source/Notifications/ObjectObserverTokens/UserChangeInfo.swift @@ -47,6 +47,7 @@ extension ZMUser: ObjectInSnapshot { #keyPath(ZMUser.readReceiptsEnabledChangedRemotely), ZMUserKeys.RichProfile, #keyPath(ZMUser.isApp), + #keyPath(ZMUser.isBot), #keyPath(ZMUser.serviceIdentifier), #keyPath(ZMUser.providerIdentifier), ZMUserKeys.legalHoldRequest, diff --git a/wire-ios-data-model/Source/Public/ZMConversation.h b/wire-ios-data-model/Source/Public/ZMConversation.h index d502e68ecb1..0979a32b485 100644 --- a/wire-ios-data-model/Source/Public/ZMConversation.h +++ b/wire-ios-data-model/Source/Public/ZMConversation.h @@ -97,7 +97,7 @@ typedef NS_ENUM(int16_t, ZMConversationListIndicator) { - (void)markAsUnread; /// If that conversation exists, it is returned, @c nil otherwise. -+ (nullable instancetype)existingOneOnOneConversationWithUser:(nonnull ZMUser *)otherUser inUserSession:(nonnull id )session; ++ (nullable instancetype)existingOneOnOneConversationWithUser:(nonnull ZMUser *)otherUser inUserSession:(nonnull id )session; @end diff --git a/wire-ios-data-model/Source/Public/ZMManagedObject.h b/wire-ios-data-model/Source/Public/ZMManagedObject.h index aadeabc2944..6072ce26613 100644 --- a/wire-ios-data-model/Source/Public/ZMManagedObject.h +++ b/wire-ios-data-model/Source/Public/ZMManagedObject.h @@ -20,15 +20,15 @@ extern NSString * _Nonnull const ZMDataPropertySuffix; -@protocol ContextProvider; +@protocol ZMContextProvider; @interface ZMManagedObject : NSManagedObject @property (nonatomic, readonly) BOOL isZombieObject; -+ (nullable NSManagedObjectID *)objectIDForURIRepresentation:(nullable NSURL *)url inUserSession:(nullable id)userSession; ++ (nullable NSManagedObjectID *)objectIDForURIRepresentation:(nullable NSURL *)url inUserSession:(nullable id)userSession; + (nullable NSManagedObjectID *)objectIDForURIRepresentation:(nullable NSURL *)url inManagedObjectContext:(nullable NSManagedObjectContext *)context; -+ (nullable instancetype)existingObjectWithID:(nullable NSManagedObjectID *)identifier inUserSession:(nullable id)userSession; ++ (nullable instancetype)existingObjectWithID:(nullable NSManagedObjectID *)identifier inUserSession:(nullable id)userSession; + (nullable instancetype)existingObjectWithObjectIdentifier:(nullable NSString *)identifier inManagedObjectContext:(nullable NSManagedObjectContext *)context; - (nullable NSString *)objectIDURLString; diff --git a/wire-ios-data-model/Source/Public/ZMUser.h b/wire-ios-data-model/Source/Public/ZMUser.h index 4fc0340562d..62d951a7f66 100644 --- a/wire-ios-data-model/Source/Public/ZMUser.h +++ b/wire-ios-data-model/Source/Public/ZMUser.h @@ -87,7 +87,7 @@ typedef NS_ENUM(int16_t, ZMBlockState) { @interface ZMUser (Utilities) -+ (ZMUser *_Nonnull)selfUserInUserSession:(id _Nonnull)session; ++ (ZMUser *_Nonnull)selfUserInUserSession:(id _Nonnull)session; @end diff --git a/wire-ios-data-model/Tests/Source/Model/TeamTests.swift b/wire-ios-data-model/Tests/Source/Model/TeamTests.swift index ce87efa36f9..eab4a21db25 100644 --- a/wire-ios-data-model/Tests/Source/Model/TeamTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/TeamTests.swift @@ -83,7 +83,7 @@ final class TeamTests: ZMConversationTestsBase { let guest = ZMUser.insertNewObject(in: uiMOC) let bot = ZMUser.insertNewObject(in: uiMOC) bot.type = .bot - XCTAssert(bot.isApp) + XCTAssert(bot.isAppOrBot) guard let conversation = ZMConversation.insertGroupConversation( moc: uiMOC, participants: [guest, bot], diff --git a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserPayloadParsingTests.swift b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserPayloadParsingTests.swift index 94d8d25608d..bd5f3b00ec7 100644 --- a/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserPayloadParsingTests.swift +++ b/wire-ios-data-model/Tests/Source/Model/User/ZMSearchUserPayloadParsingTests.swift @@ -63,7 +63,7 @@ final class ZMSearchUserPayloadParsingTests: ZMBaseManagedObjectTest { XCTAssertEqual(user.domain, domain) XCTAssertEqual(user.remoteIdentifier, uuid) XCTAssertEqual(user.zmAccentColor?.rawValue, 5) - XCTAssertFalse(user.isApp) + XCTAssertFalse(user.isAppOrBot) XCTAssertTrue(user.canBeConnected) } @@ -88,7 +88,7 @@ final class ZMSearchUserPayloadParsingTests: ZMBaseManagedObjectTest { )! // then - XCTAssertTrue(user.isApp) + XCTAssertTrue(user.isAppOrBot) XCTAssertEqual(user.summary, "Short summary") XCTAssertEqual(user.providerIdentifier, provider.transportString()) XCTAssertEqual(user.serviceIdentifier, uuid.transportString()) diff --git a/wire-ios-data-model/Tests/Source/Model/User/ZMUserTests.m b/wire-ios-data-model/Tests/Source/Model/User/ZMUserTests.m index cbcd00f8590..4c54722efac 100644 --- a/wire-ios-data-model/Tests/Source/Model/User/ZMUserTests.m +++ b/wire-ios-data-model/Tests/Source/Model/User/ZMUserTests.m @@ -1927,7 +1927,7 @@ - (void)testThatItReturnsCorrectUserNameForService user.name = @"User Name"; [user setValue:@(ZMTypeOfUserBot) forKey:@"typeValue"]; - XCTAssertTrue(user.isApp); + XCTAssertTrue(user.isAppOrBot); XCTAssertEqualObjects(user.name, @"User Name"); } diff --git a/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.h b/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.h index 86c9b7f25fe..4ea8ebd52f3 100644 --- a/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.h +++ b/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.h @@ -35,7 +35,7 @@ @interface ZMSyncStrategy : NSObject -- (instancetype _Nonnull )initWithContextProvider:(id _Nonnull)contextProvider +- (instancetype _Nonnull )initWithContextProvider:(id _Nonnull)contextProvider notificationsDispatcher:(NotificationDispatcher * _Nonnull)notificationsDispatcher operationStatus:(OperationStatus * _Nonnull)operationStatus application:(id _Nonnull)application diff --git a/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.m b/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.m index 0d65d10fd38..b05e037ddab 100644 --- a/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.m +++ b/wire-ios-sync-engine/Source/Synchronization/ZMSyncStrategy.m @@ -58,7 +58,7 @@ @implementation ZMSyncStrategy ZM_EMPTY_ASSERTING_INIT() -- (instancetype)initWithContextProvider:(id)contextProvider +- (instancetype)initWithContextProvider:(id)contextProvider notificationsDispatcher:(NotificationDispatcher *)notificationsDispatcher operationStatus:(OperationStatus *)operationStatus application:(id)application diff --git a/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift b/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift index 30b0c6a5195..f9a2776a738 100644 --- a/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift +++ b/wire-ios-sync-engine/Source/UserSession/URLActionProcessors/ConnectToBotURLActionProcessor.swift @@ -42,6 +42,7 @@ final class ConnectToBotURLActionProcessor: NSObject, URLActionProcessor { func process(urlAction: URLAction, delegate: PresentationDelegate?) { guard case let .connectBot(serviceUserData) = urlAction else { return } + let providerIdentifier = serviceUserData.provider.transportString() let serviceUser = ZMSearchUser( contextProvider: contextProvider, name: "", @@ -52,7 +53,7 @@ final class ConnectToBotURLActionProcessor: NSObject, URLActionProcessor { user: nil, searchUsersCache: searchUsersCache ) - serviceUser.providerIdentifier = serviceUserData.provider.transportString() + serviceUser.providerIdentifier = providerIdentifier serviceUser.createConversation( transportSession: transportSession, eventProcessor: eventProcessor, diff --git a/wire-ios/Tests/Mocks/MockServiceUserType.swift b/wire-ios/Tests/Mocks/MockServiceUserType.swift index a8c7d3c6700..ceb86d27587 100644 --- a/wire-ios/Tests/Mocks/MockServiceUserType.swift +++ b/wire-ios/Tests/Mocks/MockServiceUserType.swift @@ -16,8 +16,6 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation - final class MockServiceUserType: MockUserType, ServiceUser { var providerIdentifier: String? @@ -28,4 +26,8 @@ final class MockServiceUserType: MockUserType, ServiceUser { true } + override var isBot: Bool { + false + } + } diff --git a/wire-ios/Tests/Mocks/MockUser.h b/wire-ios/Tests/Mocks/MockUser.h index 4d68bcafbe0..9f21d0d63ce 100644 --- a/wire-ios/Tests/Mocks/MockUser.h +++ b/wire-ios/Tests/Mocks/MockUser.h @@ -66,6 +66,8 @@ @property (nonatomic, readwrite) NSTimeInterval expiresAfter; @property (nonatomic, assign) BOOL isSelfUser; @property (nonatomic, assign) BOOL isApp; +@property (nonatomic, assign) BOOL isBot; +@property (nonatomic, assign) BOOL isAppOrBot; @property (nonatomic, readwrite) BOOL isTeamMember; @property (nonatomic, readwrite) TeamRole teamRole; @property (nonatomic, assign) BOOL isGuestInConversation; diff --git a/wire-ios/Tests/Mocks/MockUser.m b/wire-ios/Tests/Mocks/MockUser.m index 139cfe5258c..1b2ff9321e8 100644 --- a/wire-ios/Tests/Mocks/MockUser.m +++ b/wire-ios/Tests/Mocks/MockUser.m @@ -94,6 +94,7 @@ + (MockUser *)mockServiceUser @"displayName": @"GitHub", @"isSelfUser": @false, @"isApp": @true, + @"isBot": @false, @"isConnected": @true, @"accentColorValue": @1}]; } diff --git a/wire-ios/Tests/Mocks/MockUserType.swift b/wire-ios/Tests/Mocks/MockUserType.swift index fe2e0453d04..3b09848f441 100644 --- a/wire-ios/Tests/Mocks/MockUserType.swift +++ b/wire-ios/Tests/Mocks/MockUserType.swift @@ -126,10 +126,13 @@ class MockUserType: NSObject, UserType, Decodable, EditableUserType { var isSelfUser: Bool = false - var mockedIsApp: Bool = false - var isApp: Bool { - mockedIsApp - } + var mockedIsApp = false + var isApp: Bool { mockedIsApp } + + var mockedIsBot = false + var isBot: Bool { mockedIsBot } + + var isAppOrBot: Bool { isApp || isBot } var isVerified: Bool = false diff --git a/wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift b/wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift index 3cfdfcd5aa9..9071393e563 100644 --- a/wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift +++ b/wire-ios/Wire-iOS Tests/ConversationAvatarViewModeTests.swift @@ -17,6 +17,7 @@ // import XCTest + @testable import Wire final class ConversationConnectAvatarViewModeTests: XCTestCase { @@ -46,7 +47,7 @@ final class ConversationConnectAvatarViewModeTests: XCTestCase { let mockServiceUser = MockServiceUserType() mockServiceUser.serviceIdentifier = "serviceIdentifier" mockServiceUser.providerIdentifier = "providerIdentifier" - XCTAssert(mockServiceUser.isApp) + XCTAssert(mockServiceUser.isAppOrBot) users = [mockServiceUser] diff --git a/wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift b/wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift index 41727c75be2..5d0c8a55a43 100644 --- a/wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift +++ b/wire-ios/Wire-iOS Tests/ConversationAvatarViewTests.swift @@ -85,7 +85,7 @@ final class ConversationAvatarViewTests: XCTestCase { otherUser.serviceIdentifier = "serviceIdentifier" otherUser.providerIdentifier = "providerIdentifier" otherUser.isConnected = true - XCTAssert(otherUser.isApp) + XCTAssert(otherUser.isAppOrBot) otherUser.zmAccentColor = .green let otherUserConversation = MockStableRandomParticipantsConversation() diff --git a/wire-ios/Wire-iOS Tests/ConversationMessageCell/ConversationSenderMessageDetailsCellSnapshotTests.swift b/wire-ios/Wire-iOS Tests/ConversationMessageCell/ConversationSenderMessageDetailsCellSnapshotTests.swift index feff8d4cdd5..49a3e2ab2d7 100644 --- a/wire-ios/Wire-iOS Tests/ConversationMessageCell/ConversationSenderMessageDetailsCellSnapshotTests.swift +++ b/wire-ios/Wire-iOS Tests/ConversationMessageCell/ConversationSenderMessageDetailsCellSnapshotTests.swift @@ -111,7 +111,7 @@ final class ConversationSenderMessageDetailsCellSnapshotTests: XCTestCase { let configuration = ConversationSenderMessageDetailsCell.Configuration( sender: mockUser, indicator: .none, - teamRoleIndicator: .app + teamRoleIndicator: .appOrBot ) // WHEN diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Components/UserImageView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Components/UserImageView.swift index 3b57aec5d0e..7d9391d7839 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Components/UserImageView.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Components/UserImageView.swift @@ -106,19 +106,19 @@ class UserImageView: AvatarImageView, UserObserving { /// Returns the appropriate border width for the user. private func borderWidth(for user: UserType) -> CGFloat { - user.isApp ? 0.5 : 0 + user.isAppOrBot ? 0.5 : 0 } /// Returns the appropriate border color for the user. private func borderColor(for user: UserType) -> CGColor? { - user.isApp ? UIColor.black.withAlphaComponent(0.08).cgColor : nil + user.isAppOrBot ? UIColor.black.withAlphaComponent(0.08).cgColor : nil } /// Returns the placeholder background color for the user. private func containerBackgroundColor(for user: UserType) -> UIColor { switch avatar { case .image: - user.isApp ? .white : .clear + user.isAppOrBot ? .white : .clear case .text: if user.isConnected || user.isSelfUser || user.isTeamMember || user.isWirelessUser { user.accentColor @@ -130,7 +130,7 @@ class UserImageView: AvatarImageView, UserObserving { /// Returns the appropriate avatar shape for the user. private func shape(for user: UserType) -> AvatarImageView.Shape { - user.isApp ? .relative : .circle + user.isAppOrBot ? .relative : .circle } // MARK: - Changing the Content @@ -169,7 +169,7 @@ class UserImageView: AvatarImageView, UserObserving { var desaturate = false if shouldDesaturate { - desaturate = !user.isConnected && !user.isSelfUser && !user.isTeamMember && !user.isApp + desaturate = !user.isConnected && !user.isSelfUser && !user.isTeamMember && !user.isAppOrBot } user.fetchProfileImage( diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationAvatarView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationAvatarView.swift index e9027ca0b79..21b4acc5e0b 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationAvatarView.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationAvatarView.swift @@ -55,7 +55,7 @@ final class ConversationAvatarView: UIView { configureForOneOnOne(users) case let .conversation(conversation: conversation) where conversation.conversationType == .group: let users = conversation.stableRandomParticipants.filter { !$0.isSelfUser } - if let user = users.first, user.isApp { + if let user = users.first, user.isAppOrBot { configureForOneOnOne(users) break } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationConnectAvatarView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationConnectAvatarView.swift index 505522176e7..0a05ae3100e 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationConnectAvatarView.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/ConversationConnectAvatarView.swift @@ -111,10 +111,10 @@ extension Mode { case (0, _): self = .none case (1, .group?): - let isApp = users[0].isApp - self = isApp ? .one(serviceUser: isApp) : .four + let isAppOrBot = users[0].isAppOrBot + self = isAppOrBot ? .one(serviceUser: isAppOrBot) : .four case (1, _): - self = .one(serviceUser: users[0].isApp) + self = .one(serviceUser: users[0].isAppOrBot) default: self = .four } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/IconImageView/UserTypeIconStyle.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/IconImageView/UserTypeIconStyle.swift index 1bf936ed4b4..02fa2dd6505 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/IconImageView/UserTypeIconStyle.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/IconImageView/UserTypeIconStyle.swift @@ -73,7 +73,7 @@ extension UserTypeIconStyle { } else if let conversation { self = !user.isGuest(in: conversation) || user.isSelfUser ? .member : .guest } else { - self = !selfUserHasTeam || user.isTeamMember || user.isApp + self = !selfUserHasTeam || user.isTeamMember || user.isAppOrBot ? .member : .guest } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/UserCell.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/UserCell.swift index e58f83b9c58..20d0a87909d 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/UserCell.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Components/Views/UserCell.swift @@ -35,7 +35,7 @@ final class UserCell: SeparatorCollectionViewCell, SectionListCellType { private var userIsSelfUser = false private var isSelfUserPartOfATeam = false - private var userIsApp = false + private var userIsAppOrBot = false typealias IconColors = SemanticColors.Icon typealias LabelColors = SemanticColors.Label @@ -352,7 +352,7 @@ final class UserCell: SeparatorCollectionViewCell, SectionListCellType { if !checkmarkIconView.isHidden { accessibilityHint = isSelected ? CreateConversation.SelectedUser.hint : CreateConversation.UnselectedUser .hint - } else if userIsApp { + } else if userIsAppOrBot { accessibilityHint = ServicesList.ServiceCell.hint } else { accessibilityHint = ContactsList.UserCell.hint @@ -389,7 +389,7 @@ extension UserCell { self.userStatus = userStatus self.userIsSelfUser = userIsSelfUser self.isSelfUserPartOfATeam = isSelfUserPartOfATeam - userIsApp = user.isApp + userIsAppOrBot = user.isAppOrBot let subtitle: NSAttributedString? = if overrideSubtitle == nil { self.subtitle(for: user) @@ -451,8 +451,8 @@ extension UserCell: UserCellSubtitleProtocol {} extension UserCell { private func subtitle(for user: UserType) -> NSAttributedString? { - if user.isApp, let service = user as? SearchServiceUser { - subtitle(forServiceUser: service) + if user.isAppOrBot, let appOrBot = user as? SearchServiceUser { + subtitle(forServiceUser: appOrBot) } else { subtitle(forRegularUser: user) } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ConfigurationMessageCell/Components/ConversationSenderMessageDetailsCell.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ConfigurationMessageCell/Components/ConversationSenderMessageDetailsCell.swift index b43d9bbe1fe..2bce677122e 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ConfigurationMessageCell/Components/ConversationSenderMessageDetailsCell.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ConfigurationMessageCell/Components/ConversationSenderMessageDetailsCell.swift @@ -33,7 +33,7 @@ enum TeamRoleIndicator { case guest case externalPartner case federated - case app + case appOrBot } // MARK: - ConversationSenderMessageDetailsCell @@ -180,7 +180,7 @@ final class ConversationSenderMessageDetailsCell: UIView, ConversationMessageCel private func configureAuthorLabel(object: Configuration) { let sender = object.sender - let textColor: UIColor = sender.isApp ? SemanticColors.Label.textDefault : sender.accentColor + let textColor: UIColor = sender.isAppOrBot ? SemanticColors.Label.textDefault : sender.accentColor let attributedString = NSMutableAttributedString( string: sender.name ?? L10n.Localizable.Profile.Details.Title.unavailable, attributes: [ @@ -220,7 +220,7 @@ final class ConversationSenderMessageDetailsCell: UIView, ConversationMessageCel attributedString.append(attachment) } - case .app: + case .appOrBot: accessibilityIdentifier = "img.serviceUser" if let attachment = attachment(from: .bot, size: 14) { attributedString.append(attachment) @@ -361,8 +361,8 @@ final class ConversationSenderMessageCellDescription: ConversationMessageCellDes private extension UserType { func teamRoleIndicator(selfUser: any UserType) -> TeamRoleIndicator? { - if isApp { - .app + if isAppOrBot { + .appOrBot } else if isExternalPartner { .externalPartner diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ParticipantsCellViewModel.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ParticipantsCellViewModel.swift index 3abb2bbbb85..d887b12e812 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ParticipantsCellViewModel.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Content/Cells/ParticipantsCellViewModel.swift @@ -105,8 +105,8 @@ final class ParticipantsCellViewModel { guard let users = Array(messageData.userTypes) as? [UserType] else { return false } let selfAddedToServiceConversation = users.any(\.isSelfUser) && conversation.areAppsPresent - let appAdded = users.any(\.isApp) - return selfAddedToServiceConversation || appAdded + let appOrBotAdded = users.any(\.isAppOrBot) + return selfAddedToServiceConversation || appOrBotAdded } /// Users displayed in the system message, up to 17 when not collapsed diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Create/ConversationCreationValues.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Create/ConversationCreationValues.swift index 953ebb83175..285c9028dff 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Create/ConversationCreationValues.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Create/ConversationCreationValues.swift @@ -52,8 +52,8 @@ final class ConversationCreationValues { } if !allowApps { - let noApps = result.filter { !$0.isApp } - result = UserSet(noApps) + let noAppsOrBots = result.filter { !$0.isAppOrBot } + result = UserSet(noAppsOrBots) } return result diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Mentions/Collection+MentionSearch.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Mentions/Collection+MentionSearch.swift index 56b4eefcbac..a5836dadc46 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Mentions/Collection+MentionSearch.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/Mentions/Collection+MentionSearch.swift @@ -23,7 +23,7 @@ import WireUtilities extension Collection where Iterator.Element: UserType { func searchForMentions(withQuery query: String) -> [UserType] { - let usersToSearch = filter { !$0.isSelfUser && !$0.isApp } + let usersToSearch = filter { !$0.isSelfUser && !$0.isAppOrBot } guard !query.isEmpty else { return usersToSearch } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Helpers/UserDetailViewControllerFactory.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Helpers/UserDetailViewControllerFactory.swift index 35f0ed6c82b..764701635a4 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Helpers/UserDetailViewControllerFactory.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Helpers/UserDetailViewControllerFactory.swift @@ -40,7 +40,7 @@ enum UserDetailViewControllerFactory { selfProfileUIBuilder: some SelfProfileViewControllerBuilderProtocol ) -> UIViewController { - if user.isApp, let serviceUser = user as? ServiceUser { + if user.isAppOrBot, let serviceUser = user as? ServiceUser { return ServiceDetailViewController( serviceUser: serviceUser, actionType: .removeService(conversation), diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/SearchResultsViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/SearchResultsViewController.swift index 6cebba394b3..4fd120a9e48 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/SearchResultsViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/StartUI/StartUI/SearchResultsViewController.swift @@ -449,7 +449,7 @@ extension SearchResultsViewController: SearchSectionControllerDelegate { indexPath: indexPath, section: sectionFor(controller: searchSectionController) ) - } else if let service = user as? ServiceUser, service.isApp { + } else if let service = user as? ServiceUser, service.isAppOrBot { delegate?.searchResultsViewController(self, didTapOnSeviceUser: service) } else if let searchUser = user as? ZMSearchUser { delegate?.searchResultsViewController(