Skip to content

Updated Conversation Settings - 1 #459

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 34 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
f2cf0de
update nickname setting for 1-1 convo
May 14, 2025
d190b06
implement notification settings view model
May 14, 2025
383dac9
WIP: 1-1 convo settings view model
May 14, 2025
00f7762
implement 1-1 conversation settings view model and a little bit refac…
May 15, 2025
d419a30
fix clear messages action
May 15, 2025
4f77927
implement note-to-self conversation setting view model
May 15, 2025
6315312
clean
May 15, 2025
80adea9
implement community conversation setting view model
May 15, 2025
1ff8ffc
implement non-admin group conversation settings view model
May 15, 2025
08aa8ef
WIP: implement admin group conversation settings view model
May 15, 2025
2bbcbdd
update conversation settings buttons
May 15, 2025
414e29a
clean up dual input modal for group name and description settings
May 20, 2025
37b3683
WIP: Error state for modals
May 20, 2025
50a88d7
Merge branch 'dev' into updated-convo-settings
May 20, 2025
50e6a53
add error state for modals
May 21, 2025
c14ffa9
update fallback group avatar
May 21, 2025
31e3f2c
WIP: expandable label
May 22, 2025
adaf63b
Merge branch 'dev' into updated-convo-settings
May 27, 2025
4e10431
implement view more/less for group descriptions
May 27, 2025
6fb9e48
add search functionality to SessionTableViewController
May 29, 2025
8b1e42f
Merge branch 'dev' into updated-convo-settings
Jun 1, 2025
2a2448f
clean
Jun 2, 2025
27bc079
update logic for navigation controller pop completion handler
Jun 2, 2025
fc65645
fix group convo avatar
Jun 2, 2025
1f671cf
refactor Interaction.markAsDeleted with options
Jun 2, 2025
f917fd8
fix compiling issue
Jun 2, 2025
b575bb3
Merge branch 'dev' into updated-convo-settings
Jun 3, 2025
853e7eb
Merge branch 'dev' into updated-convo-settings
Jun 3, 2025
7e82db5
update error state
Jun 3, 2025
3a834be
WIP: input error state
Jun 4, 2025
7527fe9
input error state update
Jun 4, 2025
ff4d387
update input error state for loading account
Jun 4, 2025
c7ff204
fix the UI issue on group description view more/less
Jun 5, 2025
b9bf3b2
clean
Jun 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Session.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@
945D9C582D6FDBE7003C4C0C /* _005_AddJobUniqueHash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 945D9C572D6FDBE7003C4C0C /* _005_AddJobUniqueHash.swift */; };
946F5A732D5DA3AC00A5ADCE /* Punycode in Frameworks */ = {isa = PBXBuildFile; productRef = 946F5A722D5DA3AC00A5ADCE /* Punycode */; };
9473386E2BDF5F3E00B9E169 /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 9473386D2BDF5F3E00B9E169 /* InfoPlist.xcstrings */; };
9479981C2DD44ADC008F5CD5 /* ThreadNotificationSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9479981B2DD44AC5008F5CD5 /* ThreadNotificationSettingsViewModel.swift */; };
9499E6032DDD9BF900091434 /* ExpandableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9499E6022DDD9BEE00091434 /* ExpandableLabel.swift */; };
947D7FD42D509FC900E8E413 /* SessionNetworkAPI+Models.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FD12D509FC900E8E413 /* SessionNetworkAPI+Models.swift */; };
947D7FD62D509FC900E8E413 /* SessionNetworkAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FCF2D509FC900E8E413 /* SessionNetworkAPI.swift */; };
947D7FD72D509FC900E8E413 /* SessionNetworkAPI+Network.swift in Sources */ = {isa = PBXBuildFile; fileRef = 947D7FD22D509FC900E8E413 /* SessionNetworkAPI+Network.swift */; };
Expand Down Expand Up @@ -1479,7 +1481,9 @@
945D9C572D6FDBE7003C4C0C /* _005_AddJobUniqueHash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _005_AddJobUniqueHash.swift; sourceTree = "<group>"; };
9471CAA72CACFB4E00090FB7 /* GenerateLicenses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateLicenses.swift; sourceTree = "<group>"; };
9473386D2BDF5F3E00B9E169 /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = "<group>"; };
9479981B2DD44AC5008F5CD5 /* ThreadNotificationSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadNotificationSettingsViewModel.swift; sourceTree = "<group>"; };
947AD68F2C8968FF000B2730 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
9499E6022DDD9BEE00091434 /* ExpandableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpandableLabel.swift; sourceTree = "<group>"; };
947D7FCF2D509FC900E8E413 /* SessionNetworkAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionNetworkAPI.swift; sourceTree = "<group>"; };
947D7FD02D509FC900E8E413 /* SessionNetworkAPI+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionNetworkAPI+Database.swift"; sourceTree = "<group>"; };
947D7FD12D509FC900E8E413 /* SessionNetworkAPI+Models.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionNetworkAPI+Models.swift"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3020,6 +3024,7 @@
children = (
B84A89BB25DE328A0040017D /* ProfilePictureVC.swift */,
FD7115EA28C5D78E00B47552 /* ThreadSettingsViewModel.swift */,
9479981B2DD44AC5008F5CD5 /* ThreadNotificationSettingsViewModel.swift */,
FD7115F328C71EB200B47552 /* ThreadDisappearingMessagesSettingsViewModel.swift */,
);
path = Settings;
Expand Down Expand Up @@ -3181,6 +3186,7 @@
C38EF2A3255B6D93007E1867 /* PlaceholderIcon.swift */,
C38EF2A4255B6D93007E1867 /* ProfilePictureView.swift */,
FD6673FC2D77F54400041530 /* ScreenLockViewController.swift */,
9499E6022DDD9BEE00091434 /* ExpandableLabel.swift */,
FD0150572CA27DEE005B08A1 /* ScrollableLabel.swift */,
7B8914762A7CAAE200A4C627 /* SessionHostingViewController.swift */,
FDA335F42D911576007E0EB6 /* SessionImageView.swift */,
Expand Down Expand Up @@ -5861,6 +5867,7 @@
9422EE2B2B8C3A97004C740D /* String+Utilities.swift in Sources */,
FD37EA0128A60473003AE748 /* UIKit+Theme.swift in Sources */,
FD37E9CF28A1EB1B003AE748 /* Theme.swift in Sources */,
9499E6032DDD9BF900091434 /* ExpandableLabel.swift in Sources */,
FDE754BA2C9B97B8002A2623 /* UIDevice+Utilities.swift in Sources */,
C331FFB92558FA8D00070591 /* UIView+Constraints.swift in Sources */,
FD0B77B029B69A65009169BA /* TopBannerController.swift in Sources */,
Expand Down Expand Up @@ -6576,6 +6583,7 @@
4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */,
C328253025CA55370062D0A7 /* ContextMenuWindow.swift in Sources */,
FDEF57242C3CF04700131302 /* (null) in Sources */,
9479981C2DD44ADC008F5CD5 /* ThreadNotificationSettingsViewModel.swift in Sources */,
34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */,
9422568C2C23F8C800C0FDBF /* DisplayNameScreen.swift in Sources */,
B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */,
Expand Down
10 changes: 5 additions & 5 deletions Session/Conversations/ConversationVC+Interaction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2113,11 +2113,11 @@ extension ConversationVC:
explanation: NSAttributedString(string: deletionBehaviours.body),
warning: deletionBehaviours.warning.map { NSAttributedString(string: $0) },
options: deletionBehaviours.actions.map { action in
(
action.title,
action.state != .disabled,
action.state == .enabledAndDefaultSelected,
action.accessibility
ConfirmationModal.Info.Body.RadioOptionInfo(
title: action.title,
enabled: action.state != .disabled,
selected: action.state == .enabledAndDefaultSelected,
accessibility: action.accessibility
)
}
),
Expand Down
80 changes: 36 additions & 44 deletions Session/Conversations/ConversationVC.swift
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,9 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa
viewModel.threadData.threadIsBlocked != updatedThreadData.threadIsBlocked ||
viewModel.threadData.threadIsMessageRequest != updatedThreadData.threadIsMessageRequest ||
viewModel.threadData.threadRequiresApproval != updatedThreadData.threadRequiresApproval ||
viewModel.threadData.profile != updatedThreadData.profile
viewModel.threadData.profile != updatedThreadData.profile ||
viewModel.threadData.additionalProfile != updatedThreadData.additionalProfile ||
viewModel.threadData.displayPictureFilename != updatedThreadData.displayPictureFilename
{
updateNavBarButtons(
threadData: updatedThreadData,
Expand Down Expand Up @@ -1371,51 +1373,41 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa
return
}

switch threadData.threadVariant {
case .contact:
let profilePictureView = ProfilePictureView(
size: .navigation,
dataManager: viewModel.dependencies[singleton: .imageDataManager]
)
profilePictureView.update(
publicKey: threadData.threadId, // Contact thread uses the contactId
threadVariant: threadData.threadVariant,
displayPictureFilename: nil,
profile: threadData.profile,
additionalProfile: nil,
using: viewModel.dependencies
)
profilePictureView.customWidth = (44 - 16) // Width of the standard back button
let profilePictureView = ProfilePictureView(
size: .navigation,
dataManager: viewModel.dependencies[singleton: .imageDataManager]
)
profilePictureView.update(
publicKey: threadData.threadId, // Contact thread uses the contactId
threadVariant: threadData.threadVariant,
displayPictureFilename: threadData.displayPictureFilename,
profile: threadData.profile,
additionalProfile: threadData.additionalProfile,
using: viewModel.dependencies
)
profilePictureView.customWidth = (44 - 16) // Width of the standard back button

let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openSettings))
profilePictureView.addGestureRecognizer(tapGestureRecognizer)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(openSettings))
profilePictureView.addGestureRecognizer(tapGestureRecognizer)

