Skip to content

Commit 6f7b535

Browse files
committed
Add ChatThreadListErrorBannerView
1 parent d745725 commit 6f7b535

File tree

10 files changed

+122
-7
lines changed

10 files changed

+122
-7
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// Copyright © 2024 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import SwiftUI
6+
7+
public struct ChatThreadListErrorBannerView: View {
8+
@Injected(\.colors) private var colors
9+
@Injected(\.images) private var images
10+
11+
let action: () -> Void
12+
13+
public var body: some View {
14+
HStack(alignment: .center) {
15+
Text(L10n.Thread.Error.message)
16+
.foregroundColor(Color(colors.staticColorText))
17+
Spacer()
18+
Button(action: action) {
19+
Image(uiImage: images.restart)
20+
.customizable()
21+
.frame(width: 20, height: 20)
22+
.foregroundColor(Color(colors.staticColorText))
23+
}
24+
}
25+
.padding(.all, 16)
26+
.background(Color(colors.bannerBackgroundColor))
27+
}
28+
}

Sources/StreamChatSwiftUI/ChatThreadList/ChatThreadListItem.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@ import SwiftUI
77

88
/// View for the thread list item.
99
public struct ChatThreadListItem: View {
10-
@Injected(\.fonts) private var fonts
11-
@Injected(\.colors) private var colors
1210
@Injected(\.utils) private var utils
13-
@Injected(\.images) private var images
1411
@Injected(\.chatClient) private var chatClient
1512

1613
var thread: ChatThread
@@ -101,8 +98,7 @@ struct ChatThreadListItemContentView: View {
10198
threadContainerView
10299
replyContainerView
103100
}
104-
.padding(.horizontal, 8)
105-
.padding(.vertical, 4)
101+
.padding(.all, 8)
106102
}
107103

