From 3525230ed4639b63c78eed38091275fc7ac07d49 Mon Sep 17 00:00:00 2001 From: Stream Bot Date: Thu, 21 Aug 2025 16:46:56 +0000 Subject: [PATCH 1/9] Update release version to snapshot --- .../StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift b/Sources/StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift index 549d7671..b53cd9a5 100644 --- a/Sources/StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift +++ b/Sources/StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift @@ -7,5 +7,5 @@ import Foundation enum SystemEnvironment { /// A Stream Chat version. - public static let version: String = "4.86.0" + public static let version: String = "4.87.0-SNAPSHOT" } From a7f2f419d7d07cff41e6035f875ffbade1faceba Mon Sep 17 00:00:00 2001 From: Toomas Vahter Date: Wed, 27 Aug 2025 14:22:01 +0300 Subject: [PATCH 2/9] Show attachment title instead of URL in the file attachment preview view (#930) --- CHANGELOG.md | 3 ++- .../ChannelInfo/FileAttachmentsView.swift | 2 +- .../MessageList/FileAttachmentPreview.swift | 15 +++++++++---- .../MessageList/FileAttachmentView.swift | 2 +- StreamChatSwiftUI.xcodeproj/project.pbxproj | 4 ++++ .../FileAttachmentPreview_Tests.swift | 20 ++++++++++++++++++ .../test_fileAttachmentPreview_pdf.1.png | Bin 0 -> 59397 bytes 7 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 StreamChatSwiftUITests/Tests/ChatChannel/ChannelInfo/FileAttachmentPreview_Tests.swift create mode 100644 StreamChatSwiftUITests/Tests/ChatChannel/ChannelInfo/__Snapshots__/FileAttachmentPreview_Tests/test_fileAttachmentPreview_pdf.1.png diff --git a/CHANGELOG.md b/CHANGELOG.md index eb8526a5..26a4fbde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). # Upcoming -### 🔄 Changed +### 🐞 Fixed +- Show attachment title instead of URL in the `FileAttachmentPreview` view [#930](https://github.com/GetStream/stream-chat-swiftui/pull/930) # [4.86.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.86.0) _August 21, 2025_ diff --git a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/FileAttachmentsView.swift b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/FileAttachmentsView.swift index 1b5fce92..e9238779 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/FileAttachmentsView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/FileAttachmentsView.swift @@ -60,7 +60,7 @@ public struct FileAttachmentsView: View { .padding(.vertical) } .sheet(item: $viewModel.selectedAttachment) { item in - FileAttachmentPreview(url: item.assetURL) + FileAttachmentPreview(title: item.title, url: item.assetURL) } Divider() diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/FileAttachmentPreview.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/FileAttachmentPreview.swift index f29ffdfa..cc3b989a 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/FileAttachmentPreview.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/FileAttachmentPreview.swift @@ -16,13 +16,20 @@ public struct FileAttachmentPreview: View { utils.fileCDN } + let title: String? var url: URL @State private var adjustedUrl: URL? @State private var isLoading = false - @State private var title: String? + @State private var webViewTitle: String? @State private var error: Error? - + + var navigationTitle: String { + if let title, !title.isEmpty { return title } + if let webViewTitle, !webViewTitle.isEmpty { return webViewTitle } + return url.absoluteString + } + public var body: some View { NavigationView { ZStack { @@ -35,7 +42,7 @@ public struct FileAttachmentPreview: View { WebView( url: adjustedUrl, isLoading: $isLoading, - title: $title, + title: $webViewTitle, error: $error ) } @@ -58,7 +65,7 @@ public struct FileAttachmentPreview: View { .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .principal) { - Text(title ?? url.absoluteString) + Text(navigationTitle) .font(fonts.bodyBold) .lineLimit(1) } diff --git a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/FileAttachmentView.swift b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/FileAttachmentView.swift index d8eaece2..222ce7f9 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/MessageList/FileAttachmentView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/MessageList/FileAttachmentView.swift @@ -109,7 +109,7 @@ public struct FileAttachmentView: View { .roundWithBorder() .withUploadingStateIndicator(for: attachment.uploadingState, url: attachment.assetURL) .sheet(isPresented: $fullScreenShown) { - FileAttachmentPreview(url: attachment.assetURL) + FileAttachmentPreview(title: attachment.title, url: attachment.assetURL) } .accessibilityIdentifier("FileAttachmentView") } diff --git a/StreamChatSwiftUI.xcodeproj/project.pbxproj b/StreamChatSwiftUI.xcodeproj/project.pbxproj index 169af8f8..a87db523 100644 --- a/StreamChatSwiftUI.xcodeproj/project.pbxproj +++ b/StreamChatSwiftUI.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ 4FD3592A2C05EA8F00B1D63B /* CreatePollViewModel_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FD359292C05EA8F00B1D63B /* CreatePollViewModel_Tests.swift */; }; 4FD964622D353D88001B6838 /* FilePickerView_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FD964612D353D82001B6838 /* FilePickerView_Tests.swift */; }; 4FEAB3182BFF71F70057E511 /* SwiftUI+UIAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FEAB3172BFF71F70057E511 /* SwiftUI+UIAlertController.swift */; }; + 4FEDF72B2E5DB03D00CE2676 /* FileAttachmentPreview_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4FEDF72A2E5DB03D00CE2676 /* FileAttachmentPreview_Tests.swift */; }; 8205B4142AD41CC700265B84 /* StreamSwiftTestHelpers in Frameworks */ = {isa = PBXBuildFile; productRef = 8205B4132AD41CC700265B84 /* StreamSwiftTestHelpers */; }; 8205B4182AD4267200265B84 /* StreamSwiftTestHelpers in Frameworks */ = {isa = PBXBuildFile; productRef = 8205B4172AD4267200265B84 /* StreamSwiftTestHelpers */; }; 820A61A029D6D78E002257FB /* QuotedReply_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 820A619F29D6D78E002257FB /* QuotedReply_Tests.swift */; }; @@ -635,6 +636,7 @@ 4FD359292C05EA8F00B1D63B /* CreatePollViewModel_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatePollViewModel_Tests.swift; sourceTree = ""; }; 4FD964612D353D82001B6838 /* FilePickerView_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePickerView_Tests.swift; sourceTree = ""; }; 4FEAB3172BFF71F70057E511 /* SwiftUI+UIAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SwiftUI+UIAlertController.swift"; sourceTree = ""; }; + 4FEDF72A2E5DB03D00CE2676 /* FileAttachmentPreview_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileAttachmentPreview_Tests.swift; sourceTree = ""; }; 820A619F29D6D78E002257FB /* QuotedReply_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuotedReply_Tests.swift; sourceTree = ""; }; 825AADF3283CCDB000237498 /* ThreadPage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadPage.swift; sourceTree = ""; }; 829AB4D128578ACF002DC629 /* StreamTestCase+Tags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StreamTestCase+Tags.swift"; sourceTree = ""; }; @@ -2001,6 +2003,7 @@ 84B2B5D528196FD100479CEE /* MediaAttachmentsView_Tests.swift */, 84B2B5D72819778D00479CEE /* FileAttachmentsViewModel_Tests.swift */, 84B2B5D9281985DA00479CEE /* FileAttachmentsView_Tests.swift */, + 4FEDF72A2E5DB03D00CE2676 /* FileAttachmentPreview_Tests.swift */, 84507C97281AC40F0081DDC2 /* AddUsersViewModel_Tests.swift */, 84507C99281ACCD70081DDC2 /* AddUsersView_Tests.swift */, ); @@ -3076,6 +3079,7 @@ 84C94D1327578BF2007FE2B9 /* XCTestCase+MockJSON.swift in Sources */, 84C94D5E275A3AA9007FE2B9 /* ImageCDN_Tests.swift in Sources */, 84C94C8027567D3F007FE2B9 /* ChatChannelListViewModel_Tests.swift in Sources */, + 4FEDF72B2E5DB03D00CE2676 /* FileAttachmentPreview_Tests.swift in Sources */, 84B9B20E27998E9200BFAEAE /* ColorExtensions.swift in Sources */, 8423C344277CC5020092DCF1 /* CommandsHandler_Tests.swift in Sources */, 84B2B5D2281965D000479CEE /* MediaAttachmentsViewModel_Tests.swift in Sources */, diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/ChannelInfo/FileAttachmentPreview_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannel/ChannelInfo/FileAttachmentPreview_Tests.swift new file mode 100644 index 00000000..d1b7c997 --- /dev/null +++ b/StreamChatSwiftUITests/Tests/ChatChannel/ChannelInfo/FileAttachmentPreview_Tests.swift @@ -0,0 +1,20 @@ +// +// Copyright © 2025 Stream.io Inc. All rights reserved. +// + +@testable import SnapshotTesting +@testable import StreamChatSwiftUI +@testable import StreamChatTestTools +import SwiftUI +import XCTest + +class FileAttachmentPreview_Tests: StreamChatTestCase { + func test_fileAttachmentPreview_pdf() { + let view = FileAttachmentPreview( + title: "Document title", + url: URL.localYodaQuote + ).applyDefaultSize() + + assertSnapshot(matching: view, as: .image(perceptualPrecision: precision)) + } +} diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/ChannelInfo/__Snapshots__/FileAttachmentPreview_Tests/test_fileAttachmentPreview_pdf.1.png b/StreamChatSwiftUITests/Tests/ChatChannel/ChannelInfo/__Snapshots__/FileAttachmentPreview_Tests/test_fileAttachmentPreview_pdf.1.png new file mode 100644 index 0000000000000000000000000000000000000000..30822789e9765d96f3b8bdd4d7b235c24357b960 GIT binary patch literal 59397 zcmeIbcUTkK*9J;cuu*KZ03s?X3L-6Z1r!kh8yyVFp-GMO4x$u+prRCM3P_PA(n1YL ziAwKCXcBs;LMVZdyN7f9eR{t8_r1^ed!mnK7-oh&Yt~-tec!eA5OPyf^}t@Py)-m5 z2h^^q+@_&nfYZ=0i8Jj6dyF16_<;{v$J?q(G`TIXY4F7pGd(qP4Go&JV4I0%7wthB zdgu`Fmxh*$X4lR(4b630?*DAxrak@7F?2LEK~^*j{~TigKA}H-;2${qpHKR?wEuI( zw{*XbW`Mt?|Fz8|4xRSEtY`>)>~XlJ??^+#4@S)or@ay-Y65n`tW>n_f=}=Rp+5{< z;J?#olkGj5kCe!Qpw_mJ;i}14zUsrkg zCF;qwp9QZZ*k0t==M#>`GTCk{JyaFwb5oL~Jc5QL+_Q-mz$F`I0)bYFQL3 zNWGYCiMg|SyuW^m_Q*G(yzCw$$L1Mxsq*h(7|)^Q#BS$a<6fCT_;8;GAty^!Zj`NH z+h-+(FWgad;tCX_)b&ih?uIRRP0CtqF;=b(d5T;YyNb@o*_AhEs6XM^{ZA0PbcSwo z0YeG%e(ll5rpTdTiYDgR59$SpwaPhig_i7x4%4HfQHqP{dy)eGfv1IPLP>>V-ZiGfSub+&BH8ByIeV zo{@ioOTSf*&z-%}t9_;snRhj-MUR;8rM_108g%3MUzx^vWg2kgP-~)@#VnDaHuCYYIk&lPz7@o29xej|9 zVziiLrru_D-TCv{sw*^Awal{O+ZOWE&rS8vY-ES2ST8l-v}~Av=AJOwk!$|fV1L!o zhHpPk>v?swkbyxH{^QAvuxn{!HU@6`kgHZHngSkIg@M)k*XDClIaQ>Y?(t}Zg42cbyLga{yDS`=inL!L za_N#b2PVy*(l6;hD}Qmrlmq({L!PtOCFGXd`N~W7Tc*s!JGOA@lac3Sj~*Z4P|MlL zJz=bR9ki<1o8rpMKN^B_nsE$FVcI5O7c3q;g|!dD=#j_yJ-8;Ob65P4JY%iB=0=SX zVkJJS1xebNt#Ofptc$s3`{19x9ytNBng3D$lY9 zJfXz=<7)paxr4_p*!n-+4I`2gojTOI&t_#`6dwylF&toE65kg_IEHbbPr(XL{KOX7 z&|;@tbRI)P3*2`u%TM5R12nWid!wO~mrOp<;rGLn(-b$pYJDK~80JZgp*IH?6c%K& z;=NbDcLynM{WxoUykM>vr_<^+A(qu!F(J{-sIcgNRz?c8#_GMt3(q~A!7RVhHlv;A zp-l|4kV|974mTIN+{P?gNK0KTlM!M?R9fTB-ZT5_(#ikbL z-_?zP!spGj>QDRN$(#*rz~bKq-gZB$esqw-wVKTZdBL&KaOl(0rSrnNvFfaBj2Udg zHlMzp%2`&AuCBxH=C)~*WySpbCdA$qh5=9H13in}$7eXlp2^`=ITA<6PpJWi+Arh9 zJau*j&5{D?IqZEl1YmY-`kCoIPHN4B#)?|Su&17YP5cXF?f=q`OBcHwRCQetx zt!CecA1yZ@QO+DoM7+cZN2|cbbV4srT?E~EJ%+Z58_KAV)b0|ionk_KThnQu^;xwA= z7yo0WQ!8)&QFM7>iy#kNLVjoo<(jRjurU^2-*nb-0EyKW$|>xqi1J+U94?ocE9^9H zE>KAB@)Kz3cN?MSi>Vw6@cvL2dyH*GyhybZ&BQnQ=}GDSVc;^D9Nw&~IflrZWF@>~TEUh}cVu6)(;oSiQ9~&5x>@eBTYL zkxzQAjLwI1e|QgOq@l@eTGjZkZL5UUH`clOS0+2G<`FYXFY5a|d zP~ozS@P`fS=vmWuI2EI-7iT`bxJ8RSl(b$aoo8XADB}KA-8@^Xg4JQb=3dl~?=Gv~ zlv+k&dL$*Sl`rfBfM*BwdT3P(>x`f%atoLHja6@zE^;rNPLg!ba^LPz-HzfPUj!m- z5^`;US@^sbYivW`h$;w+a!MwZRRDxh+WHZsp2*ksKXE+oGOvyNacT0vVmK>#vSFJh3KczeYQa?a^{mB7W1Y`s4wS zh5XM)2{nzu$&}ZOit9C%D5(_34z!J6ktYtni`}TLUm@DqMgK@NtVn}9I}Ml`~`=}V(Ua%~T3aii6@ zs>Hay_=qpgw!|3k5omSxj&InU1Ai?#S}|JvZbRkl*o7o9cv@xn`!bK6{B>~`1B}uu z%=V~G^T&wh%?gxtiz_&}VJ#70vj{ok@}@LhAv%UK;R|2=@cq_#wp%Ts3w3bAWUIc1 zSvdyjA+ksnMK+CEfFrc$J~ayGbY2aL2hnmk?$lg3Ur&gYQ4i~~J=bVQ7+UsP=bBHL zdbC4V`gzfj;IgYweW;yH0UB)P1Y_PD5O%SgFWNX-6Nh@$g*~_2G$F=)JED3tp&LZpm2=HjI}%fQ$~U z8Gqj^in&F8s=~)-8*N!PsI)uiHa+To^v^oZV5IucSjP&wQ&aXp(KAg`9J4}go>j0( zs+GRzygGt(yYqxtgE4>uIXp?l3FHfBM}mp_BktFJoVx%6x4<)2((K!{{!|zfN{fA9 zW17-mvk7H`k7$6UX(WsG(x~jE zSl!%xoUpEYi(_IZJQRKYVdo>f=Gj^w+q~9uQMfKz^MS|Ci#_>ZBFfIAO}(ADl2~LH zBh-U-w{BU!QDach=+U@*^9JQjZ$(AHESoB%vRdS^dTc(6_3EJ=bQnk@+ zFBxUeUAo$D-D+SrU^eLrQew7R;4awaT*+|uPg{X`$E3|B19P`I3}xFka>Xt#?gk;^ zheG&$>D{o<{BVtw^XCH~-} z4sw8+*lzKLRY}g&ZcHW(>j_s$X#rU5(f*j-G0DHXt+d4ly z@U6xV-tjdz*KV{1@f7x2c=g>6lF-Eb2;}YwB_<*O<_S0J)iVu$7yY^EO*=+a1zVVz zOT@z8^1;A;Aht%?8AD5|>if5`!PL=5DlQ8HW@Y6C?uz zjDDWYZhyJ_gZ|QkkM>u)tL=C{k3%6@hKuKMPtYMPx%?#>@p;~*NkvNOJ*Tl{xKK*Y zLIgy&#L^;sU>&ZH$f7=?eBG1SFwW*5SFC7NIU;869M2h|=UcD7nim0BRpysAY)XVe z8IqLdhaZd7vOC8eO?TyjL^NjVbn#Ht^v1g&UYV?_S~}G5_G(GNx03K+3cmBvOa#&* zw)p-#T4kJxKlBd9 zHS`Q?wc_pAmpLW-OadA<4!ERpx*kw#{GbSk;97j^gMzd(=SPBllpX~E6r8ltWWiM# zM~8xlXt)_+uCQ$~&vTGqp5tLy%~tcxD6m+;Y*bjtwpNKguy|tvg_!zTe$?Syk7N{% z7Z5G*=tr5D;M=X$94wto7Le+nFIRBc)|HB)Qge>kA^7vH&uoPFrR}yY} z)^m7s$n|RJ0K08;!)!sTf|&VInp9;bvJ`ntG4paYvOs@AgBEKDaEAW)o8lKw!*Jef zgBB8whSj3c$4;B~A9a8Sm>zYvuT!dhE#6x!ON@7)h|Ro4LFO0d`|Oef{E)`~I27mY zqL`%m?cPPp9Omg+%6Ojg#n$jW(>Zs4wcRk=Xx}>8=@+WJ(zTM0gVN}`p)@7-ZM>?_ z1!v1W$`!`LpEEl#D=<>D|BIsI+q*htKvLTN(0%iMlc zeSgF566w4>z|Y>E^Ecyj7<GbECX|ZyJyb$nW&=akJLj*q3)bE~Ig^z)l6SEnj z<&~Uo(`2fkY&e+)0OF>3Q?6~PYbC+h;uZH~D1ap`GO>+qE~=654?4g=5DW*%YF_&5 zGKBkJ3DHK##pt z=VIwe@ft9260TJfOz4uyt5G)%Jnwp8f2=xB;OlVNQt|Ii9T{Ft|6N7X2|W0$<`PxoS7)H=gR@9feR@%xX?%~h0^>M^)n;E&X`v3`A&2q z_Oi+Iguc?wT@NH9rZhXK5v^NK|%6>y<06eS0naOtNhwPnwoidD1G^91md1t zgrC6na0W~5=MYHtx>va5?sRpq=exx@$ISQ|HX+|?TE?b};m%<=8kRE22jt-|Xoi{R z%FNT0dVTRF_Bl!TIrC~Hpga<}_SwsMfTY%ZK#_?`(U|9crtcaxIUEnbkD79;I}>+Q z8MD3T;`~ex{1%B%92^7rk0FDhEQPZ<4{`1TP#T#C1J08SW%Py}A?jh!-A^x}!g8Z5CF zf#Gs43Ayj;6TET$y`KPG*5#BH(s_IL#}9i0+E?Wd58u(K1;9yzcwvWYePh!*N{c;F8{YLw z33cKJB&qBHk_s}j9?~ZoFwk`F2Dq%_VS0kg2T8z;^%+sqG@rIPqSL};szaP19cWMn z1Ao00H@syo(5!EfAhFUBbIWxo_*nA2VbU^_ZWYh~C4|>L?-R)$UCkx_Bu)aZa~1FO zt7`%eJ9V){_^TCMEOPI^Mg$yuu3nQSz&OA&K3kZQ~**M)amW-N7|Uj08{rf$yWd zs@Z;A#rgUySDt&Hb0+91%vRRacKKo=c!Vho5T^*pdRl?PeNF_DWXgvQPYPh1nLxQn z=n1IEP&GbntLU_W#J*y34$PSh;9Yq;1L%j3 zZz;0pYNy#5D_#gp5-#F3FJ>Cy@H`-tKMXxzoKZaRs8OGBx}9Y$SMZct11V<`vV? zl!X|*RwTZP* z>E_#B43f@ifGEbpr$=-w<>9unmbP4cJgyBzWa--Is+IR=zezP)K~a`sq!Hj6fv z6e^5qyr=*akdnmwPhE5<%W_CtFvXp+^TgqFhBsPV4WvTqnc)*ZW4&fg`EX^t%igB^ zjR?!?{O4xr#00HswoXmQ-{;hKKU2}q;azqA_@dh0rg6Uq!i*yz`B)=1``w2(_=T^8 zl)q>)iw!W-)i--R^W>0b%%R~B!9=}!r-0=*jRbVS^_2qAhj5^YPZY&Z%1ugF>X!`W zn4j4V%l{0V*x-l-zUlq>-HlfrBp=4<&HCXQ5m z`8g|XqtSyUw~fEBcKXg)BeIg0+oy~kIp`C6ky0oiWiSDAc18SkiR&&`y#?kn5k5LP~9=&BbC(l4tp_AVYbe{ds-kon-0#Yyji9_wTqoMsyYL}|p z-}l_s{dI|duJ!g7*#G6hiT_;s?~S&TfTTtns2fATfP@SpYwq)?=)*2{QJo<9GVyC+T2=97@e)ur4mJCpk&3l!Ztn(|c zOEbOtgjgo#`Bs*FZ?-EQ!8o?cyE0qno!-f`rTh&1U2 z1{wfeJarc!ONhHz9#RZ@Nq91k1|}X4PV(;>kTB~8K}lwI-jTO~r9{qfIe%+`Au@=9mc8iqoywASKVLu0i) z=SwY!^RAfkX9zpfbx^r7;PF}i(GAh%=U0j}0e0@yu}E~E$i}Rn90O$D8t>F=E0!## z#o6^61j&LrY&WFEnCh@q41fx%eS$eChsE9+3LQVc*qO9lh{=fuy$Oxnro7e^Ak3ze z&3UFlF4H_>c6J%|DX?#>nGzlh{*IRYcq$ zc=K5s9<&NL6!qToY~C{mu4GySSjY;C&hfTjgjLew6T!Pc|8dNMI)b{-+aW_a7gS@R zx~rpsrrnR7?we1yzx?E*Eb(c>OuB=B6!&RKcRc3Q;|Se~(IdpsL=i`SsB4HkN61J) z8BB03Bgl>|(&-E9!xZxHa?*=4dssQjFR@omULVS1a|rW}6jz1->D2S;yTH=!M$X;Z z-H_n9?SGwtNyFQ{F7GmnXJ0L8{J30jquwbAh*%Ot2vaE$xyPS`G~Nt60HpXNkMYr; zQHO)qpZdh~{q)MK!t>!`@B4rn(MD-{p(zNAz9+#jlNVJmC#?EG*Os_-2hx4w+-wqM%Yq1)PcaF!{KVh!x*9vBH3mG} zEN{-DVrhZ>{i?)IsI6eiMFP!RdoO4ZD&w3j&fj1*?q9+{T?50w4Z^dy{1G<$mN^^p zE4741g6Bx1p(44TlgIAEaOOBt__A2nkygo2WCY^N8nVl5AQ8WAbG5Q;MV_4)e&2|o zfFg6zVkyI?wjogNKYr#yksscdvgVt`UO9C|d%(x$V{1saE~3%{*wm1`7^K)__q31p z`f^D5$1&J3%!2m(~P-oZ=v68GH&~?^*h26MIh`r=x7!EfC z$p3t+09_<{d~Af3QS3~r+xNzMlIt#cV(k%LV*=fv{G&*yoT8CP3*T0GFj-{F5pBUB z4GCupw-eFvg5(3cFF~SX7GSNpWuQHzI}0%o{$ROTDvw!q_G9tc)dpX{;j$8m*%ris z+n)8LoKN4*c0;<$!zAz3%u$`15+FQfL4{%ENr@IIfxn=3uB@Em{tqQBOY2{?d_I)~ z_c7ydr%Z{Rn&FCuU*QvNZ*4TH4e3)zTrEu3ZkJM@5twi(DK|bsu2DHH)mz|n9k&!A z_AVJqE*ylpjreO9Za_4ivR@_rvtXjzgVt3bQW&%zHmLxda@LobrxHU3d(1AiJFwKyToD_6aU; z?9Vyj;(IJIgc6Zb;Q|qIpPnsjQAl}|4~a`*7AwVbEAo8&o?dyWb7zE8wm?vmPx&fH z_J-}_E}29t#@)hpR81ypYSf~8y^WyYmDY1{NBD~hg(es}RE<0$Ik(87&~VuiHX?OE zFv;r?5vpubqGe8oyUR_8ModTKMTan}<81=O>+-%%OTM_3E?$S4EXLX46pv-~3eiRA zxy*j-!KP0l-yY>jXJ)p}Ej7>cT4A}(H>xQD8c1$0RGvPBA$0}yR?a(DuFZo|L62&y zn*wvIcx~Tz4Z>|-FVIZVCv-5nrLM++Tv;=Tyo3`^bmB-yHCR4Yc!W21*QbxALPaKK1 zs7I$^nD#?bjAV;ryuh7fF*AqxHTTwZpxweA@sX!gH~F>5m2L{NFY}Rh3s!?cK(a<3 z=n<(@81bX;IA<<@)$mGsb>{3xEYr=7gaq?u%g+K0MYftGXu4VEGO4B*as4bfpuAaa|2>y`HZ8e`Bb`;~9rrZlN9TY4=2#i=h6vgk997~x%1_AAj z=Si|I`G+_dLy*TfDW$54bW1XG91S(T@~^HPV8g7YUvzYb$sOXCVGj=o1uDOwRgX3U zzg1%ST(KcB+}%|;f2JTv5PIxyh^a^SoMJLU`aq!f|g=Y6Pq1E&y? zzvaob#%I{RcR4IeS%@&c{a0tq8h*r1uv?_fmJIuJ=a$bK$P+WQ=91nWq*XQ2yv6cz z-Z{@xTg(019S@6pT>;OWqSW75$ob6NNt(&C;CM0#x;1Xr{~UcqHbqINS==ena~*Uh z6vV7A!$VIo%O#VOd5O**T`9cXjv*~b&`%?{+I#X__{=LP-pz;E3Np{96!fh-ODP3S zc~dB43`KYF2ea!`>l*N7Z#?IXTiCL8qeK>7ISo4T`KyIczlq|$TNwD;&{iiKU1ZN^ zlLXT^56~*t7bl$Xm90j@&_TRqPM&lBKKOAa&^DpHNkkR9cSI)Mt>-kcjeb+-sv|$E zZ(G#uF)Xy?9wGL4)Nvc-T9HE8*c%DwKIcPBnz+znD>fsa*?Ln~uJ)KrL9RU>xr(r9 z%}+?5lO4)q^o0_j=ew;D*U8kX2*L4xo^X3hKrOt-`!+PoYI=UYfs~A>NNARIYAIIs z1Kj2r?O4Zb)OnMI^B(boZu8lnUsM$yHE90j9rUPg86E?Z8~`nErG{xunk?0d$ zlVqsY(vUsc^mJu=WX=}|2MHv2%guCG03eiQt( zUf2)NMqq(qhRSK(PWyf^6(*xnW;negyEZ*S3dD||e#kX?Nv5Un+H!cK%mdGC7Pj~+ z9IUuITivzQOzj=(HCU$68UB0TiIHx+KlBWCY7*otb?*_=Hfe5(N}oupti$`P%0Mcd zBu~$1oyxB)0fmNB8csSe=V$cYA0rBKuO1+ZRUhAPMW_{Qz{V<;Sct@hoX^lfn?j0CXEw@S%; zSFe?yM@9^-!%r&C@G<%l>Enxf46>vYTwbwX4Et#l=i2{HQx?zSyIyO^(uSL0!*|lQ zMj6^kXX`Cg!jGZG=RmK6K%-{zr4QC!{Ex$m`Hj;KW4`sUJeV0L@YFpuuCyhLcPyh- z&IpUD4T<<}GN4&ommq>@|OJoch-1gSFYk-00lq+jN>Irw z;J%F!Lb_amRgdjo($^rc+bVF}KF}hdgd)Y>N>S>onV#JAc!`?;7B*fAqp-|7rOOig z6&}x~$-2ESe&z%3pq$Ll0p*kodxWjOl|j!4{()x;v4E2(LiDt2bLk@7A}C}+2qPnE zNdaOf=gj;w8DUGIIs;En2-9zjvSa$ng{ALdV-KA<*XK}sAm|M?uiGkULBrmov+YyL zP?&=3gL{BUJirfLOkV!#rbv>t;z8C-gHgD zJF1RL)~+(|r9a2U--jg<$GhlJOoGS9Vq00s$#o>GNHoNaR9lYA*oQgmhw<~oOYp?^ z5)P3?QYFKSVX)&(*Z7U&QHh?rufVqIzSD^=pquXZ-JC)B?u8$J%MBaa99ml)+^rn2 z&U-D*)`Gr!QYv@O`@+aN5U@Kbat*O6wic0HgqMk`883SHONJvoXh^dbin7fQA4EhB zNn(kdd;K791tG3$J|IB{yBVMyPaE^C6|`L~tSuGFVTs@Om)yoo?#78%@?y&(906c| zVbCD3V?R|0uP+HX$Dd)dkWS^PsKKJek0msC_LdJh>loHa)GZ~sY;|i9A_4-$eHKNT z)B1%&)b<6-dZ`${VQxyk(WE#Op{baHU8%twc2J2W(Rw_QwBm`7naLO$?YP`5gxL5t zL~ts-JdkiQFo`^CLADAURC0tRR_>DA0=y9FKP>|-R9x4`!k-~#I!V|X&-hh7Z>2iU zy=;e7u-V9xXK*whG1PVF;3VQKPkcR(aNvO@bx(tIsNY|weSMcyrJ~!_*6h#p0;8Ij zton1^v+@Gu|-z_uUo?!IfI{syN3I21galid|Dlj z2a}o|&JjIxhZ^vDYie`7byYWQtfBrDf_95f;wpU$C>_sQ9fm3gfW$G~kQ)gm%n=yv ztr$goexPphlPoh_=Q$RncENV2hHQ`(uXIF;CPlNi`l|0C4do{+(v2Y(iI5*h z=inpXsC(|hHL4RU>a~UETd3rd%|q^1?>`fACl)6R0oy#f1C&&mwDJt`!(X|M5Dt}G zZq5(wtia!RQ-d-nI4T~fG(BR|)iNTeuoSXQ65j<=<@0u;kf+=ngq6wj+{jFA9ORRB z)okg1^#=9%YvQhf1JtvH1cV{7M76T^89p&_;M5C`==whYmY*ZJvo~^j8^>Fzb6x zhbrdz;*`~ijz8ft8n->_K}zdv!rzX|upZz%`8k3~ZuDTFQ-cz$EnWRki^AsY(4a)} z)>UrXF1>N2hotbxus|nDBTg0Pq=7dLQXuICR|)M_&Q6mI;~ghh)QC4b9>g~|I~*;! ztU);9sutVG%Ghny565NNo_5Ci9t6egk~NQNHk|puc=}L@)*V%>hVAVIOE~GiCCSH< zn?0f34v_9j&C_uc@-4`}C+W;Hi$aY`tw_+C@$fIl`ZaG-7~oeiuO?3@#?k7}tQmFL z#{V7MR1050ZB@`>0u`_b=cLSBTfN!I6y|&Qp7>i;XGYdPvsc}$96Kp8P3~T(U>(P6 z9+|ErnZi{)o&OFY6!@?-ognaSHra`SI* z^4+dJ=&a%T(rUBYEIyGa)0Gq8p8Rc_f zLKf=gs@zba@M%OpRdG8g``&Tx4*$#{Z`-;;Ezw#qBJil)=zRvRJeO}g>f@nuqT#&RlVGFfZ{ zO>ek`;f>~ckE$~Lo)V~*yNQO>g%pGKzuQw^?K;H;CW#405Ap#`ukVqsd*GUKOLf~V z4`9J{`n1xu@1yS-%mMaWS&mlOfC|BiNi{x4MlXpC?sfnxWvmDN!+CIN^l=g1v!-PC z^o{*P`U0dA!6YnVpk9C%C!7_VYVnC%xB7*hD>qLTifeR4h_gxX8kYp+)~(E39nCBpK~q{5oN*Sh{5g7?=87;v(9W-95Q&e=RAZFyV&SNX!#x!u*i=x=>o< z9b_5FBnWkB>8*UZ|B|4fSF$nR%LKnFnX*%G{P+Yi3CyYOGMR_6E`hNMHcV;Ykv@Nl%qq}|gcTWrje%zT2kr%|^Uqrfx!&RYE()D<2KbI>5-X#mRggHZ z;^z0(p5b}<2%dcS@`(vqjz?Wu1>w{9V{ffEDww560#;n7<~SQ+z(Dc%rkJUC$39j< zTs&g&G5dZYnHOsf?!c3DWVi)f-UwtrW7dregqxl&{#rPXED5O5gfpgx(vMEPl}Za~ zIfL_&KwT2Gdj}&A%>o`DP{6rKaH8pTl1e6JZUqpevLoBo>=r=tW_1XzF3=v1U90_w z!vs2)-MKO_A#KMeV-q|g{;kg9{5Pz~*G`U8rM9RY-Nv#2Ju5s&7VAg#aVuNw>qd>L1%^e|2`h4cjLc(*}{vo?z z5bD`XpT(k7R>WAn&(+m@woq7(9Xh#xI{T@cA`cqUN-D*Kl5ec&3`y2$wkh+G+Bju} z+J)Q)icWkEnL>-r^Rn_NG{|+f+>lwkv7AFWzUA)qu$o8lcbV#W{F^6voAmy{{GPt8mufMrbW1f2jI}! z4{cPzs)>!TNB85zQXA7A47CTTcL|W@f^8+`NIi|^VAw|eclQ1g$kp&IT6SyXAW%-cp)qZY7C?3{E+>%?1ZDo}GwhdpqqlkNN- zEf=Pp`LI6Wegf$yU+;9azCU}R-A|zHHa6BuFnL;(`Pu0F*=f{C;zi_5X`5igM+Ak` z+sbr%rjtb>0uh5ZDN?A*D{?Kz4U%N#k#7^yLIW1iSy#w!Q@4Fyg(4QAVnmn4cVAv_ z(Ny;$@+Z%lUF<83Dkc}RSptzB4XC>^o+rCSCq?Pr-BOkbeP`vf)VR)No!yR@ylei3 zC+w<2lpQ`UDv38GhhSk`cT!`zvIYxRPqqq1Sl)5ClXS`_KM#YNT-vvgQD8LF7+gL6 z18}44jr}cB_lChbJ`VSgE8atn-c>-DFX<6ro;QWhWTcqC3MnY+$lPLE-QQEr`zYMe zGok?frnnK^**f9?x*1D+((5c_o?akMkKQQs2r3OANUX~!5SGX;#BSYQ3$dk1e7~e1 znG+;^aXqh70<H3xtj)ohHAcy49{&t^@*xsD2vs`b&K8WZm_ zI6Mr|^&q&cUjG1bO<*dxlM?6CuCc6vQK}d5%BROkrgoa+d)MyQ!sSixm6kYY5O^3J zkm}91l;hP7b*eDBA}3=!F1?y~&5MsNDE71LdNi@=^W@qsY|Y2B%vFrOw;~q#ef%|u z8WHeMoTj$X8*yb(rR>wJ%e3!K)xpVUfg7ylw3EV@|}_PoBV9??FmKJfYCd_QXf zx8|eZfHKm!Stc{L&Bx2Wm7@jPl-W9w45GSq^6(3BV=v7ai?$mco(PKs=MRm-8Sln<9#ZE;E%vW?t0#zF`?QGC@6xOhM6z941CP^u=*j?k%7QDFG$}6yJRNp>p zopP=v?b%3$LY=;bmEpsg3YV9|*i2sh3iLR-?H7>;haE**r()!au6UgS>QirzcR-Cy zJ;5s5^PEK$B*(Og{Q@X%)h|p^1ir%MJ?#rJ9;CQ+rA=SF|@A~ z#Y3TNBvGb~Wez8lN%9!23hF6o`GD1pI!K|fOq&lT9pk~1`G&MogTWiMhm9-1SRt&PNnu=GPw;(C2C*ah(I*d=`v^t zda85#?~dMkBVf@}0y0EzW#e#kge|Bhk_)7g5vS<=)%G&MPi0yUL8G`?=I58Pa`d($ zlb=QP(GiQ^TPC)+X+`Bh&g_Y6llfj#J%PCjuD{x<{yn0t3xYsFxT2 z`SRC(>fuz&P<@6vJwQJ&HE>X4B-IJ1>3|vusOf;34yceBum);6pr!+=6HwCuH4;$M z0W}>^Wi&`hq+$XpCZIY26%$Y+0TmNa(*YF|P%#0uzPeLlrUFAMFr)&*|I@&*nu0~~ z90b4A<`^qs_|0Aswq+(eIMZQTM%7y{p{BiGT_ey&?k*UtclosHpL?kX|866yHK^90 z&XhkR1T}8_J}0Qwpjv~0nkIgC0;)Br)}SU$YSM&e0hN0E>ffhYgUU2_VgeO0{+=GG z)}UI0nkN1b=BP>Y_bEZO2Gtr=HbG?*&@A}xWfRMpgY`5ttTJjUm+$^lr}^)_S?Uc^ z>ux(e7hq0P@&E7XooWrLHU68^95wP&BR@6rQ@e5gex=QSH>9V=I%=$=#yV=O`~M`? z`GcNM3WdpH;>v&CDA-qXfabz}y8Ky;cr5;e>*EJMM<<})hxCWmJW9t-!7PAZjr`|s z^AQJzf4^w#UUl`0yqh$?*E4~8S6Bbo{XciOnYv8FR+_g>R&@=8r!uWwy}5Dzb~*KX7}ekGO>Qy z#{vI&WbeN(zyTll!$x3q&)F|Hj0qk7_XRFEpnvBp&}|$OF&X)yd-~rOFd1>6|8;l& zSnv|npj3nY?mkox{d0a%r#v-={+=1AG4#*eOHE4DEdK{~p~9v=Kpz!{QgP_-_=<@N z?Ej=CRHj6wp1%`nDwqG0qEop%RUG+)l2X;CKkz0URlcL@p}&Vrsvi0W-lPi5RK@;x zeC0~0-=^b{(~r}@*P#a`;Rk# z4XS+iI~S$4;!t~={wN31(oy9*s(km0ZBVHvwVm;gYABU@QmN-JazX7Jp>~e^N)$Um zg4#LqN35jw*i#oR{4PsTspqfE@lVvCc8*XxNB)3QRO(5kp1%+Um3mUC=O1v2N){2Wl4ywTt9G&Oq%Vp}x@Ncj!%hLk{(oL%%oCP!}-#QTCuNV4%Jf>rd&5D&_rY zkN@%o42yGpq6cVbXg*({ Date: Wed, 27 Aug 2025 15:04:16 +0300 Subject: [PATCH 3/9] Support custom colors in ChannelTitleView (#931) --- CHANGELOG.md | 1 + .../ChatChannelHeaderViewModifier.swift | 1 + .../ChatChannel/ChatChannelHeader_Tests.swift | 17 +++++++++++++++++ .../test_channelTitleView_theme_snapshot.1.png | Bin 0 -> 15461 bytes 4 files changed, 19 insertions(+) create mode 100644 StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ChatChannelHeader_Tests/test_channelTitleView_theme_snapshot.1.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 26a4fbde..20935c4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### 🐞 Fixed - Show attachment title instead of URL in the `FileAttachmentPreview` view [#930](https://github.com/GetStream/stream-chat-swiftui/pull/930) +- Fix overriding title color in `ChannelTitleView` [#931](https://github.com/GetStream/stream-chat-swiftui/pull/931) # [4.86.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.86.0) _August 21, 2025_ diff --git a/Sources/StreamChatSwiftUI/ChatChannel/ChannelHeader/ChatChannelHeaderViewModifier.swift b/Sources/StreamChatSwiftUI/ChatChannel/ChannelHeader/ChatChannelHeaderViewModifier.swift index 2931fb8a..76add56c 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/ChannelHeader/ChatChannelHeaderViewModifier.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/ChannelHeader/ChatChannelHeaderViewModifier.swift @@ -145,6 +145,7 @@ public struct ChannelTitleView: View { VStack(spacing: 2) { Text(channelNamer(channel, currentUserId) ?? "") .font(fonts.bodyBold) + .foregroundColor(Color(colors.text)) .accessibilityIdentifier("chatName") if shouldShowTypingIndicator { diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/ChatChannelHeader_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannel/ChatChannelHeader_Tests.swift index 5e768b44..00558215 100644 --- a/StreamChatSwiftUITests/Tests/ChatChannel/ChatChannelHeader_Tests.swift +++ b/StreamChatSwiftUITests/Tests/ChatChannel/ChatChannelHeader_Tests.swift @@ -76,4 +76,21 @@ class ChatChannelHeader_Tests: StreamChatTestCase { // Then assertSnapshot(matching: view, as: .image(perceptualPrecision: precision)) } + + func test_channelTitleView_theme_snapshot() { + // Given + let channel = ChatChannel.mockDMChannel(name: "Test channel") + + // When + adjustAppearance { appearance in + appearance.colors.text = .red + appearance.colors.subtitleText = .blue + } + let size = CGSize(width: 300, height: 100) + let view = ChannelTitleView(channel: channel, shouldShowTypingIndicator: true) + .applySize(size) + + // Then + assertSnapshot(matching: view, as: .image(perceptualPrecision: precision)) + } } diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ChatChannelHeader_Tests/test_channelTitleView_theme_snapshot.1.png b/StreamChatSwiftUITests/Tests/ChatChannel/__Snapshots__/ChatChannelHeader_Tests/test_channelTitleView_theme_snapshot.1.png new file mode 100644 index 0000000000000000000000000000000000000000..6c198bafec039afe7d3788473b0eeefb71f3723d GIT binary patch literal 15461 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 literal 0 HcmV?d00001 From cf8b7ec5a2cfc67508db1dd582c4e3b59cd2ffbd Mon Sep 17 00:00:00 2001 From: Martin Mitrevski Date: Wed, 27 Aug 2025 16:05:48 +0200 Subject: [PATCH 4/9] Add option to scroll to and open a channel from the channel list (#932) --- CHANGELOG.md | 3 + .../ChatChannelList/ChatChannelList.swift | 16 ++- .../ChatChannelList/ChatChannelListView.swift | 1 + .../ChatChannelListViewModel.swift | 43 +++++++ .../ChatChannelListViewModel_Tests.swift | 120 ++++++++++++++++++ 5 files changed, 181 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20935c4c..5817c18c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). # Upcoming +### ✅ Added +- Add option to scroll to and open a channel from the channel list [#932](https://github.com/GetStream/stream-chat-swiftui/pull/932) + ### 🐞 Fixed - Show attachment title instead of URL in the `FileAttachmentPreview` view [#930](https://github.com/GetStream/stream-chat-swiftui/pull/930) - Fix overriding title color in `ChannelTitleView` [#931](https://github.com/GetStream/stream-chat-swiftui/pull/931) diff --git a/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelList.swift b/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelList.swift index 6db82261..5df8b692 100644 --- a/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelList.swift +++ b/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelList.swift @@ -14,6 +14,7 @@ public struct ChannelList: View { var channels: LazyCachedMapCollection @Binding var selectedChannel: ChannelSelectionInfo? @Binding var swipedChannelId: String? + @Binding var scrolledChannelId: String? private var scrollable: Bool private var onlineIndicatorShown: (ChatChannel) -> Bool private var imageLoader: (ChatChannel) -> UIImage @@ -30,6 +31,7 @@ public struct ChannelList: View { channels: LazyCachedMapCollection, selectedChannel: Binding, swipedChannelId: Binding, + scrolledChannelId: Binding = .constant(nil), scrollable: Bool = true, onlineIndicatorShown: ((ChatChannel) -> Bool)? = nil, imageLoader: ((ChatChannel) -> UIImage)? = nil, @@ -72,13 +74,23 @@ public struct ChannelList: View { self.scrollable = scrollable _selectedChannel = selectedChannel _swipedChannelId = swipedChannelId + _scrolledChannelId = scrolledChannelId } public var body: some View { Group { if scrollable { - ScrollView { - channelsVStack + ScrollViewReader { scrollView in + ScrollView { + channelsVStack + } + .onChange(of: scrolledChannelId) { newValue in + if let newValue { + withAnimation { + scrollView.scrollTo(newValue, anchor: .bottom) + } + } + } } } else { channelsVStack diff --git a/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListView.swift b/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListView.swift index 15524791..49b1671c 100644 --- a/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListView.swift @@ -234,6 +234,7 @@ public struct ChatChannelListContentView: View { channels: viewModel.channels, selectedChannel: $viewModel.selectedChannel, swipedChannelId: $viewModel.swipedChannelId, + scrolledChannelId: $viewModel.scrolledChannelId, onlineIndicatorShown: viewModel.onlineIndicatorShown(for:), imageLoader: channelHeaderLoader.image(for:), onItemTap: onItemTap, diff --git a/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListViewModel.swift b/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListViewModel.swift index 04dd1908..d97b1769 100644 --- a/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListViewModel.swift +++ b/Sources/StreamChatSwiftUI/ChatChannelList/ChatChannelListViewModel.swift @@ -39,6 +39,9 @@ open class ChatChannelListViewModel: ObservableObject, ChatChannelListController /// Index of the selected channel. private var selectedChannelIndex: Int? + + /// When set, scrolls to the specified channel id (if it exists). + @Published public var scrolledChannelId: String? /// Published variables. @Published public var channels = LazyCachedMapCollection() { @@ -180,6 +183,38 @@ open class ChatChannelListViewModel: ObservableObject, ChatChannelListController } } } + + /// Opens the chat channel destination with the provided channel id. + /// + /// - Parameter channelId: the id of the channel that will be shown. + public func openChannel(with channelId: ChannelId) { + func loadUntilFound() { + guard let controller else { return } + if let channel = controller.channels.first(where: { $0.id == channelId.rawValue }) { + log.debug("Showing channel with id \(channelId)") + scrollToAndOpen(channel: channel) + return + } + + // Stop if there are no more channels to load + if controller.hasLoadedAllPreviousChannels { + scrolledChannelId = nil + return + } + + controller.loadNextChannels { [weak self] error in + if error != nil { + self?.scrolledChannelId = nil + return + } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + loadUntilFound() + } + } + } + + loadUntilFound() + } public func loadAdditionalSearchResults(index: Int) { switch searchType { @@ -543,6 +578,14 @@ open class ChatChannelListViewModel: ObservableObject, ChatChannelListController markDirty = true channels = LazyCachedMapCollection(source: temp, map: { $0 }) } + + private func scrollToAndOpen(channel: ChatChannel) { + scrolledChannelId = channel.id + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in + self?.selectedChannel = .init(channel: channel, message: nil) + self?.scrolledChannelId = nil + } + } private func observeChannelDismiss() { NotificationCenter.default.addObserver( diff --git a/StreamChatSwiftUITests/Tests/ChatChannelList/ChatChannelListViewModel_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannelList/ChatChannelListViewModel_Tests.swift index 8e8d0ab0..2082d373 100644 --- a/StreamChatSwiftUITests/Tests/ChatChannelList/ChatChannelListViewModel_Tests.swift +++ b/StreamChatSwiftUITests/Tests/ChatChannelList/ChatChannelListViewModel_Tests.swift @@ -372,6 +372,126 @@ class ChatChannelListViewModel_Tests: StreamChatTestCase { XCTAssertNotNil(viewModel.messageSearchController) } + // MARK: - Open Channel + + func test_openChannel_whenChannelExistsInList_shouldScrollToAndOpenChannel() { + // Given + let channel = ChatChannel.mockDMChannel() + let channelListController = makeChannelListController(channels: [channel]) + let viewModel = ChatChannelListViewModel( + channelListController: channelListController, + selectedChannelId: nil + ) + + // When + viewModel.openChannel(with: channel.cid) + + // Then + XCTAssertEqual(viewModel.scrolledChannelId, channel.id) + + // Wait for the async delay and verify selectedChannel is set + let expectation = XCTestExpectation(description: "Channel opened") + DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) { + XCTAssertEqual(viewModel.selectedChannel?.channel.id, channel.id) + XCTAssertNil(viewModel.scrolledChannelId) + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } + + func test_openChannel_whenChannelNotInList_shouldLoadNextChannelsUntilFound() { + // Given + let existingChannel = ChatChannel.mockDMChannel() + let targetChannel = ChatChannel.mockDMChannel() + let channelListController = makeChannelListController(channels: [existingChannel]) + let viewModel = ChatChannelListViewModel( + channelListController: channelListController, + selectedChannelId: nil + ) + + // When + viewModel.openChannel(with: targetChannel.cid) + + // Then + XCTAssertEqual(channelListController.loadNextChannelsCallCount, 1) + + // Simulate the channel being found after loading + channelListController.simulate( + channels: [existingChannel, targetChannel], + changes: [.insert(targetChannel, index: .init(row: 1, section: 0))] + ) + + // When + viewModel.openChannel(with: targetChannel.cid) + + // Verify the channel is eventually opened + let expectation = XCTestExpectation(description: "Channel opened after loading") + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + XCTAssertEqual(viewModel.selectedChannel?.channel.id, targetChannel.id) + XCTAssertNil(viewModel.scrolledChannelId) + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } + + func test_openChannel_whenChannelNotFoundAndNoMoreChannels_shouldSetScrolledChannelIdToNil() { + // Given + let existingChannel = ChatChannel.mockDMChannel() + let targetChannel = ChatChannel.mockDMChannel() + let channelListController = makeChannelListController(channels: [existingChannel]) + let viewModel = ChatChannelListViewModel( + channelListController: channelListController, + selectedChannelId: nil + ) + + // When + viewModel.openChannel(with: targetChannel.cid) + + // Then + XCTAssertNil(viewModel.scrolledChannelId) + XCTAssertNil(viewModel.selectedChannel) + } + + func test_openChannel_whenChannelFoundAfterMultipleLoads_shouldEventuallyOpenChannel() { + // Given + let existingChannel = ChatChannel.mockDMChannel() + let targetChannel = ChatChannel.mockDMChannel() + let channelListController = makeChannelListController(channels: [existingChannel]) + let viewModel = ChatChannelListViewModel( + channelListController: channelListController, + selectedChannelId: nil + ) + + // When + viewModel.openChannel(with: targetChannel.cid) + + // Then + XCTAssertEqual(channelListController.loadNextChannelsCallCount, 1) + + // Simulate first load not finding the channel + channelListController.simulate( + channels: [existingChannel], + changes: [] + ) + + // Simulate second load finding the channel + channelListController.simulate( + channels: [existingChannel, targetChannel], + changes: [.insert(targetChannel, index: .init(row: 1, section: 0))] + ) + + viewModel.openChannel(with: targetChannel.cid) + + // Verify the channel is eventually opened + let expectation = XCTestExpectation(description: "Channel opened after multiple loads") + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + XCTAssertEqual(viewModel.selectedChannel?.channel.id, targetChannel.id) + XCTAssertNil(viewModel.scrolledChannelId) + expectation.fulfill() + } + wait(for: [expectation], timeout: 1.0) + } + // MARK: - private private func makeChannelListController( From a4d06da3866303b2902e2456b0aba8d113a3bb4a Mon Sep 17 00:00:00 2001 From: Toomas Vahter Date: Thu, 28 Aug 2025 11:28:42 +0300 Subject: [PATCH 5/9] Use channel capabilities for validating delete message action (#933) --- CHANGELOG.md | 1 + .../DefaultMessageActions.swift | 6 ++-- .../ChatChannel/MessageActions_Tests.swift | 35 ++++++++++++++++++- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5817c18c..ef34ea6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### 🐞 Fixed - Show attachment title instead of URL in the `FileAttachmentPreview` view [#930](https://github.com/GetStream/stream-chat-swiftui/pull/930) - Fix overriding title color in `ChannelTitleView` [#931](https://github.com/GetStream/stream-chat-swiftui/pull/931) +- Use channel capabilities for validating delete message action [#933](https://github.com/GetStream/stream-chat-swiftui/pull/933) # [4.86.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.86.0) _August 21, 2025_ diff --git a/Sources/StreamChatSwiftUI/ChatChannel/Reactions/MessageActions/DefaultMessageActions.swift b/Sources/StreamChatSwiftUI/ChatChannel/Reactions/MessageActions/DefaultMessageActions.swift index ac5fc515..8e29a81e 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/Reactions/MessageActions/DefaultMessageActions.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/Reactions/MessageActions/DefaultMessageActions.swift @@ -156,7 +156,7 @@ public extension MessageAction { } } - if message.isSentByCurrentUser { + if channel.canDeleteAnyMessage || channel.canDeleteOwnMessage && message.isSentByCurrentUser { let deleteAction = deleteMessageAction( for: message, channel: channel, @@ -166,7 +166,9 @@ public extension MessageAction { ) messageActions.append(deleteAction) - } else { + } + + if !message.isSentByCurrentUser { if channel.canFlagMessage { let flagAction = flagMessageAction( for: message, diff --git a/StreamChatSwiftUITests/Tests/ChatChannel/MessageActions_Tests.swift b/StreamChatSwiftUITests/Tests/ChatChannel/MessageActions_Tests.swift index b137dec2..f693428d 100644 --- a/StreamChatSwiftUITests/Tests/ChatChannel/MessageActions_Tests.swift +++ b/StreamChatSwiftUITests/Tests/ChatChannel/MessageActions_Tests.swift @@ -374,12 +374,45 @@ class MessageActions_Tests: StreamChatTestCase { // Then XCTAssertTrue(messageActions.contains(where: { $0.title == "Edit Message" })) } + + func test_messageActions_otherUser_deletingEnabledWhenDeleteAnyMessageCapability() { + // Given + let channel = ChatChannel.mockDMChannel(ownCapabilities: [.deleteAnyMessage]) + let message = ChatMessage.mock( + id: .unique, + cid: channel.cid, + text: "Test", + author: .mock(id: .unique), + isSentByCurrentUser: false + ) + let factory = DefaultViewFactory.shared + + // When + let messageActions = MessageAction.defaultActions( + factory: factory, + for: message, + channel: channel, + chatClient: chatClient, + onFinish: { _ in }, + onError: { _ in } + ) + + // Then + XCTAssertTrue(messageActions.contains(where: { $0.title == "Delete Message" })) + } // MARK: - Private private var mockDMChannel: ChatChannel { ChatChannel.mockDMChannel( - ownCapabilities: [.updateOwnMessage, .sendMessage, .uploadFile, .pinMessage, .readEvents] + ownCapabilities: [ + .deleteOwnMessage, + .updateOwnMessage, + .sendMessage, + .uploadFile, + .pinMessage, + .readEvents + ] ) } } From 7833297e217f74342ac9baf7d71669928adc030a Mon Sep 17 00:00:00 2001 From: Lucas Nguyen Date: Fri, 29 Aug 2025 17:22:21 +0700 Subject: [PATCH 6/9] Make MediaItem and MediaAttachmentContentView public to allow customization. (#935) --- CHANGELOG.md | 1 + .../ChannelInfo/MediaAttachmentsView.swift | 20 +++++++++++-- .../MediaAttachmentsViewModel.swift | 28 ++++++++++++++----- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef34ea6d..91ec71b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### ✅ Added - Add option to scroll to and open a channel from the channel list [#932](https://github.com/GetStream/stream-chat-swiftui/pull/932) +- Make `MediaItem` and `MediaAttachmentContentView` public to allow customization [#935](https://github.com/GetStream/stream-chat-swiftui/pull/935) ### 🐞 Fixed - Show attachment title instead of URL in the `FileAttachmentPreview` view [#930](https://github.com/GetStream/stream-chat-swiftui/pull/930) diff --git a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsView.swift b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsView.swift index 449604bf..39a2a3ad 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsView.swift @@ -102,7 +102,7 @@ public struct MediaAttachmentsView: View { } } -struct MediaAttachmentContentView: View { +public struct MediaAttachmentContentView: View { @State private var galleryShown = false let factory: Factory @@ -112,7 +112,23 @@ struct MediaAttachmentContentView: View { let itemWidth: CGFloat let index: Int - var body: some View { + public init( + factory: Factory, + mediaItem: MediaItem, + mediaAttachment: MediaAttachment, + allMediaAttachments: [MediaAttachment], + itemWidth: CGFloat, + index: Int + ) { + self.factory = factory + self.mediaItem = mediaItem + self.mediaAttachment = mediaAttachment + self.allMediaAttachments = allMediaAttachments + self.itemWidth = itemWidth + self.index = index + } + + public var body: some View { Button { galleryShown = true } label: { diff --git a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsViewModel.swift b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsViewModel.swift index 2b30405f..af15a7e1 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsViewModel.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/ChannelInfo/MediaAttachmentsViewModel.swift @@ -106,15 +106,29 @@ class MediaAttachmentsViewModel: ObservableObject, ChatMessageSearchControllerDe } } -struct MediaItem: Identifiable { - let id: String - let isVideo: Bool - let message: ChatMessage +public struct MediaItem: Identifiable { + public let id: String + public let isVideo: Bool + public let message: ChatMessage - var videoAttachment: ChatMessageVideoAttachment? - var imageAttachment: ChatMessageImageAttachment? + public var videoAttachment: ChatMessageVideoAttachment? + public var imageAttachment: ChatMessageImageAttachment? - var mediaAttachment: MediaAttachment? { + public init( + id: String, + isVideo: Bool, + message: ChatMessage, + videoAttachment: ChatMessageVideoAttachment?, + imageAttachment: ChatMessageImageAttachment? + ) { + self.id = id + self.isVideo = isVideo + self.message = message + self.videoAttachment = videoAttachment + self.imageAttachment = imageAttachment + } + + public var mediaAttachment: MediaAttachment? { if let videoAttachment { return MediaAttachment(url: videoAttachment.videoURL, type: .video) } else if let imageAttachment { From f25b9ec25e9c16ba62acf03e88b1e474b6875b17 Mon Sep 17 00:00:00 2001 From: Lucas Nguyen Date: Fri, 29 Aug 2025 20:51:12 +0700 Subject: [PATCH 7/9] Fix the video attachments now fetch the URL once on appear and pause when swiping to another item in the gallery. (#934) * Fix the video attachments now fetch the URL once on appear and pause when swiping to another item in the gallery. * Resume playback once the video appears --- CHANGELOG.md | 1 + .../ChatChannel/Gallery/GalleryView.swift | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91ec71b4..a0130b3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Show attachment title instead of URL in the `FileAttachmentPreview` view [#930](https://github.com/GetStream/stream-chat-swiftui/pull/930) - Fix overriding title color in `ChannelTitleView` [#931](https://github.com/GetStream/stream-chat-swiftui/pull/931) - Use channel capabilities for validating delete message action [#933](https://github.com/GetStream/stream-chat-swiftui/pull/933) +- Fix the video attachments now fetch the URL once on appear and pause when swiping to another item in the gallery [#934](https://github.com/GetStream/stream-chat-swiftui/pull/934) # [4.86.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.86.0) _August 21, 2025_ diff --git a/Sources/StreamChatSwiftUI/ChatChannel/Gallery/GalleryView.swift b/Sources/StreamChatSwiftUI/ChatChannel/Gallery/GalleryView.swift index d8ca2544..68ac63f8 100644 --- a/Sources/StreamChatSwiftUI/ChatChannel/Gallery/GalleryView.swift +++ b/Sources/StreamChatSwiftUI/ChatChannel/Gallery/GalleryView.swift @@ -168,6 +168,10 @@ struct StreamVideoPlayer: View { } } .onAppear { + guard avPlayer == nil else { + avPlayer?.play() + return + } fileCDN.adjustedURL(for: url) { result in switch result { case let .success(url): @@ -179,5 +183,8 @@ struct StreamVideoPlayer: View { } } } + .onDisappear { + avPlayer?.pause() + } } } From e26efa67b0dce1274c0d0fd3ea99f43358c6d998 Mon Sep 17 00:00:00 2001 From: Toomas Vahter Date: Mon, 1 Sep 2025 09:41:32 +0300 Subject: [PATCH 8/9] Update StreamChat dependency to 4.87.0 (#936) * Update StreamChat dependency to 4.87.0 * Add new updload method to the mock --- Package.swift | 2 +- StreamChatSwiftUI-XCFramework.podspec | 2 +- StreamChatSwiftUI.podspec | 2 +- StreamChatSwiftUI.xcodeproj/project.pbxproj | 2 +- .../Infrastructure/Mocks/CDNClient_Mock.swift | 6 ++++++ 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Package.swift b/Package.swift index 2aafefab..1d5cba5c 100644 --- a/Package.swift +++ b/Package.swift @@ -16,7 +16,7 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/GetStream/stream-chat-swift.git", from: "4.86.0") + .package(url: "https://github.com/GetStream/stream-chat-swift.git", from: "4.87.0") ], targets: [ .target( diff --git a/StreamChatSwiftUI-XCFramework.podspec b/StreamChatSwiftUI-XCFramework.podspec index b78dad51..36529dd7 100644 --- a/StreamChatSwiftUI-XCFramework.podspec +++ b/StreamChatSwiftUI-XCFramework.podspec @@ -19,7 +19,7 @@ Pod::Spec.new do |spec| spec.framework = 'Foundation', 'UIKit', 'SwiftUI' - spec.dependency 'StreamChat-XCFramework', '~> 4.86.0' + spec.dependency 'StreamChat-XCFramework', '~> 4.87.0' spec.cocoapods_version = '>= 1.11.0' end diff --git a/StreamChatSwiftUI.podspec b/StreamChatSwiftUI.podspec index de7f95e9..157bb71c 100644 --- a/StreamChatSwiftUI.podspec +++ b/StreamChatSwiftUI.podspec @@ -19,5 +19,5 @@ Pod::Spec.new do |spec| spec.framework = 'Foundation', 'UIKit', 'SwiftUI' - spec.dependency 'StreamChat', '~> 4.86.0' + spec.dependency 'StreamChat', '~> 4.87.0' end diff --git a/StreamChatSwiftUI.xcodeproj/project.pbxproj b/StreamChatSwiftUI.xcodeproj/project.pbxproj index a87db523..fbe53bc5 100644 --- a/StreamChatSwiftUI.xcodeproj/project.pbxproj +++ b/StreamChatSwiftUI.xcodeproj/project.pbxproj @@ -3912,7 +3912,7 @@ repositoryURL = "https://github.com/GetStream/stream-chat-swift.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 4.86.0; + minimumVersion = 4.87.0; }; }; E3A1C01A282BAC66002D1E26 /* XCRemoteSwiftPackageReference "sentry-cocoa" */ = { diff --git a/StreamChatSwiftUITests/Infrastructure/Mocks/CDNClient_Mock.swift b/StreamChatSwiftUITests/Infrastructure/Mocks/CDNClient_Mock.swift index a1d40563..37c1cbdd 100644 --- a/StreamChatSwiftUITests/Infrastructure/Mocks/CDNClient_Mock.swift +++ b/StreamChatSwiftUITests/Infrastructure/Mocks/CDNClient_Mock.swift @@ -22,4 +22,10 @@ final class CDNClient_Mock: CDNClient { ) ) } + + func uploadStandaloneAttachment( + _ attachment: StreamAttachment, + progress: ((Double) -> Void)?, + completion: @escaping (Result) -> Void + ) {} } From 7f53caf946947f54d4a6cb35ef0acba6f6f60de4 Mon Sep 17 00:00:00 2001 From: Stream Bot Date: Mon, 1 Sep 2025 07:01:10 +0000 Subject: [PATCH 9/9] Bump 4.87.0 --- CHANGELOG.md | 5 +++++ README.md | 2 +- .../Generated/SystemEnvironment+Version.swift | 2 +- Sources/StreamChatSwiftUI/Info.plist | 2 +- StreamChatSwiftUI-XCFramework.podspec | 2 +- StreamChatSwiftUI.podspec | 2 +- StreamChatSwiftUIArtifacts.json | 2 +- 7 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0130b3a..847f0358 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). # Upcoming +### 🔄 Changed + +# [4.87.0](https://github.com/GetStream/stream-chat-swiftui/releases/tag/4.87.0) +_September 01, 2025_ + ### ✅ Added - Add option to scroll to and open a channel from the channel list [#932](https://github.com/GetStream/stream-chat-swiftui/pull/932) - Make `MediaItem` and `MediaAttachmentContentView` public to allow customization [#935](https://github.com/GetStream/stream-chat-swiftui/pull/935) diff --git a/README.md b/README.md index 0c93a569..c8ecc2b2 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@

- StreamChatSwiftUI + StreamChatSwiftUI

## SwiftUI StreamChat SDK diff --git a/Sources/StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift b/Sources/StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift index b53cd9a5..27d20863 100644 --- a/Sources/StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift +++ b/Sources/StreamChatSwiftUI/Generated/SystemEnvironment+Version.swift @@ -7,5 +7,5 @@ import Foundation enum SystemEnvironment { /// A Stream Chat version. - public static let version: String = "4.87.0-SNAPSHOT" + public static let version: String = "4.87.0" } diff --git a/Sources/StreamChatSwiftUI/Info.plist b/Sources/StreamChatSwiftUI/Info.plist index 21853bfa..93c8c922 100644 --- a/Sources/StreamChatSwiftUI/Info.plist +++ b/Sources/StreamChatSwiftUI/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 4.86.0 + 4.87.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPhotoLibraryUsageDescription diff --git a/StreamChatSwiftUI-XCFramework.podspec b/StreamChatSwiftUI-XCFramework.podspec index 36529dd7..3c37a188 100644 --- a/StreamChatSwiftUI-XCFramework.podspec +++ b/StreamChatSwiftUI-XCFramework.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'StreamChatSwiftUI-XCFramework' - spec.version = '4.86.0' + spec.version = '4.87.0' spec.summary = 'StreamChat SwiftUI Chat Components' spec.description = 'StreamChatSwiftUI SDK offers flexible SwiftUI components able to display data provided by StreamChat SDK.' diff --git a/StreamChatSwiftUI.podspec b/StreamChatSwiftUI.podspec index 157bb71c..005f3f36 100644 --- a/StreamChatSwiftUI.podspec +++ b/StreamChatSwiftUI.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'StreamChatSwiftUI' - spec.version = '4.86.0' + spec.version = '4.87.0' spec.summary = 'StreamChat SwiftUI Chat Components' spec.description = 'StreamChatSwiftUI SDK offers flexible SwiftUI components able to display data provided by StreamChat SDK.' diff --git a/StreamChatSwiftUIArtifacts.json b/StreamChatSwiftUIArtifacts.json index cfd95bd1..17587a6f 100644 --- a/StreamChatSwiftUIArtifacts.json +++ b/StreamChatSwiftUIArtifacts.json @@ -1 +1 @@ -{"4.40.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.40.0/StreamChatSwiftUI.zip","4.41.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.41.0/StreamChatSwiftUI.zip","4.42.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.42.0/StreamChatSwiftUI.zip","4.43.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.43.0/StreamChatSwiftUI.zip","4.44.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.44.0/StreamChatSwiftUI.zip","4.45.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.45.0/StreamChatSwiftUI.zip","4.46.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.46.0/StreamChatSwiftUI.zip","4.47.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.47.0/StreamChatSwiftUI.zip","4.47.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.47.1/StreamChatSwiftUI.zip","4.48.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.48.0/StreamChatSwiftUI.zip","4.49.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.49.0/StreamChatSwiftUI.zip","4.50.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.50.0/StreamChatSwiftUI.zip","4.50.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.50.1/StreamChatSwiftUI.zip","4.51.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.51.0/StreamChatSwiftUI.zip","4.52.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.52.0/StreamChatSwiftUI.zip","4.53.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.53.0/StreamChatSwiftUI.zip","4.54.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.54.0/StreamChatSwiftUI.zip","4.55.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.55.0/StreamChatSwiftUI.zip","4.56.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.56.0/StreamChatSwiftUI.zip","4.57.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.57.0/StreamChatSwiftUI.zip","4.58.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.58.0/StreamChatSwiftUI.zip","4.59.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.59.0/StreamChatSwiftUI.zip","4.60.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.60.0/StreamChatSwiftUI.zip","4.61.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.61.0/StreamChatSwiftUI.zip","4.62.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.62.0/StreamChatSwiftUI.zip","4.63.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.63.0/StreamChatSwiftUI.zip","4.64.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.64.0/StreamChatSwiftUI.zip","4.65.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.65.0/StreamChatSwiftUI.zip","4.66.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.66.0/StreamChatSwiftUI.zip","4.67.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.67.0/StreamChatSwiftUI.zip","4.68.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.68.0/StreamChatSwiftUI.zip","4.69.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.69.0/StreamChatSwiftUI.zip","4.70.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.70.0/StreamChatSwiftUI.zip","4.71.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.71.0/StreamChatSwiftUI.zip","4.72.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.72.0/StreamChatSwiftUI.zip","4.73.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.73.0/StreamChatSwiftUI.zip","4.74.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.74.0/StreamChatSwiftUI.zip","4.75.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.75.0/StreamChatSwiftUI.zip","4.76.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.76.0/StreamChatSwiftUI.zip","4.77.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.77.0/StreamChatSwiftUI.zip","4.78.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.78.0/StreamChatSwiftUI.zip","4.79.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.79.0/StreamChatSwiftUI.zip","4.79.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.79.1/StreamChatSwiftUI.zip","4.80.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.80.0/StreamChatSwiftUI.zip","4.81.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.81.0/StreamChatSwiftUI.zip","4.82.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.82.0/StreamChatSwiftUI.zip","4.83.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.83.0/StreamChatSwiftUI.zip","4.84.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.84.0/StreamChatSwiftUI.zip","4.85.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.85.0/StreamChatSwiftUI.zip","4.86.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.86.0/StreamChatSwiftUI.zip"} \ No newline at end of file +{"4.40.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.40.0/StreamChatSwiftUI.zip","4.41.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.41.0/StreamChatSwiftUI.zip","4.42.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.42.0/StreamChatSwiftUI.zip","4.43.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.43.0/StreamChatSwiftUI.zip","4.44.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.44.0/StreamChatSwiftUI.zip","4.45.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.45.0/StreamChatSwiftUI.zip","4.46.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.46.0/StreamChatSwiftUI.zip","4.47.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.47.0/StreamChatSwiftUI.zip","4.47.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.47.1/StreamChatSwiftUI.zip","4.48.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.48.0/StreamChatSwiftUI.zip","4.49.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.49.0/StreamChatSwiftUI.zip","4.50.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.50.0/StreamChatSwiftUI.zip","4.50.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.50.1/StreamChatSwiftUI.zip","4.51.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.51.0/StreamChatSwiftUI.zip","4.52.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.52.0/StreamChatSwiftUI.zip","4.53.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.53.0/StreamChatSwiftUI.zip","4.54.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.54.0/StreamChatSwiftUI.zip","4.55.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.55.0/StreamChatSwiftUI.zip","4.56.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.56.0/StreamChatSwiftUI.zip","4.57.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.57.0/StreamChatSwiftUI.zip","4.58.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.58.0/StreamChatSwiftUI.zip","4.59.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.59.0/StreamChatSwiftUI.zip","4.60.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.60.0/StreamChatSwiftUI.zip","4.61.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.61.0/StreamChatSwiftUI.zip","4.62.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.62.0/StreamChatSwiftUI.zip","4.63.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.63.0/StreamChatSwiftUI.zip","4.64.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.64.0/StreamChatSwiftUI.zip","4.65.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.65.0/StreamChatSwiftUI.zip","4.66.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.66.0/StreamChatSwiftUI.zip","4.67.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.67.0/StreamChatSwiftUI.zip","4.68.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.68.0/StreamChatSwiftUI.zip","4.69.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.69.0/StreamChatSwiftUI.zip","4.70.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.70.0/StreamChatSwiftUI.zip","4.71.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.71.0/StreamChatSwiftUI.zip","4.72.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.72.0/StreamChatSwiftUI.zip","4.73.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.73.0/StreamChatSwiftUI.zip","4.74.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.74.0/StreamChatSwiftUI.zip","4.75.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.75.0/StreamChatSwiftUI.zip","4.76.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.76.0/StreamChatSwiftUI.zip","4.77.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.77.0/StreamChatSwiftUI.zip","4.78.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.78.0/StreamChatSwiftUI.zip","4.79.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.79.0/StreamChatSwiftUI.zip","4.79.1":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.79.1/StreamChatSwiftUI.zip","4.80.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.80.0/StreamChatSwiftUI.zip","4.81.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.81.0/StreamChatSwiftUI.zip","4.82.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.82.0/StreamChatSwiftUI.zip","4.83.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.83.0/StreamChatSwiftUI.zip","4.84.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.84.0/StreamChatSwiftUI.zip","4.85.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.85.0/StreamChatSwiftUI.zip","4.86.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.86.0/StreamChatSwiftUI.zip","4.87.0":"https://github.com/GetStream/stream-chat-swiftui/releases/download/4.87.0/StreamChatSwiftUI.zip"} \ No newline at end of file