badoo / Chatto

A lightweight framework to build chat applications, made in Swift
MIT License
4.49k stars 596 forks source link

How to show username in the item cell ,It only shows avatar now #479

Closed MrShiY closed 6 years ago

MrShiY commented 6 years ago

Please tell me,Thank you very much

neilabdev commented 6 years ago

+1

Daxito commented 6 years ago

I accomplished this by adding a new DecoratedChatItem for every message (see ChatItemsDecoratorProtocol)

neilabdev commented 6 years ago

Thanks so much Daxito! Works like a charm :)

MrShiY commented 6 years ago

What you said was to register ChatItemPresenter Cell, like timeSeparator and messageSendingStatus ok?

neilabdev commented 6 years ago

Hey MrShiY, as I know it takes a while to even get started, I will do my best to give you an easy example to get you in the right direction.

Basically, in my case I subclassed the BaseChatViewController in ChatAdditions where the decorator handler via property decoratorItems is specified.

class MySubclassChatViewController: BaseChatViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // The line below specifies your custom decorator
        self.chatItemsDecorator = MyChatItemsDecorator(messagesSelector: self.messagesSelector) 
    }

 override func createPresenterBuilders() -> [ChatItemType: [ChatItemPresenterBuilderProtocol]] {
        let textMessagePresenter = TextMessagePresenterBuilder(
                viewModelBuilder: TextMessageViewModelBuilder(),
                interactionHandler: TextMessageHandler(baseHandler: self.baseMessageHandler)
        )
        textMessagePresenter.baseMessageStyle = BaseMessageCollectionViewCellAvatarStyle()

        let photoMessagePresenter = PhotoMessagePresenterBuilder(
                viewModelBuilder: PhotoMessageViewModelBuilder(),
                interactionHandler: PhotoMessageHandler(baseHandler: self.baseMessageHandler)
        )
        photoMessagePresenter.baseCellStyle = BaseMessageCollectionViewCellAvatarStyle()

        return [
            TextMessageModel.chatItemType: [textMessagePresenter],
            PhotoMessageModel.chatItemType: [photoMessagePresenter],
            //NOTE: This allows your new decorator to be considered
            UsernameModel.chatItemType: [UsernamePresenterBuilder()],

            SendingStatusModel.chatItemType: [SendingStatusPresenterBuilder()],
            TimeSeparatorModel.chatItemType: [TimeSeparatorPresenterBuilder()]
        ]
    }
}

It should be noted that I literally copied my decorator from the Chatto Demo application, as I wasn't sure what needed to be there. Below its called MyChatItemsDecorator.

class MyChatItemsDecorator: ChatItemsDecoratorProtocol {
    func decorateItems(_ chatItems: [ChatItemProtocol]) -> [DecoratedChatItem] {
func decorateItems(_ chatItems: [ChatItemProtocol]) -> [DecoratedChatItem] {
        var decoratedChatItems = [DecoratedChatItem]()
        let calendar = Calendar.current

        for (index, chatItem) in chatItems.enumerated() {
            let next: ChatItemProtocol? = (index + 1 < chatItems.count) ? chatItems[index + 1] : nil
            let prev: ChatItemProtocol? = (index > 0) ? chatItems[index - 1] : nil

            var bottomMargin = self.separationAfterItem(chatItem, next: next)
            var showsTail = false
            var additionalItems = [DecoratedChatItem]()
            var addTimeSeparator = false
            var isSelected = false
            var isShowingSelectionIndicator = false

            if let currentMessage = chatItem as? MessageModelProtocol {
                if let nextMessage = next as? MessageModelProtocol {
                    showsTail = currentMessage.senderId != nextMessage.senderId
                } else {
                    showsTail = true
                }

                if let previousMessage = prev as? MessageModelProtocol {
                    addTimeSeparator = !calendar.isDate(currentMessage.date, inSameDayAs: previousMessage.date)
                } else {
                    addTimeSeparator = true
                }

                if let myMessage = chatItem as? MyTextMessageModel,
                   let username = myMessage.username as? String, showsTail == true {
                    bottomMargin = 0.0. //HERE:  I ensure there is no space after the Avatar/message if its the last message , aka showTail
                    //NOTE: Add username here , determines if the username is on left or right with the .isOwner property in my custom model.
                    additionalItems.append(
                            DecoratedChatItem(
                                    chatItem: UsernameModel(uid: "\(currentMessage.uid)-decoration-username",
                                            username: username, owner: myMessage.isOwner),
                                    decorationAttributes: nil))
                }

                if self.showsStatusForMessage(currentMessage) {
                    additionalItems.append(
                            DecoratedChatItem(
                                    chatItem: SendingStatusModel(uid: "\(currentMessage.uid)-decoration-status", status: currentMessage.status),
                                    decorationAttributes: nil)
                    )
                }

                if addTimeSeparator {
                    let dateTimeStamp = DecoratedChatItem(chatItem: TimeSeparatorModel(uid: "\(currentMessage.uid)-time-separator", date: currentMessage.date.toWeekDayAndDateString()), decorationAttributes: nil)
                    decoratedChatItems.append(dateTimeStamp)
                }

                isSelected = self.messagesSelector.isMessageSelected(currentMessage)
                isShowingSelectionIndicator = self.messagesSelector.isActive && self.messagesSelector.canSelectMessage(currentMessage)
            }

            decoratedChatItems.append(
                    DecoratedChatItem(
                            chatItem: chatItem,
                            decorationAttributes: ChatItemDecorationAttributes(bottomMargin: bottomMargin,
                                    canShowTail: showsTail, canShowAvatar: showsTail, canShowFailedIcon: true)
                    )
            )
            decoratedChatItems.append(contentsOf: additionalItems)
        }

        return decoratedChatItems
    }
    }
}

