├── .gitignore ├── README.md ├── banner.png ├── iMessageClone.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved ├── xcshareddata │ └── xcschemes │ │ └── iMessageClone.xcscheme └── xcuserdata │ └── stefanblos.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist └── iMessageClone ├── Assets.xcassets ├── AccentColor.colorset │ └── Contents.json ├── AppIcon.appiconset │ ├── App-Store.png │ ├── Contents.json │ ├── iMessage-Icon-120x120-1.png │ ├── iMessage-Icon-120x120.png │ ├── iMessage-Icon-60x60.png │ ├── iPad-Mini.png │ ├── iPad-Pro 21.16.32.png │ ├── iPhone-iPad-Notification.png │ ├── iPhone-iPad-Settings-1.png │ ├── iPhone-iPad-Settings.png │ ├── iPhone-iPad-Spotlight-1.png │ ├── iPhone-iPad-Spotlight.png │ └── iPhone.png ├── Contents.json ├── incomingTail.imageset │ ├── Contents.json │ └── incomingTail.pdf ├── outgoingTail.imageset │ ├── Contents.json │ └── outgoingTail.pdf └── store.imageset │ ├── Contents.json │ └── store.pdf ├── ChannelList ├── View │ ├── ChannelHeader │ │ ├── iMessageChannelListHeader.swift │ │ └── iMessageChannelListHeaderModifier.swift │ ├── ChannelList │ │ ├── LeadingSwipeAreaView.swift │ │ ├── PinnedChannelsView.swift │ │ ├── TrailingSwipeAreaView.swift │ │ └── iMessageChannelList.swift │ └── ChannelListItem │ │ ├── iMessageChannelListItem.swift │ │ └── iMessageChannelListItemView.swift └── ViewModel │ └── iMessageChannelListViewModel.swift ├── MessageList ├── View │ ├── Attachments │ │ └── LinkView.swift │ ├── Composer │ │ ├── ComposerInputView.swift │ │ └── LeadingComposerView.swift │ ├── Header │ │ ├── MessageListHeader.swift │ │ └── MessageListHeaderModifier.swift │ └── Message │ │ └── MessageView.swift └── ViewModel │ └── MessageListHeaderViewModel.swift ├── Preview Content └── Preview Assets.xcassets │ ├── Contents.json │ └── oval.imageset │ ├── Contents.json │ └── oval.pdf ├── StreamChat ├── AppDelegate.swift ├── iMessageViewFactory+ChannelList.swift ├── iMessageViewFactory+MessageList.swift └── iMessageViewFactory.swift └── iMessageCloneApp.swift /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/swift,swiftpackagemanager,xcode,macos 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=swift,swiftpackagemanager,xcode,macos 4 | 5 | ### macOS ### 6 | # General 7 | .DS_Store 8 | .AppleDouble 9 | .LSOverride 10 | 11 | # Icon must end with two \r 12 | Icon 13 | 14 | 15 | # Thumbnails 16 | ._* 17 | 18 | # Files that might appear in the root of a volume 19 | .DocumentRevisions-V100 20 | .fseventsd 21 | .Spotlight-V100 22 | .TemporaryItems 23 | .Trashes 24 | .VolumeIcon.icns 25 | .com.apple.timemachine.donotpresent 26 | 27 | # Directories potentially created on remote AFP share 28 | .AppleDB 29 | .AppleDesktop 30 | Network Trash Folder 31 | Temporary Items 32 | .apdisk 33 | 34 | ### Swift ### 35 | # Xcode 36 | # 37 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 38 | 39 | ## User settings 40 | xcuserdata/ 41 | 42 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 43 | *.xcscmblueprint 44 | *.xccheckout 45 | 46 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 47 | build/ 48 | DerivedData/ 49 | *.moved-aside 50 | *.pbxuser 51 | !default.pbxuser 52 | *.mode1v3 53 | !default.mode1v3 54 | *.mode2v3 55 | !default.mode2v3 56 | *.perspectivev3 57 | !default.perspectivev3 58 | 59 | ## Obj-C/Swift specific 60 | *.hmap 61 | 62 | ## App packaging 63 | *.ipa 64 | *.dSYM.zip 65 | *.dSYM 66 | 67 | ## Playgrounds 68 | timeline.xctimeline 69 | playground.xcworkspace 70 | 71 | ### Xcode ### 72 | 73 | ## Xcode 8 and earlier 74 | 75 | ### Xcode Patch ### 76 | *.xcodeproj/* 77 | !*.xcodeproj/project.pbxproj 78 | !*.xcodeproj/xcshareddata/ 79 | !*.xcworkspace/contents.xcworkspacedata 80 | /*.gcno 81 | **/xcshareddata/WorkspaceSettings.xcsettings 82 | 83 | # End of https://www.toptal.com/developers/gitignore/api/swift,swiftpackagemanager,xcode,macos 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Banner showing the name of the repository](banner.png) 2 | 3 | # iMessage clone with SwiftUI and the StreamChatSwiftUI SDK 4 | 5 | This repository contains the source code of the tutorial series on how to re-create the famous iMessage app from Apple with the StreamChatSwiftUI SDK and SwiftUI. 6 | 7 | It was created as a best-practice repository that showcases some of the best ways to customize the SDK and bring in your own style. There is a number of ongoing tutorials associated with this topic that are shown below with their current state: 8 | 9 | * [Youtube Tutorial on creating the Channel List](https://youtu.be/526swCwDMX8) (*published*) 10 | * [Youtube Tutorial on creating the Message List](https://youtu.be/8Nkmk85H8HQ) (*published*) 11 | * Youtube Tutorial on implementing Custom Message Effects (*planned*) 12 | 13 | If you want to learn more about the SDK and to create your own Maker account and get started for free, head over to [getstream.io](https://getstream.io). 14 | 15 | Other links that might be helpful to you: 16 | 17 | * [StreamChatSwiftUI Package](https://github.com/GetStream/stream-chat-swiftui) 18 | * [SwiftUI tutorial](https://getstream.io/tutorials/swiftui-chat/) on getting started with the StreamChat SDK 19 | * [SwiftUI docs](https://getstream.io/chat/docs/sdk/ios/swiftui/) 20 | 21 | If you have questions about anything SwiftUI related, also feel free to reach out to [Martin Mitrevski](https://twitter.com/mitrevski) and/or [Stefan Blos](https://twitter.com/stefanjblos) 22 | -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/swiftui-iMessage-clone/df114b42734b287fdc516c6f383bc282fa38036f/banner.png -------------------------------------------------------------------------------- /iMessageClone.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | AC02EB4B27A95CED001C4480 /* iMessageChannelListHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC02EB4A27A95CED001C4480 /* iMessageChannelListHeader.swift */; }; 11 | AC02EB4D27A95D7C001C4480 /* iMessageChannelListHeaderModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC02EB4C27A95D7C001C4480 /* iMessageChannelListHeaderModifier.swift */; }; 12 | AC24F7C0286C8F30009FBF7E /* LeadingComposerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC24F7BF286C8F30009FBF7E /* LeadingComposerView.swift */; }; 13 | AC24F7C2286C98DE009FBF7E /* ComposerInputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC24F7C1286C98DE009FBF7E /* ComposerInputView.swift */; }; 14 | AC24F7C6286CA886009FBF7E /* LinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC24F7C5286CA886009FBF7E /* LinkView.swift */; }; 15 | AC4A633C27AC473F00237420 /* PinnedChannelsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC4A633B27AC473F00237420 /* PinnedChannelsView.swift */; }; 16 | AC5277D927AA84E80065A092 /* iMessageChannelListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC5277D827AA84E80065A092 /* iMessageChannelListItemView.swift */; }; 17 | AC5277DD27AB0D510065A092 /* iMessageChannelListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC5277DC27AB0D510065A092 /* iMessageChannelListItem.swift */; }; 18 | AC5277E327AC29010065A092 /* LeadingSwipeAreaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC5277E227AC29010065A092 /* LeadingSwipeAreaView.swift */; }; 19 | AC5277E527AC29C60065A092 /* TrailingSwipeAreaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC5277E427AC29C60065A092 /* TrailingSwipeAreaView.swift */; }; 20 | AC5277E727AC2CBE0065A092 /* iMessageChannelListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC5277E627AC2CBE0065A092 /* iMessageChannelListViewModel.swift */; }; 21 | AC5277EA27AC45620065A092 /* OrderedCollections in Frameworks */ = {isa = PBXBuildFile; productRef = AC5277E927AC45620065A092 /* OrderedCollections */; }; 22 | AC65D05A286C521600E01779 /* iMessageViewFactory+ChannelList.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC65D059286C521600E01779 /* iMessageViewFactory+ChannelList.swift */; }; 23 | AC65D05F286C592200E01779 /* MessageListHeaderViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC65D05E286C592200E01779 /* MessageListHeaderViewModel.swift */; }; 24 | AC65D064286C5BA200E01779 /* MessageListHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC65D063286C5BA200E01779 /* MessageListHeader.swift */; }; 25 | AC65D066286C5D9F00E01779 /* MessageListHeaderModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC65D065286C5D9F00E01779 /* MessageListHeaderModifier.swift */; }; 26 | AC65D068286C5F3700E01779 /* iMessageViewFactory+MessageList.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC65D067286C5F3700E01779 /* iMessageViewFactory+MessageList.swift */; }; 27 | AC65D06B286C73DF00E01779 /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC65D06A286C73DF00E01779 /* MessageView.swift */; }; 28 | AC9B379827A46350004765A9 /* iMessageCloneApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC9B379727A46350004765A9 /* iMessageCloneApp.swift */; }; 29 | AC9B379A27A46350004765A9 /* iMessageChannelList.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC9B379927A46350004765A9 /* iMessageChannelList.swift */; }; 30 | AC9B379C27A46351004765A9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AC9B379B27A46351004765A9 /* Assets.xcassets */; }; 31 | AC9B379F27A46351004765A9 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AC9B379E27A46351004765A9 /* Preview Assets.xcassets */; }; 32 | AC9B37A727A46526004765A9 /* StreamChatSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = AC9B37A627A46526004765A9 /* StreamChatSwiftUI */; }; 33 | AC9B37A927A466EE004765A9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC9B37A827A466EE004765A9 /* AppDelegate.swift */; }; 34 | AC9B37AB27A4677C004765A9 /* iMessageViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC9B37AA27A4677C004765A9 /* iMessageViewFactory.swift */; }; 35 | /* End PBXBuildFile section */ 36 | 37 | /* Begin PBXFileReference section */ 38 | AC02EB4A27A95CED001C4480 /* iMessageChannelListHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iMessageChannelListHeader.swift; sourceTree = ""; }; 39 | AC02EB4C27A95D7C001C4480 /* iMessageChannelListHeaderModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iMessageChannelListHeaderModifier.swift; sourceTree = ""; }; 40 | AC24F7BF286C8F30009FBF7E /* LeadingComposerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeadingComposerView.swift; sourceTree = ""; }; 41 | AC24F7C1286C98DE009FBF7E /* ComposerInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposerInputView.swift; sourceTree = ""; }; 42 | AC24F7C5286CA886009FBF7E /* LinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkView.swift; sourceTree = ""; }; 43 | AC4A633B27AC473F00237420 /* PinnedChannelsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinnedChannelsView.swift; sourceTree = ""; }; 44 | AC5277D827AA84E80065A092 /* iMessageChannelListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iMessageChannelListItemView.swift; sourceTree = ""; }; 45 | AC5277DC27AB0D510065A092 /* iMessageChannelListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iMessageChannelListItem.swift; sourceTree = ""; }; 46 | AC5277E227AC29010065A092 /* LeadingSwipeAreaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LeadingSwipeAreaView.swift; sourceTree = ""; }; 47 | AC5277E427AC29C60065A092 /* TrailingSwipeAreaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrailingSwipeAreaView.swift; sourceTree = ""; }; 48 | AC5277E627AC2CBE0065A092 /* iMessageChannelListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iMessageChannelListViewModel.swift; sourceTree = ""; }; 49 | AC65D059286C521600E01779 /* iMessageViewFactory+ChannelList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "iMessageViewFactory+ChannelList.swift"; sourceTree = ""; }; 50 | AC65D05E286C592200E01779 /* MessageListHeaderViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageListHeaderViewModel.swift; sourceTree = ""; }; 51 | AC65D063286C5BA200E01779 /* MessageListHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageListHeader.swift; sourceTree = ""; }; 52 | AC65D065286C5D9F00E01779 /* MessageListHeaderModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageListHeaderModifier.swift; sourceTree = ""; }; 53 | AC65D067286C5F3700E01779 /* iMessageViewFactory+MessageList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "iMessageViewFactory+MessageList.swift"; sourceTree = ""; }; 54 | AC65D06A286C73DF00E01779 /* MessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageView.swift; sourceTree = ""; }; 55 | AC9B379427A46350004765A9 /* iMessageClone.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iMessageClone.app; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | AC9B379727A46350004765A9 /* iMessageCloneApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iMessageCloneApp.swift; sourceTree = ""; }; 57 | AC9B379927A46350004765A9 /* iMessageChannelList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iMessageChannelList.swift; sourceTree = ""; }; 58 | AC9B379B27A46351004765A9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 59 | AC9B379E27A46351004765A9 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 60 | AC9B37A827A466EE004765A9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 61 | AC9B37AA27A4677C004765A9 /* iMessageViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iMessageViewFactory.swift; sourceTree = ""; }; 62 | /* End PBXFileReference section */ 63 | 64 | /* Begin PBXFrameworksBuildPhase section */ 65 | AC9B379127A46350004765A9 /* Frameworks */ = { 66 | isa = PBXFrameworksBuildPhase; 67 | buildActionMask = 2147483647; 68 | files = ( 69 | AC5277EA27AC45620065A092 /* OrderedCollections in Frameworks */, 70 | AC9B37A727A46526004765A9 /* StreamChatSwiftUI in Frameworks */, 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | /* End PBXFrameworksBuildPhase section */ 75 | 76 | /* Begin PBXGroup section */ 77 | AC24F7BE286C8F23009FBF7E /* Composer */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | AC24F7BF286C8F30009FBF7E /* LeadingComposerView.swift */, 81 | AC24F7C1286C98DE009FBF7E /* ComposerInputView.swift */, 82 | ); 83 | path = Composer; 84 | sourceTree = ""; 85 | }; 86 | AC24F7C4286CA87D009FBF7E /* Attachments */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | AC24F7C5286CA886009FBF7E /* LinkView.swift */, 90 | ); 91 | path = Attachments; 92 | sourceTree = ""; 93 | }; 94 | AC65D05B286C58F900E01779 /* MessageList */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | AC65D05D286C590A00E01779 /* ViewModel */, 98 | AC65D05C286C590400E01779 /* View */, 99 | ); 100 | path = MessageList; 101 | sourceTree = ""; 102 | }; 103 | AC65D05C286C590400E01779 /* View */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | AC24F7C4286CA87D009FBF7E /* Attachments */, 107 | AC24F7BE286C8F23009FBF7E /* Composer */, 108 | AC65D069286C73D400E01779 /* Message */, 109 | AC65D062286C5B8300E01779 /* Header */, 110 | ); 111 | path = View; 112 | sourceTree = ""; 113 | }; 114 | AC65D05D286C590A00E01779 /* ViewModel */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | AC65D05E286C592200E01779 /* MessageListHeaderViewModel.swift */, 118 | ); 119 | path = ViewModel; 120 | sourceTree = ""; 121 | }; 122 | AC65D062286C5B8300E01779 /* Header */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | AC65D063286C5BA200E01779 /* MessageListHeader.swift */, 126 | AC65D065286C5D9F00E01779 /* MessageListHeaderModifier.swift */, 127 | ); 128 | path = Header; 129 | sourceTree = ""; 130 | }; 131 | AC65D069286C73D400E01779 /* Message */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | AC65D06A286C73DF00E01779 /* MessageView.swift */, 135 | ); 136 | path = Message; 137 | sourceTree = ""; 138 | }; 139 | AC9B378B27A46350004765A9 = { 140 | isa = PBXGroup; 141 | children = ( 142 | AC9B379627A46350004765A9 /* iMessageClone */, 143 | AC9B379527A46350004765A9 /* Products */, 144 | ); 145 | sourceTree = ""; 146 | }; 147 | AC9B379527A46350004765A9 /* Products */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | AC9B379427A46350004765A9 /* iMessageClone.app */, 151 | ); 152 | name = Products; 153 | sourceTree = ""; 154 | }; 155 | AC9B379627A46350004765A9 /* iMessageClone */ = { 156 | isa = PBXGroup; 157 | children = ( 158 | ACD129C62857264D007E6B97 /* StreamChat */, 159 | ACD129C32857261D007E6B97 /* ChannelList */, 160 | AC65D05B286C58F900E01779 /* MessageList */, 161 | AC9B379727A46350004765A9 /* iMessageCloneApp.swift */, 162 | AC9B379B27A46351004765A9 /* Assets.xcassets */, 163 | AC9B379D27A46351004765A9 /* Preview Content */, 164 | ); 165 | path = iMessageClone; 166 | sourceTree = ""; 167 | }; 168 | AC9B379D27A46351004765A9 /* Preview Content */ = { 169 | isa = PBXGroup; 170 | children = ( 171 | AC9B379E27A46351004765A9 /* Preview Assets.xcassets */, 172 | ); 173 | path = "Preview Content"; 174 | sourceTree = ""; 175 | }; 176 | ACD129C32857261D007E6B97 /* ChannelList */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | ACD129C528572631007E6B97 /* ViewModel */, 180 | ACD129C42857262A007E6B97 /* View */, 181 | ); 182 | path = ChannelList; 183 | sourceTree = ""; 184 | }; 185 | ACD129C42857262A007E6B97 /* View */ = { 186 | isa = PBXGroup; 187 | children = ( 188 | ACD129C8285727C6007E6B97 /* ChannelHeader */, 189 | ACD129C7285727B4007E6B97 /* ChannelList */, 190 | ACD129C9285727CD007E6B97 /* ChannelListItem */, 191 | ); 192 | path = View; 193 | sourceTree = ""; 194 | }; 195 | ACD129C528572631007E6B97 /* ViewModel */ = { 196 | isa = PBXGroup; 197 | children = ( 198 | AC5277E627AC2CBE0065A092 /* iMessageChannelListViewModel.swift */, 199 | ); 200 | path = ViewModel; 201 | sourceTree = ""; 202 | }; 203 | ACD129C62857264D007E6B97 /* StreamChat */ = { 204 | isa = PBXGroup; 205 | children = ( 206 | AC9B37A827A466EE004765A9 /* AppDelegate.swift */, 207 | AC9B37AA27A4677C004765A9 /* iMessageViewFactory.swift */, 208 | AC65D059286C521600E01779 /* iMessageViewFactory+ChannelList.swift */, 209 | AC65D067286C5F3700E01779 /* iMessageViewFactory+MessageList.swift */, 210 | ); 211 | path = StreamChat; 212 | sourceTree = ""; 213 | }; 214 | ACD129C7285727B4007E6B97 /* ChannelList */ = { 215 | isa = PBXGroup; 216 | children = ( 217 | AC9B379927A46350004765A9 /* iMessageChannelList.swift */, 218 | AC5277E227AC29010065A092 /* LeadingSwipeAreaView.swift */, 219 | AC4A633B27AC473F00237420 /* PinnedChannelsView.swift */, 220 | AC5277E427AC29C60065A092 /* TrailingSwipeAreaView.swift */, 221 | ); 222 | path = ChannelList; 223 | sourceTree = ""; 224 | }; 225 | ACD129C8285727C6007E6B97 /* ChannelHeader */ = { 226 | isa = PBXGroup; 227 | children = ( 228 | AC02EB4A27A95CED001C4480 /* iMessageChannelListHeader.swift */, 229 | AC02EB4C27A95D7C001C4480 /* iMessageChannelListHeaderModifier.swift */, 230 | ); 231 | path = ChannelHeader; 232 | sourceTree = ""; 233 | }; 234 | ACD129C9285727CD007E6B97 /* ChannelListItem */ = { 235 | isa = PBXGroup; 236 | children = ( 237 | AC5277DC27AB0D510065A092 /* iMessageChannelListItem.swift */, 238 | AC5277D827AA84E80065A092 /* iMessageChannelListItemView.swift */, 239 | ); 240 | path = ChannelListItem; 241 | sourceTree = ""; 242 | }; 243 | /* End PBXGroup section */ 244 | 245 | /* Begin PBXNativeTarget section */ 246 | AC9B379327A46350004765A9 /* iMessageClone */ = { 247 | isa = PBXNativeTarget; 248 | buildConfigurationList = AC9B37A227A46351004765A9 /* Build configuration list for PBXNativeTarget "iMessageClone" */; 249 | buildPhases = ( 250 | AC9B379027A46350004765A9 /* Sources */, 251 | AC9B379127A46350004765A9 /* Frameworks */, 252 | AC9B379227A46350004765A9 /* Resources */, 253 | ); 254 | buildRules = ( 255 | ); 256 | dependencies = ( 257 | ); 258 | name = iMessageClone; 259 | packageProductDependencies = ( 260 | AC9B37A627A46526004765A9 /* StreamChatSwiftUI */, 261 | AC5277E927AC45620065A092 /* OrderedCollections */, 262 | ); 263 | productName = iMessageClone; 264 | productReference = AC9B379427A46350004765A9 /* iMessageClone.app */; 265 | productType = "com.apple.product-type.application"; 266 | }; 267 | /* End PBXNativeTarget section */ 268 | 269 | /* Begin PBXProject section */ 270 | AC9B378C27A46350004765A9 /* Project object */ = { 271 | isa = PBXProject; 272 | attributes = { 273 | BuildIndependentTargetsInParallel = 1; 274 | LastSwiftUpdateCheck = 1320; 275 | LastUpgradeCheck = 1320; 276 | TargetAttributes = { 277 | AC9B379327A46350004765A9 = { 278 | CreatedOnToolsVersion = 13.2; 279 | }; 280 | }; 281 | }; 282 | buildConfigurationList = AC9B378F27A46350004765A9 /* Build configuration list for PBXProject "iMessageClone" */; 283 | compatibilityVersion = "Xcode 13.0"; 284 | developmentRegion = en; 285 | hasScannedForEncodings = 0; 286 | knownRegions = ( 287 | en, 288 | Base, 289 | ); 290 | mainGroup = AC9B378B27A46350004765A9; 291 | packageReferences = ( 292 | AC9B37A527A46526004765A9 /* XCRemoteSwiftPackageReference "stream-chat-swiftui" */, 293 | AC5277E827AC45620065A092 /* XCRemoteSwiftPackageReference "swift-collections" */, 294 | ); 295 | productRefGroup = AC9B379527A46350004765A9 /* Products */; 296 | projectDirPath = ""; 297 | projectRoot = ""; 298 | targets = ( 299 | AC9B379327A46350004765A9 /* iMessageClone */, 300 | ); 301 | }; 302 | /* End PBXProject section */ 303 | 304 | /* Begin PBXResourcesBuildPhase section */ 305 | AC9B379227A46350004765A9 /* Resources */ = { 306 | isa = PBXResourcesBuildPhase; 307 | buildActionMask = 2147483647; 308 | files = ( 309 | AC9B379F27A46351004765A9 /* Preview Assets.xcassets in Resources */, 310 | AC9B379C27A46351004765A9 /* Assets.xcassets in Resources */, 311 | ); 312 | runOnlyForDeploymentPostprocessing = 0; 313 | }; 314 | /* End PBXResourcesBuildPhase section */ 315 | 316 | /* Begin PBXSourcesBuildPhase section */ 317 | AC9B379027A46350004765A9 /* Sources */ = { 318 | isa = PBXSourcesBuildPhase; 319 | buildActionMask = 2147483647; 320 | files = ( 321 | AC4A633C27AC473F00237420 /* PinnedChannelsView.swift in Sources */, 322 | AC5277E327AC29010065A092 /* LeadingSwipeAreaView.swift in Sources */, 323 | AC9B37A927A466EE004765A9 /* AppDelegate.swift in Sources */, 324 | AC65D066286C5D9F00E01779 /* MessageListHeaderModifier.swift in Sources */, 325 | AC5277E527AC29C60065A092 /* TrailingSwipeAreaView.swift in Sources */, 326 | AC24F7C2286C98DE009FBF7E /* ComposerInputView.swift in Sources */, 327 | AC65D05A286C521600E01779 /* iMessageViewFactory+ChannelList.swift in Sources */, 328 | AC65D068286C5F3700E01779 /* iMessageViewFactory+MessageList.swift in Sources */, 329 | AC9B37AB27A4677C004765A9 /* iMessageViewFactory.swift in Sources */, 330 | AC24F7C0286C8F30009FBF7E /* LeadingComposerView.swift in Sources */, 331 | AC65D064286C5BA200E01779 /* MessageListHeader.swift in Sources */, 332 | AC5277DD27AB0D510065A092 /* iMessageChannelListItem.swift in Sources */, 333 | AC9B379A27A46350004765A9 /* iMessageChannelList.swift in Sources */, 334 | AC5277D927AA84E80065A092 /* iMessageChannelListItemView.swift in Sources */, 335 | AC65D06B286C73DF00E01779 /* MessageView.swift in Sources */, 336 | AC5277E727AC2CBE0065A092 /* iMessageChannelListViewModel.swift in Sources */, 337 | AC9B379827A46350004765A9 /* iMessageCloneApp.swift in Sources */, 338 | AC02EB4B27A95CED001C4480 /* iMessageChannelListHeader.swift in Sources */, 339 | AC65D05F286C592200E01779 /* MessageListHeaderViewModel.swift in Sources */, 340 | AC24F7C6286CA886009FBF7E /* LinkView.swift in Sources */, 341 | AC02EB4D27A95D7C001C4480 /* iMessageChannelListHeaderModifier.swift in Sources */, 342 | ); 343 | runOnlyForDeploymentPostprocessing = 0; 344 | }; 345 | /* End PBXSourcesBuildPhase section */ 346 | 347 | /* Begin XCBuildConfiguration section */ 348 | AC9B37A027A46351004765A9 /* Debug */ = { 349 | isa = XCBuildConfiguration; 350 | buildSettings = { 351 | ALWAYS_SEARCH_USER_PATHS = NO; 352 | CLANG_ANALYZER_NONNULL = YES; 353 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 354 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 355 | CLANG_CXX_LIBRARY = "libc++"; 356 | CLANG_ENABLE_MODULES = YES; 357 | CLANG_ENABLE_OBJC_ARC = YES; 358 | CLANG_ENABLE_OBJC_WEAK = YES; 359 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 360 | CLANG_WARN_BOOL_CONVERSION = YES; 361 | CLANG_WARN_COMMA = YES; 362 | CLANG_WARN_CONSTANT_CONVERSION = YES; 363 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 364 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 365 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 366 | CLANG_WARN_EMPTY_BODY = YES; 367 | CLANG_WARN_ENUM_CONVERSION = YES; 368 | CLANG_WARN_INFINITE_RECURSION = YES; 369 | CLANG_WARN_INT_CONVERSION = YES; 370 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 371 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 372 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 373 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 374 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 375 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 376 | CLANG_WARN_STRICT_PROTOTYPES = YES; 377 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 378 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 379 | CLANG_WARN_UNREACHABLE_CODE = YES; 380 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 381 | COPY_PHASE_STRIP = NO; 382 | DEBUG_INFORMATION_FORMAT = dwarf; 383 | ENABLE_STRICT_OBJC_MSGSEND = YES; 384 | ENABLE_TESTABILITY = YES; 385 | GCC_C_LANGUAGE_STANDARD = gnu11; 386 | GCC_DYNAMIC_NO_PIC = NO; 387 | GCC_NO_COMMON_BLOCKS = YES; 388 | GCC_OPTIMIZATION_LEVEL = 0; 389 | GCC_PREPROCESSOR_DEFINITIONS = ( 390 | "DEBUG=1", 391 | "$(inherited)", 392 | ); 393 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 394 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 395 | GCC_WARN_UNDECLARED_SELECTOR = YES; 396 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 397 | GCC_WARN_UNUSED_FUNCTION = YES; 398 | GCC_WARN_UNUSED_VARIABLE = YES; 399 | IPHONEOS_DEPLOYMENT_TARGET = 15.2; 400 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 401 | MTL_FAST_MATH = YES; 402 | ONLY_ACTIVE_ARCH = YES; 403 | SDKROOT = iphoneos; 404 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 405 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 406 | }; 407 | name = Debug; 408 | }; 409 | AC9B37A127A46351004765A9 /* Release */ = { 410 | isa = XCBuildConfiguration; 411 | buildSettings = { 412 | ALWAYS_SEARCH_USER_PATHS = NO; 413 | CLANG_ANALYZER_NONNULL = YES; 414 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 415 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 416 | CLANG_CXX_LIBRARY = "libc++"; 417 | CLANG_ENABLE_MODULES = YES; 418 | CLANG_ENABLE_OBJC_ARC = YES; 419 | CLANG_ENABLE_OBJC_WEAK = YES; 420 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 421 | CLANG_WARN_BOOL_CONVERSION = YES; 422 | CLANG_WARN_COMMA = YES; 423 | CLANG_WARN_CONSTANT_CONVERSION = YES; 424 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 425 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 426 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 427 | CLANG_WARN_EMPTY_BODY = YES; 428 | CLANG_WARN_ENUM_CONVERSION = YES; 429 | CLANG_WARN_INFINITE_RECURSION = YES; 430 | CLANG_WARN_INT_CONVERSION = YES; 431 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 432 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 433 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 434 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 435 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 436 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 437 | CLANG_WARN_STRICT_PROTOTYPES = YES; 438 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 439 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 440 | CLANG_WARN_UNREACHABLE_CODE = YES; 441 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 442 | COPY_PHASE_STRIP = NO; 443 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 444 | ENABLE_NS_ASSERTIONS = NO; 445 | ENABLE_STRICT_OBJC_MSGSEND = YES; 446 | GCC_C_LANGUAGE_STANDARD = gnu11; 447 | GCC_NO_COMMON_BLOCKS = YES; 448 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 449 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 450 | GCC_WARN_UNDECLARED_SELECTOR = YES; 451 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 452 | GCC_WARN_UNUSED_FUNCTION = YES; 453 | GCC_WARN_UNUSED_VARIABLE = YES; 454 | IPHONEOS_DEPLOYMENT_TARGET = 15.2; 455 | MTL_ENABLE_DEBUG_INFO = NO; 456 | MTL_FAST_MATH = YES; 457 | SDKROOT = iphoneos; 458 | SWIFT_COMPILATION_MODE = wholemodule; 459 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 460 | VALIDATE_PRODUCT = YES; 461 | }; 462 | name = Release; 463 | }; 464 | AC9B37A327A46351004765A9 /* Debug */ = { 465 | isa = XCBuildConfiguration; 466 | buildSettings = { 467 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 468 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 469 | CODE_SIGN_STYLE = Automatic; 470 | CURRENT_PROJECT_VERSION = 1; 471 | DEVELOPMENT_ASSET_PATHS = "\"iMessageClone/Preview Content\""; 472 | DEVELOPMENT_TEAM = JY49624W8T; 473 | ENABLE_PREVIEWS = YES; 474 | GENERATE_INFOPLIST_FILE = YES; 475 | INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "In order to send images we need to access them."; 476 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 477 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 478 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 479 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 480 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 481 | LD_RUNPATH_SEARCH_PATHS = ( 482 | "$(inherited)", 483 | "@executable_path/Frameworks", 484 | ); 485 | MARKETING_VERSION = 1.0; 486 | PRODUCT_BUNDLE_IDENTIFIER = com.stefanblos.iMessageClone; 487 | PRODUCT_NAME = "$(TARGET_NAME)"; 488 | SWIFT_EMIT_LOC_STRINGS = YES; 489 | SWIFT_VERSION = 5.0; 490 | TARGETED_DEVICE_FAMILY = "1,2"; 491 | }; 492 | name = Debug; 493 | }; 494 | AC9B37A427A46351004765A9 /* Release */ = { 495 | isa = XCBuildConfiguration; 496 | buildSettings = { 497 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 498 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 499 | CODE_SIGN_STYLE = Automatic; 500 | CURRENT_PROJECT_VERSION = 1; 501 | DEVELOPMENT_ASSET_PATHS = "\"iMessageClone/Preview Content\""; 502 | DEVELOPMENT_TEAM = JY49624W8T; 503 | ENABLE_PREVIEWS = YES; 504 | GENERATE_INFOPLIST_FILE = YES; 505 | INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "In order to send images we need to access them."; 506 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 507 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 508 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 509 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 510 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 511 | LD_RUNPATH_SEARCH_PATHS = ( 512 | "$(inherited)", 513 | "@executable_path/Frameworks", 514 | ); 515 | MARKETING_VERSION = 1.0; 516 | PRODUCT_BUNDLE_IDENTIFIER = com.stefanblos.iMessageClone; 517 | PRODUCT_NAME = "$(TARGET_NAME)"; 518 | SWIFT_EMIT_LOC_STRINGS = YES; 519 | SWIFT_VERSION = 5.0; 520 | TARGETED_DEVICE_FAMILY = "1,2"; 521 | }; 522 | name = Release; 523 | }; 524 | /* End XCBuildConfiguration section */ 525 | 526 | /* Begin XCConfigurationList section */ 527 | AC9B378F27A46350004765A9 /* Build configuration list for PBXProject "iMessageClone" */ = { 528 | isa = XCConfigurationList; 529 | buildConfigurations = ( 530 | AC9B37A027A46351004765A9 /* Debug */, 531 | AC9B37A127A46351004765A9 /* Release */, 532 | ); 533 | defaultConfigurationIsVisible = 0; 534 | defaultConfigurationName = Release; 535 | }; 536 | AC9B37A227A46351004765A9 /* Build configuration list for PBXNativeTarget "iMessageClone" */ = { 537 | isa = XCConfigurationList; 538 | buildConfigurations = ( 539 | AC9B37A327A46351004765A9 /* Debug */, 540 | AC9B37A427A46351004765A9 /* Release */, 541 | ); 542 | defaultConfigurationIsVisible = 0; 543 | defaultConfigurationName = Release; 544 | }; 545 | /* End XCConfigurationList section */ 546 | 547 | /* Begin XCRemoteSwiftPackageReference section */ 548 | AC5277E827AC45620065A092 /* XCRemoteSwiftPackageReference "swift-collections" */ = { 549 | isa = XCRemoteSwiftPackageReference; 550 | repositoryURL = "https://github.com/apple/swift-collections.git"; 551 | requirement = { 552 | kind = upToNextMajorVersion; 553 | minimumVersion = 1.0.0; 554 | }; 555 | }; 556 | AC9B37A527A46526004765A9 /* XCRemoteSwiftPackageReference "stream-chat-swiftui" */ = { 557 | isa = XCRemoteSwiftPackageReference; 558 | repositoryURL = "https://github.com/getstream/stream-chat-swiftui"; 559 | requirement = { 560 | kind = upToNextMajorVersion; 561 | minimumVersion = 4.16.0; 562 | }; 563 | }; 564 | /* End XCRemoteSwiftPackageReference section */ 565 | 566 | /* Begin XCSwiftPackageProductDependency section */ 567 | AC5277E927AC45620065A092 /* OrderedCollections */ = { 568 | isa = XCSwiftPackageProductDependency; 569 | package = AC5277E827AC45620065A092 /* XCRemoteSwiftPackageReference "swift-collections" */; 570 | productName = OrderedCollections; 571 | }; 572 | AC9B37A627A46526004765A9 /* StreamChatSwiftUI */ = { 573 | isa = XCSwiftPackageProductDependency; 574 | package = AC9B37A527A46526004765A9 /* XCRemoteSwiftPackageReference "stream-chat-swiftui" */; 575 | productName = StreamChatSwiftUI; 576 | }; 577 | /* End XCSwiftPackageProductDependency section */ 578 | }; 579 | rootObject = AC9B378C27A46350004765A9 /* Project object */; 580 | } 581 | -------------------------------------------------------------------------------- /iMessageClone.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /iMessageClone.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /iMessageClone.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "difference", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/krzysztofzablocki/Difference.git", 7 | "state" : { 8 | "revision" : "02fe1111edc8318c4f8a0da96336fcbcc201f38b", 9 | "version" : "1.0.1" 10 | } 11 | }, 12 | { 13 | "identity" : "gifu", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/kaishin/Gifu", 16 | "state" : { 17 | "revision" : "51f2eab32903e336f590c013267cfa4d7f8b06c4", 18 | "version" : "3.3.1" 19 | } 20 | }, 21 | { 22 | "identity" : "nuke", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/kean/Nuke.git", 25 | "state" : { 26 | "revision" : "a002b7fd786f2df2ed4333fe73a9727499fd9d97", 27 | "version" : "10.11.2" 28 | } 29 | }, 30 | { 31 | "identity" : "nukeui", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/kean/NukeUI.git", 34 | "state" : { 35 | "revision" : "ebfed3c9a4e97e310b0ff8ee0fffe5579887a825", 36 | "version" : "0.8.2" 37 | } 38 | }, 39 | { 40 | "identity" : "stream-chat-swift", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/GetStream/stream-chat-swift.git", 43 | "state" : { 44 | "revision" : "2d7bf8364ad105fca7b09c1df20bfc3f3007fb79", 45 | "version" : "4.17.0" 46 | } 47 | }, 48 | { 49 | "identity" : "stream-chat-swift-test-helpers", 50 | "kind" : "remoteSourceControl", 51 | "location" : "https://github.com/GetStream/stream-chat-swift-test-helpers.git", 52 | "state" : { 53 | "revision" : "89bb36ae690d76506d6a3af34370966342f39106", 54 | "version" : "0.2.1" 55 | } 56 | }, 57 | { 58 | "identity" : "stream-chat-swiftui", 59 | "kind" : "remoteSourceControl", 60 | "location" : "https://github.com/getstream/stream-chat-swiftui", 61 | "state" : { 62 | "revision" : "44f1d5ed3a97852ab9ee99483383e07f9f81d605", 63 | "version" : "4.17.0" 64 | } 65 | }, 66 | { 67 | "identity" : "swift-collections", 68 | "kind" : "remoteSourceControl", 69 | "location" : "https://github.com/apple/swift-collections.git", 70 | "state" : { 71 | "revision" : "48254824bb4248676bf7ce56014ff57b142b77eb", 72 | "version" : "1.0.2" 73 | } 74 | }, 75 | { 76 | "identity" : "swifter", 77 | "kind" : "remoteSourceControl", 78 | "location" : "https://github.com/httpswift/swifter", 79 | "state" : { 80 | "revision" : "9483a5d459b45c3ffd059f7b55f9638e268632fd", 81 | "version" : "1.5.0" 82 | } 83 | } 84 | ], 85 | "version" : 2 86 | } 87 | -------------------------------------------------------------------------------- /iMessageClone.xcodeproj/xcshareddata/xcschemes/iMessageClone.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 45 | 51 | 52 | 53 | 54 | 60 | 62 | 68 | 69 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /iMessageClone.xcodeproj/xcuserdata/stefanblos.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Stream (Playground) 1.xcscheme 8 | 9 | isShown 10 | 11 | orderHint 12 | 5 13 | 14 | Stream (Playground) 2.xcscheme 15 | 16 | isShown 17 | 18 | orderHint 19 | 6 20 | 21 | Stream (Playground) 3.xcscheme 22 | 23 | isShown 24 | 25 | orderHint 26 | 8 27 | 28 | Stream (Playground) 4.xcscheme 29 | 30 | isShown 31 | 32 | orderHint 33 | 9 34 | 35 | Stream (Playground) 5.xcscheme 36 | 37 | isShown 38 | 39 | orderHint 40 | 10 41 | 42 | Stream (Playground).xcscheme 43 | 44 | isShown 45 | 46 | orderHint 47 | 3 48 | 49 | iMessageClone.xcscheme_^#shared#^_ 50 | 51 | orderHint 52 | 0 53 | 54 | 55 | SuppressBuildableAutocreation 56 | 57 | AC9B379327A46350004765A9 58 | 59 | primary 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/AppIcon.appiconset/App-Store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/swiftui-iMessage-clone/df114b42734b287fdc516c6f383bc282fa38036f/iMessageClone/Assets.xcassets/AppIcon.appiconset/App-Store.png -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "filename" : "iMessage-Icon-60x60.png", 10 | "idiom" : "iphone", 11 | "scale" : "3x", 12 | "size" : "20x20" 13 | }, 14 | { 15 | "filename" : "iPhone-iPad-Settings-1.png", 16 | "idiom" : "iphone", 17 | "scale" : "2x", 18 | "size" : "29x29" 19 | }, 20 | { 21 | "idiom" : "iphone", 22 | "scale" : "3x", 23 | "size" : "29x29" 24 | }, 25 | { 26 | "filename" : "iPhone-iPad-Spotlight.png", 27 | "idiom" : "iphone", 28 | "scale" : "2x", 29 | "size" : "40x40" 30 | }, 31 | { 32 | "filename" : "iMessage-Icon-120x120.png", 33 | "idiom" : "iphone", 34 | "scale" : "3x", 35 | "size" : "40x40" 36 | }, 37 | { 38 | "filename" : "iMessage-Icon-120x120-1.png", 39 | "idiom" : "iphone", 40 | "scale" : "2x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "filename" : "iPhone.png", 45 | "idiom" : "iphone", 46 | "scale" : "3x", 47 | "size" : "60x60" 48 | }, 49 | { 50 | "idiom" : "ipad", 51 | "scale" : "1x", 52 | "size" : "20x20" 53 | }, 54 | { 55 | "idiom" : "ipad", 56 | "scale" : "2x", 57 | "size" : "20x20" 58 | }, 59 | { 60 | "idiom" : "ipad", 61 | "scale" : "1x", 62 | "size" : "29x29" 63 | }, 64 | { 65 | "filename" : "iPhone-iPad-Settings.png", 66 | "idiom" : "ipad", 67 | "scale" : "2x", 68 | "size" : "29x29" 69 | }, 70 | { 71 | "idiom" : "ipad", 72 | "scale" : "1x", 73 | "size" : "40x40" 74 | }, 75 | { 76 | "filename" : "iPhone-iPad-Spotlight-1.png", 77 | "idiom" : "ipad", 78 | "scale" : "2x", 79 | "size" : "40x40" 80 | }, 81 | { 82 | "filename" : "iPhone-iPad-Notification.png", 83 | "idiom" : "ipad", 84 | "scale" : "1x", 85 | "size" : "76x76" 86 | }, 87 | { 88 | "filename" : "iPad-Mini.png", 89 | "idiom" : "ipad", 90 | "scale" : "2x", 91 | "size" : "76x76" 92 | }, 93 | { 94 | "filename" : "iPad-Pro 21.16.32.png", 95 | "idiom" : "ipad", 96 | "scale" : "2x", 97 | "size" : "83.5x83.5" 98 | }, 99 | { 100 | "filename" : "App-Store.png", 101 | "idiom" : "ios-marketing", 102 | "scale" : "1x", 103 | "size" : "1024x1024" 104 | } 105 | ], 106 | "info" : { 107 | "author" : "xcode", 108 | "version" : 1 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/AppIcon.appiconset/iMessage-Icon-120x120-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/swiftui-iMessage-clone/df114b42734b287fdc516c6f383bc282fa38036f/iMessageClone/Assets.xcassets/AppIcon.appiconset/iMessage-Icon-120x120-1.png -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/AppIcon.appiconset/iMessage-Icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/swiftui-iMessage-clone/df114b42734b287fdc516c6f383bc282fa38036f/iMessageClone/Assets.xcassets/AppIcon.appiconset/iMessage-Icon-120x120.png -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/AppIcon.appiconset/iMessage-Icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/swiftui-iMessage-clone/df114b42734b287fdc516c6f383bc282fa38036f/iMessageClone/Assets.xcassets/AppIcon.appiconset/iMessage-Icon-60x60.png -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/AppIcon.appiconset/iPad-Mini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/swiftui-iMessage-clone/df114b42734b287fdc516c6f383bc282fa38036f/iMessageClone/Assets.xcassets/AppIcon.appiconset/iPad-Mini.png -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/AppIcon.appiconset/iPad-Pro 21.16.32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/swiftui-iMessage-clone/df114b42734b287fdc516c6f383bc282fa38036f/iMessageClone/Assets.xcassets/AppIcon.appiconset/iPad-Pro 21.16.32.png -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/AppIcon.appiconset/iPhone-iPad-Notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/swiftui-iMessage-clone/df114b42734b287fdc516c6f383bc282fa38036f/iMessageClone/Assets.xcassets/AppIcon.appiconset/iPhone-iPad-Notification.png -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/AppIcon.appiconset/iPhone-iPad-Settings-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/swiftui-iMessage-clone/df114b42734b287fdc516c6f383bc282fa38036f/iMessageClone/Assets.xcassets/AppIcon.appiconset/iPhone-iPad-Settings-1.png -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/AppIcon.appiconset/iPhone-iPad-Settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/swiftui-iMessage-clone/df114b42734b287fdc516c6f383bc282fa38036f/iMessageClone/Assets.xcassets/AppIcon.appiconset/iPhone-iPad-Settings.png -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/AppIcon.appiconset/iPhone-iPad-Spotlight-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/swiftui-iMessage-clone/df114b42734b287fdc516c6f383bc282fa38036f/iMessageClone/Assets.xcassets/AppIcon.appiconset/iPhone-iPad-Spotlight-1.png -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/AppIcon.appiconset/iPhone-iPad-Spotlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/swiftui-iMessage-clone/df114b42734b287fdc516c6f383bc282fa38036f/iMessageClone/Assets.xcassets/AppIcon.appiconset/iPhone-iPad-Spotlight.png -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/AppIcon.appiconset/iPhone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/swiftui-iMessage-clone/df114b42734b287fdc516c6f383bc282fa38036f/iMessageClone/Assets.xcassets/AppIcon.appiconset/iPhone.png -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/incomingTail.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "incomingTail.pdf", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/incomingTail.imageset/incomingTail.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/swiftui-iMessage-clone/df114b42734b287fdc516c6f383bc282fa38036f/iMessageClone/Assets.xcassets/incomingTail.imageset/incomingTail.pdf -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/outgoingTail.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "outgoingTail.pdf", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/outgoingTail.imageset/outgoingTail.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/swiftui-iMessage-clone/df114b42734b287fdc516c6f383bc282fa38036f/iMessageClone/Assets.xcassets/outgoingTail.imageset/outgoingTail.pdf -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/store.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "store.pdf", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iMessageClone/Assets.xcassets/store.imageset/store.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/swiftui-iMessage-clone/df114b42734b287fdc516c6f383bc282fa38036f/iMessageClone/Assets.xcassets/store.imageset/store.pdf -------------------------------------------------------------------------------- /iMessageClone/ChannelList/View/ChannelHeader/iMessageChannelListHeader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iMessageChannelListHeader.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 01.02.22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct iMessageChannelListHeader: ToolbarContent { 11 | 12 | var body: some ToolbarContent { 13 | ToolbarItem(placement: .navigationBarLeading) { 14 | Button { 15 | // TBD 16 | } label: { 17 | Text("Edit") 18 | } 19 | } 20 | 21 | ToolbarItem(placement: .navigationBarTrailing) { 22 | Button { 23 | // TBD 24 | } label: { 25 | Image(systemName: "square.and.pencil") 26 | } 27 | 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /iMessageClone/ChannelList/View/ChannelHeader/iMessageChannelListHeaderModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iMessageChannelListHeaderModifier.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 01.02.22. 6 | // 7 | 8 | import SwiftUI 9 | import StreamChatSwiftUI 10 | 11 | struct iMessageChannelListHeaderModifier: ChannelListHeaderViewModifier { 12 | 13 | var title: String 14 | 15 | func body(content: Content) -> some View { 16 | content.toolbar { 17 | iMessageChannelListHeader() 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /iMessageClone/ChannelList/View/ChannelList/LeadingSwipeAreaView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LeadingSwipeAreaView.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 03.02.22. 6 | // 7 | 8 | import SwiftUI 9 | import StreamChat 10 | import StreamChatSwiftUI 11 | 12 | struct LeadingSwipeAreaView: View { 13 | 14 | var channel: ChatChannel 15 | var buttonWidth: CGFloat 16 | @Binding var swipedChannelId: String? 17 | var buttonTapped: (ChatChannel) -> Void 18 | 19 | var body: some View { 20 | HStack { 21 | ActionItemButton(imageName: "pin.fill") { 22 | buttonTapped(channel) 23 | } 24 | .frame(width: buttonWidth) 25 | .foregroundColor(.white) 26 | .background(Color.yellow) 27 | 28 | Spacer() 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /iMessageClone/ChannelList/View/ChannelList/PinnedChannelsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PinnedChannelsView.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 03.02.22. 6 | // 7 | 8 | import SwiftUI 9 | import StreamChatSwiftUI 10 | 11 | struct PinnedChannelsView: View { 12 | 13 | @ObservedObject var viewModel: iMessageChannelListViewModel 14 | @ObservedObject var channelHeaderLoader: ChannelHeaderLoader 15 | var factory: iMessageViewFactory 16 | 17 | let columnStructure = [ 18 | GridItem(.flexible()), 19 | GridItem(.flexible()), 20 | GridItem(.flexible()) 21 | ] 22 | 23 | var body: some View { 24 | LazyVGrid(columns: columnStructure) { 25 | ForEach(viewModel.pinnedChannels) { channel in 26 | ZStack { 27 | Button { 28 | viewModel.selectedChannel = channel.channelSelectionInfo 29 | } label: { 30 | VStack { 31 | Image(uiImage: channelHeaderLoader.image(for: channel)) 32 | .resizable() 33 | .scaledToFill() 34 | .clipShape(Circle()) 35 | .shadow(radius: 6) 36 | .frame(width: 80, height: 80) 37 | 38 | Text(viewModel.name(forChannel: channel)) 39 | .font(.caption) 40 | .foregroundColor(.secondary) 41 | } 42 | } 43 | 44 | NavigationLink(tag: channel.channelSelectionInfo, selection: $viewModel.selectedChannel) { 45 | factory.makeChannelDestination()(channel.channelSelectionInfo) 46 | } label: { 47 | EmptyView() 48 | } 49 | } 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /iMessageClone/ChannelList/View/ChannelList/TrailingSwipeAreaView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TrailingSwipeAreaView.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 03.02.22. 6 | // 7 | 8 | import SwiftUI 9 | import StreamChat 10 | import StreamChatSwiftUI 11 | 12 | struct TrailingSwipeAreaView: View { 13 | 14 | var channel: ChatChannel 15 | var buttonWidth: CGFloat 16 | @Binding var swipedChannelId: String? 17 | var leftButtonTapped: (ChatChannel) -> Void 18 | var rightButtonTapped: (ChatChannel) -> Void 19 | 20 | var body: some View { 21 | HStack(spacing: 0) { 22 | Spacer() 23 | 24 | ActionItemButton(imageName: "bell.fill") { 25 | leftButtonTapped(channel) 26 | } 27 | .frame(width: buttonWidth) 28 | .foregroundColor(.white) 29 | .background(Color.indigo) 30 | 31 | ActionItemButton(imageName: "trash.fill") { 32 | rightButtonTapped(channel) 33 | } 34 | .frame(width: buttonWidth) 35 | .foregroundColor(.white) 36 | .background(Color.red) 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /iMessageClone/ChannelList/View/ChannelList/iMessageChannelList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iMessageChannelList.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 28.01.22. 6 | // 7 | 8 | import SwiftUI 9 | import StreamChatSwiftUI 10 | 11 | struct iMessageChannelList: View { 12 | 13 | @StateObject var viewModel = iMessageChannelListViewModel() 14 | @StateObject var channelHeaderLoader = ChannelHeaderLoader() 15 | var viewFactory: iMessageViewFactory 16 | 17 | var body: some View { 18 | NavigationView { 19 | ScrollView { 20 | if viewModel.pinnedChannels.count > 0 { 21 | PinnedChannelsView(viewModel: viewModel, channelHeaderLoader: channelHeaderLoader, factory: viewFactory) 22 | } 23 | 24 | ChannelList( 25 | factory: viewFactory, 26 | channels: viewModel.nonPinnedChannels, 27 | selectedChannel: $viewModel.selectedChannel, 28 | swipedChannelId: $viewModel.swipedChannelId, 29 | scrollable: false, 30 | onlineIndicatorShown: viewModel.onlineIndicatorShown(for:), 31 | imageLoader: channelHeaderLoader.image(for:), 32 | onItemTap: { channel in 33 | viewModel.selectedChannel = channel.channelSelectionInfo 34 | }, 35 | onItemAppear: viewModel.checkForChannels(index:), 36 | channelNaming: viewModel.name(forChannel:), 37 | channelDestination: viewFactory.makeChannelDestination(), 38 | trailingSwipeRightButtonTapped: viewModel.onDeleteTapped(channel:), 39 | trailingSwipeLeftButtonTapped: viewModel.muteTapped(_:), 40 | leadingSwipeButtonTapped: viewModel.pinChannelTapped(_:) 41 | ) 42 | .alert(isPresented: $viewModel.alertShown) { 43 | switch viewModel.channelAlertType { 44 | case let .deleteChannel(channel): 45 | return Alert( 46 | title: Text("Delete channel"), 47 | message: Text("Are you sure you want to delete this channel?"), 48 | primaryButton: .destructive(Text("Delete")) { 49 | viewModel.delete(channel: channel) 50 | }, 51 | secondaryButton: .cancel() 52 | ) 53 | default: 54 | return Alert.defaultErrorAlert 55 | } 56 | } 57 | .navigationTitle("Messages") 58 | .toolbar { 59 | iMessageChannelListHeader() 60 | } 61 | } 62 | } 63 | } 64 | } 65 | 66 | struct ContentView_Previews: PreviewProvider { 67 | static var previews: some View { 68 | iMessageChannelList(viewFactory: iMessageViewFactory()) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /iMessageClone/ChannelList/View/ChannelListItem/iMessageChannelListItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iMessageChannelListItem.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 02.02.22. 6 | // 7 | 8 | import SwiftUI 9 | import StreamChat 10 | import StreamChatSwiftUI 11 | 12 | struct iMessageChannelListItem: View { 13 | 14 | let channel: ChatChannel 15 | let channelName: String 16 | let avatar: UIImage 17 | let channelDestination: (ChannelSelectionInfo) -> ChatChannelView 18 | @Binding var selectedChannel: ChannelSelectionInfo? 19 | let onItemTap: (ChatChannel) -> Void 20 | 21 | var body: some View { 22 | ZStack { 23 | iMessageChannelListItemView( 24 | channelName: channelName, 25 | avatar: avatar, 26 | lastMessageAt: channel.lastMessageAt ?? Date(), 27 | hasUnreadMessages: channel.unreadCount.messages > 0, 28 | lastMessage: channel.latestMessages.first?.text ?? "No messages", 29 | isMuted: channel.isMuted 30 | ) 31 | .padding(.trailing) 32 | .onTapGesture { 33 | onItemTap(channel) 34 | } 35 | 36 | NavigationLink(tag: channel.channelSelectionInfo, selection: $selectedChannel) { 37 | // use LazyView 38 | channelDestination(channel.channelSelectionInfo) 39 | } label: { 40 | EmptyView() 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /iMessageClone/ChannelList/View/ChannelListItem/iMessageChannelListItemView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iMessageChannelListItemView.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 02.02.22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct iMessageChannelListItemView: View { 11 | 12 | let channelName: String 13 | let avatar: UIImage 14 | let lastMessageAt: Date 15 | let hasUnreadMessages: Bool 16 | let lastMessage: String 17 | let isMuted: Bool 18 | 19 | var lastMessageStamp: String { 20 | let formatter = DateFormatter() 21 | formatter.dateStyle = .none 22 | formatter.timeStyle = .short 23 | return formatter.string(from: lastMessageAt) 24 | } 25 | 26 | var body: some View { 27 | HStack { 28 | ZStack { 29 | if hasUnreadMessages { 30 | Circle() 31 | .foregroundColor(.blue) 32 | } 33 | } 34 | .frame(width: 12) 35 | 36 | Image(uiImage: avatar) 37 | .resizable() 38 | .aspectRatio(contentMode: .fill) 39 | .frame(width: 50, height: 50) 40 | .clipShape(Circle()) 41 | 42 | VStack(alignment: .leading, spacing: 0) { 43 | HStack { 44 | Text(channelName) 45 | .font(.headline) 46 | .foregroundColor(.primary) 47 | .lineLimit(1) 48 | 49 | Spacer() 50 | 51 | HStack(spacing: 10) { 52 | Text(lastMessageStamp) 53 | .foregroundColor(.secondary) 54 | 55 | Image(systemName: "chevron.right") 56 | .foregroundColor(.secondary) 57 | } 58 | .font(.subheadline) 59 | } 60 | 61 | HStack(alignment: .top, spacing: 4) { 62 | Text(lastMessage) 63 | .foregroundColor(.secondary) 64 | .font(.subheadline) 65 | .fixedSize(horizontal: false, vertical: true) 66 | .lineLimit(2) 67 | .frame(maxWidth: .infinity, minHeight: 40, alignment: .topLeading) 68 | 69 | if isMuted { 70 | Image(systemName: "bell.slash.fill") 71 | .resizable() 72 | .scaledToFit() 73 | .foregroundColor(.secondary) 74 | .frame(width: 12) 75 | .padding(.top, 4) 76 | } 77 | } 78 | } 79 | } 80 | .padding(.vertical, 4) 81 | .background(Color(uiColor: .systemBackground)) 82 | } 83 | } 84 | 85 | struct iMessageChannelListItemView_Previews: PreviewProvider { 86 | static var previews: some View { 87 | Group { 88 | iMessageChannelListItemView(channelName: "Preview Channel", avatar: UIImage(named: "oval")!, lastMessageAt: Date(), hasUnreadMessages: true, lastMessage: "We have a lot of great ideas to bring forward the SDK in the future and it's going to be great!", isMuted: false) 89 | .previewLayout(.fixed(width: 400, height: 120)) 90 | .padding() 91 | iMessageChannelListItemView(channelName: "Preview Channel", avatar: UIImage(named: "oval")!, lastMessageAt: Date(), hasUnreadMessages: true, lastMessage: "New", isMuted: true) 92 | .previewLayout(.fixed(width: 400, height: 120)) 93 | .padding() 94 | 95 | iMessageChannelListItemView(channelName: "Preview Channel", avatar: UIImage(named: "oval")!, lastMessageAt: Date(), hasUnreadMessages: false, lastMessage: "We have a lot of great ideas to bring forward the SDK in the future and it's going to be great!", isMuted: true) 96 | .previewLayout(.fixed(width: 400, height: 120)) 97 | .padding() 98 | 99 | iMessageChannelListItemView(channelName: "Preview Channel", avatar: UIImage(named: "oval")!, lastMessageAt: Date(), hasUnreadMessages: true, lastMessage: "We have a lot of great ideas to bring forward the SDK in the future and it's going to be great!", isMuted: false) 100 | .preferredColorScheme(.dark) 101 | .previewLayout(.fixed(width: 400, height: 120)) 102 | .padding() 103 | 104 | iMessageChannelListItemView(channelName: "Preview Channel", avatar: UIImage(named: "oval")!, lastMessageAt: Date(), hasUnreadMessages: false, lastMessage: "We have a lot of great ideas to bring forward the SDK in the future and it's going to be great!", isMuted: true) 105 | .preferredColorScheme(.dark) 106 | .previewLayout(.fixed(width: 400, height: 120)) 107 | .padding() 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /iMessageClone/ChannelList/ViewModel/iMessageChannelListViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iMessageChannelListViewModel.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 03.02.22. 6 | // 7 | 8 | import Foundation 9 | import StreamChat 10 | import StreamChatSwiftUI 11 | import OrderedCollections 12 | 13 | class iMessageChannelListViewModel: ChatChannelListViewModel { 14 | 15 | @Injected(\.chatClient) var chatClient 16 | 17 | @Published var pinnedChannels: OrderedSet = [] 18 | 19 | var nonPinnedChannels: LazyCachedMapCollection { 20 | self.channels 21 | .filter({ channel in 22 | !self.pinnedChannels.contains(channel) 23 | }) 24 | .lazyCachedMap { $0 } 25 | } 26 | 27 | func pinChannelTapped(_ channel: ChatChannel) { 28 | if pinnedChannels.contains(channel) { 29 | pinnedChannels.remove(channel) 30 | } else { 31 | pinnedChannels.append(channel) 32 | } 33 | } 34 | 35 | func muteTapped(_ channel: ChatChannel) { 36 | let controller = chatClient.channelController(for: channel.cid) 37 | 38 | if channel.isMuted { 39 | controller.unmuteChannel { error in 40 | if let error = error { 41 | print("Error occurred: \(error.localizedDescription)") 42 | } else { 43 | print("Successfully unmuted channel") 44 | } 45 | } 46 | } else { 47 | controller.muteChannel { error in 48 | if let error = error { 49 | print("Error occurred: \(error.localizedDescription)") 50 | } else { 51 | print("Successfully muted channel") 52 | } 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /iMessageClone/MessageList/View/Attachments/LinkView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LinkView.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 29.06.22. 6 | // 7 | 8 | import SwiftUI 9 | import Nuke 10 | import NukeUI 11 | import StreamChat 12 | import StreamChatSwiftUI 13 | 14 | struct LinkView: View { 15 | 16 | @Injected(\.colors) var colors 17 | @Injected(\.fonts) var fonts 18 | 19 | var linkAttachment: ChatMessageLinkAttachment 20 | var width: CGFloat 21 | 22 | let padding: CGFloat = 8 23 | 24 | var body: some View { 25 | VStack(alignment: .leading, spacing: 0) { 26 | if let preview = linkAttachment.previewURL { 27 | LazyImage(source: preview) 28 | .onDisappear(.cancel) 29 | .processors([ImageProcessors.Resize(width: width)]) 30 | .priority(.high) 31 | .frame(width: width - 2 * padding, height: (width - 2 * padding) / 2) 32 | } 33 | 34 | VStack(alignment: .leading, spacing: 4) { 35 | if let title = linkAttachment.title { 36 | Text(title) 37 | .font(fonts.subheadlineBold) 38 | .lineLimit(2) 39 | .frame(maxWidth: .infinity, alignment: .leading) 40 | } 41 | 42 | if let baseUrl = linkAttachment.originalURL { 43 | Text("\(baseUrl)") 44 | .font(fonts.footnote) 45 | .foregroundColor(.secondary) 46 | .lineLimit(1) 47 | } 48 | } 49 | .padding(.vertical, 12) 50 | .padding(.horizontal, padding) 51 | .frame(maxWidth: width - 2 * padding) 52 | } 53 | .background(Color(uiColor: colors.background1)) 54 | .clipShape( 55 | RoundedRectangle(cornerRadius: 20, style: .continuous) 56 | ) 57 | .onTapGesture { 58 | if UIApplication.shared.canOpenURL(linkAttachment.originalURL) { 59 | UIApplication.shared.open(linkAttachment.originalURL, options: [:]) 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /iMessageClone/MessageList/View/Composer/ComposerInputView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ComposerInputView.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 29.06.22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ComposerInputView: View { 11 | 12 | @Binding var input: String 13 | var send: (String) -> Void 14 | 15 | @State private var showNotImplementedAlert = false 16 | 17 | var body: some View { 18 | HStack(alignment: .center, spacing: 10) { 19 | TextField("iMessage", text: $input) 20 | .multilineTextAlignment(.leading) 21 | .lineLimit(0) 22 | .padding(.leading, 12) 23 | 24 | if !input.isEmpty { 25 | Button { 26 | send(input) 27 | 28 | input = "" 29 | } label: { 30 | Image(systemName: "arrow.up.circle.fill") 31 | .resizable() 32 | .scaledToFit() 33 | .frame(width: 30, height: 30) 34 | .foregroundColor(.blue) 35 | .font(.body.weight(.semibold)) 36 | } 37 | } else { 38 | Button { 39 | showNotImplementedAlert = true 40 | } label: { 41 | Image(systemName: "waveform.circle.fill") 42 | .resizable() 43 | .scaledToFit() 44 | .frame(width: 26, height: 26) 45 | .foregroundColor(.secondary) 46 | } 47 | .alert("This is not yet implemented", isPresented: $showNotImplementedAlert) { 48 | Button("OK", role: .cancel) {} 49 | } 50 | } 51 | } 52 | .padding(4) 53 | .overlay(Capsule() 54 | .stroke(.tertiary, lineWidth: 1) 55 | .opacity(0.7) 56 | ) 57 | .padding(.trailing, 8) 58 | } 59 | } 60 | 61 | struct ComposerInputView_Previews: PreviewProvider { 62 | static var previews: some View { 63 | Group { 64 | ComposerInputView(input: .constant("")) { message in 65 | print("Yes") 66 | } 67 | .padding() 68 | .previewLayout(.fixed(width: 360, height: 100.0)) 69 | 70 | ComposerInputView(input: .constant("This is my message to you")) { message in 71 | print("Yes") 72 | } 73 | .padding() 74 | .previewLayout(.fixed(width: 360, height: 100.0)) 75 | 76 | ComposerInputView(input: .constant("")) { message in 77 | print("Yes") 78 | } 79 | .padding() 80 | .preferredColorScheme(.dark) 81 | .previewLayout(.fixed(width: 360, height: 100.0)) 82 | 83 | ComposerInputView(input: .constant("This is my message to you")) { message in 84 | print("Yes") 85 | } 86 | .padding() 87 | .preferredColorScheme(.dark) 88 | .previewLayout(.fixed(width: 360, height: 100.0)) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /iMessageClone/MessageList/View/Composer/LeadingComposerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LeadingComposerView.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 29.06.22. 6 | // 7 | 8 | import SwiftUI 9 | import StreamChatSwiftUI 10 | 11 | struct LeadingComposerView: View { 12 | 13 | @Binding var pickerTypeState:PickerTypeState 14 | 15 | var body: some View { 16 | switch pickerTypeState { 17 | case .expanded(let attachmentPickerType): 18 | switch attachmentPickerType { 19 | case .none: 20 | HStack(spacing: 16) { 21 | Button { 22 | withAnimation { 23 | pickerTypeState = .expanded(.media) 24 | } 25 | } label: { 26 | Image(systemName: "camera.fill") 27 | .resizable() 28 | .scaledToFit() 29 | .frame(height: 22) 30 | .foregroundColor(.gray) 31 | } 32 | 33 | Button { 34 | withAnimation { 35 | pickerTypeState = .expanded(.none) 36 | } 37 | } label: { 38 | Image("store") 39 | .resizable() 40 | .renderingMode(.template) 41 | .scaledToFit() 42 | .frame(height: 24) 43 | .foregroundColor(.primary) 44 | } 45 | } 46 | .padding(.horizontal, 8) 47 | .frame(maxHeight: 32) 48 | case .media, .instantCommands, .custom: 49 | closeButton() 50 | } 51 | case .collapsed: 52 | closeButton() 53 | } 54 | } 55 | 56 | @ViewBuilder func closeButton() -> some View { 57 | VStack { 58 | Spacer() 59 | 60 | Button { 61 | withAnimation { 62 | pickerTypeState = .expanded(.none) 63 | } 64 | } label: { 65 | Image(systemName: "xmark.circle") 66 | .resizable() 67 | .foregroundColor(.gray) 68 | .frame(width: 20, height: 20, alignment: .center) 69 | } 70 | 71 | Spacer() 72 | } 73 | .frame(maxHeight: 40) 74 | } 75 | } 76 | 77 | struct LeadingComposerView_Previews: PreviewProvider { 78 | static var previews: some View { 79 | LeadingComposerView(pickerTypeState: .constant(.expanded(.none))) 80 | .previewLayout(.fixed(width: 400.0, height: 100.0)) 81 | 82 | LeadingComposerView(pickerTypeState: .constant(.collapsed)) 83 | .previewLayout(.fixed(width: 400.0, height: 100.0)) 84 | 85 | HStack(alignment: .bottom) { 86 | LeadingComposerView(pickerTypeState: .constant(.expanded(.none))) 87 | 88 | Capsule() 89 | .stroke(.gray, lineWidth: 2) 90 | .frame(maxHeight: 50) 91 | 92 | } 93 | .preferredColorScheme(.dark) 94 | .previewLayout(.fixed(width: 400.0, height: 100.0)) 95 | 96 | LeadingComposerView(pickerTypeState: .constant(.expanded(.none))) 97 | .preferredColorScheme(.dark) 98 | .previewLayout(.fixed(width: 400.0, height: 100.0)) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /iMessageClone/MessageList/View/Header/MessageListHeader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageListHeader.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 29.06.22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct MessageListHeader: ToolbarContent { 11 | 12 | @ObservedObject var viewModel: MessageListHeaderViewModel 13 | @Binding var isInfoSheetShown: Bool 14 | @Binding var isVideoSheetShown: Bool 15 | 16 | var body: some ToolbarContent { 17 | ToolbarItem(placement: .principal) { 18 | Button { 19 | isInfoSheetShown = true 20 | } label: { 21 | VStack(spacing: 4) { 22 | if let headerImage = viewModel.headerImage { 23 | Image(uiImage: headerImage) 24 | .resizable() 25 | .frame(width: 20, height: 20) 26 | } else { 27 | Circle() 28 | .fill(.cyan) 29 | .frame(width: 20, height: 20) 30 | } 31 | 32 | HStack(alignment: .firstTextBaseline, spacing: 2) { 33 | Text(viewModel.channelName ?? "Unknown") 34 | .font(.caption) 35 | .foregroundColor(.primary) 36 | 37 | Image(systemName: "chevron.right") 38 | .resizable() 39 | .scaledToFit() 40 | .frame(width: 8, height: 8) 41 | .foregroundColor(.secondary) 42 | } 43 | } 44 | .padding(.bottom, 6) 45 | } 46 | } 47 | 48 | ToolbarItem(placement: .navigationBarTrailing) { 49 | Button { 50 | isVideoSheetShown = true 51 | } label: { 52 | Image(systemName: "video") 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /iMessageClone/MessageList/View/Header/MessageListHeaderModifier.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageListHeaderModifier.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 29.06.22. 6 | // 7 | 8 | import SwiftUI 9 | import StreamChat 10 | import StreamChatSwiftUI 11 | 12 | struct MessageListHeaderModifier: ChatChannelHeaderViewModifier { 13 | var channel: ChatChannel 14 | 15 | @State private var infoScreenShown = false 16 | @State private var videoScreenShown = false 17 | 18 | func body(content: Content) -> some View { 19 | content.toolbar { 20 | MessageListHeader( 21 | viewModel: MessageListHeaderViewModel(channel: channel), 22 | isInfoSheetShown: $infoScreenShown, 23 | isVideoSheetShown: $videoScreenShown) 24 | } 25 | .sheet(isPresented: $infoScreenShown) { 26 | ChatChannelInfoView(channel: channel) 27 | } 28 | .sheet(isPresented: $videoScreenShown) { 29 | Text("Video not implemented yet.") 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /iMessageClone/MessageList/View/Message/MessageView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageView.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 29.06.22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct MessageView: View { 11 | 12 | var message: String 13 | var isCurrentUser: Bool 14 | var isFirst: Bool 15 | 16 | var body: some View { 17 | Text(message) 18 | .foregroundColor(isCurrentUser ? .white : .primary) 19 | .padding(.horizontal) 20 | .padding(.vertical, 8) 21 | .background( 22 | isCurrentUser ? .blue : Color(uiColor: .secondarySystemBackground), 23 | in: RoundedRectangle(cornerRadius: 20, style: .continuous) 24 | ) 25 | .background(alignment: isCurrentUser ? .bottomTrailing : .bottomLeading) { 26 | isFirst 27 | ? 28 | Image(isCurrentUser ? "outgoingTail" : "incomingTail") 29 | .renderingMode(.template) 30 | .foregroundStyle(isCurrentUser ? .blue : Color(uiColor: .secondarySystemBackground)) 31 | : nil 32 | } 33 | } 34 | } 35 | 36 | struct MessageView_Previews: PreviewProvider { 37 | static var previews: some View { 38 | MessageView(message: "This is a test message", isCurrentUser: false, isFirst: true) 39 | .previewLayout(.fixed(width: 400, height: 140)) 40 | MessageView(message: "Can we already say how long their space will take? (Not in it anymore)", isCurrentUser: true, isFirst: true) 41 | .previewLayout(.fixed(width: 400, height: 140)) 42 | MessageView(message: "This is a test message", isCurrentUser: false, isFirst: true) 43 | .preferredColorScheme(.dark) 44 | .previewLayout(.fixed(width: 400, height: 140)) 45 | MessageView(message: "This is a test message", isCurrentUser: true, isFirst: true) 46 | .preferredColorScheme(.dark) 47 | .previewLayout(.fixed(width: 400, height: 140)) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /iMessageClone/MessageList/ViewModel/MessageListHeaderViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageListHeaderViewModel.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 29.06.22. 6 | // 7 | 8 | import SwiftUI 9 | import StreamChat 10 | import StreamChatSwiftUI 11 | 12 | class MessageListHeaderViewModel: ObservableObject { 13 | 14 | @Injected(\.utils) var utils 15 | @Injected(\.chatClient) var chatClient 16 | 17 | @Published var headerImage: UIImage? 18 | @Published var channelName: String? 19 | 20 | init(channel: ChatChannel) { 21 | headerImage = ChannelHeaderLoader().image(for: channel) 22 | channelName = utils.channelNamer(channel, chatClient.currentUserId ?? "") 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /iMessageClone/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /iMessageClone/Preview Content/Preview Assets.xcassets/oval.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "oval.pdf", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /iMessageClone/Preview Content/Preview Assets.xcassets/oval.imageset/oval.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/swiftui-iMessage-clone/df114b42734b287fdc516c6f383bc282fa38036f/iMessageClone/Preview Content/Preview Assets.xcassets/oval.imageset/oval.pdf -------------------------------------------------------------------------------- /iMessageClone/StreamChat/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 28.01.22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import StreamChat 11 | import StreamChatSwiftUI 12 | 13 | class AppDelegate: NSObject, UIApplicationDelegate { 14 | 15 | @Injected(\.utils) var utils 16 | 17 | var streamChat: StreamChat? 18 | 19 | var chatClient: ChatClient = { 20 | var config = ChatClientConfig(apiKey: .init("8br4watad788")) 21 | config.applicationGroupIdentifier = "group.io.getstream.iOS.ChatDemoAppSwiftUI" 22 | 23 | let client = ChatClient(config: config) 24 | return client 25 | }() 26 | 27 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 28 | streamChat = StreamChat(chatClient: chatClient) 29 | 30 | connectUser() 31 | 32 | return true 33 | } 34 | 35 | private func connectUser() { 36 | let token = try! Token(rawValue: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoibHVrZV9za3l3YWxrZXIifQ.kFSLHRB5X62t0Zlc7nwczWUfsQMwfkpylC6jCUZ6Mc0") 37 | 38 | chatClient.connectUser( 39 | userInfo: .init(id: "luke_skywalker", 40 | name: "Luke Skywalker", 41 | imageURL: URL(string: "https://vignette.wikia.nocookie.net/starwars/images/2/20/LukeTLJ.jpg")!), 42 | token: token 43 | ) { error in 44 | if let error = error { 45 | log.error("connecting the user failed \(error)") 46 | return 47 | } 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /iMessageClone/StreamChat/iMessageViewFactory+ChannelList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iMessageViewFactory+ChannelList.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 29.06.22. 6 | // 7 | 8 | import SwiftUI 9 | import StreamChatSwiftUI 10 | import StreamChat 11 | 12 | extension iMessageViewFactory { 13 | func makeChannelListHeaderViewModifier(title: String) -> iMessageChannelListHeaderModifier { 14 | iMessageChannelListHeaderModifier(title: "Messages") 15 | } 16 | 17 | func makeChannelListItem( 18 | channel: ChatChannel, 19 | channelName: String, 20 | avatar: UIImage, 21 | onlineIndicatorShown: Bool, 22 | disabled: Bool, 23 | selectedChannel: Binding, 24 | swipedChannelId: Binding, 25 | channelDestination: @escaping (ChannelSelectionInfo) -> ChatChannelView, 26 | onItemTap: @escaping (ChatChannel) -> Void, 27 | trailingSwipeRightButtonTapped: @escaping (ChatChannel) -> Void, 28 | trailingSwipeLeftButtonTapped: @escaping (ChatChannel) -> Void, 29 | leadingSwipeButtonTapped: @escaping (ChatChannel) -> Void 30 | ) -> some View { 31 | let listItem = iMessageChannelListItem( 32 | channel: channel, 33 | channelName: channelName, 34 | avatar: avatar, 35 | channelDestination: channelDestination, 36 | selectedChannel: selectedChannel 37 | ) { chatChannel in 38 | self.channelId = chatChannel.cid 39 | onItemTap(chatChannel) 40 | } 41 | 42 | return ChatChannelSwipeableListItem( 43 | factory: self, 44 | channelListItem: listItem, 45 | swipedChannelId: swipedChannelId, 46 | channel: channel, 47 | trailingRightButtonTapped: trailingSwipeRightButtonTapped, 48 | trailingLeftButtonTapped: trailingSwipeLeftButtonTapped, 49 | leadingSwipeButtonTapped: leadingSwipeButtonTapped 50 | ) 51 | } 52 | 53 | func makeLeadingSwipeActionsView( 54 | channel: ChatChannel, 55 | offsetX: CGFloat, 56 | buttonWidth: CGFloat, 57 | swipedChannelId: Binding, 58 | buttonTapped: @escaping (ChatChannel) -> Void 59 | ) -> some View { 60 | LeadingSwipeAreaView( 61 | channel: channel, 62 | buttonWidth: buttonWidth, 63 | swipedChannelId: swipedChannelId, 64 | buttonTapped: buttonTapped 65 | ) 66 | } 67 | 68 | func makeTrailingSwipeActionsView( 69 | channel: ChatChannel, 70 | offsetX: CGFloat, 71 | buttonWidth: CGFloat, 72 | swipedChannelId: Binding, 73 | leftButtonTapped: @escaping (ChatChannel) -> Void, 74 | rightButtonTapped: @escaping (ChatChannel) -> Void 75 | ) -> some View { 76 | TrailingSwipeAreaView( 77 | channel: channel, 78 | buttonWidth: buttonWidth, 79 | swipedChannelId: swipedChannelId, 80 | leftButtonTapped: leftButtonTapped, 81 | rightButtonTapped: rightButtonTapped 82 | ) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /iMessageClone/StreamChat/iMessageViewFactory+MessageList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iMessageViewFactory+MessageList.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 29.06.22. 6 | // 7 | 8 | import SwiftUI 9 | import StreamChat 10 | import StreamChatSwiftUI 11 | 12 | extension iMessageViewFactory { 13 | 14 | func makeChannelHeaderViewModifier(for channel: ChatChannel) -> MessageListHeaderModifier { 15 | MessageListHeaderModifier(channel: channel) 16 | } 17 | 18 | func makeMessageTextView( 19 | for message: ChatMessage, 20 | isFirst: Bool, 21 | availableWidth: CGFloat, 22 | scrolledId: Binding 23 | ) -> MessageView { 24 | guard let userId = chatClient.currentUserId else { 25 | return MessageView(message: message.text, isCurrentUser: false, isFirst: isFirst) 26 | } 27 | 28 | return MessageView(message: message.text, isCurrentUser: userId == message.author.id, isFirst: isFirst) 29 | } 30 | 31 | func makeMessageDateView(for message: ChatMessage) -> some View { 32 | EmptyView() 33 | } 34 | 35 | func makeMessageReadIndicatorView(channel: ChatChannel, message: ChatMessage) -> some View { 36 | EmptyView() 37 | } 38 | 39 | func makeComposerInputView( 40 | text: Binding, 41 | selectedRangeLocation: Binding, 42 | command: Binding, 43 | addedAssets: [AddedAsset], 44 | addedFileURLs: [URL], 45 | addedCustomAttachments: [CustomAttachment], 46 | quotedMessage: Binding, 47 | maxMessageLength: Int?, 48 | cooldownDuration: Int, 49 | onCustomAttachmentTap: @escaping (CustomAttachment) -> Void, 50 | shouldScroll: Bool, 51 | removeAttachmentWithId: @escaping (String) -> Void 52 | ) -> some View { 53 | ComposerInputView(input: text) { [unowned self] message in 54 | guard let channelId = channelId else { 55 | return 56 | } 57 | 58 | chatClient 59 | .channelController(for: channelId) 60 | .createNewMessage(text: message) 61 | } 62 | } 63 | 64 | func makeLeadingComposerView( 65 | state: Binding, 66 | channelConfig: ChannelConfig?) -> some View { 67 | LeadingComposerView(pickerTypeState: state) 68 | } 69 | 70 | func makeTrailingComposerView( 71 | enabled: Bool, 72 | cooldownDuration: Int, 73 | onTap: @escaping () -> Void 74 | ) -> some View { 75 | EmptyView() 76 | } 77 | 78 | func makeLinkAttachmentView( 79 | for message: ChatMessage, 80 | isFirst: Bool, 81 | availableWidth: CGFloat, 82 | scrolledId: Binding 83 | ) -> some View { 84 | ZStack { 85 | if message.linkAttachments.isEmpty { 86 | Text("Link not available") 87 | } else { 88 | LinkView(linkAttachment: message.linkAttachments[0], width: availableWidth) 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /iMessageClone/StreamChat/iMessageViewFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iMessageViewFactory.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 28.01.22. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import StreamChatSwiftUI 11 | import StreamChat 12 | 13 | class iMessageViewFactory: ViewFactory { 14 | 15 | @Injected(\.chatClient) var chatClient: ChatClient 16 | 17 | var channelId: ChannelId? 18 | 19 | } 20 | -------------------------------------------------------------------------------- /iMessageClone/iMessageCloneApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iMessageCloneApp.swift 3 | // iMessageClone 4 | // 5 | // Created by Stefan Blos on 28.01.22. 6 | // 7 | 8 | import SwiftUI 9 | import StreamChatSwiftUI 10 | 11 | @main 12 | struct iMessageCloneApp: App { 13 | 14 | @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate 15 | 16 | var body: some Scene { 17 | WindowGroup { 18 | iMessageChannelList(viewFactory: iMessageViewFactory()) 19 | } 20 | } 21 | } 22 | --------------------------------------------------------------------------------