Skip to content

Commit

Permalink
Merge pull request #33 from jaesung-0o0/feature/jaesung/next-message-…
Browse files Browse the repository at this point in the history
…field

Added `NextMessageField` to provide easy-customizable contents
  • Loading branch information
x-0o0 authored Aug 1, 2023
2 parents 98a0898 + 31f54cf commit 8298be6
Show file tree
Hide file tree
Showing 4 changed files with 346 additions and 11 deletions.
101 changes: 91 additions & 10 deletions Sources/ChatUI/ChatInChannel/MessageField/MessageField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,10 @@ import PhotosUI
To publish a new message, you can create a new `MessageStyle` object and send it using `send(_:)`.

```swift
let _ = Empty<Void, Never>()
.sink(
receiveCompletion: { _ in
// Create `MessageStyle` object
let style = MessageStyle.text("{TEXT}")
// Publish the created style object via `send(_:)`
sendMessagePublisher.send(style)
},
receiveValue: { _ in }
)
// Create `MessageStyle` object
let style = MessageStyle.text("{TEXT}")
// Publish the created style object via `send(_:)`
sendMessagePublisher.send(style)
```

You can subscribe to `sendMessagePublisher` to handle new messages.
Expand Down Expand Up @@ -226,6 +220,7 @@ public struct MessageField: View {
isTextFieldFocused = false
}

// TODO: Publishers: To customize buttons in message field and connect actions to appropriate publishers
func onTapMore() {
isMenuItemPresented.toggle()
}
Expand Down Expand Up @@ -262,3 +257,89 @@ public struct MessageField: View {
isVoiceFieldPresented = false
}
}


// TODO: MessageField Options Extend

/**
```swift
struct MyAppCameraButton {
var body: some View {
Button {
MessageField.cameraTapGesturePublisher.send()
} label: {
Image.camera.medium
}
.tint(appearance.tint)
.disabled(isMenuItemPresented) // how...
.frame(width: 36, height: 36)
}
}

MessageField(sendAction: ...) {
MessageTextField()
.fieldbar {
ItemGroup(placement: .leading) {
MyAppCameraButton()
}

FieldItemGroup(placement: .trailing) {
VoiceButton()

EmojiButton()
}
}
}


```
*/

extension MessageField {
public enum Style {
case fieldOption
}

public enum Placement {
case leading
case trailing
}

public struct FieldOptionModifier<Label: View>: ViewModifier {
let placement: MessageField.Placement
let label: () -> Label

public func body(content: Content) -> some View {
HStack(alignment: .bottom) {
if placement == .leading {
label()
}

content

if placement == .trailing {
label()
}
}
}

init(_ placement: MessageField.Placement, @ViewBuilder label: @escaping () -> Label) {
self.placement = placement
self.label = label
}
}
}

extension MessageField {
public func fieldOption<Label: View>(_ placement: MessageField.Placement, @ViewBuilder label: @escaping () -> Label) -> some View {
return AnyView(
modifier(
MessageField.FieldOptionModifier(
placement,
label: label
)
)
)
}
}

184 changes: 184 additions & 0 deletions Sources/ChatUI/ChatInChannel/MessageField/NextMessageField.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
//
// NextMessageField.swift
//
//
// Created by Jaesung Lee on 2023/03/28.
//

import SwiftUI

// TODO: Provide pre-implemented send button