108104
var threadContainerView: some View {

Sources/StreamChatSwiftUI/ChatThreadList/ChatThreadListLoadingView.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@ struct ChatThreadListLoadingView: View {
1919
replyMessageText: placeholder(length: 50),
2020
replyTimestampText: placeholder(length: 8)
2121
)
22-
.padding(.horizontal, 8)
23-
.padding(.vertical, 4)
2422
.shimmering(duration: 0.8, delay: 0.1)
2523
.redacted(reason: .placeholder)
2624

Sources/StreamChatSwiftUI/ChatThreadList/ChatThreadListView.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ public struct ChatThreadListView<Factory: ViewFactory>: View {
7272
)
7373
}
7474
}
75+
.bottomBanner(isPresented: viewModel.failedToLoadThreads) {
76+
viewFactory.makeThreadsListErrorBannerView {
77+
viewModel.loadThreads()
78+
}
79+
}
7580
.accentColor(colors.tintColor)
7681
.background(
7782
viewFactory.makeThreadListBackground(colors: colors)

Sources/StreamChatSwiftUI/ChatThreadList/ChatThreadListViewModel.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ open class ChatThreadListViewModel: ObservableObject, ChatThreadListControllerDe
3232
/// A boolean indicating that there is no data from server.
3333
@Published public var isEmpty = false
3434

35+
/// A boolean indicating if it failed loading the initial data from the server.
36+
@Published public var failedToLoadThreads = false
3537
/// Creates a view model for the `ChatThreadListView`.
3638
///
3739
/// - Parameters:
@@ -49,9 +51,11 @@ open class ChatThreadListViewModel: ObservableObject, ChatThreadListControllerDe
4951
public func loadThreads() {
5052
controller?.delegate = self
5153
isLoading = controller?.threads.isEmpty == true
54+
failedToLoadThreads = false
5255
controller?.synchronize { [weak self] error in
5356
self?.isLoading = false
5457
self?.hasLoadedThreads = error == nil
58+
self?.failedToLoadThreads = error != nil
5559
self?.isEmpty = self?.controller?.threads.isEmpty == true
5660
}
5761
}

Sources/StreamChatSwiftUI/ColorPalette.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ public struct ColorPalette {
8585
public lazy var composerPlaceholderColor: UIColor = subtitleText
8686
public lazy var composerInputBackground: UIColor = background
8787
public lazy var composerInputHighlightedBorder: UIColor = innerBorder
88+
89+
// MARK: - Threads
90+
91+
public var bannerBackgroundColor: UIColor = .streamDarkGray
8892
}
8993

9094
// Those colors are default defined stream constants, which are fallback values if you don't implement your color theme.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//
2+
// Copyright © 2024 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import SwiftUI
6+
7+
extension View {
8+
@ViewBuilder
9+
func topBanner(isPresented: Bool, _ bannerView: @escaping () -> some View) -> some View {
10+
modifier(
11+
FloatingBannerViewModifier(
12+
isPresented: isPresented,
13+
alignment: .top,
14+
bannerView
15+
)
16+
)
17+
}
18+
19+
@ViewBuilder
20+
func bottomBanner(isPresented: Bool, _ bannerView: @escaping () -> some View) -> some View {
21+
modifier(
22+
FloatingBannerViewModifier(
23+
isPresented: isPresented,
24+
alignment: .bottom,
25+
bannerView
26+
)
27+
)
28+
}
29+
}
30+
31+
32+
struct FloatingBannerViewModifier<BannerView: View>: ViewModifier {
33+
let alignment: Alignment
34+
var isPresented: Bool
35+
36+
@ViewBuilder
37+
let bannerView: () -> BannerView
38+
39+
init(
40+
isPresented: Bool,
41+
alignment: Alignment = .bottom,
42+
_ bannerView: @escaping () -> BannerView
43+
) {
44+
self.alignment = alignment
45+
self.isPresented = isPresented
46+
self.bannerView = bannerView
47+
}
48+
49+
func body(content: Content) -> some View {
50+
ZStack(alignment: alignment) {
51+
content
52+
if isPresented {
53+
bannerView()
54+
.animation(.easeInOut)
55+
.transition(
56+
.move(edge: alignment == .bottom ? .bottom : .top)
57+
.combined(with: .opacity)
58+
)
59+
}
60+
}
61+
}
62+
}

Sources/StreamChatSwiftUI/DefaultViewFactory.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,10 @@ extension ViewFactory {
995995
NoThreadsView()
996996
}
997997

998+
public func makeThreadsListErrorBannerView(onRefreshAction: @escaping () -> Void) -> some View {
999+
ChatThreadListErrorBannerView(action: onRefreshAction)
1000+
}
1001+
9981002
public func makeThreadListLoadingView() -> some View {
9991003
ChatThreadListLoadingView()
10001004
}

Sources/StreamChatSwiftUI/ViewFactory.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,6 +1006,12 @@ public protocol ViewFactory: AnyObject {
10061006
/// Creates the view that is displayed when there are no threads available.
10071007
func makeNoThreadsView() -> NoThreads
10081008

1009+
associatedtype ThreadListErrorBannerView: View
1010+
/// Creates the error view that is displayed at the bottom of the thread list.
1011+
/// - Parameter onRefreshAction: The refresh action, to reload the threads.
1012+
/// - Returns: Returns the error view shown as a banner at the bottom of the thread list.
1013+
func makeThreadsListErrorBannerView(onRefreshAction: @escaping () -> Void) -> ThreadListErrorBannerView
1014+
10091015
associatedtype ThreadListLoadingView: View
10101016
/// Creates a loading view for the thread list.
10111017
func makeThreadListLoadingView() -> ThreadListLoadingView

StreamChatSwiftUI.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,6 +515,8 @@
515515
AD3AB65C2CB730090014D4D7 /* Shimmer.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD3AB65B2CB730090014D4D7 /* Shimmer.swift */; };
516516
AD3AB65E2CB731360014D4D7 /* ChatThreadListLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD3AB65D2CB731360014D4D7 /* ChatThreadListLoadingView.swift */; };
517517
AD3AB6602CB7403C0014D4D7 /* ChatThreadListHeaderViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD3AB65F2CB7403C0014D4D7 /* ChatThreadListHeaderViewModifier.swift */; };
518+
ADE0F55E2CB838420053B8B9 /* ChatThreadListErrorBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADE0F55D2CB838420053B8B9 /* ChatThreadListErrorBannerView.swift */; };
519+
ADE0F5602CB846EC0053B8B9 /* FloatingBannerViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = ADE0F55F2CB846EC0053B8B9 /* FloatingBannerViewModifier.swift */; };
518520
C14A465B284665B100EF498E /* SDKIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14A465A284665B100EF498E /* SDKIdentifier.swift */; };
519521
E3A1C01C282BAC66002D1E26 /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = E3A1C01B282BAC66002D1E26 /* Sentry */; };
520522
/* End PBXBuildFile section */
@@ -1099,6 +1101,8 @@
10991101
AD3AB65B2CB730090014D4D7 /* Shimmer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shimmer.swift; sourceTree = "<group>"; };
11001102
AD3AB65D2CB731360014D4D7 /* ChatThreadListLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatThreadListLoadingView.swift; sourceTree = "<group>"; };
11011103
AD3AB65F2CB7403C0014D4D7 /* ChatThreadListHeaderViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatThreadListHeaderViewModifier.swift; sourceTree = "<group>"; };
1104+
ADE0F55D2CB838420053B8B9 /* ChatThreadListErrorBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatThreadListErrorBannerView.swift; sourceTree = "<group>"; };
1105+
ADE0F55F2CB846EC0053B8B9 /* FloatingBannerViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingBannerViewModifier.swift; sourceTree = "<group>"; };
11021106
C14A465A284665B100EF498E /* SDKIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SDKIdentifier.swift; sourceTree = "<group>"; };
11031107
/* End PBXFileReference section */
11041108