Basically, what is happening is each message is iterated, and the decorator reviews each row providing the ability to add additional items as if they were added to the dataSource specifically. In this case, by virtue of addition of a Status message, or userName to the decoratedChatItems that is returned, they are respectively turned into new CollectionViewCell's you specify below each row, etc.

As a result, you will need to create a new cell for your username, though since Status message cell only displays text which is true for your username, you could use it instead. HOwever, because I wanted my username to be of a different font and float left/right respectively depending on who made the comment, I literally copied the SendingStatusPresenter and replaced the cells/logic used with my own.

class UsernameCollectionViewCell: UICollectionViewCell {
    @IBOutlet weak var username: UILabel!

}

class UsernamePresenter: ChatItemPresenterProtocol {

    let usernameModel: UsernameModel

    init(model: UsernameModel) {
        self.usernameModel = model
    }

    static func registerCells(_ collectionView: UICollectionView) { //NOTE:  USE MY OWN CELL
        collectionView.register(UINib(nibName: "UsernameCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "UsernameCollectionViewCell")
    }

    func dequeueCell(collectionView: UICollectionView, indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "UsernameCollectionViewCell", for: indexPath)
        return cell
    }

    func configureCell(_ cell: UICollectionViewCell, decorationAttributes: ChatItemDecorationAttributesProtocol?) {
        guard let statusCell = cell as? UsernameCollectionViewCell else {
            assert(false, "expecting status cell")
            return
        }

        let attrs = [
            NSFontAttributeName: Stylesheet.boldFont(size: 12),
            NSForegroundColorAttributeName: UIColor.blue
        ]

        if let usernameCell = cell as? UsernameCollectionViewCell {
           //NOTE: Notice I float my text left/right by considered the new property I added to my username model (e.g. isOwner)'
            usernameCell.username.textAlignment = self.usernameModel.isOwner ? .left : .right
            usernameCell.username.attributedText = NSAttributedString(
                    string: "@\(self.usernameModel.username)",
                    attributes: attrs)
        }
    }

    var canCalculateHeightInBackground: Bool {
        return true
    }

    func heightForCell(maximumWidth width: CGFloat, decorationAttributes: ChatItemDecorationAttributesProtocol?) -> CGFloat {
        return 19
    }
}

public class UsernamePresenterBuilder: ChatItemPresenterBuilderProtocol {

    public func canHandleChatItem(_ chatItem: ChatItemProtocol) -> Bool {
        return chatItem is UsernameModel ? true : false
    }

    public func createPresenterWithChatItem(_ chatItem: ChatItemProtocol) -> ChatItemPresenterProtocol {
        assert(self.canHandleChatItem(chatItem))
        return UsernamePresenter(model: chatItem as! UsernameModel)
    }

    public var presenterType: ChatItemPresenterProtocol.Type {
        return UsernamePresenter.self
    }
}

While its a big pill to swallow, copying the implementation of each decorated item in the Chatto app could be of help as I hope this minor example might be. Additional applicable links are below: Thanks!

Decorator explanation: https://github.com/badoo/Chatto/wiki/Decorator

How to create a new cell (in your case, username) https://github.com/badoo/Chatto/wiki/Tutorial_Add_Custom_Cell

MrShiY commented 6 years ago

Very cool, thank neilabdev and Daxito for your help