@@ -12,6 +12,15 @@ class ChatContainerView: UIView {
12
12
private let contentView = UIView ( )
13
13
private let messagesView : MessagesCollectionView
14
14
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
+
15
24
private let text : Binding < String >
16
25
var onSendMessage : ( ( ) -> Void ) ?
17
26
@@ -20,42 +29,23 @@ class ChatContainerView: UIView {
20
29
init ( frame: CGRect , fullMessages: [ FullMessage ] , text: Binding < String > ) {
21
30
self . text = text
22
31
self . messagesView = MessagesCollectionView ( fullMessages: fullMessages)
32
+ self . topComposeView = UIHostingController (
33
+ rootView: TopComposeView ( replyingMessageId: ChatState . shared. replyingMessageId ?? 0 )
34
+ )
23
35
24
36
super. init ( frame: frame)
25
37
26
38
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 ( )
41
41
}
42
42
43
43
@available ( * , unavailable)
44
44
required init ? ( coder: NSCoder ) {
45
45
fatalError ( " init(coder:) has not been implemented " )
46
46
}
47
47
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
59
49
60
50
private func setupViews( ) {
61
51
// Add contentView to main view
@@ -64,12 +54,17 @@ class ChatContainerView: UIView {
64
54
65
55
// Add views to contentView
66
56
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)
68
63
69
64
messagesView. translatesAutoresizingMaskIntoConstraints = false
65
+ topComposeView. view. translatesAutoresizingMaskIntoConstraints = false
70
66
composeView. translatesAutoresizingMaskIntoConstraints = false
71
67
72
- // Setup constraints
73
68
NSLayoutConstraint . activate ( [
74
69
// ContentView constraints
75
70
contentView. topAnchor. constraint ( equalTo: topAnchor) ,
@@ -81,24 +76,80 @@ class ChatContainerView: UIView {
81
76
messagesView. topAnchor. constraint ( equalTo: contentView. topAnchor) ,
82
77
messagesView. leadingAnchor. constraint ( equalTo: contentView. leadingAnchor) ,
83
78
messagesView. trailingAnchor. constraint ( equalTo: contentView. trailingAnchor) ,
84
- messagesView. bottomAnchor. constraint ( equalTo: composeView . topAnchor) ,
79
+ messagesView. bottomAnchor. constraint ( equalTo: composeStack . topAnchor) ,
85
80
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 )
90
88
] )
89
+
90
+ // Initial state
91
+ topComposeView. view. isHidden = ChatState . shared. replyingMessageId == nil
91
92
}
92
93
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
+ }
97
139
}
98
140
99
141
func updateMessages( _ messages: [ FullMessage ] ) {
100
142
messagesView. updateMessages ( messages)
101
143
}
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
+ }
102
153
}
103
154
104
155
// MARK: - ChatContainerViewRepresentable (SwiftUI Bridge)
@@ -107,7 +158,6 @@ struct ChatContainerViewRepresentable: UIViewRepresentable {
107
158
var fullMessages : [ FullMessage ]
108
159
var text : Binding < String >
109
160
var onSendMessage : ( ) -> Void
110
-
111
161
func makeUIView( context: Context ) -> ChatContainerView {
112
162
let view = ChatContainerView (
113
163
frame: . zero,
@@ -145,12 +195,12 @@ struct ChatContainerViewRepresentable: UIViewRepresentable {
145
195
146
196
struct ChatView : View {
147
197
var peer : Peer
148
-
149
198
@State var text : String = " "
150
199
151
200
@EnvironmentStateObject var fullChatViewModel : FullChatViewModel
152
201
@EnvironmentObject var nav : Navigation
153
202
@EnvironmentObject var dataManager : DataManager
203
+
154
204
@Environment ( \. appDatabase) var database
155
205
@Environment ( \. scenePhase) var scenePhase
156
206
@@ -244,9 +294,11 @@ struct ChatView: View {
244
294
peerThreadId: peerThreadId,
245
295
chatId: chatId,
246
296
out: true ,
247
- status: . sending
297
+ status: . sending,
298
+ repliedToMessageId: ChatState . shared. replyingMessageId
248
299
)
249
300
301
+ print ( " Sending message with repliedToMessageId: \( message. repliedToMessageId) \( message) " )
250
302
// Save message to database
251
303
try await database. dbWriter. write { db in
252
304
try message. save ( db)
@@ -259,8 +311,10 @@ struct ChatView: View {
259
311
peerThreadId: peerThreadId,
260
312
text: messageText,
261
313
peerId: peer,
262
- randomId: randomId
314
+ randomId: randomId,
315
+ repliedToMessageId: message. repliedToMessageId
263
316
)
317
+ ChatState . shared. clearReplyingMessageId ( )
264
318
} catch {
265
319
Log . shared. error ( " Failed to send message " , error: error)
266
320
}
0 commit comments