Skip to content

Commit b87189f

Browse files
committed
Added navigation bar dropdown option
1 parent 20b41b3 commit b87189f

File tree

6 files changed

+177
-20
lines changed

6 files changed

+177
-20
lines changed

Session.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1174,6 +1174,8 @@
11741174
FDFE75B52ABD46B700655640 /* MockUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD83B9D127D59495005E1583 /* MockUserDefaults.swift */; };
11751175
FDFF9FDF2A787F57005E0628 /* JSONEncoder+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FDFF9FDE2A787F57005E0628 /* JSONEncoder+Utilities.swift */; };
11761176
FE2883272EA70C640097E240 /* MessageSelectionManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE2883262EA70C640097E240 /* MessageSelectionManager.swift */; };
1177+
FE28832B2EA74D440097E240 /* ManualDropdownPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE28832A2EA74D440097E240 /* ManualDropdownPresenter.swift */; };
1178+
FE28832D2EA74D680097E240 /* CustomMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE28832C2EA74D680097E240 /* CustomMenuView.swift */; };
11771179
FED288F32E4C28CF00C31171 /* AppReviewPromptDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = FED288F22E4C28CF00C31171 /* AppReviewPromptDialog.swift */; };
11781180
FED288F82E4C3BE100C31171 /* AppReviewPromptModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FED288F72E4C3BE100C31171 /* AppReviewPromptModel.swift */; };
11791181
/* End PBXBuildFile section */
@@ -2469,6 +2471,8 @@
24692471
FDFE75B02ABD2D2400655640 /* _030_MakeBrokenProfileTimestampsNullable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _030_MakeBrokenProfileTimestampsNullable.swift; sourceTree = "<group>"; };
24702472
FDFF9FDE2A787F57005E0628 /* JSONEncoder+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JSONEncoder+Utilities.swift"; sourceTree = "<group>"; };
24712473
FE2883262EA70C640097E240 /* MessageSelectionManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSelectionManager.swift; sourceTree = "<group>"; };
2474+
FE28832A2EA74D440097E240 /* ManualDropdownPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManualDropdownPresenter.swift; sourceTree = "<group>"; };
2475+
FE28832C2EA74D680097E240 /* CustomMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomMenuView.swift; sourceTree = "<group>"; };
24722476
FED288F22E4C28CF00C31171 /* AppReviewPromptDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReviewPromptDialog.swift; sourceTree = "<group>"; };
24732477
FED288F72E4C3BE100C31171 /* AppReviewPromptModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReviewPromptModel.swift; sourceTree = "<group>"; };
24742478
/* End PBXFileReference section */
@@ -5306,6 +5310,8 @@
53065310
FE2883252EA70C5D0097E240 /* Selection */ = {
53075311
isa = PBXGroup;
53085312
children = (
5313+
FE28832C2EA74D680097E240 /* CustomMenuView.swift */,
5314+
FE28832A2EA74D440097E240 /* ManualDropdownPresenter.swift */,
53095315
FE2883262EA70C640097E240 /* MessageSelectionManager.swift */,
53105316
);
53115317
path = Selection;
@@ -7073,6 +7079,7 @@
70737079
34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */,
70747080
9422568C2C23F8C800C0FDBF /* DisplayNameScreen.swift in Sources */,
70757081
7B9F71D72853100A006DFE7B /* Emoji+Available.swift in Sources */,
7082+
FE28832D2EA74D680097E240 /* CustomMenuView.swift in Sources */,
70767083
FD09C5E628260FF9000CE219 /* MediaGalleryViewModel.swift in Sources */,
70777084
7B9F71D32852EEE2006DFE7B /* Emoji.swift in Sources */,
70787085
C328250F25CA06020062D0A7 /* VoiceMessageView.swift in Sources */,
@@ -7084,6 +7091,7 @@
70847091
FD4B200E283492210034334B /* InsetLockableTableView.swift in Sources */,
70857092
FD12A8472AD63C3400EEBA0D /* PagedObservationSource.swift in Sources */,
70867093
FDC1BD682CFE6EEB002CDC71 /* DeveloperSettingsViewModel.swift in Sources */,
7094+
FE28832B2EA74D440097E240 /* ManualDropdownPresenter.swift in Sources */,
70877095
7B0EFDF0275084AA00FFAAE7 /* CallMessageCell.swift in Sources */,
70887096
C3AAFFF225AE99710089E6DD /* AppDelegate.swift in Sources */,
70897097
FD10AF0C2AF32B9A007709E5 /* SessionListViewModel.swift in Sources */,

Session/Conversations/ConversationVC+Interaction.swift

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3585,17 +3585,28 @@ extension ConversationVC: SelectionManagerDelegate {
35853585
}
35863586

35873587
func showInfo(for message: MessageViewModel, withSender sender: UIBarButtonItem) {
3588-
/*guard
3589-
let actions = ContextMenuVC.navigationActions(
3590-
for: message,
3591-
in: viewModel.threadData,
3592-
delegate: self,
3593-
using: viewModel.dependencies
3594-
)
3595-
else { return }*/
3588+
if dropdownPresenter != nil {
3589+
dropdownPresenter?.hide()
3590+
dropdownPresenter = nil
3591+
}
3592+
3593+
let actions = ContextMenuVC.navigationActions(
3594+
for: message,
3595+
in: viewModel.threadData,
3596+
delegate: self,
3597+
using: viewModel.dependencies
3598+
) ?? []
35963599

3597-
// TODO: - Show context menu below navigationbar for now navigate to info page
3598-
info(message)
3599-
resetSelection()
3600+
let presenter = ManualDropdownPresenter()
3601+
self.dropdownPresenter = presenter
3602+
3603+
presenter.show(
3604+
actions: actions,
3605+
anchorView: navigationController?.navigationBar.subviews.first,
3606+
using: viewModel.dependencies
3607+
) { [weak self] in
3608+
self?.dropdownPresenter = nil
3609+
self?.resetSelection()
3610+
}
36003611
}
36013612
}