/**
The view for sending messages.

When creating a ``NextMessageField``, you can provide an action for how to handle a new ``MessageStyle`` information in the `onSend` parameter. ``MessageStyle`` contains different types of messages, such as text, media (photo, video, document, contact) and voice. you can also provide a message-sending button by using ``sendMessagePublisher`` in the `rightLabel`. ``sendMessagePublisher`` will invoke `onSend` handler.

```swift
@State private var text: String = ""

NextMessageField(text) { messageStyle in
guard !text.isEmpty else { return }
viewModel.sendMessage($0)
text = ""
} rightLabel: {
Button {
// send message by using `sendMessagePublisher`. This will invoke `onSend` handler.
sendMessagePublisher.send(.text(text))
} label: {
// send button icon
Image.send.medium
}
.frame(width: 36, height: 36)
}
```

To add some button on the left of the text field,
```swift
NextMessageField(text) { messageStyle in
...
} leftLabel: {
HStack {
Button(aciton: showCamera) {
Image.camera.medium
}
.frame(width: 36, height: 36)

Button(action: showLibrary) {
Image.photoLibrary.medium
}
.frame(width: 36, height: 36)
}
```

To add some button on the right of the text field,
```swift
NextMessageField(text) { messageStyle in
...
} rightLabel: {
HStack {
Button(aciton: { sendMessagePublisher.send(.text(text)) }) {
Image.send.medium
}
.frame(width: 36, height: 36)
}
}
```

To publish a new message, you can create a new `MessageStyle` object and send it using `send(_:)`.

```swift
// Create `MessageStyle` object
let style = MessageStyle.text("{TEXT}")
// Publish the created style object via `send(_:)`
sendMessagePublisher.send(style)
```

You can make other views to subscribe to `sendMessagePublisher` to handle new messages.

```swift
.onReceive(sendMessagePublisher) { messageStyle in
// Handle `messageStyle` here (e.g., sending message with the style)
}
```
*/
public struct NextMessageField<LeftLabel: View, RightLabel: View>: View {
@EnvironmentObject private var configuration: ChatConfiguration

@Environment(\.appearance) var appearance

@Binding public var text: String

@FocusState private var isTextFieldFocused: Bool
@State private var textFieldHeight: CGFloat = 20

let leftLabel: (() -> LeftLabel)?
let rightLabel: (() -> RightLabel)?
let showsSendButtonAlways: Bool = false
let characterLimit: Int?
let onSend: (_ messageStyle: MessageStyle) -> ()

public var body: some View {
HStack(alignment: .bottom) {
if let leftLabel {
leftLabel()
.tint(appearance.tint)
}

// TextField
HStack(alignment: .bottom) {
MessageTextField(text: $text, height: $textFieldHeight, characterLimit: characterLimit)
.frame(height: textFieldHeight < 90 ? textFieldHeight : 90)
.padding(.leading, 9)
.padding(.trailing, 4)
.focused($isTextFieldFocused)
}
.padding(6)
.background {
appearance.secondaryBackground
.clipShape(RoundedRectangle(cornerRadius: 18))
}

if let rightLabel {
rightLabel()
.tint(appearance.tint)
}
}
.padding(16)
.onReceive(sendMessagePublisher) { messageStyle in
onSend(messageStyle)
}
}

public init(
_ text: Binding<String>,
characterLimit: Int? = nil,
onSend: @escaping (_ messageStyle: MessageStyle) -> (),
@ViewBuilder leftLabel: @escaping () -> LeftLabel,
@ViewBuilder rightLabel: @escaping () -> RightLabel
) {
self._text = text
self.characterLimit = characterLimit
self.onSend = onSend
self.leftLabel = leftLabel
self.rightLabel = rightLabel
}

public init(
_ text: Binding<String>,
characterLimit: Int? = nil,
onSend: @escaping (_ messageStyle: MessageStyle) -> (),
@ViewBuilder rightLabel: @escaping () -> RightLabel
) where LeftLabel == EmptyView {
self._text = text
self.characterLimit = characterLimit
self.onSend = onSend
self.leftLabel = nil
self.rightLabel = rightLabel
}

public init(
_ text: Binding<String>,
characterLimit: Int? = nil,
onSend: @escaping (_ messageStyle: MessageStyle) -> (),
@ViewBuilder leftLabel: @escaping () -> LeftLabel
) where RightLabel == EmptyView {
self._text = text
self.characterLimit = characterLimit
self.onSend = onSend
self.leftLabel = leftLabel
self.rightLabel = nil
}

public init(
_ text: Binding<String>,
characterLimit: Int? = nil,
onSend: @escaping (_ messageStyle: MessageStyle) -> ()
) where LeftLabel == EmptyView, RightLabel == EmptyView {
self._text = text
self.characterLimit = characterLimit
self.onSend = onSend
self.leftLabel = nil
self.rightLabel = nil
}
}
2 changes: 1 addition & 1 deletion Sources/ChatUI/ChatInChannel/MessageRow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public struct MessageRow<M: MessageProtocol>: View {
.padding(.horizontal, 8)
}

if let reactableMessage = message as? MessageReactable {
if let reactableMessage = message as? (any MessageReactable) {
switch reactableMessage.reaction {
case .none:
EmptyView()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//
// NextMessageField.Previews.swift
//
//
// Created by Jaesung Lee on 2023/08/02.
//

import SwiftUI

struct NextMessageField_Previews: PreviewProvider {
static var previews: some View {
Preview()
.previewDisplayName("Next Message Field")
}

struct Preview: View {
@State private var pendingMessage: Message?
@State private var text: String = ""

var body: some View {
VStack {
if let pendingMessage = pendingMessage {
Text(pendingMessage.id)

Text(String(describing: pendingMessage.style))
}

Spacer()

NextMessageField($text) { messageStyle in
pendingMessage = Message(
id: UUID().uuidString,
sender: User.user1,
sentAt: Date().timeIntervalSince1970,
readReceipt: .sending,
style: messageStyle
)
} leftLabel: {
HStack {
Button(action: {}) {
Image.camera.medium
}
.frame(width: 36, height: 36)

Button(action: {}) {
Image.photoLibrary.medium
}
.frame(width: 36, height: 36)

Button(action: {}) {
Image.mic.medium
}
.frame(width: 36, height: 36)
}
} rightLabel: {
Button {
sendMessagePublisher.send(.text(text))
} label: {
Image.send.medium
}
.frame(width: 36, height: 36)
}
.environment(\.appearance, Appearance())

}
}
}

}

0 comments on commit 8298be6

Please sign in to comment.