├── .gitignore ├── Cleverbot.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── Cleverbot.xcworkspace └── contents.xcworkspacedata ├── Cleverbot ├── Resources │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Base.lproj │ │ └── LaunchScreen.storyboard │ ├── en.lproj │ │ └── Localizable.strings │ └── ko.lproj │ │ └── Localizable.strings ├── Sources │ ├── Configuration.swift │ ├── Entry │ │ ├── AppDelegate.swift │ │ └── CompositionRoot.swift │ ├── Models │ │ ├── IncomingMessage.swift │ │ ├── Message.swift │ │ ├── ModelType.swift │ │ └── OutgoingMessage.swift │ ├── Sections │ │ └── ChatViewSection.swift │ ├── Services │ │ └── CleverbotService.swift │ ├── Utils │ │ ├── Array+SectionModel.swift │ │ ├── Snap.swift │ │ ├── String+BoundingRect.swift │ │ ├── String+Localized.swift │ │ ├── UICollectionView+CellWidth.swift │ │ └── UICollectionView+ScrollToBottom.swift │ ├── ViewControllers │ │ ├── BaseViewController.swift │ │ ├── ChatViewController.swift │ │ └── ChatViewReactor.swift │ └── Views │ │ ├── BaseCollectionViewCell.swift │ │ ├── BaseMessageCell.swift │ │ ├── IncomingMessageCell.swift │ │ ├── MessageCellReactor.swift │ │ ├── MessageInputBar.swift │ │ └── OutgoingMessageCell.swift └── Supporting Files │ └── Info.plist ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Podfile ├── Podfile.lock ├── README.md └── poeditor.yml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | build 4 | 5 | **/xcuserdata 6 | *.pbxuser 7 | *.mode1v3 8 | 9 | Pods 10 | vendor 11 | .bundle/ 12 | -------------------------------------------------------------------------------- /Cleverbot.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0310C2741FD67A1500C0BDCD /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0310C2731FD67A1500C0BDCD /* AppDelegate.swift */; }; 11 | 0310C2761FD67A2500C0BDCD /* CompositionRoot.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0310C2751FD67A2500C0BDCD /* CompositionRoot.swift */; }; 12 | 0365BF901E65E9440039F7CE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0365BF881E65E9440039F7CE /* Assets.xcassets */; }; 13 | 0365BF911E65E9440039F7CE /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0365BF891E65E9440039F7CE /* LaunchScreen.storyboard */; }; 14 | 0366669B1E65EB6800508EFD /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666991E65EB6800508EFD /* BaseViewController.swift */; }; 15 | 036666A21E65ECAC00508EFD /* ModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666A11E65ECAC00508EFD /* ModelType.swift */; }; 16 | 036666A41E65ECF600508EFD /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666A31E65ECF600508EFD /* Message.swift */; }; 17 | 036666A61E65ED7800508EFD /* IncomingMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666A51E65ED7800508EFD /* IncomingMessage.swift */; }; 18 | 036666A81E65EDB200508EFD /* OutgoingMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666A71E65EDB200508EFD /* OutgoingMessage.swift */; }; 19 | 036666AA1E65EEA000508EFD /* ChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666A91E65EEA000508EFD /* ChatViewController.swift */; }; 20 | 036666AC1E65EEBB00508EFD /* ChatViewReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666AB1E65EEBB00508EFD /* ChatViewReactor.swift */; }; 21 | 036666B01E65EF6600508EFD /* BaseCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666AF1E65EF6600508EFD /* BaseCollectionViewCell.swift */; }; 22 | 036666B21E65EF7B00508EFD /* BaseMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666B11E65EF7B00508EFD /* BaseMessageCell.swift */; }; 23 | 036666B61E65EF9900508EFD /* Snap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666B31E65EF9900508EFD /* Snap.swift */; }; 24 | 036666B71E65EF9900508EFD /* String+BoundingRect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666B41E65EF9900508EFD /* String+BoundingRect.swift */; }; 25 | 036666B81E65EF9900508EFD /* UICollectionView+CellWidth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666B51E65EF9900508EFD /* UICollectionView+CellWidth.swift */; }; 26 | 036666BA1E65EFBA00508EFD /* MessageCellReactor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666B91E65EFBA00508EFD /* MessageCellReactor.swift */; }; 27 | 036666BC1E65EFDB00508EFD /* IncomingMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666BB1E65EFDB00508EFD /* IncomingMessageCell.swift */; }; 28 | 036666BE1E65EFE200508EFD /* OutgoingMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666BD1E65EFE200508EFD /* OutgoingMessageCell.swift */; }; 29 | 036666C31E65F0AD00508EFD /* ChatViewSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666C21E65F0AD00508EFD /* ChatViewSection.swift */; }; 30 | 036666C51E65F31A00508EFD /* MessageInputBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666C41E65F31A00508EFD /* MessageInputBar.swift */; }; 31 | 036666C71E65F32F00508EFD /* String+Localized.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666C61E65F32F00508EFD /* String+Localized.swift */; }; 32 | 036666C91E65F37800508EFD /* UICollectionView+ScrollToBottom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666C81E65F37800508EFD /* UICollectionView+ScrollToBottom.swift */; }; 33 | 036666CD1E65F43800508EFD /* CleverbotService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666CC1E65F43800508EFD /* CleverbotService.swift */; }; 34 | 036666CF1E65F8DB00508EFD /* Configuration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 036666CE1E65F8DB00508EFD /* Configuration.swift */; }; 35 | 036666D21E660C6900508EFD /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 036666D41E660C6900508EFD /* Localizable.strings */; }; 36 | 03FD987B1E8C2A6C00F33157 /* Array+SectionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03FD987A1E8C2A6C00F33157 /* Array+SectionModel.swift */; }; 37 | F61ED895DAB83521475EF0E4 /* Pods_Cleverbot.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50860FFD13D0A9F789E359A0 /* Pods_Cleverbot.framework */; }; 38 | /* End PBXBuildFile section */ 39 | 40 | /* Begin PBXFileReference section */ 41 | 0310C2731FD67A1500C0BDCD /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 42 | 0310C2751FD67A2500C0BDCD /* CompositionRoot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionRoot.swift; sourceTree = ""; }; 43 | 0365BF721E65E90A0039F7CE /* Cleverbot.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Cleverbot.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 0365BF881E65E9440039F7CE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 45 | 0365BF8A1E65E9440039F7CE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 46 | 0365BF8F1E65E9440039F7CE /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 47 | 036666991E65EB6800508EFD /* BaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = ""; }; 48 | 036666A11E65ECAC00508EFD /* ModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModelType.swift; sourceTree = ""; }; 49 | 036666A31E65ECF600508EFD /* Message.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; }; 50 | 036666A51E65ED7800508EFD /* IncomingMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IncomingMessage.swift; sourceTree = ""; }; 51 | 036666A71E65EDB200508EFD /* OutgoingMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingMessage.swift; sourceTree = ""; }; 52 | 036666A91E65EEA000508EFD /* ChatViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatViewController.swift; sourceTree = ""; }; 53 | 036666AB1E65EEBB00508EFD /* ChatViewReactor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatViewReactor.swift; sourceTree = ""; }; 54 | 036666AF1E65EF6600508EFD /* BaseCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseCollectionViewCell.swift; sourceTree = ""; }; 55 | 036666B11E65EF7B00508EFD /* BaseMessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseMessageCell.swift; sourceTree = ""; }; 56 | 036666B31E65EF9900508EFD /* Snap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Snap.swift; sourceTree = ""; }; 57 | 036666B41E65EF9900508EFD /* String+BoundingRect.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+BoundingRect.swift"; sourceTree = ""; }; 58 | 036666B51E65EF9900508EFD /* UICollectionView+CellWidth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionView+CellWidth.swift"; sourceTree = ""; }; 59 | 036666B91E65EFBA00508EFD /* MessageCellReactor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCellReactor.swift; sourceTree = ""; }; 60 | 036666BB1E65EFDB00508EFD /* IncomingMessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IncomingMessageCell.swift; sourceTree = ""; }; 61 | 036666BD1E65EFE200508EFD /* OutgoingMessageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingMessageCell.swift; sourceTree = ""; }; 62 | 036666C21E65F0AD00508EFD /* ChatViewSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatViewSection.swift; sourceTree = ""; }; 63 | 036666C41E65F31A00508EFD /* MessageInputBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageInputBar.swift; sourceTree = ""; }; 64 | 036666C61E65F32F00508EFD /* String+Localized.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Localized.swift"; sourceTree = ""; }; 65 | 036666C81E65F37800508EFD /* UICollectionView+ScrollToBottom.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionView+ScrollToBottom.swift"; sourceTree = ""; }; 66 | 036666CC1E65F43800508EFD /* CleverbotService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CleverbotService.swift; sourceTree = ""; }; 67 | 036666CE1E65F8DB00508EFD /* Configuration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; 68 | 036666D31E660C6900508EFD /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; 69 | 036666D51E660C7200508EFD /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = ""; }; 70 | 03FD987A1E8C2A6C00F33157 /* Array+SectionModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+SectionModel.swift"; sourceTree = ""; }; 71 | 50860FFD13D0A9F789E359A0 /* Pods_Cleverbot.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Cleverbot.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 72 | 9C539215C39E6C4FB41683F9 /* Pods-Cleverbot.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Cleverbot.release.xcconfig"; path = "Pods/Target Support Files/Pods-Cleverbot/Pods-Cleverbot.release.xcconfig"; sourceTree = ""; }; 73 | EBEADB1197DD1ACCE235178A /* Pods-Cleverbot.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Cleverbot.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Cleverbot/Pods-Cleverbot.debug.xcconfig"; sourceTree = ""; }; 74 | /* End PBXFileReference section */ 75 | 76 | /* Begin PBXFrameworksBuildPhase section */ 77 | 0365BF6F1E65E90A0039F7CE /* Frameworks */ = { 78 | isa = PBXFrameworksBuildPhase; 79 | buildActionMask = 2147483647; 80 | files = ( 81 | F61ED895DAB83521475EF0E4 /* Pods_Cleverbot.framework in Frameworks */, 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | /* End PBXFrameworksBuildPhase section */ 86 | 87 | /* Begin PBXGroup section */ 88 | 0310C2721FD67A0800C0BDCD /* Entry */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 0310C2731FD67A1500C0BDCD /* AppDelegate.swift */, 92 | 0310C2751FD67A2500C0BDCD /* CompositionRoot.swift */, 93 | ); 94 | path = Entry; 95 | sourceTree = ""; 96 | }; 97 | 0365BF691E65E90A0039F7CE = { 98 | isa = PBXGroup; 99 | children = ( 100 | 0365BF741E65E90A0039F7CE /* Cleverbot */, 101 | 0365BF731E65E90A0039F7CE /* Products */, 102 | 70C369DDA916705308645C3C /* Pods */, 103 | 86FF3FE7AD180810202E0EC4 /* Frameworks */, 104 | ); 105 | sourceTree = ""; 106 | }; 107 | 0365BF731E65E90A0039F7CE /* Products */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 0365BF721E65E90A0039F7CE /* Cleverbot.app */, 111 | ); 112 | name = Products; 113 | sourceTree = ""; 114 | }; 115 | 0365BF741E65E90A0039F7CE /* Cleverbot */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 0365BF8B1E65E9440039F7CE /* Sources */, 119 | 0365BF871E65E9440039F7CE /* Resources */, 120 | 0365BF8E1E65E9440039F7CE /* Supporting Files */, 121 | ); 122 | path = Cleverbot; 123 | sourceTree = ""; 124 | }; 125 | 0365BF871E65E9440039F7CE /* Resources */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 0365BF881E65E9440039F7CE /* Assets.xcassets */, 129 | 0365BF891E65E9440039F7CE /* LaunchScreen.storyboard */, 130 | 036666D41E660C6900508EFD /* Localizable.strings */, 131 | ); 132 | path = Resources; 133 | sourceTree = ""; 134 | }; 135 | 0365BF8B1E65E9440039F7CE /* Sources */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | 036666CE1E65F8DB00508EFD /* Configuration.swift */, 139 | 0310C2721FD67A0800C0BDCD /* Entry */, 140 | 036666971E65EB6800508EFD /* Services */, 141 | 036666951E65EB6800508EFD /* Models */, 142 | 036666C11E65F0A800508EFD /* Sections */, 143 | 036666981E65EB6800508EFD /* ViewControllers */, 144 | 0366669A1E65EB6800508EFD /* Views */, 145 | 0366669C1E65EB7B00508EFD /* Utils */, 146 | ); 147 | path = Sources; 148 | sourceTree = ""; 149 | }; 150 | 0365BF8E1E65E9440039F7CE /* Supporting Files */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 0365BF8F1E65E9440039F7CE /* Info.plist */, 154 | ); 155 | path = "Supporting Files"; 156 | sourceTree = ""; 157 | }; 158 | 036666951E65EB6800508EFD /* Models */ = { 159 | isa = PBXGroup; 160 | children = ( 161 | 036666A11E65ECAC00508EFD /* ModelType.swift */, 162 | 036666A31E65ECF600508EFD /* Message.swift */, 163 | 036666A51E65ED7800508EFD /* IncomingMessage.swift */, 164 | 036666A71E65EDB200508EFD /* OutgoingMessage.swift */, 165 | ); 166 | path = Models; 167 | sourceTree = ""; 168 | }; 169 | 036666971E65EB6800508EFD /* Services */ = { 170 | isa = PBXGroup; 171 | children = ( 172 | 036666CC1E65F43800508EFD /* CleverbotService.swift */, 173 | ); 174 | path = Services; 175 | sourceTree = ""; 176 | }; 177 | 036666981E65EB6800508EFD /* ViewControllers */ = { 178 | isa = PBXGroup; 179 | children = ( 180 | 036666991E65EB6800508EFD /* BaseViewController.swift */, 181 | 036666A91E65EEA000508EFD /* ChatViewController.swift */, 182 | 036666AB1E65EEBB00508EFD /* ChatViewReactor.swift */, 183 | ); 184 | path = ViewControllers; 185 | sourceTree = ""; 186 | }; 187 | 0366669A1E65EB6800508EFD /* Views */ = { 188 | isa = PBXGroup; 189 | children = ( 190 | 036666AF1E65EF6600508EFD /* BaseCollectionViewCell.swift */, 191 | 036666B11E65EF7B00508EFD /* BaseMessageCell.swift */, 192 | 036666BB1E65EFDB00508EFD /* IncomingMessageCell.swift */, 193 | 036666BD1E65EFE200508EFD /* OutgoingMessageCell.swift */, 194 | 036666B91E65EFBA00508EFD /* MessageCellReactor.swift */, 195 | 036666C41E65F31A00508EFD /* MessageInputBar.swift */, 196 | ); 197 | path = Views; 198 | sourceTree = ""; 199 | }; 200 | 0366669C1E65EB7B00508EFD /* Utils */ = { 201 | isa = PBXGroup; 202 | children = ( 203 | 036666B31E65EF9900508EFD /* Snap.swift */, 204 | 036666B41E65EF9900508EFD /* String+BoundingRect.swift */, 205 | 036666C61E65F32F00508EFD /* String+Localized.swift */, 206 | 036666B51E65EF9900508EFD /* UICollectionView+CellWidth.swift */, 207 | 036666C81E65F37800508EFD /* UICollectionView+ScrollToBottom.swift */, 208 | 03FD987A1E8C2A6C00F33157 /* Array+SectionModel.swift */, 209 | ); 210 | path = Utils; 211 | sourceTree = ""; 212 | }; 213 | 036666C11E65F0A800508EFD /* Sections */ = { 214 | isa = PBXGroup; 215 | children = ( 216 | 036666C21E65F0AD00508EFD /* ChatViewSection.swift */, 217 | ); 218 | path = Sections; 219 | sourceTree = ""; 220 | }; 221 | 70C369DDA916705308645C3C /* Pods */ = { 222 | isa = PBXGroup; 223 | children = ( 224 | EBEADB1197DD1ACCE235178A /* Pods-Cleverbot.debug.xcconfig */, 225 | 9C539215C39E6C4FB41683F9 /* Pods-Cleverbot.release.xcconfig */, 226 | ); 227 | name = Pods; 228 | sourceTree = ""; 229 | }; 230 | 86FF3FE7AD180810202E0EC4 /* Frameworks */ = { 231 | isa = PBXGroup; 232 | children = ( 233 | 50860FFD13D0A9F789E359A0 /* Pods_Cleverbot.framework */, 234 | ); 235 | name = Frameworks; 236 | sourceTree = ""; 237 | }; 238 | /* End PBXGroup section */ 239 | 240 | /* Begin PBXNativeTarget section */ 241 | 0365BF711E65E90A0039F7CE /* Cleverbot */ = { 242 | isa = PBXNativeTarget; 243 | buildConfigurationList = 0365BF841E65E90A0039F7CE /* Build configuration list for PBXNativeTarget "Cleverbot" */; 244 | buildPhases = ( 245 | 74A3B1B457D76F51ACB1AF7C /* [CP] Check Pods Manifest.lock */, 246 | 0365BF6E1E65E90A0039F7CE /* Sources */, 247 | 0365BF6F1E65E90A0039F7CE /* Frameworks */, 248 | 0365BF701E65E90A0039F7CE /* Resources */, 249 | 9BD21E9280D0C1B47C14CC2B /* [CP] Embed Pods Frameworks */, 250 | D025CCDA2C23C6AC23EB4A23 /* [CP] Copy Pods Resources */, 251 | ); 252 | buildRules = ( 253 | ); 254 | dependencies = ( 255 | ); 256 | name = Cleverbot; 257 | productName = Cleverbot; 258 | productReference = 0365BF721E65E90A0039F7CE /* Cleverbot.app */; 259 | productType = "com.apple.product-type.application"; 260 | }; 261 | /* End PBXNativeTarget section */ 262 | 263 | /* Begin PBXProject section */ 264 | 0365BF6A1E65E90A0039F7CE /* Project object */ = { 265 | isa = PBXProject; 266 | attributes = { 267 | LastSwiftUpdateCheck = 0820; 268 | LastUpgradeCheck = 0920; 269 | ORGANIZATIONNAME = "Suyeol Jeon"; 270 | TargetAttributes = { 271 | 0365BF711E65E90A0039F7CE = { 272 | CreatedOnToolsVersion = 8.2.1; 273 | ProvisioningStyle = Automatic; 274 | }; 275 | }; 276 | }; 277 | buildConfigurationList = 0365BF6D1E65E90A0039F7CE /* Build configuration list for PBXProject "Cleverbot" */; 278 | compatibilityVersion = "Xcode 3.2"; 279 | developmentRegion = English; 280 | hasScannedForEncodings = 0; 281 | knownRegions = ( 282 | en, 283 | Base, 284 | ko, 285 | ); 286 | mainGroup = 0365BF691E65E90A0039F7CE; 287 | productRefGroup = 0365BF731E65E90A0039F7CE /* Products */; 288 | projectDirPath = ""; 289 | projectRoot = ""; 290 | targets = ( 291 | 0365BF711E65E90A0039F7CE /* Cleverbot */, 292 | ); 293 | }; 294 | /* End PBXProject section */ 295 | 296 | /* Begin PBXResourcesBuildPhase section */ 297 | 0365BF701E65E90A0039F7CE /* Resources */ = { 298 | isa = PBXResourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | 0365BF901E65E9440039F7CE /* Assets.xcassets in Resources */, 302 | 036666D21E660C6900508EFD /* Localizable.strings in Resources */, 303 | 0365BF911E65E9440039F7CE /* LaunchScreen.storyboard in Resources */, 304 | ); 305 | runOnlyForDeploymentPostprocessing = 0; 306 | }; 307 | /* End PBXResourcesBuildPhase section */ 308 | 309 | /* Begin PBXShellScriptBuildPhase section */ 310 | 74A3B1B457D76F51ACB1AF7C /* [CP] Check Pods Manifest.lock */ = { 311 | isa = PBXShellScriptBuildPhase; 312 | buildActionMask = 2147483647; 313 | files = ( 314 | ); 315 | inputPaths = ( 316 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 317 | "${PODS_ROOT}/Manifest.lock", 318 | ); 319 | name = "[CP] Check Pods Manifest.lock"; 320 | outputPaths = ( 321 | "$(DERIVED_FILE_DIR)/Pods-Cleverbot-checkManifestLockResult.txt", 322 | ); 323 | runOnlyForDeploymentPostprocessing = 0; 324 | shellPath = /bin/sh; 325 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 326 | showEnvVarsInLog = 0; 327 | }; 328 | 9BD21E9280D0C1B47C14CC2B /* [CP] Embed Pods Frameworks */ = { 329 | isa = PBXShellScriptBuildPhase; 330 | buildActionMask = 2147483647; 331 | files = ( 332 | ); 333 | inputPaths = ( 334 | "${SRCROOT}/Pods/Target Support Files/Pods-Cleverbot/Pods-Cleverbot-frameworks.sh", 335 | "${BUILT_PRODUCTS_DIR}/Alamofire/Alamofire.framework", 336 | "${BUILT_PRODUCTS_DIR}/CGFloatLiteral/CGFloatLiteral.framework", 337 | "${BUILT_PRODUCTS_DIR}/Differentiator/Differentiator.framework", 338 | "${BUILT_PRODUCTS_DIR}/ManualLayout/ManualLayout.framework", 339 | "${BUILT_PRODUCTS_DIR}/ReactorKit/ReactorKit.framework", 340 | "${BUILT_PRODUCTS_DIR}/ReusableKit/ReusableKit.framework", 341 | "${BUILT_PRODUCTS_DIR}/RxCocoa/RxCocoa.framework", 342 | "${BUILT_PRODUCTS_DIR}/RxCodable/RxCodable.framework", 343 | "${BUILT_PRODUCTS_DIR}/RxDataSources/RxDataSources.framework", 344 | "${BUILT_PRODUCTS_DIR}/RxKeyboard/RxKeyboard.framework", 345 | "${BUILT_PRODUCTS_DIR}/RxOptional/RxOptional.framework", 346 | "${BUILT_PRODUCTS_DIR}/RxSwift/RxSwift.framework", 347 | "${BUILT_PRODUCTS_DIR}/RxViewController/RxViewController.framework", 348 | "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", 349 | "${BUILT_PRODUCTS_DIR}/SwiftyColor/SwiftyColor.framework", 350 | "${BUILT_PRODUCTS_DIR}/SwiftyImage/SwiftyImage.framework", 351 | "${BUILT_PRODUCTS_DIR}/Then/Then.framework", 352 | "${BUILT_PRODUCTS_DIR}/UITextView+Placeholder/UITextView_Placeholder.framework", 353 | ); 354 | name = "[CP] Embed Pods Frameworks"; 355 | outputPaths = ( 356 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Alamofire.framework", 357 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CGFloatLiteral.framework", 358 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Differentiator.framework", 359 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ManualLayout.framework", 360 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactorKit.framework", 361 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReusableKit.framework", 362 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCocoa.framework", 363 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxCodable.framework", 364 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxDataSources.framework", 365 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxKeyboard.framework", 366 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxOptional.framework", 367 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxSwift.framework", 368 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RxViewController.framework", 369 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", 370 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyColor.framework", 371 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyImage.framework", 372 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Then.framework", 373 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/UITextView_Placeholder.framework", 374 | ); 375 | runOnlyForDeploymentPostprocessing = 0; 376 | shellPath = /bin/sh; 377 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Cleverbot/Pods-Cleverbot-frameworks.sh\"\n"; 378 | showEnvVarsInLog = 0; 379 | }; 380 | D025CCDA2C23C6AC23EB4A23 /* [CP] Copy Pods Resources */ = { 381 | isa = PBXShellScriptBuildPhase; 382 | buildActionMask = 2147483647; 383 | files = ( 384 | ); 385 | inputPaths = ( 386 | ); 387 | name = "[CP] Copy Pods Resources"; 388 | outputPaths = ( 389 | ); 390 | runOnlyForDeploymentPostprocessing = 0; 391 | shellPath = /bin/sh; 392 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Cleverbot/Pods-Cleverbot-resources.sh\"\n"; 393 | showEnvVarsInLog = 0; 394 | }; 395 | /* End PBXShellScriptBuildPhase section */ 396 | 397 | /* Begin PBXSourcesBuildPhase section */ 398 | 0365BF6E1E65E90A0039F7CE /* Sources */ = { 399 | isa = PBXSourcesBuildPhase; 400 | buildActionMask = 2147483647; 401 | files = ( 402 | 036666A81E65EDB200508EFD /* OutgoingMessage.swift in Sources */, 403 | 036666C51E65F31A00508EFD /* MessageInputBar.swift in Sources */, 404 | 036666C91E65F37800508EFD /* UICollectionView+ScrollToBottom.swift in Sources */, 405 | 036666B81E65EF9900508EFD /* UICollectionView+CellWidth.swift in Sources */, 406 | 036666AA1E65EEA000508EFD /* ChatViewController.swift in Sources */, 407 | 036666CD1E65F43800508EFD /* CleverbotService.swift in Sources */, 408 | 036666B71E65EF9900508EFD /* String+BoundingRect.swift in Sources */, 409 | 036666A21E65ECAC00508EFD /* ModelType.swift in Sources */, 410 | 036666CF1E65F8DB00508EFD /* Configuration.swift in Sources */, 411 | 036666A41E65ECF600508EFD /* Message.swift in Sources */, 412 | 03FD987B1E8C2A6C00F33157 /* Array+SectionModel.swift in Sources */, 413 | 0310C2741FD67A1500C0BDCD /* AppDelegate.swift in Sources */, 414 | 036666B01E65EF6600508EFD /* BaseCollectionViewCell.swift in Sources */, 415 | 036666A61E65ED7800508EFD /* IncomingMessage.swift in Sources */, 416 | 0366669B1E65EB6800508EFD /* BaseViewController.swift in Sources */, 417 | 036666AC1E65EEBB00508EFD /* ChatViewReactor.swift in Sources */, 418 | 036666B21E65EF7B00508EFD /* BaseMessageCell.swift in Sources */, 419 | 036666C31E65F0AD00508EFD /* ChatViewSection.swift in Sources */, 420 | 036666BA1E65EFBA00508EFD /* MessageCellReactor.swift in Sources */, 421 | 036666BC1E65EFDB00508EFD /* IncomingMessageCell.swift in Sources */, 422 | 036666BE1E65EFE200508EFD /* OutgoingMessageCell.swift in Sources */, 423 | 036666C71E65F32F00508EFD /* String+Localized.swift in Sources */, 424 | 0310C2761FD67A2500C0BDCD /* CompositionRoot.swift in Sources */, 425 | 036666B61E65EF9900508EFD /* Snap.swift in Sources */, 426 | ); 427 | runOnlyForDeploymentPostprocessing = 0; 428 | }; 429 | /* End PBXSourcesBuildPhase section */ 430 | 431 | /* Begin PBXVariantGroup section */ 432 | 0365BF891E65E9440039F7CE /* LaunchScreen.storyboard */ = { 433 | isa = PBXVariantGroup; 434 | children = ( 435 | 0365BF8A1E65E9440039F7CE /* Base */, 436 | ); 437 | name = LaunchScreen.storyboard; 438 | sourceTree = ""; 439 | }; 440 | 036666D41E660C6900508EFD /* Localizable.strings */ = { 441 | isa = PBXVariantGroup; 442 | children = ( 443 | 036666D31E660C6900508EFD /* en */, 444 | 036666D51E660C7200508EFD /* ko */, 445 | ); 446 | name = Localizable.strings; 447 | sourceTree = ""; 448 | }; 449 | /* End PBXVariantGroup section */ 450 | 451 | /* Begin XCBuildConfiguration section */ 452 | 0365BF821E65E90A0039F7CE /* Debug */ = { 453 | isa = XCBuildConfiguration; 454 | buildSettings = { 455 | ALWAYS_SEARCH_USER_PATHS = NO; 456 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 457 | CLANG_ANALYZER_NONNULL = YES; 458 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 459 | CLANG_CXX_LIBRARY = "libc++"; 460 | CLANG_ENABLE_MODULES = YES; 461 | CLANG_ENABLE_OBJC_ARC = YES; 462 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 463 | CLANG_WARN_BOOL_CONVERSION = YES; 464 | CLANG_WARN_COMMA = YES; 465 | CLANG_WARN_CONSTANT_CONVERSION = YES; 466 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 467 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 468 | CLANG_WARN_EMPTY_BODY = YES; 469 | CLANG_WARN_ENUM_CONVERSION = YES; 470 | CLANG_WARN_INFINITE_RECURSION = YES; 471 | CLANG_WARN_INT_CONVERSION = YES; 472 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 473 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 474 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 475 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 476 | CLANG_WARN_STRICT_PROTOTYPES = YES; 477 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 478 | CLANG_WARN_UNREACHABLE_CODE = YES; 479 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 480 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 481 | COPY_PHASE_STRIP = NO; 482 | DEBUG_INFORMATION_FORMAT = dwarf; 483 | ENABLE_STRICT_OBJC_MSGSEND = YES; 484 | ENABLE_TESTABILITY = YES; 485 | GCC_C_LANGUAGE_STANDARD = gnu99; 486 | GCC_DYNAMIC_NO_PIC = NO; 487 | GCC_NO_COMMON_BLOCKS = YES; 488 | GCC_OPTIMIZATION_LEVEL = 0; 489 | GCC_PREPROCESSOR_DEFINITIONS = ( 490 | "DEBUG=1", 491 | "$(inherited)", 492 | ); 493 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 494 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 495 | GCC_WARN_UNDECLARED_SELECTOR = YES; 496 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 497 | GCC_WARN_UNUSED_FUNCTION = YES; 498 | GCC_WARN_UNUSED_VARIABLE = YES; 499 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 500 | MTL_ENABLE_DEBUG_INFO = YES; 501 | ONLY_ACTIVE_ARCH = YES; 502 | SDKROOT = iphoneos; 503 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 504 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 505 | SWIFT_VERSION = 4.0; 506 | }; 507 | name = Debug; 508 | }; 509 | 0365BF831E65E90A0039F7CE /* Release */ = { 510 | isa = XCBuildConfiguration; 511 | buildSettings = { 512 | ALWAYS_SEARCH_USER_PATHS = NO; 513 | CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; 514 | CLANG_ANALYZER_NONNULL = YES; 515 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 516 | CLANG_CXX_LIBRARY = "libc++"; 517 | CLANG_ENABLE_MODULES = YES; 518 | CLANG_ENABLE_OBJC_ARC = YES; 519 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 520 | CLANG_WARN_BOOL_CONVERSION = YES; 521 | CLANG_WARN_COMMA = YES; 522 | CLANG_WARN_CONSTANT_CONVERSION = YES; 523 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 524 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 525 | CLANG_WARN_EMPTY_BODY = YES; 526 | CLANG_WARN_ENUM_CONVERSION = YES; 527 | CLANG_WARN_INFINITE_RECURSION = YES; 528 | CLANG_WARN_INT_CONVERSION = YES; 529 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 530 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 531 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 532 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 533 | CLANG_WARN_STRICT_PROTOTYPES = YES; 534 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 535 | CLANG_WARN_UNREACHABLE_CODE = YES; 536 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 537 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 538 | COPY_PHASE_STRIP = NO; 539 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 540 | ENABLE_NS_ASSERTIONS = NO; 541 | ENABLE_STRICT_OBJC_MSGSEND = YES; 542 | GCC_C_LANGUAGE_STANDARD = gnu99; 543 | GCC_NO_COMMON_BLOCKS = YES; 544 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 545 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 546 | GCC_WARN_UNDECLARED_SELECTOR = YES; 547 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 548 | GCC_WARN_UNUSED_FUNCTION = YES; 549 | GCC_WARN_UNUSED_VARIABLE = YES; 550 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 551 | MTL_ENABLE_DEBUG_INFO = NO; 552 | SDKROOT = iphoneos; 553 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 554 | SWIFT_VERSION = 4.0; 555 | VALIDATE_PRODUCT = YES; 556 | }; 557 | name = Release; 558 | }; 559 | 0365BF851E65E90A0039F7CE /* Debug */ = { 560 | isa = XCBuildConfiguration; 561 | baseConfigurationReference = EBEADB1197DD1ACCE235178A /* Pods-Cleverbot.debug.xcconfig */; 562 | buildSettings = { 563 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 564 | INFOPLIST_FILE = "$(SRCROOT)/Cleverbot/Supporting Files/Info.plist"; 565 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 566 | PRODUCT_BUNDLE_IDENTIFIER = kr.xoul.Cleverbot; 567 | PRODUCT_NAME = "$(TARGET_NAME)"; 568 | }; 569 | name = Debug; 570 | }; 571 | 0365BF861E65E90A0039F7CE /* Release */ = { 572 | isa = XCBuildConfiguration; 573 | baseConfigurationReference = 9C539215C39E6C4FB41683F9 /* Pods-Cleverbot.release.xcconfig */; 574 | buildSettings = { 575 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 576 | INFOPLIST_FILE = "$(SRCROOT)/Cleverbot/Supporting Files/Info.plist"; 577 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 578 | PRODUCT_BUNDLE_IDENTIFIER = kr.xoul.Cleverbot; 579 | PRODUCT_NAME = "$(TARGET_NAME)"; 580 | }; 581 | name = Release; 582 | }; 583 | /* End XCBuildConfiguration section */ 584 | 585 | /* Begin XCConfigurationList section */ 586 | 0365BF6D1E65E90A0039F7CE /* Build configuration list for PBXProject "Cleverbot" */ = { 587 | isa = XCConfigurationList; 588 | buildConfigurations = ( 589 | 0365BF821E65E90A0039F7CE /* Debug */, 590 | 0365BF831E65E90A0039F7CE /* Release */, 591 | ); 592 | defaultConfigurationIsVisible = 0; 593 | defaultConfigurationName = Release; 594 | }; 595 | 0365BF841E65E90A0039F7CE /* Build configuration list for PBXNativeTarget "Cleverbot" */ = { 596 | isa = XCConfigurationList; 597 | buildConfigurations = ( 598 | 0365BF851E65E90A0039F7CE /* Debug */, 599 | 0365BF861E65E90A0039F7CE /* Release */, 600 | ); 601 | defaultConfigurationIsVisible = 0; 602 | defaultConfigurationName = Release; 603 | }; 604 | /* End XCConfigurationList section */ 605 | }; 606 | rootObject = 0365BF6A1E65E90A0039F7CE /* Project object */; 607 | } 608 | -------------------------------------------------------------------------------- /Cleverbot.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Cleverbot.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Cleverbot/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Cleverbot/Resources/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Cleverbot/Resources/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "say_something" = "Say something..."; 2 | "send" = "Send"; 3 | -------------------------------------------------------------------------------- /Cleverbot/Resources/ko.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "say_something" = "내용을 입력하세요..."; 2 | "send" = "보내기"; 3 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Configuration.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Configuration.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 01/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | /// Global application configuration 10 | struct Configuration { 11 | /// Cleverbot API key. You may issue the free API key on the cleverbot website. 12 | /// 13 | /// - seealso: https://www.cleverbot.com/api/ 14 | static let apiKey = "3bd32ce29dee047caae09ad3198c35d1" 15 | } 16 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Entry/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 01/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | import CGFloatLiteral 12 | import ManualLayout 13 | import RxOptional 14 | import SnapKit 15 | import SwiftyColor 16 | import SwiftyImage 17 | import Then 18 | import UITextView_Placeholder 19 | 20 | @UIApplicationMain 21 | class AppDelegate: UIResponder, UIApplicationDelegate { 22 | var dependency: AppDependency! 23 | var window: UIWindow? 24 | 25 | func application( 26 | _ application: UIApplication, 27 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? 28 | ) -> Bool { 29 | self.dependency = self.dependency ?? CompositionRoot.resolve() 30 | self.window = self.dependency.window 31 | return true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Entry/CompositionRoot.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CompositionRoot.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 05/12/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct AppDependency { 12 | let window: UIWindow 13 | } 14 | 15 | enum CompositionRoot { 16 | static func resolve() -> AppDependency { 17 | // service 18 | let cleverbotService = CleverbotService() 19 | 20 | // root view controller 21 | let chatViewReactor = ChatViewReactor(cleverbotService: cleverbotService) 22 | let chatViewController = ChatViewController(reactor: chatViewReactor) 23 | let navigationController = UINavigationController(rootViewController: chatViewController) 24 | navigationController.navigationBar.prefersLargeTitles = true 25 | 26 | // window 27 | let window = UIWindow(frame: UIScreen.main.bounds) 28 | window.backgroundColor = .white 29 | window.rootViewController = navigationController 30 | window.makeKeyAndVisible() 31 | 32 | return AppDependency(window: window) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Models/IncomingMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IncomingMessage.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 01/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// The message from cleverbot 12 | struct IncomingMessage: ModelType { 13 | var cs: String 14 | var text: String 15 | 16 | enum CodingKeys: String, CodingKey { 17 | case cs 18 | case text = "output" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Models/Message.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Message.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 01/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum Message { 12 | case incoming(IncomingMessage) 13 | case outgoing(OutgoingMessage) 14 | 15 | var text: String { 16 | switch self { 17 | case let .incoming(message): 18 | return message.text 19 | case let .outgoing(message): 20 | return message.text 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Models/ModelType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ModelType.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 01/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import Then 10 | 11 | /// Every model types should conform to this protocol. 12 | protocol ModelType: Decodable, Then { 13 | static var dateDecodingStrategy: JSONDecoder.DateDecodingStrategy { get } 14 | } 15 | 16 | extension ModelType { 17 | static var dateDecodingStrategy: JSONDecoder.DateDecodingStrategy { 18 | return .iso8601 19 | } 20 | 21 | static var decoder: JSONDecoder { 22 | let decoder = JSONDecoder() 23 | decoder.dateDecodingStrategy = self.dateDecodingStrategy 24 | return decoder 25 | } 26 | 27 | init(data: Data) throws { 28 | self = try Self.decoder.decode(Self.self, from: data) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Models/OutgoingMessage.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OutgoingMessage.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 01/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | struct OutgoingMessage: ModelType { 10 | var text: String 11 | 12 | init(text: String) { 13 | self.text = text 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Sections/ChatViewSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChatViewSection.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 01/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import RxDataSources 10 | 11 | struct ChatViewSection { 12 | var items: [ChatViewSectionItem] 13 | 14 | init(items: [ChatViewSectionItem]) { 15 | self.items = items 16 | } 17 | } 18 | 19 | extension ChatViewSection: SectionModelType { 20 | init(original: ChatViewSection, items: [ChatViewSectionItem]) { 21 | self = original 22 | self.items = items 23 | } 24 | } 25 | 26 | enum ChatViewSectionItem { 27 | case incomingMessage(MessageCellReactor) 28 | case outgoingMessage(MessageCellReactor) 29 | } 30 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Services/CleverbotService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CleverbotService.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 01/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import Alamofire 10 | import RxSwift 11 | 12 | protocol CleverbotServiceType { 13 | func getReply(text: String, cs: String?) -> Observable 14 | } 15 | 16 | final class CleverbotService: CleverbotServiceType { 17 | 18 | /// Sends the message and returns the observable of received message. 19 | /// 20 | /// - parameter text: Message to send 21 | /// - parameter cs: Cleverbot state. Send the `cs` property of the last received message. 22 | /// 23 | /// - seealso: https://www.cleverbot.com/api/howto/ 24 | func getReply(text: String, cs: String?) -> Observable { 25 | let urlString = "https://www.cleverbot.com/getreply" 26 | let apiKey = Configuration.apiKey 27 | var parameters: [String: Any] = [ 28 | "key": apiKey, 29 | "input": text, 30 | ] 31 | if let cs = cs { 32 | parameters["cs"] = cs 33 | } 34 | return Observable.create { observer in 35 | let request = Alamofire.request(urlString, parameters: parameters).responseData { response in 36 | switch response.result { 37 | case let .success(jsonData): 38 | do { 39 | let incomingMessage = try IncomingMessage(data: jsonData) 40 | observer.onNext(incomingMessage) 41 | observer.onCompleted() 42 | } catch let error { 43 | observer.onError(error) 44 | } 45 | case let .failure(error): 46 | observer.onError(error) 47 | } 48 | } 49 | return Disposables.create { 50 | request.cancel() 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Utils/Array+SectionModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Array+SectionModel.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 30/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import RxDataSources 10 | 11 | extension Array where Element: SectionModelType { 12 | 13 | public subscript(indexPath: IndexPath) -> Element.Item { 14 | get { 15 | return self[indexPath.section].items[indexPath.item] 16 | } 17 | mutating set { 18 | self.update(section: indexPath.section) { items in 19 | items[indexPath.item] = newValue 20 | } 21 | } 22 | } 23 | 24 | public mutating func insert(newElement: Element.Item, at indexPath: IndexPath) { 25 | self.update(section: indexPath.section) { items in 26 | items.insert(newElement, at: indexPath.item) 27 | } 28 | } 29 | 30 | @discardableResult 31 | public mutating func remove(at indexPath: IndexPath) -> Element.Item { 32 | return self.update(section: indexPath.section) { items in 33 | return items.remove(at: indexPath.item) 34 | } 35 | } 36 | 37 | private mutating func replace(section: Int, items: [Element.Item]) { 38 | self[section] = Element.init(original: self[section], items: items) 39 | } 40 | 41 | private mutating func update(section: Int, mutate: (inout [Element.Item]) -> T) -> T { 42 | var items = self[section].items 43 | let value = mutate(&items) 44 | self[section] = Element.init(original: self[section], items: items) 45 | return value 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Utils/Snap.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CeilUtil.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 01/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// Ceil to snap pixel 12 | func snap(_ x: CGFloat) -> CGFloat { 13 | let scale = UIScreen.main.scale 14 | return ceil(x * scale) / scale 15 | } 16 | 17 | func snap(_ point: CGPoint) -> CGPoint { 18 | return CGPoint(x: snap(point.x), y: snap(point.y)) 19 | } 20 | 21 | func snap(_ size: CGSize) -> CGSize { 22 | return CGSize(width: snap(size.width), height: snap(size.height)) 23 | } 24 | 25 | func snap(_ rect: CGRect) -> CGRect { 26 | return CGRect(origin: snap(rect.origin), size: snap(rect.size)) 27 | } 28 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Utils/String+BoundingRect.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+BoundingRect.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 01/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension String { 12 | func boundingRect(with size: CGSize, attributes: [NSAttributedStringKey: Any]?) -> CGRect { 13 | let options: NSStringDrawingOptions = [.usesLineFragmentOrigin, .usesFontLeading] 14 | let rect = self.boundingRect(with: size, options: options, attributes: attributes, context: nil) 15 | return snap(rect) 16 | } 17 | 18 | func size(thatFits size: CGSize, font: UIFont, maximumNumberOfLines: Int = 0) -> CGSize { 19 | var size = self.boundingRect(with: size, attributes: [.font: font]).size 20 | if maximumNumberOfLines > 0 { 21 | size.height = min(size.height, CGFloat(maximumNumberOfLines) * font.lineHeight) 22 | } 23 | return snap(size) 24 | } 25 | 26 | func width(with font: UIFont, maximumNumberOfLines: Int = 0) -> CGFloat { 27 | let size = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude) 28 | return self.size(thatFits: size, font: font, maximumNumberOfLines: maximumNumberOfLines).width 29 | } 30 | 31 | func height(thatFitsWidth width: CGFloat, font: UIFont, maximumNumberOfLines: Int = 0) -> CGFloat { 32 | let size = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude) 33 | return self.size(thatFits: size, font: font, maximumNumberOfLines: maximumNumberOfLines).height 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Utils/String+Localized.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Localized.swift 3 | // Zeta 4 | // 5 | // Created by Suyeol Jeon on 16/02/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension String { 12 | var localized: String { 13 | return NSLocalizedString(self, comment: "") 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Utils/UICollectionView+CellWidth.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+CellWidth.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 01/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UICollectionView { 12 | 13 | func cellWidth(forSectionAt section: Int) -> CGFloat { 14 | var width = self.width 15 | width -= self.contentInset.left 16 | width -= self.contentInset.right 17 | 18 | if let delegate = self.delegate as? UICollectionViewDelegateFlowLayout, 19 | let inset = delegate.collectionView?(self, layout: self.collectionViewLayout, insetForSectionAt: section) { 20 | width -= inset.left 21 | width -= inset.right 22 | } else if let layout = self.collectionViewLayout as? UICollectionViewFlowLayout { 23 | width -= layout.sectionInset.left 24 | width -= layout.sectionInset.right 25 | } 26 | 27 | return width 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Utils/UICollectionView+ScrollToBottom.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionView+ScrollToBottom.swift 3 | // Zeta 4 | // 5 | // Created by Suyeol Jeon on 16/02/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UICollectionView { 12 | 13 | func isReachedBottom(withOffset offset: CGFloat = 0) -> Bool { 14 | guard self.contentSize.height > self.height, self.height > 0 else { return true } 15 | let contentOffsetBottom = self.contentOffset.y + self.height 16 | return contentOffsetBottom - offset >= self.contentSize.height 17 | } 18 | 19 | func scrollToBottom(animated: Bool) { 20 | let scrollHeight = self.contentSize.height + self.contentInset.top + self.contentInset.bottom 21 | guard scrollHeight > self.height, self.height > 0 else { return } 22 | let targetOffset = CGPoint(x: 0, y: self.contentSize.height + self.contentInset.bottom - self.height) 23 | self.setContentOffset(targetOffset, animated: animated) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Cleverbot/Sources/ViewControllers/BaseViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseViewController.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 01/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | import RxSwift 12 | 13 | class BaseViewController: UIViewController { 14 | 15 | // MARK: Initializing 16 | 17 | init() { 18 | super.init(nibName: nil, bundle: nil) 19 | } 20 | 21 | required convenience init?(coder aDecoder: NSCoder) { 22 | self.init() 23 | } 24 | 25 | 26 | // MARK: Rx 27 | 28 | var disposeBag = DisposeBag() 29 | 30 | 31 | // MARK: Layout Constraints 32 | 33 | private(set) var didSetupConstraints = false 34 | 35 | override func viewDidLoad() { 36 | self.view.setNeedsUpdateConstraints() 37 | } 38 | 39 | override func updateViewConstraints() { 40 | if !self.didSetupConstraints { 41 | self.setupConstraints() 42 | self.didSetupConstraints = true 43 | } 44 | super.updateViewConstraints() 45 | } 46 | 47 | func setupConstraints() { 48 | // Override point 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /Cleverbot/Sources/ViewControllers/ChatViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChatViewController.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 01/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | import ReactorKit 12 | import ReusableKit 13 | import RxDataSources 14 | import RxKeyboard 15 | import RxSwift 16 | 17 | final class ChatViewController: BaseViewController, View { 18 | typealias Reactor = ChatViewReactor 19 | 20 | // MARK: Constants 21 | 22 | fileprivate struct Metric { 23 | static let messageSectionInsetTop = 10.f 24 | static let messageSectionInsetBottom = 10.f 25 | static let messageSectionInsetLeftRight = 10.f 26 | static let messageSectionItemSpacing = 10.f 27 | } 28 | 29 | private enum Font { 30 | static let placeholderLabel = UIFont.boldSystemFont(ofSize: 18) 31 | } 32 | 33 | private enum Color { 34 | static let placeholderLabelText = 0xCCCCCC.color 35 | } 36 | 37 | fileprivate struct Reusable { 38 | static let incomingMessageCell = ReusableCell() 39 | static let outgoingMessageCell = ReusableCell() 40 | } 41 | 42 | 43 | // MARK: Properties 44 | 45 | private lazy var dataSource = self.createDataSource() 46 | 47 | 48 | // MARK: UI 49 | 50 | fileprivate let collectionView = UICollectionView( 51 | frame: .zero, collectionViewLayout: UICollectionViewFlowLayout() 52 | ).then { 53 | $0.backgroundColor = .clear 54 | $0.alwaysBounceVertical = true 55 | $0.keyboardDismissMode = .interactive 56 | $0.register(Reusable.incomingMessageCell) 57 | $0.register(Reusable.outgoingMessageCell) 58 | } 59 | fileprivate let messageInputBar = MessageInputBar() 60 | private let placeholderLabel: UILabel = UILabel().then { 61 | $0.font = Font.placeholderLabel 62 | $0.text = "Say hi 👋" 63 | $0.textColor = Color.placeholderLabelText 64 | $0.isUserInteractionEnabled = false 65 | } 66 | 67 | 68 | // MARK: Initializing 69 | 70 | init(reactor: Reactor) { 71 | super.init() 72 | self.title = "Cleverbot 🤖" 73 | self.reactor = reactor 74 | } 75 | 76 | required convenience init?(coder aDecoder: NSCoder) { 77 | fatalError("init(coder:) has not been implemented") 78 | } 79 | 80 | private func createDataSource() -> RxCollectionViewSectionedReloadDataSource { 81 | return .init( 82 | configureCell: { dataSource, collectionView, indexPath, sectionItem in 83 | switch sectionItem { 84 | case let .incomingMessage(reactor): 85 | let cell = collectionView.dequeue(Reusable.incomingMessageCell, for: indexPath) 86 | cell.reactor = reactor 87 | return cell 88 | 89 | case let .outgoingMessage(reactor): 90 | let cell = collectionView.dequeue(Reusable.outgoingMessageCell, for: indexPath) 91 | cell.reactor = reactor 92 | return cell 93 | } 94 | } 95 | ) 96 | } 97 | 98 | 99 | // MARK: View Life Cycle 100 | 101 | override func viewDidLoad() { 102 | super.viewDidLoad() 103 | self.view.backgroundColor = .white 104 | 105 | self.collectionView.contentInset.bottom = self.messageInputBar.intrinsicContentSize.height 106 | self.collectionView.scrollIndicatorInsets = self.collectionView.contentInset 107 | 108 | self.view.addSubview(self.collectionView) 109 | self.view.addSubview(self.placeholderLabel) 110 | self.view.addSubview(self.messageInputBar) 111 | } 112 | 113 | override func setupConstraints() { 114 | self.collectionView.snp.makeConstraints { make in 115 | make.edges.equalToSuperview() 116 | } 117 | self.placeholderLabel.snp.makeConstraints { make in 118 | make.center.equalTo(self.collectionView) 119 | } 120 | self.messageInputBar.snp.makeConstraints { make in 121 | make.left.right.equalToSuperview() 122 | make.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom) 123 | } 124 | } 125 | 126 | 127 | // MARK: Configuring 128 | 129 | func bind(reactor: Reactor) { 130 | // Delegate 131 | self.collectionView.rx 132 | .setDelegate(self) 133 | .disposed(by: self.disposeBag) 134 | 135 | // Action 136 | self.messageInputBar.rx.sendButtonTap.map(Reactor.Action.send) 137 | .bind(to: reactor.action) 138 | .disposed(by: self.disposeBag) 139 | 140 | // State 141 | reactor.state.map { $0.sections } 142 | .bind(to: self.collectionView.rx.items(dataSource: self.dataSource)) 143 | .disposed(by: self.disposeBag) 144 | 145 | // UI 146 | let wasReachedBottom: Observable = self.collectionView.rx.contentOffset 147 | .map { [weak self] _ in 148 | self?.collectionView.isReachedBottom() ?? false 149 | } 150 | 151 | reactor.state.map { $0.sections } 152 | .debounce(0.1, scheduler: MainScheduler.instance) 153 | .withLatestFrom(wasReachedBottom) { ($0, $1) } 154 | .filter { _, wasReachedBottom in wasReachedBottom == true } 155 | .subscribe(onNext: { [weak self] _ in 156 | // scroll to bottom when receive message only if last content offset was at the bottom 157 | self?.collectionView.scrollToBottom(animated: true) 158 | }) 159 | .disposed(by: self.disposeBag) 160 | 161 | reactor.state.map { $0.sections.first?.items.isEmpty != true } 162 | .bind(to: self.placeholderLabel.rx.isHidden) 163 | .disposed(by: self.disposeBag) 164 | 165 | // Keyboard 166 | RxKeyboard.instance.visibleHeight 167 | .drive(onNext: { [weak self] keyboardVisibleHeight in 168 | guard let `self` = self, self.didSetupConstraints else { return } 169 | self.messageInputBar.snp.updateConstraints { make in 170 | var offset: CGFloat = -keyboardVisibleHeight 171 | if keyboardVisibleHeight > 0 { 172 | offset += self.view.safeAreaInsets.bottom 173 | } 174 | make.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom).offset(offset) 175 | } 176 | self.view.setNeedsLayout() 177 | UIView.animate(withDuration: 0) { 178 | self.collectionView.contentInset.bottom = keyboardVisibleHeight + self.messageInputBar.height 179 | self.collectionView.scrollIndicatorInsets.bottom = self.collectionView.contentInset.bottom 180 | self.view.layoutIfNeeded() 181 | } 182 | }) 183 | .disposed(by: self.disposeBag) 184 | 185 | RxKeyboard.instance.willShowVisibleHeight 186 | .drive(onNext: { [weak self] keyboardVisibleHeight in 187 | self?.collectionView.scrollToBottom(animated: true) 188 | }) 189 | .disposed(by: self.disposeBag) 190 | } 191 | 192 | } 193 | 194 | 195 | // MARK: - UICollectionViewDelegateFlowLayout 196 | 197 | extension ChatViewController: UICollectionViewDelegateFlowLayout { 198 | 199 | func collectionView( 200 | _ collectionView: UICollectionView, 201 | layout collectionViewLayout: UICollectionViewLayout, 202 | sizeForItemAt indexPath: IndexPath 203 | ) -> CGSize { 204 | let cellWidth = collectionView.cellWidth(forSectionAt: indexPath.section) 205 | let reactor = self.dataSource[indexPath] 206 | switch reactor { 207 | case let .incomingMessage(reactor): 208 | return IncomingMessageCell.size(thatFitsWidth: cellWidth, reactor: reactor) 209 | 210 | case let .outgoingMessage(reactor): 211 | return OutgoingMessageCell.size(thatFitsWidth: cellWidth, reactor: reactor) 212 | } 213 | } 214 | 215 | func collectionView( 216 | _ collectionView: UICollectionView, 217 | layout collectionViewLayout: UICollectionViewLayout, 218 | insetForSectionAt section: Int 219 | ) -> UIEdgeInsets { 220 | return UIEdgeInsets( 221 | top: Metric.messageSectionInsetTop, 222 | left: Metric.messageSectionInsetLeftRight, 223 | bottom: Metric.messageSectionInsetBottom, 224 | right: Metric.messageSectionInsetLeftRight 225 | ) 226 | } 227 | 228 | } 229 | -------------------------------------------------------------------------------- /Cleverbot/Sources/ViewControllers/ChatViewReactor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChatViewReactor.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 01/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import ReactorKit 10 | import RxCocoa 11 | import RxSwift 12 | 13 | final class ChatViewReactor: Reactor { 14 | enum Action { 15 | case send(String) 16 | } 17 | 18 | enum Mutation { 19 | case addMessage(Message) 20 | } 21 | 22 | struct State { 23 | var sections: [ChatViewSection] = [ChatViewSection(items: [])] 24 | var cleverbotState: String? = nil 25 | } 26 | 27 | let initialState = State() 28 | private let cleverbotService: CleverbotServiceType 29 | 30 | init(cleverbotService: CleverbotService) { 31 | self.cleverbotService = cleverbotService 32 | } 33 | 34 | func mutate(action: Action) -> Observable { 35 | switch action { 36 | case let .send(text): 37 | let outgoingMessage = Observable.just(Message.outgoing(.init(text: text))) 38 | let incomingMessage = self.cleverbotService 39 | .getReply(text: text, cs: self.currentState.cleverbotState) 40 | .map { incomingMessage in Message.incoming(incomingMessage) } 41 | return Observable.of(outgoingMessage, incomingMessage).merge() 42 | .map { message in Mutation.addMessage(message) } 43 | } 44 | } 45 | 46 | func reduce(state: State, mutation: Mutation) -> State { 47 | var state = state 48 | switch mutation { 49 | case let .addMessage(message): 50 | let reactor = MessageCellReactor(message: message) 51 | let sectionItem: ChatViewSectionItem 52 | switch message { 53 | case .incoming: 54 | sectionItem = .incomingMessage(reactor) 55 | case .outgoing: 56 | sectionItem = .outgoingMessage(reactor) 57 | } 58 | state.sections[0].items.append(sectionItem) 59 | return state 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Views/BaseCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseCollectionViewCell.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 01/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | import RxSwift 12 | 13 | class BaseCollectionViewCell: UICollectionViewCell { 14 | 15 | // MARK: Properties 16 | 17 | var disposeBag: DisposeBag = DisposeBag() 18 | 19 | 20 | // MARK: Initializing 21 | 22 | override init(frame: CGRect) { 23 | super.init(frame: frame) 24 | } 25 | 26 | required convenience init?(coder aDecoder: NSCoder) { 27 | self.init(frame: .zero) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Views/BaseMessageCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BaseMessageCell.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 01/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | import ReactorKit 12 | import SwiftyImage 13 | 14 | class BaseMessageCell: BaseCollectionViewCell, View { 15 | 16 | // MARK: Types 17 | 18 | struct Appearance { 19 | let bubbleViewBackgroundColor: UIColor 20 | let bubbleViewAlignment: BubbleViewAlignment 21 | let messageLabelTextColor: UIColor 22 | } 23 | 24 | enum BubbleViewAlignment { 25 | case left, right 26 | } 27 | 28 | 29 | // MARK: Constants 30 | 31 | fileprivate struct Metric { 32 | static let bubbleViewMaximumWidth = ceil(UIScreen.main.bounds.width * 2 / 3) 33 | static let messageLabelTopBottom = 10.f 34 | static let messageLabelLeftRight = 12.f 35 | } 36 | 37 | fileprivate struct Font { 38 | static let messageLabel = UIFont.systemFont(ofSize: 15) 39 | } 40 | 41 | 42 | // MARK: Properties 43 | 44 | fileprivate let appearance: Appearance 45 | 46 | 47 | // MARK: UI 48 | 49 | fileprivate let bubbleView = UIImageView() 50 | fileprivate let messageLabel = UILabel().then { 51 | $0.font = Font.messageLabel 52 | $0.numberOfLines = 0 53 | } 54 | 55 | 56 | // MARK: Initializing 57 | 58 | init(frame: CGRect, appearance: Appearance) { 59 | self.appearance = appearance 60 | super.init(frame: frame) 61 | 62 | self.bubbleView.image = UIImage.resizable() 63 | .corner(radius: 18) 64 | .color(appearance.bubbleViewBackgroundColor) 65 | .image 66 | self.messageLabel.textColor = appearance.messageLabelTextColor 67 | 68 | self.bubbleView.addSubview(self.messageLabel) 69 | self.contentView.addSubview(self.bubbleView) 70 | } 71 | 72 | required convenience init?(coder aDecoder: NSCoder) { 73 | fatalError("init(coder:) has not been implemented") 74 | } 75 | 76 | 77 | // MARK: Configuring 78 | 79 | func bind(reactor: MessageCellReactor) { 80 | reactor.state.map { $0.message } 81 | .bind(to: self.messageLabel.rx.text) 82 | .disposed(by: self.disposeBag) 83 | self.setNeedsLayout() 84 | } 85 | 86 | 87 | // MARK: Size 88 | 89 | class func size(thatFitsWidth width: CGFloat, reactor: MessageCellReactor) -> CGSize { 90 | var height: CGFloat = 0 91 | let bubbleWidth = min(width, Metric.bubbleViewMaximumWidth) 92 | if let message = reactor.currentState.message { 93 | let messageLabelWidth = bubbleWidth - Metric.messageLabelLeftRight * 2 94 | height += Metric.messageLabelTopBottom 95 | height += message.height(thatFitsWidth: messageLabelWidth, font: Font.messageLabel) 96 | height += Metric.messageLabelTopBottom 97 | } 98 | return CGSize(width: width, height: height) 99 | } 100 | 101 | 102 | // MARK: Layout 103 | 104 | override func layoutSubviews() { 105 | super.layoutSubviews() 106 | 107 | self.messageLabel.top = Metric.messageLabelTopBottom 108 | self.messageLabel.left = Metric.messageLabelLeftRight 109 | self.messageLabel.width = min(self.contentView.width, Metric.bubbleViewMaximumWidth) 110 | - Metric.messageLabelLeftRight * 2 111 | self.messageLabel.sizeToFit() 112 | 113 | self.bubbleView.width = self.messageLabel.width + Metric.messageLabelLeftRight * 2 114 | self.bubbleView.height = self.contentView.height 115 | 116 | switch self.appearance.bubbleViewAlignment { 117 | case .left: 118 | self.bubbleView.left = 0 119 | case .right: 120 | self.bubbleView.right = self.contentView.width 121 | } 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Views/IncomingMessageCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IncomingMessageCell.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 01/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class IncomingMessageCell: BaseMessageCell { 12 | 13 | // MARK: Constants 14 | 15 | fileprivate struct Color { 16 | static let bubbleViewBackground = 0xD9D9D9.color 17 | static let messageLabelText = UIColor.black 18 | } 19 | 20 | 21 | // MARK: Initializing 22 | 23 | @objc init(frame: CGRect) { 24 | let appearance = Appearance( 25 | bubbleViewBackgroundColor: Color.bubbleViewBackground, 26 | bubbleViewAlignment: .left, 27 | messageLabelTextColor: Color.messageLabelText 28 | ) 29 | super.init(frame: frame, appearance: appearance) 30 | } 31 | 32 | required convenience init?(coder aDecoder: NSCoder) { 33 | fatalError("init(coder:) has not been implemented") 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Views/MessageCellReactor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageCellReactor.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 01/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import ReactorKit 10 | import RxCocoa 11 | import RxSwift 12 | 13 | final class MessageCellReactor: Reactor { 14 | typealias Action = NoAction 15 | struct State { 16 | var message: String? 17 | } 18 | 19 | let initialState: State 20 | 21 | init(message: Message) { 22 | self.initialState = State(message: message.text) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Views/MessageInputBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MessageInputBar.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 01/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | import RxCocoa 12 | import RxSwift 13 | 14 | final class MessageInputBar: UIView { 15 | 16 | // MARK: Constants 17 | 18 | private enum Metric { 19 | static let barHeight = 48.f 20 | static let barPaddingTopBottom = 7.f 21 | static let barPaddingLeftRight = 10.f 22 | static let sendButtonLeft = 7.f 23 | } 24 | 25 | // MARK: UI 26 | 27 | fileprivate let toolbar = UIToolbar() 28 | fileprivate let textView = UITextView().then { 29 | $0.placeholder = "say_something".localized 30 | $0.font = UIFont.systemFont(ofSize: 15) 31 | $0.isEditable = true 32 | $0.showsVerticalScrollIndicator = false 33 | $0.textContainerInset.left = 8 34 | $0.textContainerInset.right = 8 35 | $0.layer.borderColor = UIColor.lightGray.withAlphaComponent(0.6).cgColor 36 | $0.layer.borderWidth = 1 / UIScreen.main.scale 37 | $0.layer.cornerRadius = Metric.barHeight / 2 - Metric.barPaddingTopBottom 38 | } 39 | fileprivate let sendButton = UIButton(type: .system).then { 40 | $0.titleLabel?.font = UIFont.boldSystemFont(ofSize: 15) 41 | $0.setTitle("send".localized, for: .normal) 42 | } 43 | 44 | 45 | // MARK: Initializing 46 | 47 | override init(frame: CGRect) { 48 | super.init(frame: frame) 49 | self.translatesAutoresizingMaskIntoConstraints = false 50 | self.addSubview(self.toolbar) 51 | self.addSubview(self.textView) 52 | self.addSubview(self.sendButton) 53 | 54 | self.toolbar.snp.makeConstraints { make in 55 | make.top.left.right.equalToSuperview() 56 | make.bottom.equalToSuperview().offset(44) // enlarge background for iPhone X 57 | } 58 | 59 | self.textView.snp.makeConstraints { make in 60 | make.top.equalTo(Metric.barPaddingTopBottom) 61 | make.bottom.equalTo(-Metric.barPaddingTopBottom) 62 | make.left.equalTo(Metric.barPaddingLeftRight) 63 | make.right.equalTo(self.sendButton.snp.left).offset(-Metric.sendButtonLeft) 64 | } 65 | 66 | self.sendButton.snp.makeConstraints { make in 67 | make.top.equalTo(Metric.barPaddingLeftRight) 68 | make.bottom.equalTo(-Metric.barPaddingLeftRight) 69 | make.right.equalTo(-Metric.barPaddingLeftRight) 70 | } 71 | } 72 | 73 | required init?(coder aDecoder: NSCoder) { 74 | fatalError("init(coder:) has not been implemented") 75 | } 76 | 77 | 78 | // MARK: Size 79 | 80 | override var intrinsicContentSize: CGSize { 81 | return CGSize(width: self.width, height: Metric.barHeight) 82 | } 83 | } 84 | 85 | 86 | // MARK: - Reactive 87 | 88 | extension Reactive where Base: MessageInputBar { 89 | 90 | var sendButtonTap: ControlEvent { 91 | let source: Observable = self.base.sendButton.rx.tap 92 | .withLatestFrom(self.base.textView.rx.text.asObservable()) 93 | .flatMap { text -> Observable in 94 | if let text = text, !text.isEmpty { 95 | return .just(text) 96 | } else { 97 | return .empty() 98 | } 99 | } 100 | .do(onNext: { [weak base = self.base] _ in 101 | base?.textView.text = nil 102 | }) 103 | return ControlEvent(events: source) 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /Cleverbot/Sources/Views/OutgoingMessageCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OutgoingMessageCell.swift 3 | // Cleverbot 4 | // 5 | // Created by Suyeol Jeon on 01/03/2017. 6 | // Copyright © 2017 Suyeol Jeon. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class OutgoingMessageCell: BaseMessageCell { 12 | 13 | // MARK: Constants 14 | 15 | fileprivate struct Color { 16 | static let bubbleViewBackground = 0x1680FA.color 17 | static let messageLabelText = UIColor.white 18 | } 19 | 20 | 21 | // MARK: Initializing 22 | 23 | @objc init(frame: CGRect) { 24 | let appearance = Appearance( 25 | bubbleViewBackgroundColor: Color.bubbleViewBackground, 26 | bubbleViewAlignment: .right, 27 | messageLabelTextColor: Color.messageLabelText 28 | ) 29 | super.init(frame: frame, appearance: appearance) 30 | } 31 | 32 | required convenience init?(coder aDecoder: NSCoder) { 33 | fatalError("init(coder:) has not been implemented") 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Cleverbot/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "cocoapods" 4 | gem "poeditor-cli" 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (2.3.5) 5 | activesupport (4.2.8) 6 | i18n (~> 0.7) 7 | minitest (~> 5.1) 8 | thread_safe (~> 0.3, >= 0.3.4) 9 | tzinfo (~> 1.1) 10 | claide (1.0.1) 11 | cocoapods (1.2.0) 12 | activesupport (>= 4.0.2, < 5) 13 | claide (>= 1.0.1, < 2.0) 14 | cocoapods-core (= 1.2.0) 15 | cocoapods-deintegrate (>= 1.0.1, < 2.0) 16 | cocoapods-downloader (>= 1.1.3, < 2.0) 17 | cocoapods-plugins (>= 1.0.0, < 2.0) 18 | cocoapods-search (>= 1.0.0, < 2.0) 19 | cocoapods-stats (>= 1.0.0, < 2.0) 20 | cocoapods-trunk (>= 1.1.2, < 2.0) 21 | cocoapods-try (>= 1.1.0, < 2.0) 22 | colored (~> 1.2) 23 | escape (~> 0.0.4) 24 | fourflusher (~> 2.0.1) 25 | gh_inspector (~> 1.0) 26 | molinillo (~> 0.5.5) 27 | nap (~> 1.0) 28 | ruby-macho (~> 0.2.5) 29 | xcodeproj (>= 1.4.1, < 2.0) 30 | cocoapods-core (1.2.0) 31 | activesupport (>= 4.0.2, < 5) 32 | fuzzy_match (~> 2.0.4) 33 | nap (~> 1.0) 34 | cocoapods-deintegrate (1.0.1) 35 | cocoapods-downloader (1.1.3) 36 | cocoapods-plugins (1.0.0) 37 | nap 38 | cocoapods-search (1.0.0) 39 | cocoapods-stats (1.0.0) 40 | cocoapods-trunk (1.1.2) 41 | nap (>= 0.8, < 2.0) 42 | netrc (= 0.7.8) 43 | cocoapods-try (1.1.0) 44 | colored (1.2) 45 | colorize (0.8.1) 46 | escape (0.0.4) 47 | fourflusher (2.0.1) 48 | fuzzy_match (2.0.4) 49 | gh_inspector (1.0.3) 50 | i18n (0.8.1) 51 | minitest (5.10.1) 52 | molinillo (0.5.6) 53 | nanaimo (0.2.3) 54 | nap (1.1.0) 55 | netrc (0.7.8) 56 | poeditor-cli (0.3.1) 57 | colorize (~> 0.8) 58 | ruby-macho (0.2.6) 59 | thread_safe (0.3.6) 60 | tzinfo (1.2.2) 61 | thread_safe (~> 0.1) 62 | xcodeproj (1.4.2) 63 | CFPropertyList (~> 2.3.3) 64 | activesupport (>= 3) 65 | claide (>= 1.0.1, < 2.0) 66 | colored (~> 1.2) 67 | nanaimo (~> 0.2.3) 68 | 69 | PLATFORMS 70 | ruby 71 | 72 | DEPENDENCIES 73 | cocoapods 74 | poeditor-cli 75 | 76 | BUNDLED WITH 77 | 1.14.3 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Suyeol Jeon (xoul.kr) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '9.0' 2 | 3 | target 'Cleverbot' do 4 | use_frameworks! 5 | 6 | # Architecture 7 | pod 'ReactorKit' 8 | 9 | # Networking 10 | pod 'Alamofire' 11 | 12 | # Rx 13 | pod 'RxSwift' 14 | pod 'RxCocoa' 15 | pod 'RxDataSources' 16 | pod 'RxOptional' 17 | pod 'RxKeyboard' 18 | pod 'RxViewController' 19 | pod 'RxCodable' 20 | 21 | # UI 22 | pod 'SnapKit' 23 | pod 'ManualLayout' 24 | 25 | # Misc 26 | pod 'Then' 27 | pod 'CGFloatLiteral' 28 | pod 'ReusableKit' 29 | pod 'UITextView+Placeholder' 30 | pod 'SwiftyColor' 31 | pod 'SwiftyImage' 32 | end 33 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (4.6.0) 3 | - CGFloatLiteral (0.4.0) 4 | - Differentiator (3.0.2) 5 | - ManualLayout (1.3.0) 6 | - ReactorKit (1.1.0): 7 | - RxSwift (>= 4.0.0) 8 | - ReusableKit (2.0.0): 9 | - ReusableKit/Core (= 2.0.0) 10 | - ReusableKit/RxSwift (= 2.0.0) 11 | - ReusableKit/Core (2.0.0) 12 | - ReusableKit/RxSwift (2.0.0): 13 | - ReusableKit/Core 14 | - RxCocoa (>= 4.0.0-beta.0) 15 | - RxSwift (>= 4.0.0-beta.0) 16 | - RxCocoa (4.0.0): 17 | - RxSwift (~> 4.0) 18 | - RxCodable (0.3.0): 19 | - RxCodable/Core (= 0.3.0) 20 | - RxCodable/Core (0.3.0): 21 | - RxSwift (>= 4.0.0-beta.0) 22 | - RxDataSources (3.0.2): 23 | - Differentiator (~> 3.0) 24 | - RxCocoa (~> 4.0) 25 | - RxSwift (~> 4.0) 26 | - RxKeyboard (0.7.1): 27 | - RxCocoa (>= 4.0.0) 28 | - RxSwift (>= 4.0.0) 29 | - RxOptional (3.3.0): 30 | - RxCocoa (~> 4.0) 31 | - RxSwift (~> 4.0) 32 | - RxSwift (4.0.0) 33 | - RxViewController (0.3.0): 34 | - RxCocoa (>= 4.0.0) 35 | - RxSwift (>= 4.0.0) 36 | - SnapKit (4.0.0) 37 | - SwiftyColor (1.0.0) 38 | - SwiftyImage (1.2.0) 39 | - Then (2.3.0) 40 | - UITextView+Placeholder (1.2.0) 41 | 42 | DEPENDENCIES: 43 | - Alamofire 44 | - CGFloatLiteral 45 | - ManualLayout 46 | - ReactorKit 47 | - ReusableKit 48 | - RxCocoa 49 | - RxCodable 50 | - RxDataSources 51 | - RxKeyboard 52 | - RxOptional 53 | - RxSwift 54 | - RxViewController 55 | - SnapKit 56 | - SwiftyColor 57 | - SwiftyImage 58 | - Then 59 | - UITextView+Placeholder 60 | 61 | SPEC CHECKSUMS: 62 | Alamofire: f41a599bd63041760b26d393ec1069d9d7b917f4 63 | CGFloatLiteral: 2ab558b74124b584dd023a35b7e41795a61d8140 64 | Differentiator: a87be69eba49ec4ab460c7671143ee3a9eececfd 65 | ManualLayout: 68ac8cfa6b5f656f7a9fadec3730208b95986880 66 | ReactorKit: 26470a9eff5094fae909a30af0be7cd0cf8a182d 67 | ReusableKit: 668daa3e98e614ec2e135db2028a958baa2abd6f 68 | RxCocoa: d62846ca96495d862fa4c59ea7d87e5031d7340e 69 | RxCodable: 12d41ab0ba58f455cfc83965fcc927ac4bad3e7f 70 | RxDataSources: cb7c31e652a87ebb919da45f716bbb87b3765f6b 71 | RxKeyboard: eae01da0d1750e1c4852d20798279cf7b318c304 72 | RxOptional: e5448d3e21024489e07c31f6a262f2b50c975367 73 | RxSwift: fd680d75283beb5e2559486f3c0ff852f0d35334 74 | RxViewController: 203510707e73dd95f147f0c387710e3ec80d2b0f 75 | SnapKit: a42d492c16e80209130a3379f73596c3454b7694 76 | SwiftyColor: 7fa09db14051bc5d7f539e1c4576665975225992 77 | SwiftyImage: ebaa7c7b6163cd4ad102f3bb05a8fb276d35b4f3 78 | Then: ee21c97b85ff6062b9b0080c9abb1eea46743345 79 | UITextView+Placeholder: 77680995fcdd07c3f52ec92fe1150874a2ac89ff 80 | 81 | PODFILE CHECKSUM: 1e5a0a1b5b8704537b89dc08cee20174fae7bc51 82 | 83 | COCOAPODS: 1.3.1 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cleverbot 2 | 3 | ![Swift](https://img.shields.io/badge/Swift-4.0-orange.svg) 4 | 5 | [Cleverbot](https://www.cleverbot.com/) for iOS using [ReactorKit](https://github.com/devxoul/ReactorKit). 6 | 7 | ![cleverbot](https://user-images.githubusercontent.com/931655/33596175-30185e76-d9de-11e7-9348-b66472b9e582.png) 8 | 9 | ## Features 10 | 11 | * Using [ReactorKit](https://github.com/devxoul/ReactorKit) 12 | * Using [RxDataSources](https://github.com/RxSwiftCommunity/RxDataSources) 13 | * Messenger interface 14 | * Interactive dismissing keyboards using [RxKeyboard](https://github.com/RxSwiftCommunity/RxKeyboard) 15 | 16 | ## Requirements 17 | 18 | * iOS 11+ 19 | * Swift 4 20 | * CocoaPods 21 | * [Cleverbot API Key (free)](https://www.cleverbot.com/api/) 22 | 23 | ## Contribution 24 | 25 | Discussions and pull requests are welcomed 💖 26 | 27 | ## License 28 | 29 | Cleverbot for iOS is under MIT license. See the [LICENSE](LICENSE) file for more info. 30 | -------------------------------------------------------------------------------- /poeditor.yml: -------------------------------------------------------------------------------- 1 | api_key: ce16047a0f66aebf6d62cf821b5b031e 2 | project_id: 98681 3 | type: apple_strings 4 | 5 | languages: [en, ko] 6 | path: Cleverbot/Resources/{LANGUAGE}.lproj/Localizable.strings 7 | --------------------------------------------------------------------------------