Session/Conversations/ConversationVC.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ final class ConversationVC: BaseVC, LibSessionRespondingViewController, Conversa
124124
delegate: self
125125
)
126126

127+
// Reference to dropdown view
128+
var dropdownPresenter: ManualDropdownPresenter?
129+
130+
// Search
127131
lazy var searchController: ConversationSearchController = {
128132
let result: ConversationSearchController = ConversationSearchController(
129133
threadId: self.viewModel.threadData.threadId
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved.
2+
3+
import UIKit
4+
import SessionUtilitiesKit
5+
import SessionUIKit
6+
7+
class CustomMenuView: UIView {
8+
private lazy var stackView: UIStackView = {
9+
let result = UIStackView()
10+
result.axis = .vertical
11+
result.spacing = 0
12+
result.distribution = .fillEqually
13+
result.translatesAutoresizingMaskIntoConstraints = false
14+
return result
15+
}()
16+
17+
init() {
18+
super.init(frame: .zero)
19+
setupView()
20+
}
21+
22+
required init?(coder: NSCoder) {
23+
fatalError("init(coder:) has not been implemented")
24+
}
25+
26+
private func setupView() {
27+
self.themeBackgroundColor = .contextMenu_background
28+
self.layer.cornerRadius = 8
29+
self.clipsToBounds = true
30+
31+
addSubview(stackView)
32+
33+
NSLayoutConstraint.activate([
34+
stackView.topAnchor.constraint(equalTo: self.topAnchor),
35+
stackView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
36+
stackView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
37+
stackView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
38+
])
39+
}
40+
41+
func createMenuButtons(_ actions: [ContextMenuVC.Action], using dependencies: Dependencies, dismiss: @escaping () -> Void) {
42+
actions.forEach { action in
43+
let item = ContextMenuVC.ActionView(
44+
for: action,
45+
using: dependencies,
46+
dismiss: dismiss
47+
)
48+
stackView.addArrangedSubview(item)
49+
}
50+
51+
let buttonHeight: CGFloat = 42.0
52+
let menuWidth: CGFloat = 180.0
53+
let totalHeight = buttonHeight * CGFloat(actions.count)
54+
55+
self.frame = CGRect(origin: .zero, size: CGSize(width: menuWidth, height: totalHeight))
56+
}
57+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright © 2025 Rangeproof Pty Ltd. All rights reserved.
2+
3+
import UIKit
4+
import SessionUtilitiesKit
5+
6+
class ManualDropdownPresenter: NSObject {
7+
private lazy var menuView: CustomMenuView = {
8+
let result = CustomMenuView()
9+
result.layer.shadowOffset = CGSize.zero
10+
result.layer.shadowOpacity = 0.4
11+
result.layer.shadowRadius = 4
12+
return result
13+
}()
14+
15+
private lazy var overlayView: UIView = {
16+
let result = UIView()
17+
result.backgroundColor = .black.withAlphaComponent(0.01)
18+
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(hide))
19+
result.addGestureRecognizer(tapGesture)
20+
return result
21+
}()
22+
23+
private weak var presentingViewController: UIViewController?
24+
25+
@MainActor
26+
func show(actions: [ContextMenuVC.Action], anchorView: UIView?, using dependencies: Dependencies, completion: @escaping () -> Void) {
27+
guard
28+
let topViewController = dependencies[singleton: .appContext].frontMostViewController,
29+
let barButtonView = anchorView
30+
else {
31+
return
32+
}
33+
34+
menuView.createMenuButtons(
35+
actions,
36+
using: dependencies
37+
) { [weak self] in
38+
self?.menuView.removeFromSuperview()
39+
self?.overlayView.removeFromSuperview()
40+
41+
completion()
42+
}
43+
44+
self.presentingViewController = topViewController
45+
46+
guard let targetView = topViewController.view else {
47+
return
48+
}
49+
50+
overlayView.frame = targetView.bounds
51+
targetView.addSubview(overlayView)
52+
53+
targetView.addSubview(menuView)
54+
55+
let buttonFrame = barButtonView.convert(barButtonView.bounds, to: targetView)
56+
57+
let menuX = buttonFrame.maxX - menuView.frame.width
58+
let menuY = buttonFrame.maxY + 5
59+
60+
menuView.frame.origin = CGPoint(x: menuX, y: menuY)
61+
menuView.alpha = 0
62+
menuView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
63+
64+
UIView.animate(withDuration: 0.2) {
65+
self.menuView.alpha = 1
66+
self.menuView.transform = .identity
67+
}
68+
}
69+
70+
@objc func hide() {
71+
UIView.animate(withDuration: 0.2, animations: {
72+
self.menuView.alpha = 0
73+
self.menuView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
74+
}) { _ in
75+
self.menuView.removeFromSuperview()
76+
self.overlayView.removeFromSuperview()
77+
78+
self.presentingViewController = nil
79+
}
80+
}
81+
}

Session/Conversations/Selection/MessageSelectionManager.swift

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,15 @@ class MessageSelectionManager: NSObject {
2424
}
2525

2626
private func onButtonCreation(icon: UIImage?, accessibilityLabel: String, action: Selector) -> UIBarButtonItem? {
27-
let item = UIBarButtonItem(
27+
let result = UIBarButtonItem(
2828
image: icon,
2929
style: .plain,
3030
target: self,
3131
action: action
3232
)
33-
item.accessibilityLabel = accessibilityLabel
34-
item.isAccessibilityElement = true
35-
return item
33+
result.accessibilityLabel = accessibilityLabel
34+
result.isAccessibilityElement = true
35+
return result
3636
}
3737

3838
@MainActor
@@ -63,6 +63,7 @@ class MessageSelectionManager: NSObject {
6363
accessibilityLabel: "More",
6464
action: #selector(moreOptions)
6565
)
66+
moreButtonItem?.tag = 99
6667

6768
var showDownload: Bool {
6869
guard
@@ -145,11 +146,6 @@ class MessageSelectionManager: NSObject {
145146
guard let selectedMessage = selectedMessages.first else {
146147
return
147148
}
148-
149-
// TODO: Show drop down instead
150149
delegate?.showInfo(for: selectedMessage, withSender: sender)
151-
// delegate?.info(selectedMessage)
152-
//
153-
// delegate?.shouldResetSelectionState()
154150
}
155151
}

0 commit comments

Comments
 (0)