let settingsButtonItem: UIBarButtonItem = UIBarButtonItem(customView: profilePictureView)
settingsButtonItem.accessibilityLabel = "More options"
settingsButtonItem.isAccessibilityElement = true

if shouldHaveCallButton {
let callButton = UIBarButtonItem(
image: UIImage(named: "Phone"),
style: .plain,
target: self,
action: #selector(startCall)
)
callButton.accessibilityLabel = "Call"
callButton.isAccessibilityElement = true

navigationItem.rightBarButtonItems = [settingsButtonItem, callButton]
}
else {
navigationItem.rightBarButtonItems = [settingsButtonItem]
}

default:
let rightBarButtonItem: UIBarButtonItem = UIBarButtonItem(image: UIImage(named: "Gear"), style: .plain, target: self, action: #selector(openSettings))
rightBarButtonItem.accessibilityLabel = "More options"
rightBarButtonItem.isAccessibilityElement = true

navigationItem.rightBarButtonItems = [rightBarButtonItem]
let settingsButtonItem: UIBarButtonItem = UIBarButtonItem(customView: profilePictureView)
settingsButtonItem.accessibilityLabel = "More options"
settingsButtonItem.isAccessibilityElement = true

if shouldHaveCallButton {
let callButton = UIBarButtonItem(
image: UIImage(named: "Phone"),
style: .plain,
target: self,
action: #selector(startCall)
)
callButton.accessibilityLabel = "Call"
callButton.isAccessibilityElement = true

navigationItem.rightBarButtonItems = [settingsButtonItem, callButton]
}
else {
navigationItem.rightBarButtonItems = [settingsButtonItem]
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import SessionMessagingKit
import SessionUtilitiesKit
import SessionSnodeKit

class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel, NavigationItemSource, NavigatableStateHolder, ObservableTableSource {
class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTableSource {
typealias TableItem = String

public let dependencies: Dependencies
Expand Down Expand Up @@ -47,10 +47,6 @@ class ThreadDisappearingMessagesSettingsViewModel: SessionTableViewModel, Naviga

// MARK: - Config

enum NavItem: Equatable {
case save
}

public enum Section: SessionTableSection {
case type
case timerLegacy
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved.

import Foundation
import Combine
import GRDB
import DifferenceKit
import SessionUIKit
import SessionMessagingKit
import SessionUtilitiesKit
import SessionSnodeKit

class ThreadNotificationSettingsViewModel: SessionTableViewModel, NavigatableStateHolder, ObservableTableSource {
public let dependencies: Dependencies
public let navigatableState: NavigatableState = NavigatableState()
public let state: TableDataState<Section, TableItem> = TableDataState()
public let observableState: ObservableTableSourceState<Section, TableItem> = ObservableTableSourceState()

private let threadViewModel: SessionThreadViewModel
private var threadViewModelSubject: CurrentValueSubject<SessionThreadViewModel, Never>

// MARK: - Initialization

init(
threadViewModel: SessionThreadViewModel,
using dependencies: Dependencies
) {
self.dependencies = dependencies
self.threadViewModel = threadViewModel
self.threadViewModelSubject = CurrentValueSubject(threadViewModel)
}

// MARK: - Config

public enum Section: SessionTableSection {
case type
}

public enum TableItem: Differentiable {
case allMessages
case mentionsOnly
case mute
}

// MARK: - Content

let title: String = "sessionNotifications".localized()

lazy var footerButtonInfo: AnyPublisher<SessionButton.Info?, Never> = threadViewModelSubject
.map { [threadViewModel] updatedThreadViewModel -> Bool in
// Need to explicitly compare values because 'lastChangeTimestampMs' will differ
return (
updatedThreadViewModel.threadOnlyNotifyForMentions != threadViewModel.threadOnlyNotifyForMentions ||
updatedThreadViewModel.threadMutedUntilTimestamp != threadViewModel.threadMutedUntilTimestamp
)
}
.removeDuplicates()
.map { [weak self] shouldShowConfirmButton -> SessionButton.Info? in
guard shouldShowConfirmButton else { return nil }

return SessionButton.Info(
style: .bordered,
title: "set".localized(),
isEnabled: true,
accessibility: Accessibility(
identifier: "Set button",
label: "Set button"
),
minWidth: 110,
onTap: {
self?.saveChanges()
self?.dismissScreen()
}
)
}
.eraseToAnyPublisher()

lazy var observation: TargetObservation = ObservationBuilder
.subject(threadViewModelSubject)
.compactMap { [weak self] threadViewModel -> [SectionModel]? in self?.content(threadViewModel) }

private func content(_ threadViewModel: SessionThreadViewModel) -> [SectionModel] {
let notificationTypeSection = SectionModel(
model: .type,
elements: [
SessionCell.Info(
id: .allMessages,
leadingAccessory: .icon(.volume2),
title: "notificationsAllMessages".localized(),
trailingAccessory: .radio(
isSelected: (threadViewModel.threadOnlyNotifyForMentions != true && threadViewModel.threadMutedUntilTimestamp == nil),
accessibility: Accessibility(
identifier: "All messages - Radio"
)
),
accessibility: Accessibility(
identifier: "All messages notification setting",
label: "All messages"
),
onTap: { [weak self] in
self?.threadViewModelSubject.send(
threadViewModel.with(
threadMutedUntilTimestamp: nil
).with(
threadOnlyNotifyForMentions: false
)
)
}
),

SessionCell.Info(
id: .mentionsOnly,
leadingAccessory: .icon(.atSign),
title: "notificationsMentionsOnly".localized(),
trailingAccessory: .radio(
isSelected: (threadViewModel.threadOnlyNotifyForMentions == true),
accessibility: Accessibility(
identifier: "Mentions only - Radio"
)
),
accessibility: Accessibility(
identifier: "Mentions only notification setting",
label: "Mentions only"
),
onTap: { [weak self] in
self?.threadViewModelSubject.send(
threadViewModel.with(
threadMutedUntilTimestamp: nil
).with(
threadOnlyNotifyForMentions: true
)
)
}
),

SessionCell.Info(
id: .mute,
leadingAccessory: .icon(.volumeOff),
title: "notificationsMute".localized(),
trailingAccessory: .radio(
isSelected: (threadViewModel.threadMutedUntilTimestamp != nil),
accessibility: Accessibility(
identifier: "Mute - Radio"
)
),
accessibility: Accessibility(
identifier: "\(ThreadSettingsViewModel.self).mute",
label: "Mute notifications"
),
onTap: { [weak self] in
self?.threadViewModelSubject.send(
threadViewModel.with(
threadMutedUntilTimestamp: Date.distantFuture.timeIntervalSince1970
).with(
threadOnlyNotifyForMentions: false
)
)
}
)
].compactMap { $0 }
)

return [notificationTypeSection]
}

// MARK: - Functions

private func saveChanges() {
let threadViewModel: SessionThreadViewModel = self.threadViewModelSubject.value

guard self.threadViewModel != threadViewModel else { return }

dependencies[singleton: .storage].writeAsync { db in
try SessionThread
.filter(id: threadViewModel.threadId)
.updateAll(
db,
SessionThread.Columns.onlyNotifyForMentions
.set(to: threadViewModel.threadOnlyNotifyForMentions),
SessionThread.Columns.mutedUntilTimestamp
.set(to: threadViewModel.threadMutedUntilTimestamp)
)
}
}
}
Loading