mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00
Settings to enable and disable sending public RRs (#2384)
This commit is contained in:
parent
29c8049559
commit
e755be4cda
@ -120,6 +120,11 @@
|
||||
"common_everyone" = "Everyone";
|
||||
"common_face_id_ios" = "Face ID";
|
||||
"common_file" = "File";
|
||||
"common_filter_favourites" = "Favourites";
|
||||
"common_filter_low_priority" = "Low Priority";
|
||||
"common_filter_people" = "People";
|
||||
"common_filter_rooms" = "Rooms";
|
||||
"common_filter_unreads" = "Unreads";
|
||||
"common_forward_message" = "Forward message";
|
||||
"common_gif" = "GIF";
|
||||
"common_image" = "Image";
|
||||
@ -298,6 +303,8 @@
|
||||
"screen_advanced_settings_developer_mode" = "Developer mode";
|
||||
"screen_advanced_settings_developer_mode_description" = "Enable to have access to features and functionality for developers.";
|
||||
"screen_advanced_settings_rich_text_editor_description" = "Disable the rich text editor to type Markdown manually.";
|
||||
"screen_advanced_settings_send_read_receipts" = "Read receipts";
|
||||
"screen_advanced_settings_send_read_receipts_description" = "If turned off, your read receipts won't be sent to anyone. You will still receive read receipts from other users.";
|
||||
"screen_advanced_settings_view_source_description" = "Enable option to view message source in the timeline.";
|
||||
"screen_analytics_prompt_data_usage" = "We won't record or profile any personal data";
|
||||
"screen_analytics_prompt_help_us_improve" = "Share anonymous usage data to help us identify issues.";
|
||||
|
@ -37,6 +37,7 @@ final class AppSettings {
|
||||
case viewSourceEnabled
|
||||
case richTextEditorEnabled
|
||||
case appAppearance
|
||||
case sendReadReceiptsEnabled
|
||||
|
||||
case elementCallBaseURL
|
||||
case elementCallEncryptionEnabled
|
||||
@ -261,6 +262,11 @@ final class AppSettings {
|
||||
// maptiler api key
|
||||
let mapTilerApiKey = InfoPlistReader.main.mapLibreAPIKey
|
||||
|
||||
// MARK: - Read Receipts
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.sendReadReceiptsEnabled, defaultValue: true, storageType: .userDefaults(store))
|
||||
var sendReadReceiptsEnabled
|
||||
|
||||
// MARK: - Feature Flags
|
||||
|
||||
@UserPreference(key: UserDefaultsKeys.userSuggestionsEnabled, defaultValue: false, storageType: .volatile)
|
||||
|
@ -274,6 +274,16 @@ public enum L10n {
|
||||
public static var commonFaceIdIos: String { return L10n.tr("Localizable", "common_face_id_ios") }
|
||||
/// File
|
||||
public static var commonFile: String { return L10n.tr("Localizable", "common_file") }
|
||||
/// Favourites
|
||||
public static var commonFilterFavourites: String { return L10n.tr("Localizable", "common_filter_favourites") }
|
||||
/// Low Priority
|
||||
public static var commonFilterLowPriority: String { return L10n.tr("Localizable", "common_filter_low_priority") }
|
||||
/// People
|
||||
public static var commonFilterPeople: String { return L10n.tr("Localizable", "common_filter_people") }
|
||||
/// Rooms
|
||||
public static var commonFilterRooms: String { return L10n.tr("Localizable", "common_filter_rooms") }
|
||||
/// Unreads
|
||||
public static var commonFilterUnreads: String { return L10n.tr("Localizable", "common_filter_unreads") }
|
||||
/// Forward message
|
||||
public static var commonForwardMessage: String { return L10n.tr("Localizable", "common_forward_message") }
|
||||
/// GIF
|
||||
@ -718,6 +728,10 @@ public enum L10n {
|
||||
public static var screenAdvancedSettingsElementCallBaseUrlValidationError: String { return L10n.tr("Localizable", "screen_advanced_settings_element_call_base_url_validation_error") }
|
||||
/// Disable the rich text editor to type Markdown manually.
|
||||
public static var screenAdvancedSettingsRichTextEditorDescription: String { return L10n.tr("Localizable", "screen_advanced_settings_rich_text_editor_description") }
|
||||
/// Read receipts
|
||||
public static var screenAdvancedSettingsSendReadReceipts: String { return L10n.tr("Localizable", "screen_advanced_settings_send_read_receipts") }
|
||||
/// If turned off, your read receipts won't be sent to anyone. You will still receive read receipts from other users.
|
||||
public static var screenAdvancedSettingsSendReadReceiptsDescription: String { return L10n.tr("Localizable", "screen_advanced_settings_send_read_receipts_description") }
|
||||
/// Enable option to view message source in the timeline.
|
||||
public static var screenAdvancedSettingsViewSourceDescription: String { return L10n.tr("Localizable", "screen_advanced_settings_view_source_description") }
|
||||
/// We won't record or profile any personal data
|
||||
|
@ -2789,23 +2789,23 @@ class TimelineProxyMock: TimelineProxyProtocol {
|
||||
}
|
||||
//MARK: - sendReadReceipt
|
||||
|
||||
var sendReadReceiptForCallsCount = 0
|
||||
var sendReadReceiptForCalled: Bool {
|
||||
return sendReadReceiptForCallsCount > 0
|
||||
var sendReadReceiptForTypeCallsCount = 0
|
||||
var sendReadReceiptForTypeCalled: Bool {
|
||||
return sendReadReceiptForTypeCallsCount > 0
|
||||
}
|
||||
var sendReadReceiptForReceivedEventID: String?
|
||||
var sendReadReceiptForReceivedInvocations: [String] = []
|
||||
var sendReadReceiptForReturnValue: Result<Void, TimelineProxyError>!
|
||||
var sendReadReceiptForClosure: ((String) async -> Result<Void, TimelineProxyError>)?
|
||||
var sendReadReceiptForTypeReceivedArguments: (eventID: String, type: ReceiptType)?
|
||||
var sendReadReceiptForTypeReceivedInvocations: [(eventID: String, type: ReceiptType)] = []
|
||||
var sendReadReceiptForTypeReturnValue: Result<Void, TimelineProxyError>!
|
||||
var sendReadReceiptForTypeClosure: ((String, ReceiptType) async -> Result<Void, TimelineProxyError>)?
|
||||
|
||||
func sendReadReceipt(for eventID: String) async -> Result<Void, TimelineProxyError> {
|
||||
sendReadReceiptForCallsCount += 1
|
||||
sendReadReceiptForReceivedEventID = eventID
|
||||
sendReadReceiptForReceivedInvocations.append(eventID)
|
||||
if let sendReadReceiptForClosure = sendReadReceiptForClosure {
|
||||
return await sendReadReceiptForClosure(eventID)
|
||||
func sendReadReceipt(for eventID: String, type: ReceiptType) async -> Result<Void, TimelineProxyError> {
|
||||
sendReadReceiptForTypeCallsCount += 1
|
||||
sendReadReceiptForTypeReceivedArguments = (eventID: eventID, type: type)
|
||||
sendReadReceiptForTypeReceivedInvocations.append((eventID: eventID, type: type))
|
||||
if let sendReadReceiptForTypeClosure = sendReadReceiptForTypeClosure {
|
||||
return await sendReadReceiptForTypeClosure(eventID, type)
|
||||
} else {
|
||||
return sendReadReceiptForReturnValue
|
||||
return sendReadReceiptForTypeReturnValue
|
||||
}
|
||||
}
|
||||
//MARK: - sendMessageEventContent
|
||||
|
@ -73,7 +73,7 @@ struct InvitesScreen_Previews: PreviewProvider, TestablePreview {
|
||||
NavigationView {
|
||||
InvitesScreen(context: InvitesScreenViewModel.someInvite.context)
|
||||
}
|
||||
.snapshot(delay: 2.0)
|
||||
.snapshot(delay: 3.0)
|
||||
.previewDisplayName("Some Invite")
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ protocol AdvancedSettingsProtocol: AnyObject {
|
||||
var viewSourceEnabled: Bool { get set }
|
||||
var richTextEditorEnabled: Bool { get set }
|
||||
var appAppearance: AppAppearance { get set }
|
||||
var sendReadReceiptsEnabled: Bool { get set }
|
||||
}
|
||||
|
||||
extension AppSettings: AdvancedSettingsProtocol { }
|
||||
|
@ -36,6 +36,10 @@ struct AdvancedSettingsScreen: View {
|
||||
|
||||
ListRow(label: .plain(title: L10n.actionViewSource),
|
||||
kind: .toggle($context.viewSourceEnabled))
|
||||
|
||||
ListRow(label: .plain(title: L10n.screenAdvancedSettingsSendReadReceipts,
|
||||
description: L10n.screenAdvancedSettingsSendReadReceiptsDescription),
|
||||
kind: .toggle($context.sendReadReceiptsEnabled))
|
||||
}
|
||||
}
|
||||
.compoundList()
|
||||
|
@ -57,7 +57,7 @@ class MockRoomTimelineController: RoomTimelineControllerProtocol {
|
||||
|
||||
func sendReadReceipt(for itemID: TimelineItemIdentifier) async -> Result<Void, RoomTimelineControllerError> {
|
||||
guard let roomProxy, let eventID = itemID.eventID else { return .failure(.generic) }
|
||||
switch await roomProxy.timeline.sendReadReceipt(for: eventID) {
|
||||
switch await roomProxy.timeline.sendReadReceipt(for: eventID, type: .read) {
|
||||
case .success:
|
||||
return .success(())
|
||||
case .failure:
|
||||
|
@ -93,7 +93,8 @@ class RoomTimelineController: RoomTimelineControllerProtocol {
|
||||
let eventID = itemID.eventID
|
||||
else { return .success(()) }
|
||||
|
||||
switch await roomProxy.timeline.sendReadReceipt(for: eventID) {
|
||||
switch await roomProxy.timeline.sendReadReceipt(for: eventID,
|
||||
type: appSettings.sendReadReceiptsEnabled ? .read : .readPrivate) {
|
||||
case .success:
|
||||
return .success(())
|
||||
case .failure:
|
||||
|
@ -453,7 +453,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func sendReadReceipt(for eventID: String) async -> Result<Void, TimelineProxyError> {
|
||||
func sendReadReceipt(for eventID: String, type: ReceiptType) async -> Result<Void, TimelineProxyError> {
|
||||
MXLog.info("Sending read receipt for eventID: \(eventID)")
|
||||
|
||||
sendMessageBackgroundTask = await backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
|
||||
@ -463,7 +463,7 @@ final class TimelineProxy: TimelineProxyProtocol {
|
||||
|
||||
return await Task.dispatch(on: lowPriorityDispatchQueue) {
|
||||
do {
|
||||
try self.timeline.sendReadReceipt(receiptType: .read, eventId: eventID)
|
||||
try self.timeline.sendReadReceipt(receiptType: type, eventId: eventID)
|
||||
MXLog.info("Finished sending read receipt for eventID: \(eventID)")
|
||||
return .success(())
|
||||
} catch {
|
||||
|
@ -102,7 +102,7 @@ protocol TimelineProxyProtocol {
|
||||
progressSubject: CurrentValueSubject<Double, Never>?,
|
||||
requestHandle: @MainActor (SendAttachmentJoinHandleProtocol) -> Void) async -> Result<Void, TimelineProxyError>
|
||||
|
||||
func sendReadReceipt(for eventID: String) async -> Result<Void, TimelineProxyError>
|
||||
func sendReadReceipt(for eventID: String, type: ReceiptType) async -> Result<Void, TimelineProxyError>
|
||||
|
||||
func sendMessageEventContent(_ messageContent: RoomMessageEventContentWithoutRelation) async -> Result<Void, TimelineProxyError>
|
||||
|
||||
|
@ -25,6 +25,7 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
|
||||
override func setUp() async throws {
|
||||
AppSettings.reset()
|
||||
cancellables.removeAll()
|
||||
userIndicatorControllerMock = UserIndicatorControllerMock.default
|
||||
}
|
||||
@ -456,8 +457,10 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Then the receipt should be sent.
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForCalled, true)
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForReceivedEventID, "t3")
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForTypeCalled, true)
|
||||
let arguments = timelineProxy.sendReadReceiptForTypeReceivedArguments
|
||||
XCTAssertEqual(arguments?.eventID, "t3")
|
||||
XCTAssertEqual(arguments?.type, .read)
|
||||
|
||||
// And the notifications should be cleared.
|
||||
XCTAssertEqual(notificationCenter.postNameObjectReceivedArguments?.aName, .roomMarkedAsRead)
|
||||
@ -473,16 +476,20 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
let (viewModel, _, timelineProxy, timelineController, _) = readReceiptsConfiguration(with: items)
|
||||
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id))
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForCallsCount, 1)
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForReceivedEventID, "t3")
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForTypeCallsCount, 1)
|
||||
var arguments = timelineProxy.sendReadReceiptForTypeReceivedArguments
|
||||
XCTAssertEqual(arguments?.eventID, "t3")
|
||||
XCTAssertEqual(arguments?.type, .read)
|
||||
|
||||
// When sending a receipt for the first item in the timeline.
|
||||
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.first!.id))
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Then the request should be ignored.
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForCallsCount, 1)
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForReceivedEventID, "t3")
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForTypeCallsCount, 1)
|
||||
arguments = timelineProxy.sendReadReceiptForTypeReceivedArguments
|
||||
XCTAssertEqual(arguments?.eventID, "t3")
|
||||
XCTAssertEqual(arguments?.type, .read)
|
||||
|
||||
// When a new message is received and marked as read.
|
||||
let newMessage = TextRoomTimelineItem(eventID: "t4")
|
||||
@ -494,8 +501,10 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Then the request should be made.
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForCallsCount, 2)
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForReceivedEventID, "t4")
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForTypeCallsCount, 2)
|
||||
arguments = timelineProxy.sendReadReceiptForTypeReceivedArguments
|
||||
XCTAssertEqual(arguments?.eventID, "t4")
|
||||
XCTAssertEqual(arguments?.type, .read)
|
||||
}
|
||||
|
||||
func testSendReadReceiptWithoutEvents() async throws {
|
||||
@ -510,7 +519,7 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Then nothing should be sent.
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForCalled, false)
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForTypeCalled, false)
|
||||
}
|
||||
|
||||
func testSendReadReceiptVirtualLast() async throws {
|
||||
@ -525,8 +534,10 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Then a read receipt should be sent for the item before it.
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForCalled, true)
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForReceivedEventID, "t2")
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForTypeCalled, true)
|
||||
let arguments = timelineProxy.sendReadReceiptForTypeReceivedArguments
|
||||
XCTAssertEqual(arguments?.eventID, "t2")
|
||||
XCTAssertEqual(arguments?.type, .read)
|
||||
}
|
||||
|
||||
func testSendReadReceiptMultipleRequests() async throws {
|
||||
@ -537,15 +548,17 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
let (viewModel, _, timelineProxy, _, _) = readReceiptsConfiguration(with: items)
|
||||
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id))
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForCallsCount, 1)
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForReceivedEventID, "t2")
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForTypeCallsCount, 1)
|
||||
let arguments = timelineProxy.sendReadReceiptForTypeReceivedArguments
|
||||
XCTAssertEqual(arguments?.eventID, "t2")
|
||||
XCTAssertEqual(arguments?.type, .read)
|
||||
|
||||
// When sending the same receipt again
|
||||
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id))
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Then the second call should be ignored.
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForCallsCount, 1)
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForTypeCallsCount, 1)
|
||||
}
|
||||
|
||||
// swiftlint:enable force_unwrapping
|
||||
@ -564,7 +577,7 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
roomProxy.timeline = timelineProxy
|
||||
let timelineController = MockRoomTimelineController()
|
||||
|
||||
timelineProxy.sendReadReceiptForReturnValue = .success(())
|
||||
timelineProxy.sendReadReceiptForTypeReturnValue = .success(())
|
||||
|
||||
roomProxy.underlyingHasUnreadNotifications = true
|
||||
timelineController.timelineItems = items
|
||||
@ -580,7 +593,6 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
notificationCenter: notificationCenter)
|
||||
|
||||
return (viewModel, roomProxy, timelineProxy, timelineController, notificationCenter)
|
||||
}
|
||||
|
||||
|
BIN
UnitTests/__Snapshots__/PreviewTests/test_advancedSettingsScreen.1.png
(Stored with Git LFS)
BIN
UnitTests/__Snapshots__/PreviewTests/test_advancedSettingsScreen.1.png
(Stored with Git LFS)
Binary file not shown.
1
changelog.d/2319.feature
Normal file
1
changelog.d/2319.feature
Normal file
@ -0,0 +1 @@
|
||||
Added an advanced setting to turn off sending read receipts.
|
Loading…
x
Reference in New Issue
Block a user