GetStream / stream-chat-swiftui

SwiftUI Chat SDK ➜ Stream Chat 💬
https://getstream.io/chat/sdk/swiftui/
Other
348 stars 87 forks source link

Repeatedly kicked out of thread #144

Closed moubry closed 2 years ago

moubry commented 2 years ago

Using the sample app included in the project, you get repeatedly kicked back out of threads upon navigating into them.

This only seems to happen when 1. you have navigated into the thread recently (doesn't happen 1st time you navigate into the thread, but happens every time after), and 2. the message list view is scrolled up (doesn't occur when you're scrolled to the bottom).

What did you do?

Reproduction steps:

What did you expect to happen?

I expected for the navigation to only go back when I tap the "Back" button.

What happened instead?

I was automatically kicked out into the channel without swiping back or tapping "Back". As soon as the animation pushing the thread view onto the stack finishes, it starts the animation to navigation back, popping the thread view off the stack.

Note: If you quit the app, then relaunch it, then scroll up, then go into a thread, you are NOT kicked out the 1st time you enter it. But it happens the 2nd time you attempt to navigate to it, and every time after that. So the two preconditions for this to occur are: 1. you must be scrolled up in the message list view (not at the bottom of the scroll view), and 2. you must have entered the thread before.

GetStream Environment

GetStream Chat version: main GetStream Chat frameworks: StreamChat, StreamChatSwiftUI iOS version: Latest version of iOS 15 Swift version: latest Xcode version: Version 13.4.1 (13F100) Device: iPhone 11 Pro

Additional context

I originally reported this issue here: https://github.com/GetStream/stream-chat-swift/issues/2181.

Here is a video demonstrating the issue. Watch with sound on:

https://user-images.githubusercontent.com/144283/180090246-084f7151-ee21-496b-801f-f65d70a8e06b.MOV

martinmitrevski commented 2 years ago

Hey @moubry,

Thanks for the detailed analysis and the explanation of the bug.

We will analyse this internally and get back to you when we have an update.

Best, Martin

martinmitrevski commented 2 years ago

Hey @moubry,

The issue was fixed with https://github.com/GetStream/stream-chat-swiftui/issues/122.

The fix was already included in yesterday's 4.19.0 release and it's also available on our main branch.

Thanks again for the detailed report and let us know if we can help with something else.

Best, Martin

moubry commented 2 years ago

✅ Yes! Thank you. I can confirm this fixes the issue for me on all platforms.

Note: There was ONE time while testing that I was kicked out of a thread back into the channel. It happened once right after restarting the app. And then never happened again. So it can still happen under rare circumstances? I have no idea how to reproduce it, though. Certainly does not happen often enough to bother worrying about, probably.

zeeshansuleman commented 2 years ago

@martinmitrevski I am facing same issue. When I go to the chat from channel list then type message then it's automatically pops back to the channels list view.

I am using my custom ChannelListItem. ChannelListItem re-render when I type something in chat textfield.

struct ChannelListItem: View {
    var chatManager = Container.default.resolver.resolve(ChatManager.self)!

    @Injected(\.chatClient) public var chatClient

    var channel: ChatChannel

    @State var navigateToChannel: Bool = false

    var imageSize: CGFloat = 40
    var defaultFontSize: CGFloat = 12
    var nameFontSize: CGFloat = 16
    var messageFontSize: CGFloat = 16
    var unreadMessagesCountCircleSize: CGFloat = 18

    var body: some View {
        VStack {
            Button {
                chatManager.selectedChannel = channel
                navigateToChannel.toggle()
            } label: {
                if let member = channel.lastActiveMembers.filter({$0.id != chatClient.currentUserId}).first {
                    HStack(spacing: Spacing.spacingS.rawValue) {
                        if let customData = member.extraData["custom_data"]?.dictionaryValue,
                           let profileImage = customData["profile_image"]?.stringValue {
                            ZStack(alignment: .topLeading) {

                                NukeLazyImage(sourceImageURL: profileImage)
                                    .clipShape(Circle())
                                    .frame(width: imageSize, height: imageSize)
                            }
                        } else {
                            ZStack {
                                Circle()
                                    .fill(Color(.skillrPink1))
                                    .frame(width: imageSize, height: imageSize)

                                Text(member.name?.getInitials() ?? "")
                                    .font(.redHatBold(size: defaultFontSize))
                                    .foregroundColor(.white)
                                    .lineLimit(1)
                            }

                        }
                        VStack(alignment: .leading, spacing: Spacing.spacingXXS.rawValue) {
                            HStack {
                                Text(member.name ?? "")
                                    .font(.system(size: nameFontSize))
                                    .fontWeight(.semibold)
                                    .foregroundColor(Color(.richBlue8))
                                    .lineLimit(1)

                                Spacer()

                                Text(channel.lastMessageAt?.stringFromDate(formate: "h:mm a") ?? "")
                                    .font(.system(size: messageFontSize))
                                    .foregroundColor(Color(.richBlue7))
                            }

                            HStack {
                                Text(channel.lastMessageText ?? "")
                                    .font(.system(size: messageFontSize))
                                    .foregroundColor(Color(.richBlue7))
                                    .lineLimit(1)

                                Spacer()

                                if (channel.unreadCount.messages > 0) {
                                    Text("\(channel.unreadCount.messages)")
                                        .font(.system(size: defaultFontSize))
                                        .fontWeight(.bold)
                                        .foregroundColor(.white)
                                        .frame(width: unreadMessagesCountCircleSize, height: unreadMessagesCountCircleSize)
                                        .background(Color(.skillrPink1))
                                        .clipShape(Circle())
                                }
                            }
                        }
                    }
                }

            }

            NavigationLink(
                  "",
                  destination: ChatChannelView(
                    viewFactory: CustomChatViewFactory.shared,
                    channelController: chatClient.channelController(for: channel.cid)
                  ),
                  isActive: $navigateToChannel
            )
        }
        .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
        .padding(.horizontal, Spacing.spacingXL.rawValue)
    }
}

Using ChannelListItem into the makeChannelListItem like this.

func makeChannelListItem(channel: ChatChannel, channelName: String, avatar: UIImage, onlineIndicatorShown: Bool, disabled: Bool, selectedChannel: Binding<ChannelSelectionInfo?>, swipedChannelId: Binding<String?>, channelDestination: @escaping (ChannelSelectionInfo) -> ChatChannelView<CustomChatViewFactory>, onItemTap: @escaping (ChatChannel) -> Void, trailingSwipeRightButtonTapped: @escaping (ChatChannel) -> Void, trailingSwipeLeftButtonTapped: @escaping (ChatChannel) -> Void, leadingSwipeButtonTapped: @escaping (ChatChannel) -> Void) -> some View {

            ChannelListItem(channel: channel)
            .frame(height: 76)

    }

For reference you can see the following video

https://user-images.githubusercontent.com/12757747/190211001-1b802293-ad3d-4619-8038-26d9720546ed.mov

martinmitrevski commented 2 years ago

Hey @zeeshansuleman,

This is actually a different issue - you're not setting any id to the channel list item, therefore it redraws every time the state changes.

Please check our default implementation here: https://github.com/GetStream/stream-chat-swiftui/blob/5b5c839c2e5a7d0a7ac88271ad6d20ebe05242d1/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelNavigatableListItem.swift#L61.

zeeshansuleman commented 2 years ago

@martinmitrevski I have used the id inside the ChannelListItem as you mentioned but not working. View re-render again and again and navigate back to the Channel List View.

            NavigationLink(
                "",
                destination: ChatChannelView(
                    viewFactory: CustomChatViewFactory.shared,
                    channelController: chatClient.channelController(for: channel.cid)
                ),
                isActive: $navigateToChannel
            )

        }
        .id("\(channel.id)-navigatable")
        .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
        .padding(.horizontal, Spacing.spacingXL.rawValue)
martinmitrevski commented 2 years ago

Hey @zeeshansuleman,

I've tried your code and it actually works for me. The only difference I've made is that I passed the binding of the selectedChannel (since I don't have the ChatManager you're using).

It's the selectedChannel: Binding<ChannelSelectionInfo?> in the parameters list. You can use it instead of the following line:

chatManager.selectedChannel = channel // old
selectedChannel = channel.channelSelectionInfo // use this instead

Hope this helps.

zeeshansuleman commented 2 years ago

@martinmitrevski Thanks for your kind support. We fixed that issue to present the chat using Chat view on the top of the tabbar by using the ZStack.