@@ -1687,6 +1691,7 @@
16871691
children = (
16881692
8465FCFA2746A95600AF091E /* ActionItemView.swift */,
16891693
4F6D83502C1079A00098C298 /* AlertBannerViewModifier.swift */,
1694+
ADE0F55F2CB846EC0053B8B9 /* FloatingBannerViewModifier.swift */,
16901695
4F077EF72C85E05700F06D83 /* DelayedRenderingViewModifier.swift */,
16911696
84AB7B1C2771F4AA00631A10 /* DiscardButtonView.swift */,
16921697
84F2908D276B92A40045472D /* GalleryHeaderView.swift */,
@@ -2240,6 +2245,7 @@
22402245
AD3AB6532CB54F310014D4D7 /* ChatThreadListItem.swift */,
22412246
AD3AB65D2CB731360014D4D7 /* ChatThreadListLoadingView.swift */,
22422247
AD3AB65F2CB7403C0014D4D7 /* ChatThreadListHeaderViewModifier.swift */,
2248+
ADE0F55D2CB838420053B8B9 /* ChatThreadListErrorBannerView.swift */,
22432249
AD2DDA602CB040EA0040B8D4 /* NoThreadsView.swift */,
22442250
);
22452251
path = ChatThreadList;
@@ -2559,6 +2565,7 @@
25592565
82D64BE92AD7E5B700C5C79E /* TaskLoadData.swift in Sources */,
25602566
847CEFEE27C38ABE00606257 /* MessageCachingUtils.swift in Sources */,
25612567
82D64BF62AD7E5B700C5C79E /* ImageProcessors+CoreImage.swift in Sources */,
2568+
ADE0F55E2CB838420053B8B9 /* ChatThreadListErrorBannerView.swift in Sources */,
25622569
8451C4912BD7096000849955 /* PollAttachmentView.swift in Sources */,
25632570
8465FD792746A95700AF091E /* DeletedMessageView.swift in Sources */,
25642571
8492975227B156D100A8EEB0 /* SlowModeView.swift in Sources */,
@@ -2646,6 +2653,7 @@
26462653
84DEC8EC27611CAE00172876 /* SendInChannelView.swift in Sources */,
26472654
84F130C12AEAA957006E7B52 /* StreamLazyImage.swift in Sources */,
26482655
82D64BD12AD7E5B700C5C79E /* Image.swift in Sources */,
2656+
ADE0F5602CB846EC0053B8B9 /* FloatingBannerViewModifier.swift in Sources */,
26492657
82D64BD52AD7E5B700C5C79E /* AnimatedFrame.swift in Sources */,
26502658
8465FD9F2746A95700AF091E /* ChatChannelExtensions.swift in Sources */,
26512659
844D1D6628510304000CCCB9 /* ChannelControllerFactory.swift in Sources */,

0 commit comments

Comments
 (0)