@@ -11,17 +11,42 @@ import SharedModels
11
11
import SwiftUI
12
12
import UserStore
13
13
14
- struct MessageActions : View {
14
+ struct MessageActionsBar : View {
15
15
@ObservedObject var viewModel : ConversationViewModel
16
16
@Binding var message : DataState < BaseMessage >
17
17
let conversationPath : ConversationPath ?
18
18
19
19
var body : some View {
20
- Group {
21
- ReplyInThreadButton ( viewModel: viewModel, message: $message, conversationPath: conversationPath)
22
- ForwardButton ( viewModel: viewModel, message: $message)
20
+ MessageActions ( viewModel: viewModel, message: $message, conversationPath: conversationPath)
21
+ . environment ( \. actionsDisplayMode, . bar)
22
+ }
23
+ }
24
+
25
+ struct MessageActionsMenu : View {
26
+ @ObservedObject var viewModel : ConversationViewModel
27
+ @Binding var message : DataState < BaseMessage >
28
+ let conversationPath : ConversationPath ?
29
+
30
+ var body : some View {
31
+ MessageActions ( viewModel: viewModel, message: $message, conversationPath: conversationPath)
32
+ . background ( . bar, in: . rect( cornerRadius: 10 ) )
33
+ . fontWeight ( . semibold)
34
+ }
35
+ }
36
+
37
+ private struct MessageActions : View {
38
+ @ObservedObject var viewModel : ConversationViewModel
39
+ @Binding var message : DataState < BaseMessage >
40
+ let conversationPath : ConversationPath ?
41
+
42
+ var body : some View {
43
+ MenuGroup {
44
+ HorizontalMenuGroup {
45
+ ReplyButton ( viewModel: viewModel, message: $message, conversationPath: conversationPath)
46
+ ForwardButton ( viewModel: viewModel, message: $message)
47
+ BookmarkButton ( viewModel: viewModel, message: $message)
48
+ }
23
49
CopyTextButton ( viewModel: viewModel, message: $message)
24
- BookmarkButton ( viewModel: viewModel, message: $message)
25
50
PinButton ( viewModel: viewModel, message: $message)
26
51
MarkResolvingButton ( viewModel: viewModel, message: $message)
27
52
EditDeleteSection ( viewModel: viewModel, message: $message)
@@ -30,7 +55,7 @@ struct MessageActions: View {
30
55
. font ( . title3)
31
56
}
32
57
33
- struct ReplyInThreadButton : View {
58
+ struct ReplyButton : View {
34
59
@EnvironmentObject var navigationController : NavigationController
35
60
@ObservedObject var viewModel : ConversationViewModel
36
61
@Binding var message : DataState < BaseMessage >
@@ -39,7 +64,7 @@ struct MessageActions: View {
39
64
var body : some View {
40
65
if message. value is Message ,
41
66
let conversationPath {
42
- Button ( R . string. localizable. replyInThread ( ) , systemImage: " text.bubble " ) {
67
+ Button ( R . string. localizable. replyInThread ( ) , systemImage: " arrowshape.turn.up.left " ) {
43
68
if let messagePath = MessagePath (
44
69
message: $message,
45
70
conversationPath: conversationPath,
@@ -64,7 +89,7 @@ struct MessageActions: View {
64
89
}
65
90
66
91
var body : some View {
67
- Button ( R . string. localizable. forwardMessageShort ( ) , systemImage: " arrowshape.turn.up.right.fill " ) {
92
+ Button ( R . string. localizable. forwardMessageShort ( ) , systemImage: " arrowshape.turn.up.right " ) {
68
93
viewModel. showForwardSheet = true
69
94
}
70
95
. sheet ( isPresented: $viewModel. showForwardSheet) {
@@ -73,7 +98,6 @@ struct MessageActions: View {
73
98
ForwardMessageView ( viewModel: viewModel)
74
99
. font ( nil )
75
100
}
76
- Divider ( )
77
101
}
78
102
}
79
103
@@ -119,16 +143,13 @@ struct MessageActions: View {
119
143
}
120
144
121
145
var body : some View {
122
- Group {
123
- Divider ( )
124
- if message. value? . isBookmarked ?? false {
125
- Button ( R . string. localizable. removeBookmark ( ) , systemImage: " bookmark.slash " ) {
126
- viewModel. toggleBookmark ( )
127
- }
128
- } else {
129
- Button ( R . string. localizable. addBookmark ( ) , systemImage: " bookmark " ) {
130
- viewModel. toggleBookmark ( )
131
- }
146
+ if message. value? . isBookmarked ?? false {
147
+ Button ( R . string. localizable. removeBookmark ( ) , systemImage: " bookmark.slash " ) {
148
+ viewModel. toggleBookmark ( )
149
+ }
150
+ } else {
151
+ Button ( R . string. localizable. addBookmark ( ) , systemImage: " bookmark " ) {
152
+ viewModel. toggleBookmark ( )
132
153
}
133
154
}
134
155
}
@@ -143,33 +164,28 @@ struct MessageActions: View {
143
164
}
144
165
145
166
var body : some View {
146
- Group {
147
- if viewModel . canEdit || viewModel . canDelete {
148
- Divider ( )
167
+ if viewModel . canEdit {
168
+ Button ( R . string . localizable . editMessage ( ) , systemImage : " pencil " ) {
169
+ viewModel . showEditSheet = true
149
170
}
150
- if viewModel. canEdit {
151
- Button ( R . string . localizable . editMessage ( ) , systemImage : " pencil " ) {
152
- viewModel . showEditSheet = true
153
- }
154
- . sheet ( isPresented : $viewModel . showEditSheet ) {
155
- viewModel . conversationViewModel . selectedMessageId = nil
156
- } content : {
157
- EditMessageView ( viewModel: viewModel )
158
- . font ( nil )
159
- }
171
+ . sheet ( isPresented : $ viewModel. showEditSheet ) {
172
+ viewModel . conversationViewModel . selectedMessageId = nil
173
+ } content : {
174
+ EditMessageView ( viewModel : viewModel )
175
+ . font ( nil )
176
+ }
177
+ }
178
+ if viewModel. canDelete {
179
+ Button ( R . string . localizable . deleteMessage ( ) , systemImage : " trash " , role : . destructive ) {
180
+ viewModel . showDeleteAlert = true
160
181
}
161
- if viewModel. canDelete {
162
- Button ( R . string. localizable. deleteMessage ( ) , systemImage: " trash " , role: . destructive) {
163
- viewModel. showDeleteAlert = true
182
+ . foregroundStyle ( . red)
183
+ . alert ( R . string. localizable. confirmDeletionTitle ( ) , isPresented: $viewModel. showDeleteAlert) {
184
+ Button ( R . string. localizable. confirm ( ) , role: . destructive) {
185
+ viewModel. deleteMessage ( navController: navigationController)
164
186
}
165
- . foregroundStyle ( . red)
166
- . alert ( R . string. localizable. confirmDeletionTitle ( ) , isPresented: $viewModel. showDeleteAlert) {
167
- Button ( R . string. localizable. confirm ( ) , role: . destructive) {
168
- viewModel. deleteMessage ( navController: navigationController)
169
- }
170
- Button ( R . string. localizable. cancel ( ) , role: . cancel) {
171
- viewModel. conversationViewModel. selectedMessageId = nil
172
- }
187
+ Button ( R . string. localizable. cancel ( ) , role: . cancel) {
188
+ viewModel. conversationViewModel. selectedMessageId = nil
173
189
}
174
190
}
175
191
}
@@ -186,15 +202,11 @@ struct MessageActions: View {
186
202
}
187
203
188
204
var body : some View {
189
- Group {
190
- if viewModel. canPin {
191
- Divider ( )
192
-
193
- if ( message. value as? Message ) ? . displayPriority == . pinned {
194
- Button ( R . string. localizable. unpinMessage ( ) , systemImage: " pin.slash " , action: viewModel. togglePinned)
195
- } else {
196
- Button ( R . string. localizable. pinMessage ( ) , systemImage: " pin " , action: viewModel. togglePinned)
197
- }
205
+ if viewModel. canPin {
206
+ if ( message. value as? Message ) ? . displayPriority == . pinned {
207
+ Button ( R . string. localizable. unpinMessage ( ) , systemImage: " pin.slash " , action: viewModel. togglePinned)
208
+ } else {
209
+ Button ( R . string. localizable. pinMessage ( ) , systemImage: " pin " , action: viewModel. togglePinned)
198
210
}
199
211
}
200
212
}
@@ -224,15 +236,11 @@ struct MessageActions: View {
224
236
}
225
237
226
238
var body : some View {
227
- Group {
228
- if isAbleToMarkResolving {
229
- Divider ( )
230
-
231
- if ( message. value as? AnswerMessage ) ? . resolvesPost ?? false {
232
- Button ( R . string. localizable. unmarkAsResolving ( ) , systemImage: " xmark " , action: toggleResolved)
233
- } else {
234
- Button ( R . string. localizable. markAsResolving ( ) , systemImage: " checkmark " , action: toggleResolved)
235
- }
239
+ if isAbleToMarkResolving {
240
+ if ( message. value as? AnswerMessage ) ? . resolvesPost ?? false {
241
+ Button ( R . string. localizable. unmarkAsResolving ( ) , systemImage: " xmark " , action: toggleResolved)
242
+ } else {
243
+ Button ( R . string. localizable. markAsResolving ( ) , systemImage: " checkmark " , action: toggleResolved)
236
244
}
237
245
}
238
246
}
@@ -248,41 +256,45 @@ struct MessageActions: View {
248
256
}
249
257
}
250
258
251
- struct MessageActionsMenu : View {
252
- @ObservedObject var viewModel : ConversationViewModel
253
- @Binding var message : DataState < BaseMessage >
254
- let conversationPath : ConversationPath ?
255
-
256
- init ( viewModel: ConversationViewModel , message: Binding < DataState < BaseMessage > > , conversationPath: ConversationPath ? ) {
257
- self . viewModel = viewModel
258
- self . _message = message
259
- self . conversationPath = conversationPath
260
- }
261
-
262
- var body : some View {
263
- VStack {
264
- MessageActions ( viewModel: viewModel, message: $message, conversationPath: conversationPath)
259
+ private struct MessageActionsStyleModifier : ViewModifier {
260
+ @Environment ( \. actionsDisplayMode) private var displayMode
261
+
262
+ func body( content: Content ) -> some View {
263
+ if displayMode == . menu {
264
+ content
265
+ . symbolVariant ( . fill)
266
+ . labelStyle ( ContextMenuLabelStyle ( ) )
267
+ . buttonStyle ( . plain)
268
+ } else {
269
+ content
265
270
}
266
- . padding ( . vertical, . s)
267
- . background ( . bar, in: . rect( cornerRadius: 10 ) )
268
- . fontWeight ( . semibold)
269
- . symbolVariant ( . fill)
270
- . labelStyle ( ContextMenuLabelStyle ( ) )
271
- . buttonStyle ( . plain)
272
- . loadingIndicator ( isLoading: $viewModel. isLoading)
273
- . alert ( isPresented: $viewModel. showError, error: viewModel. error, actions: { } )
274
271
}
275
272
}
276
273
277
274
private struct ContextMenuLabelStyle : LabelStyle {
275
+ @Environment ( \. actionsDisplayMode) var displayMode
276
+
277
+ var layout : AnyLayout {
278
+ if displayMode == . menuCompact {
279
+ AnyLayout ( VStackLayout ( ) )
280
+ } else {
281
+ AnyLayout ( HStackLayout ( ) )
282
+ }
283
+ }
284
+
278
285
func makeBody( configuration: Configuration ) -> some View {
279
- HStack {
280
- configuration. title
281
- Spacer ( )
282
- configuration. icon
286
+ layout {
287
+ if displayMode == . menuCompact {
288
+ configuration. icon. frame ( height: 40 )
289
+ configuration. title. font ( . callout)
290
+ } else {
291
+ configuration. title
292
+ Spacer ( )
293
+ configuration. icon
294
+ }
283
295
}
284
296
. padding ( . horizontal)
285
- . padding ( . vertical, . s )
297
+ . padding ( . vertical, 10 )
286
298
. contentShape ( . rect)
287
299
}
288
300
}
@@ -303,3 +315,75 @@ extension EnvironmentValues {
303
315
}
304
316
}
305
317
}
318
+
319
+ // MARK: - Layout
320
+ private enum ActionsDisplayMode {
321
+ case menu, menuCompact, bar
322
+ }
323
+
324
+ private struct HorizontalMenuGroup < Content: View > : View {
325
+ @Environment ( \. actionsDisplayMode) private var displayMode
326
+ @ViewBuilder var content : Content
327
+
328
+ var isMenu : Bool { displayMode == . menu }
329
+
330
+ var body : some View {
331
+ HStack ( spacing: isMenu ? 0 : . m) {
332
+ Group ( subviews: content) { subviews in
333
+ ForEach ( subviews. dropLast ( ) ) { subview in
334
+ subview
335
+ . frame ( maxWidth: isMenu ? . infinity : nil )
336
+ Divider ( )
337
+ }
338
+ subviews. last
339
+ . frame ( maxWidth: isMenu ? . infinity : nil )
340
+ }
341
+ }
342
+ . fixedSize ( horizontal: false , vertical: true )
343
+ . environment ( \. actionsDisplayMode, isMenu ? . menuCompact : displayMode)
344
+ }
345
+ }
346
+
347
+ private struct MenuGroup < Content: View > : View {
348
+ @Environment ( \. actionsDisplayMode) private var displayMode
349
+ @ViewBuilder var content : Content
350
+
351
+ var layout : AnyLayout {
352
+ if displayMode == . menu {
353
+ AnyLayout ( VStackLayout ( spacing: 0 ) )
354
+ } else {
355
+ AnyLayout ( HStackLayout ( spacing: . m) )
356
+ }
357
+ }
358
+
359
+ var body : some View {
360
+ layout {
361
+ Group ( subviews: content) { subviews in
362
+ ForEach ( subviews. dropLast ( ) ) { subview in
363
+ subview
364
+ Divider ( )
365
+ }
366
+ subviews. last
367
+ }
368
+ }
369
+ . fixedSize ( horizontal: false , vertical: true )
370
+ . modifier ( MessageActionsStyleModifier ( ) )
371
+ }
372
+ }
373
+
374
+ // MARK: Environment+ActionsDisplayMode
375
+
376
+ private enum ActionsDisplayModeEnvironmentKey : EnvironmentKey {
377
+ static let defaultValue : ActionsDisplayMode = . menu
378
+ }
379
+
380
+ private extension EnvironmentValues {
381
+ var actionsDisplayMode : ActionsDisplayMode {
382
+ get {
383
+ self [ ActionsDisplayModeEnvironmentKey . self]
384
+ }
385
+ set {
386
+ self [ ActionsDisplayModeEnvironmentKey . self] = newValue
387
+ }
388
+ }
389
+ }
0 commit comments