Added disappearing messages support to updated groups

Added disappearing messages support to updated groups
Added the 10s & 60s debug disappearing message setting options to the DeveloperSettingsViewModel
Copy tweaks on the DeveloperSettingsViewModel
Removed some unused code
pull/941/head
Morgan Pretty 1 year ago
parent 988fa5c550
commit b7b7b4af6a

@ -85,15 +85,18 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel, Naviga
let title: String = "DISAPPEARING_MESSAGES".localized()
lazy var subtitle: String? = {
guard dependencies[feature: .updatedDisappearingMessages] else {
return (isNoteToSelf ? nil : "DISAPPERING_MESSAGES_SUBTITLE_CONTACTS".localized())
}
if threadVariant == .contact && !isNoteToSelf {
return "DISAPPERING_MESSAGES_SUBTITLE_CONTACTS".localized()
switch (threadVariant, isNoteToSelf) {
case (.contact, false): return "DISAPPERING_MESSAGES_SUBTITLE_CONTACTS".localized()
case (.group, _): return "DISAPPERING_MESSAGES_SUBTITLE_GROUPS".localized()
case (.community, _): return nil
case (.legacyGroup, _), (_, true):
guard dependencies[feature: .updatedDisappearingMessages] else {
return (isNoteToSelf ? nil : "DISAPPERING_MESSAGES_SUBTITLE_CONTACTS".localized())
}
return "DISAPPERING_MESSAGES_SUBTITLE_GROUPS".localized()
}
return "DISAPPERING_MESSAGES_SUBTITLE_GROUPS".localized()
}()
lazy var footerButtonInfo: AnyPublisher<SessionButton.Info?, Never> = configSubject
@ -272,7 +275,7 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel, Naviga
}
return (currentConfig.type ?? .disappearAfterSend)
}())
}(), using: dependencies)
.map { duration in
let title: String = duration.formatted(format: .long)
@ -302,8 +305,77 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel, Naviga
)
)
].compactMap { $0 }
case (.group, _):
return [
SectionModel(
model: .group,
elements: [
SessionCell.Info(
id: "DISAPPEARING_MESSAGES_OFF".localized(),
title: "DISAPPEARING_MESSAGES_OFF".localized(),
trailingAccessory: .radio(
isSelected: !currentConfig.isEnabled
),
isEnabled: (currentUserIsClosedGroupAdmin == true),
accessibility: Accessibility(
identifier: "Disable disappearing messages (Off option)",
label: "Disable disappearing messages (Off option)"
),
onTap: {
self?.configSubject.send(
currentConfig.with(
isEnabled: false,
durationSeconds: DisappearingMessagesConfiguration.DefaultDuration.off.seconds,
lastChangeTimestampMs: SnodeAPI.currentOffsetTimestampMs()
)
)
}
)
]
.appending(
contentsOf: DisappearingMessagesConfiguration
.validDurationsSeconds(.disappearAfterSend, using: dependencies)
.map { duration in
let title: String = duration.formatted(format: .long)
case (.legacyGroup, _), (.group, _), (_, true):
return SessionCell.Info(
id: title,
title: title,
trailingAccessory: .radio(
isSelected: (
currentConfig.isEnabled &&
currentConfig.durationSeconds == duration
)
),
isEnabled: (currentUserIsClosedGroupAdmin == true),
accessibility: Accessibility(
identifier: "Time option",
label: "Time option"
),
onTap: {
// If the new disappearing messages config feature flag isn't
// enabled then the 'isEnabled' and 'type' values are set via
// the first section so pass `nil` values to keep the existing
// setting
self?.configSubject.send(
currentConfig.with(
isEnabled: true,
durationSeconds: duration,
type: .disappearAfterSend,
lastChangeTimestampMs: SnodeAPI.currentOffsetTimestampMs(
using: dependencies
)
)
)
}
)
}
)
)
]
case (.legacyGroup, _), (_, true):
return [
(dependencies[feature: .updatedDisappearingMessages] ? nil :
SectionModel(
@ -415,7 +487,7 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel, Naviga
.compactMap { $0 }
.appending(
contentsOf: DisappearingMessagesConfiguration
.validDurationsSeconds(.disappearAfterSend)
.validDurationsSeconds(.disappearAfterSend, using: dependencies)
.map { duration in
let title: String = duration.formatted(format: .long)
@ -483,7 +555,7 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel, Naviga
.filter(Interaction.Columns.variant == Interaction.Variant.infoDisappearingMessagesUpdate)
.deleteAll(db)
let currentOffsetTimestampMs: Int64 = SnodeAPI.currentOffsetTimestampMs()
let currentOffsetTimestampMs: Int64 = SnodeAPI.currentOffsetTimestampMs(using: dependencies)
let interaction: Interaction = try Interaction(
threadId: threadId,
@ -496,22 +568,46 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel, Naviga
)
.inserted(db)
let duration: UInt32? = {
guard !dependencies[feature: .updatedDisappearingMessages] else { return nil }
return UInt32(floor(updatedConfig.isEnabled ? updatedConfig.durationSeconds : 0))
}()
// Send a control message that the disappearing messages setting changed
switch threadVariant {
case .group:
try MessageSender.send(
db,
message: GroupUpdateInfoChangeMessage(
changeType: .disappearingMessages,
updatedExpiration: UInt32(updatedConfig.isEnabled ? updatedConfig.durationSeconds : 0),
sentTimestamp: UInt64(currentOffsetTimestampMs),
authMethod: try Authentication.with(
db,
sessionIdHexString: threadId,
using: dependencies
),
using: dependencies
),
interactionId: nil,
threadId: threadId,
threadVariant: .group,
using: dependencies
)
default:
let duration: UInt32? = {
guard !dependencies[feature: .updatedDisappearingMessages] else { return nil }
return UInt32(floor(updatedConfig.isEnabled ? updatedConfig.durationSeconds : 0))
}()
try MessageSender.send(
db,
message: ExpirationTimerUpdate(
syncTarget: nil,
duration: duration
),
interactionId: interaction.id,
threadId: threadId,
threadVariant: threadVariant,
using: dependencies
)
try MessageSender.send(
db,
message: ExpirationTimerUpdate(
syncTarget: nil,
duration: duration
),
interactionId: interaction.id,
threadId: threadId,
threadVariant: threadVariant,
using: dependencies
)
}
}
// Contacts & legacy closed groups need to update the SessionUtil

@ -13,8 +13,6 @@ import SessionUtilitiesKit
import SignalCoreKit
class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTableSource {
typealias TableItem = Section
public let dependencies: Dependencies
public let navigatableState: NavigatableState = NavigatableState()
public let state: TableDataState<Section, TableItem> = TableDataState()
@ -30,29 +28,61 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder,
// MARK: - Section
public enum Section: SessionTableSection, CaseIterable {
public enum Section: SessionTableSection {
case developerMode
case network
case disappearingMessages
case groups
case database
var title: String? {
switch self {
case .developerMode: return nil
case .network: return "Network"
case .disappearingMessages: return "Disappearing Messages"
case .groups: return "Groups"
case .database: return "Database"
}
}
//default: return .titleRoundedContent // .padding
var style: SessionTableSectionStyle {
switch self {
case .developerMode: return .padding
default: return .titleRoundedContent
}
}
}
public enum TableItem: Differentiable, CaseIterable {
case developerMode
case serviceNetwork
case networkLayer
case updatedDisappearingMessages
case debugDisappearingMessageDurations
case updatedGroups
case updatedGroupsRemoveMessagesOnKick
case updatedGroupsAllowHistoricAccessOnInvite
case updatedGroupsAllowDisplayPicture
case updatedGroupsAllowDescriptionEditing
case updatedGroupsAllowPromotions
case exportDatabase
var style: SessionTableSectionStyle { .padding }
case exportDatabase
}
// MARK: - Content
private struct State: Equatable {
let developerMode: Bool
let serviceNetwork: ServiceNetwork
let networkLayer: Network.Layers
let debugDisappearingMessageDurations: Bool
let updatedDisappearingMessages: Bool
let updatedGroups: Bool
let updatedGroupsRemoveMessagesOnKick: Bool
let updatedGroupsAllowHistoricAccessOnInvite: Bool
@ -69,6 +99,7 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder,
developerMode: dependencies[singleton: .storage, key: .developerModeEnabled],
serviceNetwork: dependencies[feature: .serviceNetwork],
networkLayer: dependencies[feature: .networkLayers],
debugDisappearingMessageDurations: dependencies[feature: .debugDisappearingMessageDurations],
updatedDisappearingMessages: dependencies[feature: .updatedDisappearingMessages],
updatedGroups: dependencies[feature: .updatedGroups],
updatedGroupsRemoveMessagesOnKick: dependencies[feature: .updatedGroupsRemoveMessagesOnKick],
@ -87,9 +118,11 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder,
id: .developerMode,
title: "Developer Mode",
subtitle: """
Developer Mode grants the device access to the settings on this screen.
Grants access to this screen.
Disabling this setting will reset all of the below settings back to default (removing data as described below) and revoke access to this screen unless Developer Mode is re-enabled.
Disabling this setting will:
Reset all the below settings to default (removing data as described below)
Revoke access to this screen unless Developer Mode is re-enabled
""",
trailingAccessory: .toggle(
.boolValue(current.developerMode, oldValue: (previous ?? current).developerMode)
@ -103,15 +136,16 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder,
]
),
SectionModel(
model: .serviceNetwork,
model: .network,
elements: [
SessionCell.Info(
id: .serviceNetwork,
title: "Network",
title: "Environment",
subtitle: """
The service network which should be used for sending requests and storing messages.
The environment used for sending requests and storing messages.
<b>Warning:</b> These networks cannot communicate with each other so changing this network will result in all conversation and snode data being cleared and any pending network requests being cancelled.
<b>Warning:</b>
Changing this setting will result in all conversation and snode data being cleared and any pending network requests being cancelled.
""",
trailingAccessory: .dropDown(
.dynamicString { current.serviceNetwork.title }
@ -120,7 +154,7 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder,
self?.transitionToScreen(
SessionTableViewController(
viewModel: SessionListViewModel<ServiceNetwork>(
title: "Network",
title: "Environment",
options: ServiceNetwork.allCases,
behaviour: .autoDismiss(
initialSelection: current.serviceNetwork,
@ -131,19 +165,17 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder,
)
)
}
)
]
),
SectionModel(
model: .networkLayer,
elements: [
),
SessionCell.Info(
id: .networkLayer,
title: "Network Layer",
title: "Routing",
subtitle: """
The network layer which all network traffic should be routed through. We do support sending network traffic through multiple network layers, if multiple layers are selected then requests will wait for a response from all layers before completing with the first successful response.
The network layer which all network traffic should be routed through.
We do support sending network traffic through multiple network layers, if multiple layers are selected then requests will wait for a response from all layers before completing with the first successful response.
<b>Warning:</b> Different network layers offer different levels of privacy, make sure to read the description of the network layers before making a selection.
<b>Warning:</b>
Different network layers offer different levels of privacy, make sure to read the description of the network layers before making a selection.
""",
trailingAccessory: .dropDown(
.dynamicString { current.networkLayer.title }
@ -152,7 +184,7 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder,
self?.transitionToScreen(
SessionTableViewController(
viewModel: SessionListViewModel<Network.Layers>(
title: "Network Layer",
title: "Routing",
options: Network.Layers.allCases,
behaviour: .singleSelect(
initialSelection: current.networkLayer,
@ -167,13 +199,34 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder,
]
),
SectionModel(
model: .updatedDisappearingMessages,
model: .disappearingMessages,
elements: [
SessionCell.Info(
id: .debugDisappearingMessageDurations,
title: "Debug Durations",
subtitle: """
Adds 10 and 60 second durations for Disappearing Message settings.
These should only be used for debugging purposes and can result in odd behaviours.
""",
trailingAccessory: .toggle(
.boolValue(
current.debugDisappearingMessageDurations,
oldValue: (previous ?? current).debugDisappearingMessageDurations
)
),
onTap: {
self?.updateFlag(
for: .debugDisappearingMessageDurations,
to: !current.debugDisappearingMessageDurations
)
}
),
SessionCell.Info(
id: .updatedDisappearingMessages,
title: "Updated Disappearing Messages",
title: "Use Updated Disappearing Messages",
subtitle: """
This setting controls whether legacy or updated disappearing messages should be used.
Controls whether legacy or updated disappearing messages should be used.
""",
trailingAccessory: .toggle(
.boolValue(
@ -191,29 +244,24 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder,
]
),
SectionModel(
model: .updatedGroups,
model: .groups,
elements: [
SessionCell.Info(
id: .updatedGroups,
title: "Updated Groups",
title: "Use Updated Groups",
subtitle: """
This settings controls whether newly created groups should use the updated groups or legacy groups.
Controls whether newly created groups are updated or legacy groups.
""",
trailingAccessory: .toggle(
.boolValue(current.updatedGroups, oldValue: (previous ?? current).updatedGroups)
),
onTap: { self?.updateFlag(for: .updatedGroups, to: !current.updatedGroups) }
)
]
),
SectionModel(
model: .updatedGroupsRemoveMessagesOnKick,
elements: [
),
SessionCell.Info(
id: .updatedGroupsRemoveMessagesOnKick,
title: "Remove Messages when Kicking from Updated Groups",
title: "Remove Messages on Kick",
subtitle: """
This settings controls whether a group members messages should be removed when they are kicked from an updated group.
Controls whether a group members messages should be removed when they are kicked from an updated group.
<b>Note:</b> In a future release we will offer this as an option when removing members but for the initial release it can be controlled via this flag for testing purposes.
""",
@ -229,17 +277,12 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder,
to: !current.updatedGroupsRemoveMessagesOnKick
)
}
)
]
),
SectionModel(
model: .updatedGroupsAllowHistoricAccessOnInvite,
elements: [
),
SessionCell.Info(
id: .updatedGroupsAllowHistoricAccessOnInvite,
title: "Allow access to historic messages when inviting to an updated group",
title: "Allow Historic Message Access",
subtitle: """
This settings controls whether a group members should be granted access to hsitoric messages when invited to an updated group.
Controls whether members should be granted access to historic messages when invited to an updated group.
<b>Note:</b> In a future release we will offer this as an option when inviting members but for the initial release it can be controlled via this flag for testing purposes.
""",
@ -255,17 +298,12 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder,
to: !current.updatedGroupsAllowHistoricAccessOnInvite
)
}
)
]
),
SectionModel(
model: .updatedGroupsAllowDisplayPicture,
elements: [
),
SessionCell.Info(
id: .updatedGroupsAllowDisplayPicture,
title: "Shows UI for setting updated group custom display pictures",
title: "Custom Display Pictures",
subtitle: """
This settings controls whether the UI allows group admins to set a custom display picture for a group.
Controls whether the UI allows group admins to set a custom display picture for a group.
<b>Note:</b> In a future release we will offer this functionality but for the initial release it may not be fully supported across platforms so can be controlled via this flag for testing purposes.
""",
@ -281,17 +319,12 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder,
to: !current.updatedGroupsAllowDisplayPicture
)
}
)
]
),
SectionModel(
model: .updatedGroupsAllowDescriptionEditing,
elements: [
),
SessionCell.Info(
id: .updatedGroupsAllowDescriptionEditing,
title: "Show UI for editing updated group descriptions",
title: "Edit Group Descriptions",
subtitle: """
This settings controls whether the UI allows group admins to modify the descriptions of updated groups.
Controls whether the UI allows group admins to modify the descriptions of updated groups.
<b>Note:</b> In a future release we will offer this functionality but for the initial release it may not be fully supported across platforms so can be controlled via this flag for testing purposes.
""",
@ -307,17 +340,12 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder,
to: !current.updatedGroupsAllowDescriptionEditing
)
}
)
]
),
SectionModel(
model: .updatedGroupsAllowPromotions,
elements: [
),
SessionCell.Info(
id: .updatedGroupsAllowPromotions,
title: "Show UI for updated group promotions",
title: "Allow Group Promotions",
subtitle: """
This settings controls whether the UI allows group admins promote other group members to admin within an updated group.
Controls whether the UI allows group admins promote other group members to admin within an updated group.
<b>Note:</b> In a future release we will offer this functionality but for the initial release it may not be fully supported across platforms so can be controlled via this flag for testing purposes.
""",
@ -337,7 +365,7 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder,
]
),
SectionModel(
model: .exportDatabase,
model: .database,
elements: [
SessionCell.Info(
id: .exportDatabase,
@ -362,14 +390,18 @@ class DeveloperSettingsViewModel: SessionTableViewModel, NavigatableStateHolder,
private func disableDeveloperMode() {
/// Loop through all of the sections and reset the features back to default for each one as needed (this way if a new section is added
/// then we will get a compile error if it doesn't get resetting instructions added)
Section.allCases.forEach { section in
switch section {
TableItem.allCases.forEach { item in
switch item {
case .developerMode: break // Not a feature
case .exportDatabase: break // Not a feature
case .serviceNetwork: updateServiceNetwork(to: nil)
case .networkLayer: updateNetworkLayers(to: nil)
case .debugDisappearingMessageDurations:
updateFlag(for: .debugDisappearingMessageDurations, to: nil)
case .updatedDisappearingMessages: updateFlag(for: .updatedDisappearingMessages, to: nil)
case .updatedGroups: updateFlag(for: .updatedGroups, to: nil)
case .updatedGroupsRemoveMessagesOnKick: updateFlag(for: .updatedGroupsRemoveMessagesOnKick, to: nil)
case .updatedGroupsAllowHistoricAccessOnInvite:

@ -247,32 +247,15 @@ public extension DisappearingMessagesConfiguration {
// MARK: - UI Constraints
extension DisappearingMessagesConfiguration {
// TODO: Remove this when disappearing messages V2 is up and running
public static var validDurationsSeconds: [TimeInterval] {
return [
5,
10,
30,
(1 * 60),
(5 * 60),
(30 * 60),
(1 * 60 * 60),
(6 * 60 * 60),
(12 * 60 * 60),
(24 * 60 * 60),
(7 * 24 * 60 * 60)
]
}
public static var maxDurationSeconds: TimeInterval = {
return (validDurationsSeconds.max() ?? 0)
}()
public static func validDurationsSeconds(_ type: DisappearingMessageType) -> [TimeInterval] {
public static func validDurationsSeconds(
_ type: DisappearingMessageType,
using dependencies: Dependencies
) -> [TimeInterval] {
switch type {
case .disappearAfterRead:
var result = [
return [
(dependencies[feature: .debugDisappearingMessageDurations] ? 10 : nil),
(dependencies[feature: .debugDisappearingMessageDurations] ? 60 : nil),
(5 * 60),
(1 * 60 * 60),
(12 * 60 * 60),
@ -280,35 +263,19 @@ extension DisappearingMessagesConfiguration {
(7 * 24 * 60 * 60),
(2 * 7 * 24 * 60 * 60)
]
.map { TimeInterval($0) }
#if targetEnvironment(simulator)
result.insert(
TimeInterval(60),
at: 0
)
result.insert(
TimeInterval(10),
at: 0
)
#endif
return result
.compactMap { duration in duration.map { TimeInterval($0) } }
case .disappearAfterSend:
var result = [
return [
(dependencies[feature: .debugDisappearingMessageDurations] ? 10 : nil),
(12 * 60 * 60),
(24 * 60 * 60),
(7 * 24 * 60 * 60),
(2 * 7 * 24 * 60 * 60)
]
.map { TimeInterval($0) }
#if targetEnvironment(simulator)
result.insert(
TimeInterval(10),
at: 0
)
#endif
return result
default:
return []
}
.compactMap { duration in duration.map { TimeInterval($0) } }
default: return []
}
}
}

@ -172,33 +172,6 @@ public extension Profile {
// MARK: - Protobuf
public extension Profile {
static func fromProto(_ proto: SNProtoDataMessage, id: String) -> Profile? {
guard let profileProto = proto.profile, let displayName = profileProto.displayName else { return nil }
var profileKey: Data?
var profilePictureUrl: String?
let sentTimestamp: TimeInterval = TimeInterval(proto.hasTimestamp ? (Double(proto.timestamp) / 1000) : 0)
// If we have both a `profileKey` and a `profilePicture` then the key MUST be valid
if let profileKeyData: Data = proto.profileKey, profileProto.profilePicture != nil {
profileKey = profileKeyData
profilePictureUrl = profileProto.profilePicture
}
return Profile(
id: id,
name: displayName,
lastNameUpdate: sentTimestamp,
nickname: nil,
profilePictureUrl: profilePictureUrl,
profilePictureFileName: nil,
profileEncryptionKey: profileKey,
lastProfilePictureUpdate: sentTimestamp,
blocksCommunityMessageRequests: (proto.hasBlocksCommunityMessageRequests ? proto.blocksCommunityMessageRequests : nil),
lastBlocksCommunityMessageRequests: (proto.hasBlocksCommunityMessageRequests ? sentTimestamp : nil)
)
}
func toProto() -> SNProtoDataMessage? {
let dataMessageProto = SNProtoDataMessage.builder()
let profileProto = SNProtoLokiProfile.builder()

@ -74,12 +74,12 @@ public extension Network {
switch self {
case .onionRequest:
return """
This network layer will send requests via the original Onion Request mechanism, requests will be routed between 3 service nodes before reaching their destination.
Requests will be sent via the original Onion Request mechanism, they will be routed between 3 service nodes before reaching their destination.
"""
case .direct:
return """
This network layer will send requests directly over HTTPS
Requests will be sent directly over HTTPS.
<b>Warning:</b> This network layer offers no IP protections so should only be used for debugging purposes.
"""

@ -9,6 +9,10 @@ public final class Features {
}
public extension FeatureStorage {
static let debugDisappearingMessageDurations: FeatureConfig<Bool> = Dependencies.create(
identifier: "debugDisappearingMessageDurations"
)
static let updatedDisappearingMessages: FeatureConfig<Bool> = Dependencies.create(
identifier: "updatedDisappearingMessages",
automaticChangeBehaviour: Feature<Bool>.ChangeBehaviour(
@ -53,10 +57,15 @@ public protocol FeatureOption: RawRepresentable, CaseIterable, Equatable where R
static var defaultOption: Self { get }
var isValidOption: Bool { get }
var title: String { get }
var subtitle: String? { get }
}
public extension FeatureOption {
var isValidOption: Bool { true }
}
// MARK: - FeatureEvent
public protocol FeatureEvent: Equatable, Hashable {
@ -116,7 +125,7 @@ public struct Feature<T: FeatureOption>: FeatureType {
/// If we have an explicitly set `selectedOption` then we should use that, otherwise we should check if any of the
/// `automaticChangeBehaviour` conditions have been met, and if so use the specified value
guard let selectedOption: T = maybeSelectedOption else {
guard let selectedOption: T = maybeSelectedOption, selectedOption.isValidOption else {
func automaticChangeConditionMet(_ condition: ChangeCondition) -> Bool {
switch condition {
case .after(let timestamp): return (dependencies.dateNow.timeIntervalSince1970 >= timestamp)

Loading…
Cancel
Save