Skip to content

Commit f17f6c3

Browse files
Added redacted loading view
1 parent 78bca05 commit f17f6c3

File tree

17 files changed

+196
-12
lines changed

17 files changed

+196
-12
lines changed

Sources/StreamChatSwiftUI/ChatChannel/ChatChannelScreen.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import StreamChat
66
import SwiftUI
77

88
/// Screen component for the chat channel view.
9-
struct ChatChannelScreen: View {
10-
var chatChannelController: ChatChannelController
9+
public struct ChatChannelScreen: View {
10+
public var chatChannelController: ChatChannelController
1111

12-
var body: some View {
12+
public var body: some View {
1313
ChatChannelView(
1414
viewFactory: DefaultViewFactory.shared,
1515
channelController: chatChannelController

Sources/StreamChatSwiftUI/ChatChannel/ChatChannelViewModel.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,10 @@ open class ChatChannelViewModel: ObservableObject, MessagesDataSource {
320320
}
321321

322322
private func shouldAnimate(changes: [ListChange<ChatMessage>]) -> Bool {
323+
if !utils.messageListConfig.messageDisplayOptions.animateChanges {
324+
return false
325+
}
326+
323327
for change in changes {
324328
switch change {
325329
case .insert(_, index: _),

Sources/StreamChatSwiftUI/ChatChannel/Composer/MessageComposerViewModel.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,17 @@ open class MessageComposerViewModel: ObservableObject {
3636
@Published public var text = "" {
3737
didSet {
3838
if text != "" {
39+
checkTypingSuggestions()
3940
if pickerTypeState != .collapsed {
40-
withAnimation {
41+
if composerCommand == nil {
42+
withAnimation {
43+
pickerTypeState = .collapsed
44+
}
45+
} else {
4146
pickerTypeState = .collapsed
4247
}
4348
}
4449
channelController.sendKeystrokeEvent()
45-
checkTypingSuggestions()
4650
} else {
4751
if composerCommand?.displayInfo?.isInstant == false {
4852
composerCommand = nil

Sources/StreamChatSwiftUI/ChatChannel/MessageList/MessageListConfig.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,16 @@ public struct MessageDisplayOptions {
6060

6161
let showAvatars: Bool
6262
let showMessageDate: Bool
63+
let animateChanges: Bool
6364

64-
public init(showAvatars: Bool = true, showMessageDate: Bool = true) {
65+
public init(
66+
showAvatars: Bool = true,
67+
showMessageDate: Bool = true,
68+
animateChanges: Bool = true
69+
) {
6570
self.showAvatars = showAvatars
6671
self.showMessageDate = showMessageDate
72+
self.animateChanges = animateChanges
6773
}
6874
}
6975

Sources/StreamChatSwiftUI/ColorPalette.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public struct ColorPalette {
2626
// MARK: - Text interactions
2727

2828
public var highlightedColorForColor: (UIColor) -> UIColor = { $0.withAlphaComponent(0.5) }
29-
public var disabledColorForColor: (UIColor) -> UIColor = { _ in .lightGray }
29+
public var disabledColorForColor: (UIColor) -> UIColor = { _ in .streamDisabled }
3030
public var unselectedColorForColor: (UIColor) -> UIColor = { _ in .lightGray }
3131

3232
// MARK: - Background
@@ -94,6 +94,7 @@ private extension UIColor {
9494
static let streamGrayDisabledText = mode(0x72767e, 0x72767e)
9595
static let streamInnerBorder = mode(0xdbdde1, 0x272a30)
9696
static let streamHighlight = mode(0xfbf4dd, 0x333024)
97+
static let streamDisabled = mode(0xb4b7bb, 0x4c525c)
9798

9899
// Currently we are not using the correct shadow color from figma's color palette. This is to avoid
99100
// an issue with snapshots inconsistency between Intel vs M1. We can't use shadows with transparency.

Sources/StreamChatSwiftUI/CommonViews/LoadingView.swift

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import SwiftUI
66

7-
/// Default loading view.
7+
/// Simple loading view with a progress indicator.
88
public struct LoadingView: View {
99
public var body: some View {
1010
VStack {
@@ -15,3 +15,131 @@ public struct LoadingView: View {
1515
.frame(maxWidth: .infinity)
1616
}
1717
}
18+
19+
/// Loading view showing redacted channel list data.
20+
public struct RedactedLoadingView<Factory: ViewFactory>: View {
21+
22+
public var factory: Factory
23+
24+
public var body: some View {
25+
ScrollView {
26+
VStack {
27+
factory.makeChannelListTopView(
28+
searchText: .constant("")
29+
)
30+
31+
VStack(spacing: 0) {
32+
ForEach(0..<20) { _ in
33+
RedactedChannelCell()
34+
Divider()
35+
}
36+
}
37+
.shimmering()
38+
}
39+
}
40+
}
41+
}
42+
43+
struct RedactedChannelCell: View {
44+
45+
@Injected(\.colors) private var colors
46+
47+
private let circleSize: CGFloat = 48
48+
49+
private var redactedColor: Color {
50+
Color(colors.disabledColorForColor(colors.text))
51+
}
52+
53+
public var body: some View {
54+
HStack {
55+
Circle()
56+
.fill(redactedColor)
57+
.frame(width: circleSize, height: circleSize)
58+
59+
VStack(alignment: .leading) {
60+
RedactedRectangle(width: 70, redactedColor: redactedColor)
61+
62+
HStack {
63+
RedactedRectangle(redactedColor: redactedColor)
64+
RedactedRectangle(width: 50, redactedColor: redactedColor)
65+
}
66+
}
67+
}
68+
.padding(.all, 8)
69+
}
70+
}
71+
72+
struct RedactedRectangle: View {
73+
74+
var width: CGFloat?
75+
var redactedColor: Color
76+
77+
var body: some View {
78+
RoundedRectangle(cornerRadius: 12)
79+
.fill(redactedColor)
80+
.frame(width: width, height: 16)
81+
}
82+
}
83+
84+
struct Shimmer: ViewModifier {
85+
@State private var phase: CGFloat = 0
86+
var duration = 1.5
87+
88+
public func body(content: Content) -> some View {
89+
content
90+
.modifier(AnimatedMask(phase: phase).animation(
91+
Animation
92+
.linear(duration: duration)
93+
.repeatForever(autoreverses: false)
94+
))
95+
.onAppear { phase = 0.8 }
96+
}
97+
98+
/// An animatable modifier to interpolate between `phase` values.
99+
struct AnimatedMask: AnimatableModifier {
100+
var phase: CGFloat = 0
101+
102+
var animatableData: CGFloat {
103+
get { phase }
104+
set { phase = newValue }
105+
}
106+
107+
func body(content: Content) -> some View {
108+
content
109+
.mask(GradientMask(phase: phase).scaleEffect(3))
110+
}
111+
}
112+
113+
/// A slanted, animatable gradient between transparent and opaque to use as mask.
114+
/// The `phase` parameter shifts the gradient, moving the opaque band.
115+
struct GradientMask: View {
116+
let phase: CGFloat
117+
let centerColor = Color.black
118+
let edgeColor = Color.black.opacity(0.3)
119+
120+
var body: some View {
121+
LinearGradient(
122+
gradient:
123+
Gradient(stops: [
124+
.init(color: edgeColor, location: phase),
125+
.init(color: centerColor, location: phase + 0.1),
126+
.init(color: edgeColor, location: phase + 0.2)
127+
]),
128+
startPoint: .topLeading,
129+
endPoint: .bottomTrailing
130+
)
131+
}
132+
}
133+
}
134+
135+
extension View {
136+
/// Adds an animated shimmering effect to any view, typically to show that
137+
/// an operation is in progress.
138+
/// - Parameters:
139+
/// - duration: The duration of a shimmer cycle in seconds. Default: `1.5`.
140+
func shimmering(
141+
duration: Double = 1.5
142+
) -> some View {
143+
modifier(Shimmer(duration: duration))
144+
}
145+
}

Sources/StreamChatSwiftUI/CommonViews/SearchBar.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import SwiftUI
66

77
/// Search bar used in the message search.
8-
struct SearchBar: View {
8+
struct SearchBar: View, KeyboardReadable {
99

1010
@Injected(\.colors) private var colors
1111
@Injected(\.fonts) private var fonts
@@ -50,7 +50,7 @@ struct SearchBar: View {
5050
self.isEditing = true
5151
}
5252
.transition(.identity)
53-
.animation(.easeInOut)
53+
.animation(.easeInOut, value: isEditing)
5454

5555
if isEditing {
5656
Button(action: {
@@ -69,5 +69,10 @@ struct SearchBar: View {
6969
}
7070
}
7171
.padding(.top, 8)
72+
.onReceive(keyboardWillChangePublisher) { shown in
73+
if !shown && isEditing {
74+
self.isEditing = false
75+
}
76+
}
7277
}
7378
}

Sources/StreamChatSwiftUI/DefaultViewFactory.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ extension ViewFactory {
1515
}
1616

1717
public func makeLoadingView() -> some View {
18-
LoadingView()
18+
RedactedLoadingView(factory: self)
1919
}
2020

2121
public func navigationBarDisplayMode() -> NavigationBarItem.TitleDisplayMode {

StreamChatSwiftUI.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@
271271
84C94D62275A5BB7007FE2B9 /* ChatChannelNamer_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C94D61275A5BB7007FE2B9 /* ChatChannelNamer_Tests.swift */; };
272272
84C94D66275A660B007FE2B9 /* MessageActionsViewModel_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C94D65275A660B007FE2B9 /* MessageActionsViewModel_Tests.swift */; };
273273
84C94D68275A6AFD007FE2B9 /* ChannelHeaderLoader_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84C94D67275A6AFD007FE2B9 /* ChannelHeaderLoader_Tests.swift */; };
274+
84D6B55A27DF6EC7009C6D07 /* LoadingView_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D6B55927DF6EC7009C6D07 /* LoadingView_Tests.swift */; };
274275
84DEC8DB27609FA200172876 /* MoreChannelActionsView_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DEC8DA27609FA200172876 /* MoreChannelActionsView_Tests.swift */; };
275276
84DEC8DD2760A10500172876 /* NoChannelsView_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DEC8DC2760A10500172876 /* NoChannelsView_Tests.swift */; };
276277
84DEC8DF2760A1D100172876 /* MessageView_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84DEC8DE2760A1D100172876 /* MessageView_Tests.swift */; };
@@ -595,6 +596,7 @@
595596
84C94D61275A5BB7007FE2B9 /* ChatChannelNamer_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatChannelNamer_Tests.swift; sourceTree = "<group>"; };
596597
84C94D65275A660B007FE2B9 /* MessageActionsViewModel_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageActionsViewModel_Tests.swift; sourceTree = "<group>"; };
597598
84C94D67275A6AFD007FE2B9 /* ChannelHeaderLoader_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelHeaderLoader_Tests.swift; sourceTree = "<group>"; };
599+
84D6B55927DF6EC7009C6D07 /* LoadingView_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView_Tests.swift; sourceTree = "<group>"; };
598600
84DEC8DA27609FA200172876 /* MoreChannelActionsView_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreChannelActionsView_Tests.swift; sourceTree = "<group>"; };
599601
84DEC8DC2760A10500172876 /* NoChannelsView_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoChannelsView_Tests.swift; sourceTree = "<group>"; };
600602
84DEC8DE2760A1D100172876 /* MessageView_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageView_Tests.swift; sourceTree = "<group>"; };
@@ -1030,6 +1032,7 @@
10301032
84DEC8DA27609FA200172876 /* MoreChannelActionsView_Tests.swift */,
10311033
84DEC8DC2760A10500172876 /* NoChannelsView_Tests.swift */,
10321034
8413D90127A9654600A89432 /* SearchResultsView_Tests.swift */,
1035+
84D6B55927DF6EC7009C6D07 /* LoadingView_Tests.swift */,
10331036
);
10341037
path = ChatChannelList;
10351038
sourceTree = "<group>";
@@ -1679,6 +1682,7 @@
16791682
84C94CD027578B92007FE2B9 /* ChatMessage_Mock.swift in Sources */,
16801683
8423C342277CBA280092DCF1 /* TypingSuggester_Tests.swift in Sources */,
16811684
84C94D0627578BF2007FE2B9 /* UnwrapAsync.swift in Sources */,
1685+
84D6B55A27DF6EC7009C6D07 /* LoadingView_Tests.swift in Sources */,
16821686
84C94D0427578BF2007FE2B9 /* TestError.swift in Sources */,
16831687
84C94D0A27578BF2007FE2B9 /* TestDataModel.xcdatamodeld in Sources */,
16841688
84C94D422757C16D007FE2B9 /* ChatChannelListTestHelpers.swift in Sources */,
Loading

0 commit comments

Comments
 (0)