Skip to content

Commit f658896

Browse files
committed
WIP reply
1 parent b4c1b06 commit f658896

File tree

13 files changed

+1414
-207
lines changed

13 files changed

+1414
-207
lines changed

apple/InlineIOS/Chat/ChatView.swift

+94-40
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ class ChatContainerView: UIView {
1212
private let contentView = UIView()
1313
private let messagesView: MessagesCollectionView
1414
private let composeView = ComposeView()
15+
private let topComposeView: UIHostingController<TopComposeView>
16+
private let composeStack: UIStackView = {
17+
let stack = UIStackView()
18+
stack.axis = .vertical
19+
stack.spacing = 0
20+
stack.alignment = .fill
21+
return stack
22+
}()
23+
1524
private let text: Binding<String>
1625
var onSendMessage: (() -> Void)?
1726

@@ -20,42 +29,23 @@ class ChatContainerView: UIView {
2029
init(frame: CGRect, fullMessages: [FullMessage], text: Binding<String>) {
2130
self.text = text
2231
self.messagesView = MessagesCollectionView(fullMessages: fullMessages)
32+
self.topComposeView = UIHostingController(
33+
rootView: TopComposeView(replyingMessageId: ChatState.shared.replyingMessageId ?? 0)
34+
)
2335

2436
super.init(frame: frame)
2537

2638
setupViews()
27-
28-
composeView.onTextChange = { [weak self] newText in
29-
30-
self?.text.wrappedValue = newText
31-
}
32-
composeView.onSend = { [weak self] in
33-
print("Send called")
34-
self?.onSendMessage?()
35-
}
36-
37-
composeView.text = text.wrappedValue
38-
39-
contentView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
40-
contentView.setContentHuggingPriority(.defaultLow, for: .horizontal)
39+
setupChatStateObserver()
40+
setupComposeView()
4141
}
4242

4343
@available(*, unavailable)
4444
required init?(coder: NSCoder) {
4545
fatalError("init(coder:) has not been implemented")
4646
}
4747

48-
private var didSetupConstraints = false
49-
50-
override func didMoveToWindow() {
51-
super.didMoveToWindow()
52-
53-
// Only setup once when we have a valid window/hierarchy
54-
guard !didSetupConstraints else { return }
55-
didSetupConstraints = true
56-
57-
setupViews()
58-
}
48+
// MARK: - Setup Methods
5949

6050
private func setupViews() {
6151
// Add contentView to main view
@@ -64,12 +54,17 @@ class ChatContainerView: UIView {
6454

6555
// Add views to contentView
6656
contentView.addSubview(messagesView)
67-
contentView.addSubview(composeView)
57+
contentView.addSubview(composeStack)
58+
59+
// Setup compose stack
60+
composeStack.translatesAutoresizingMaskIntoConstraints = false
61+
composeStack.addArrangedSubview(topComposeView.view)
62+
composeStack.addArrangedSubview(composeView)
6863

6964
messagesView.translatesAutoresizingMaskIntoConstraints = false
65+
topComposeView.view.translatesAutoresizingMaskIntoConstraints = false
7066
composeView.translatesAutoresizingMaskIntoConstraints = false
7167

72-
// Setup constraints
7368
NSLayoutConstraint.activate([
7469
// ContentView constraints
7570
contentView.topAnchor.constraint(equalTo: topAnchor),
@@ -81,24 +76,80 @@ class ChatContainerView: UIView {
8176
messagesView.topAnchor.constraint(equalTo: contentView.topAnchor),
8277
messagesView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
8378
messagesView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
84-
messagesView.bottomAnchor.constraint(equalTo: composeView.topAnchor),
79+
messagesView.bottomAnchor.constraint(equalTo: composeStack.topAnchor),
8580

86-
// ComposeView constraints
87-
composeView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
88-
composeView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
89-
composeView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
81+
// ComposeStack constraints
82+
composeStack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
83+
composeStack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
84+
composeStack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
85+
86+
// Fixed height for topComposeView
87+
topComposeView.view.heightAnchor.constraint(equalToConstant: 58)
9088
])
89+
90+
// Initial state
91+
topComposeView.view.isHidden = ChatState.shared.replyingMessageId == nil
9192
}
9293

93-
override func layoutSubviews() {
94-
super.layoutSubviews()
95-
print("ContentView frame: \(contentView.frame)")
96-
print("Compose frame: \(composeView.frame)")
94+
private func setupComposeView() {
95+
composeView.onTextChange = { [weak self] newText in
96+
self?.text.wrappedValue = newText
97+
}
98+
composeView.onSend = { [weak self] in
99+
self?.onSendMessage?()
100+
}
101+
composeView.text = text.wrappedValue
102+
}
103+
104+
private func setupChatStateObserver() {
105+
Task { @MainActor in
106+
for await _ in ChatState.shared.$replyingMessageId.values {
107+
updateTopComposeView()
108+
}
109+
}
110+
}
111+
112+
// MARK: - Update Methods
113+
114+
private func updateTopComposeView() {
115+
if let replyingId = ChatState.shared.replyingMessageId {
116+
print("Showing reply view for message: \(replyingId)")
117+
topComposeView.rootView = TopComposeView(replyingMessageId: replyingId)
118+
119+
if topComposeView.view.isHidden {
120+
topComposeView.view.alpha = 0
121+
topComposeView.view.isHidden = false
122+
123+
UIView.animate(withDuration: 0.3) {
124+
self.topComposeView.view.alpha = 1
125+
self.layoutIfNeeded()
126+
}
127+
}
128+
} else {
129+
print("Hiding reply view")
130+
guard !topComposeView.view.isHidden else { return }
131+
132+
UIView.animate(withDuration: 0.3) {
133+
self.topComposeView.view.alpha = 0
134+
} completion: { _ in
135+
self.topComposeView.view.isHidden = true
136+
self.layoutIfNeeded()
137+
}
138+
}
97139
}
98140

99141
func updateMessages(_ messages: [FullMessage]) {
100142
messagesView.updateMessages(messages)
101143
}
144+
145+
// MARK: - Layout
146+
147+
override func layoutSubviews() {
148+
super.layoutSubviews()
149+
print("ContentView frame: \(contentView.frame)")
150+
print("TopComposeView frame: \(topComposeView.view.frame)")
151+
print("Compose frame: \(composeView.frame)")
152+
}
102153
}
103154

104155
// MARK: - ChatContainerViewRepresentable (SwiftUI Bridge)
@@ -107,7 +158,6 @@ struct ChatContainerViewRepresentable: UIViewRepresentable {
107158
var fullMessages: [FullMessage]
108159
var text: Binding<String>
109160
var onSendMessage: () -> Void
110-
111161
func makeUIView(context: Context) -> ChatContainerView {
112162
let view = ChatContainerView(
113163
frame: .zero,
@@ -145,12 +195,12 @@ struct ChatContainerViewRepresentable: UIViewRepresentable {
145195

146196
struct ChatView: View {
147197
var peer: Peer
148-
149198
@State var text: String = ""
150199

151200
@EnvironmentStateObject var fullChatViewModel: FullChatViewModel
152201
@EnvironmentObject var nav: Navigation
153202
@EnvironmentObject var dataManager: DataManager
203+
154204
@Environment(\.appDatabase) var database
155205
@Environment(\.scenePhase) var scenePhase
156206

@@ -244,9 +294,11 @@ struct ChatView: View {
244294
peerThreadId: peerThreadId,
245295
chatId: chatId,
246296
out: true,
247-
status: .sending
297+
status: .sending,
298+
repliedToMessageId: ChatState.shared.replyingMessageId
248299
)
249300

301+
print("Sending message with repliedToMessageId: \(message.repliedToMessageId) \(message)")
250302
// Save message to database
251303
try await database.dbWriter.write { db in
252304
try message.save(db)
@@ -259,8 +311,10 @@ struct ChatView: View {
259311
peerThreadId: peerThreadId,
260312
text: messageText,
261313
peerId: peer,
262-
randomId: randomId
314+
randomId: randomId,
315+
repliedToMessageId: message.repliedToMessageId
263316
)
317+
ChatState.shared.clearReplyingMessageId()
264318
} catch {
265319
Log.shared.error("Failed to send message", error: error)
266320
}

apple/InlineIOS/Chat/ComposeView.swift

+3-1
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,9 @@ final class OptimizedTextStorage: NSTextStorage {
180180

181181
override var string: String { storage.string }
182182

183-
override func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [NSAttributedString.Key: Any] {
183+
override func attributes(at location: Int, effectiveRange range: NSRangePointer?)
184+
-> [NSAttributedString.Key: Any]
185+
{
184186
storage.attributes(at: location, effectiveRange: range)
185187
}
186188

0 commit comments

Comments
 (0)