Promote "history sharing on invite" out of developer options (#5480)

* Enable key-share-on-invite irrespective of feature flag

* Remove feature-flag dep: warning on starting chat with new people

* Remove feature-flag dep: invite from room member details

* Remove feature-flag dep: warning on new users in invite screen

* Remove feature-flag dep: from room details screen

* Remove feature-flag dep: starting chat from user profile screen

* Remove feature-flag dep: timeline info on forwarded keys

* Remove feature-flag dep: RoomScreenModel

* Remove `enableKeyShareOnInvite` from AppSettings

* Remove `enableKeyShareOnInvite` feature flag

* Remove outdated comments

* Update preview test room snapshots as their header now includes the history sharing icon
This commit is contained in:
Richard van der Hoff
2026-04-27 12:43:18 +01:00
committed by GitHub
parent eb72815e74
commit 9a2a852af3
39 changed files with 53 additions and 201 deletions
@@ -61,8 +61,7 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
case .cancel:
actionsSubject.send(.dismiss)
case .proceed:
guard appSettings.enableKeyShareOnInvite,
roomProxy.details.historySharingState != RoomHistorySharingState.hidden,
guard roomProxy.details.historySharingState != RoomHistorySharingState.hidden,
!state.usersToConfirm.isEmpty,
!state.isSkippable else {
inviteUsers(state.selectedUsers.map(\.userID), roomProxy: roomProxy)
@@ -107,11 +106,7 @@ class InviteUsersScreenViewModel: InviteUsersScreenViewModelType, InviteUsersScr
}
private func inviteUsers(_ users: [String], roomProxy: JoinedRoomProxyProtocol) {
if appSettings.enableKeyShareOnInvite {
showLoadingIndicator(title: L10n.screenRoomDetailsInvitePeoplePreparing, message: L10n.screenRoomDetailsInvitePeopleDontClose)
} else {
showLoadingIndicator()
}
showLoadingIndicator(title: L10n.screenRoomDetailsInvitePeoplePreparing, message: L10n.screenRoomDetailsInvitePeopleDontClose)
Task {
defer {
@@ -61,13 +61,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
let topic = attributedStringBuilder.fromPlain(roomProxy.infoPublisher.value.topic)
// Clear details.historySharingState manually while we are still behind a feature flag.
var details = roomProxy.details
if !appSettings.enableKeyShareOnInvite {
details.historySharingState = nil
}
super.init(initialViewState: .init(details: details,
super.init(initialViewState: .init(details: roomProxy.details,
isEncrypted: roomProxy.infoPublisher.value.isEncrypted,
isDirect: roomProxy.infoPublisher.value.isDirect,
topic: topic,
@@ -280,14 +274,7 @@ class RoomDetailsScreenViewModel: RoomDetailsScreenViewModelType, RoomDetailsScr
state.joinedMembersCount = roomInfo.joinedMembersCount
state.details = roomProxy.details
// Set state.details.historySharingState manually while we are still behind
// a feature flag.
if appSettings.enableKeyShareOnInvite {
state.details.historySharingState = roomInfo.historySharingState
} else {
state.details.historySharingState = nil
}
state.details.historySharingState = roomInfo.historySharingState
let topic = attributedStringBuilder.fromPlain(roomInfo.topic)
state.topic = topic
@@ -374,7 +374,6 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
}
private static func makeGenericRoomViewModel(historyVisibility: RoomHistoryVisibility) -> RoomDetailsScreenViewModel {
ServiceLocator.shared.settings.enableKeyShareOnInvite = true
ServiceLocator.shared.settings.knockingEnabled = true
let knockRequests: [KnockRequestProxyMock] = [.init()]
@@ -418,7 +417,6 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
}
private static func makeSimpleRoomViewModel() -> RoomDetailsScreenViewModel {
ServiceLocator.shared.settings.enableKeyShareOnInvite = true
ServiceLocator.shared.settings.knockingEnabled = true
let knockRequests: [KnockRequestProxyMock] = [.init()]
@@ -448,8 +446,6 @@ struct RoomDetailsScreen_Previews: PreviewProvider, TestablePreview {
}
private static func makeDMViewModel(verificationState: UserIdentityVerificationState) -> RoomDetailsScreenViewModel {
ServiceLocator.shared.settings.enableKeyShareOnInvite = true
let members: [RoomMemberProxyMock] = [
.mockMe,
.mockDan
@@ -200,7 +200,7 @@ class RoomMemberDetailsScreenViewModel: RoomMemberDetailsScreenViewModelType, Ro
case .success(let roomID):
if let roomID {
actionsSubject.send(.openDirectChat(roomID: roomID))
} else if appSettings.enableKeyShareOnInvite, roomProxy.details.historySharingState != RoomHistorySharingState.hidden {
} else if roomProxy.details.historySharingState != RoomHistorySharingState.hidden {
Task {
let identity = await self.userSession.clientProxy.userIdentity(for: roomMemberProxy.userID, fallBackToServer: false)
let user: UserProfileProxy = .init(userID: roomMemberProxy.userID, displayName: roomMemberProxy.displayName, avatarURL: roomMemberProxy.avatarURL)
@@ -94,7 +94,7 @@ struct RoomScreenViewState: BindableState {
(canAcceptKnocks || canDeclineKnocks || canBan)
}
/// If `enableKeyShareOnInvite` is set, determines the current history sharing state.
/// The current history sharing state.
var roomHistorySharingState: RoomHistorySharingState?
var footerDetails: RoomScreenFooterViewDetails?
@@ -67,18 +67,12 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
self.initialSelectedPinnedEventID = initialSelectedPinnedEventID
pinnedEventStringBuilder = .pinnedEventStringBuilder(userID: roomProxy.ownUserID)
let roomHistorySharingState: RoomHistorySharingState? = if appSettings.enableKeyShareOnInvite {
roomProxy.infoPublisher.value.historySharingState
} else {
nil
}
let viewState = RoomScreenViewState(roomTitle: roomProxy.infoPublisher.value.displayName ?? roomProxy.id,
roomAvatar: roomProxy.infoPublisher.value.avatar,
hasOngoingCall: roomProxy.infoPublisher.value.hasRoomCall,
isDirectOneToOneRoom: roomProxy.isDirectOneToOneRoom,
hasSuccessor: roomProxy.infoPublisher.value.successor != nil,
roomHistorySharingState: roomHistorySharingState)
roomHistorySharingState: roomProxy.infoPublisher.value.historySharingState)
super.init(initialViewState: appHooks.roomScreenHook.update(viewState),
mediaProvider: userSession.mediaProvider)
@@ -371,13 +365,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
state.canBan = powerLevels.canOwnUserBan()
}
// This causes the UI to become inconsistent with the user's mental model if the user
// does not restart the app after disabling the feature flag. We can probably ignore
// such cases, since we explicitly ask for an app restart in the caption of the feature
// flag switch.
if appSettings.enableKeyShareOnInvite {
state.roomHistorySharingState = roomInfo.historySharingState
}
state.roomHistorySharingState = roomInfo.historySharingState
}
private func setupPinnedEventsTimelineItemProviderIfNeeded() {
@@ -55,7 +55,6 @@ protocol DeveloperOptionsProtocol: AnyObject {
var traceLogPacks: Set<TraceLogPack> { get set }
var enableOnlySignedDeviceIsolationMode: Bool { get set }
var enableKeyShareOnInvite: Bool { get set }
var hideQuietNotificationAlerts: Bool { get set }
var focusEventOnNotificationTap: Bool { get set }
var automaticBackPaginationEnabled: Bool { get set }
@@ -113,16 +113,6 @@ struct DeveloperOptionsScreen: View {
Text("This setting controls how end-to-end encryption (E2EE) keys are exchanged. Enabling it will prevent the inclusion of devices that have not been explicitly verified by their owners.")
}
Section {
Toggle(isOn: $context.enableKeyShareOnInvite) {
Text("Share encrypted history with new members")
Text("Requires app reboot")
}
} footer: {
Text("When inviting a user to an encrypted room that has history visibility set to \"shared\", share encrypted history with that user, and accept encrypted history when you are invited to such a room.")
Text("WARNING: this feature is EXPERIMENTAL and not all security precautions are implemented. Do not enable on production accounts.")
}
Section("Element Call remote URL override") {
TextField("Leave empty to use EC locally", text: $elementCallURLOverrideString)
.autocorrectionDisabled(true)
@@ -67,20 +67,15 @@ class StartChatScreenViewModel: StartChatScreenViewModelType, StartChatScreenVie
hideLoadingIndicator()
actionsSubject.send(.showRoom(roomID: roomId))
case .success:
if appSettings.enableKeyShareOnInvite {
Task {
// If an error occured while fetching the identity, assume they are unknown.
let isUnknown = if case .success(let identity) = await self.userSession.clientProxy.userIdentity(for: user.userID, fallBackToServer: false) {
identity == nil
} else {
true
}
self.state.bindings.selectedUserToInvite = UserToInvite(user: user, isUnknown: isUnknown)
hideLoadingIndicator()
Task {
// If an error occured while fetching the identity, assume they are unknown.
let isUnknown = if case .success(let identity) = await self.userSession.clientProxy.userIdentity(for: user.userID, fallBackToServer: false) {
identity == nil
} else {
true
}
} else {
self.state.bindings.selectedUserToInvite = UserToInvite(user: user, isUnknown: isUnknown)
hideLoadingIndicator()
state.bindings.selectedUserToInvite = UserToInvite(user: user, isUnknown: false)
}
case .failure:
hideLoadingIndicator()
@@ -144,8 +144,6 @@ struct TimelineViewState: BindableState {
var mapTilerConfiguration: MapTilerConfiguration
var enableKeyShareOnInvite: Bool
var stoppedLiveLocationIDs: Set<TimelineItemIdentifier> = []
var bindings: TimelineViewStateBindings
@@ -107,7 +107,6 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol {
emojiProvider: emojiProvider,
linkMetadataProvider: hideTimelineMedia ? nil : linkMetadataProvider,
mapTilerConfiguration: appSettings.mapTilerConfiguration,
enableKeyShareOnInvite: appSettings.enableKeyShareOnInvite,
bindings: .init(reactionsCollapsed: [:])),
mediaProvider: userSession.mediaProvider)
@@ -335,7 +335,6 @@ private extension TimelineItemKeyForwarder {
struct TimelineItemBubbledStylerView_Previews: PreviewProvider, TestablePreview {
static let viewModel: TimelineViewModel = {
let appSettings = AppSettings()
appSettings.enableKeyShareOnInvite = true
appSettings.threadsEnabled = true
let roomProxy = JoinedRoomProxyMock(.init())
@@ -15,8 +15,7 @@ extension View {
adjustedDeliveryStatus: TimelineItemDeliveryStatus?,
context: TimelineViewModel.Context) -> some View {
modifier(TimelineItemSendInfoModifier(sendInfo: .init(timelineItem: timelineItem,
adjustedDeliveryStatus: adjustedDeliveryStatus,
enableKeyShareOnInvite: context.viewState.enableKeyShareOnInvite),
adjustedDeliveryStatus: adjustedDeliveryStatus),
context: context))
}
}
@@ -150,7 +149,7 @@ private struct TimelineItemSendInfo {
}
private extension TimelineItemSendInfo {
init(timelineItem: EventBasedTimelineItemProtocol, adjustedDeliveryStatus: TimelineItemDeliveryStatus?, enableKeyShareOnInvite: Bool) {
init(timelineItem: EventBasedTimelineItemProtocol, adjustedDeliveryStatus: TimelineItemDeliveryStatus?) {
itemID = timelineItem.id
localizedString = timelineItem.localizedSendInfo
@@ -158,7 +157,7 @@ private extension TimelineItemSendInfo {
.sendingFailed
} else if let authenticity = timelineItem.properties.encryptionAuthenticity {
.encryptionAuthenticity(authenticity)
} else if enableKeyShareOnInvite, let forwarder = timelineItem.properties.encryptionForwarder {
} else if let forwarder = timelineItem.properties.encryptionForwarder {
.encryptionForwarder(forwarder)
} else {
nil
@@ -127,17 +127,13 @@ class UserProfileScreenViewModel: UserProfileScreenViewModelType, UserProfileScr
if let roomID {
actionsSubject.send(.openDirectChat(roomID: roomID))
} else {
if appSettings.enableKeyShareOnInvite {
Task {
let isUnknown = if case let .success(identity) = await userSession.clientProxy.userIdentity(for: userProfile.userID, fallBackToServer: false) {
identity == nil
} else {
true
}
state.bindings.inviteConfirmationUser = .init(user: userProfile, isUnknown: isUnknown)
Task {
let isUnknown = if case let .success(identity) = await userSession.clientProxy.userIdentity(for: userProfile.userID, fallBackToServer: false) {
identity == nil
} else {
true
}
} else {
state.bindings.inviteConfirmationUser = .init(user: userProfile, isUnknown: false)
state.bindings.inviteConfirmationUser = .init(user: userProfile, isUnknown: isUnknown)
}
}
case .failure: