git.iko.soy/dashared/GDproject
on master help sign in
46 files changed+4277−656
since splitsnapshots
unifiedsplit
Cartfile+5−4 GDproject.xcodeproj/project.pbxproj+150−78 GDproject/Base.lproj/Main.storyboard+632−74 GDproject/Constants.swift+19 GDproject/Controller/ News and channels/AddToChannelVC.swift+132 GDproject/Controller/ News and channels/ChannelController.swift+27−9 GDproject/Controller/ News and channels/ChannelListController.swift+14−15 GDproject/Controller/ News and channels/ChannelViewController.swift+208 GDproject/Controller/ News and channels/FullPostController.swift+3−19 GDproject/Controller/ News and channels/NewPostViewController.swift+56−20 GDproject/Controller/ News and channels/NewsController.swift+51−124 GDproject/Controller/ News and channels/NewsVC.swift+55−15 GDproject/Controller/ News and channels/PostViewCell.swift+7−3 GDproject/Controller/ News and channels/TaggsSelectionViewController.swift+106 GDproject/Controller/ News and channels/TagsSuggestionController.swift+52 GDproject/Controller/Coordinators/BaseCoordinator.swift+1−1 GDproject/Controller/Coordinators/ChannelsCoordinator.swift+65−32 GDproject/Controller/Coordinators/LogInCoordinator.swift+37−8 GDproject/Controller/Coordinators/MessagesCoordinator.swift+87 GDproject/Controller/Coordinators/ProfileCoordinator.swift+22−16 GDproject/Controller/Coordinators/TabbarController.swift+6−1 GDproject/Controller/Coordinators/TabbarCoordinator.swift+12−1 GDproject/Controller/Log In/CodeViewController.swift+144 GDproject/Controller/Log In/FacultyTableViewController.swift+88 GDproject/Controller/Log In/InititalsViewCell.swift+23 GDproject/Controller/Log In/LogInCoordinator1.swift−48 GDproject/Controller/Log In/LogInViewController.swift+30−5 GDproject/Controller/Log In/RegisterTableViewController.swift+198 GDproject/Controller/Messages/ChatInfoViewController.swift+308 GDproject/Controller/Messages/Dialog/DialogCell.swift+132 GDproject/Controller/Messages/Dialog/DialogViewController.swift+340 GDproject/Controller/Messages/MessageViewCell.swift+38 GDproject/Controller/Messages/MessagesViewController.swift+181 GDproject/Controller/Messages/PeopleToWriteViewController.swift+67 GDproject/Controller/Profile/BasicInfoController.swift+16−11 GDproject/Controller/Profile/InfoCell.swift+2−1 GDproject/Controller/Profile/ProfileViewController.swift+139−55 GDproject/DataStorage.swift+8 GDproject/Extentions.swift+47 GDproject/Simple model/CompletionTree.swift+44 GDproject/Simple model/Model.swift+697−49 GDproject/Simple model/Post.swift−26 GDproject/Simple model/User.swift−41 GDproject/Supporting files/AppDelegate.swift+7 GDproject/Supporting files/Assets.xcassets/chat.imageset/Contents.json+21 GDproject/Supporting files/Assets.xcassets/chat.imageset/chat.pdf
Cartfile+5−4
@@ -2,7 +2,8 @@ github "robb/Cartography"
22github "ivanbruel/MarkdownKit"
33github "roberthein/TinyConstraints"
44github "macteo/Marklight"
5−github "Alamofire/Alamofire" "5.0.0-beta.2"
6−github "whitesmith/WSTagsField"
7−github "ReactiveCocoa/ReactiveCocoa" ~> 8.0
8−github "52inc/Pulley"
5+github "Alamofire/Alamofire" "5.0.0-beta.5"
6+github "52inc/Pulley"
7+github "HeroTransitions/Hero"
8+github "ReactiveCocoa/ReactiveCocoa" ~> 9.0
9+github "ZaidSA/TaggerKit" "bb826a7"
GDproject.xcodeproj/project.pbxproj+150−78
@@ -8,23 +8,36 @@
88
99/* Begin PBXBuildFile section */
1010 1210A0BB223940310080686D /* SimplifiedChannelsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1210A0BA223940310080686D /* SimplifiedChannelsList.swift */; };
11− 123AD0E3223C1C3900326173 /* WSTagsField.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 123AD0E2223C1C3900326173 /* WSTagsField.framework */; };
12− 123AD0E5223C1C4300326173 /* WSTagsField.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 123AD0E2223C1C3900326173 /* WSTagsField.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
11+ 121A8972226B1EAC005EE599 /* TagsSuggestionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 121A8971226B1EAC005EE599 /* TagsSuggestionController.swift */; };
12+ 1220FF94227C41270092C6BC /* TaggsSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1220FF93227C41270092C6BC /* TaggsSelectionViewController.swift */; };
13+ 123589FD22899339002F8028 /* MessageViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 123589FC22899339002F8028 /* MessageViewCell.swift */; };
1314 123E37A5221F1B3200F6E42A /* LogInViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 123E37A4221F1B3200F6E42A /* LogInViewController.swift */; };
1415 123E37A7221F1DD700F6E42A /* MainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 123E37A6221F1DD700F6E42A /* MainController.swift */; };
1516 124CC7032221C00C009DF531 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 124CC7022221C00C009DF531 /* Model.swift */; };
16− 124CC7052221C2BB009DF531 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 124CC7042221C2BA009DF531 /* Alamofire.framework */; };
17− 124CC7072221C2C3009DF531 /* Alamofire.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 124CC7042221C2BA009DF531 /* Alamofire.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
17+ 125A9A8A2285C3DA00513E2A /* CodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 125A9A892285C3DA00513E2A /* CodeViewController.swift */; };
18+ 125A9A8E2286068A00513E2A /* RegisterTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 125A9A8D2286068A00513E2A /* RegisterTableViewController.swift */; };
19+ 125A9A90228609C500513E2A /* InititalsViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 125A9A8F228609C500513E2A /* InititalsViewCell.swift */; };
20+ 125A9A922286100800513E2A /* FacultyTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 125A9A912286100800513E2A /* FacultyTableViewController.swift */; };
1821 125BD57D22171D2A008A3575 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 125BD57C22171D2A008A3575 /* ProfileViewController.swift */; };
1922 125BD57F22171D73008A3575 /* Extentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 125BD57E22171D73008A3575 /* Extentions.swift */; };
2023 125BD5812217314A008A3575 /* NewsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 125BD5802217314A008A3575 /* NewsVC.swift */; };
24+ 1261BB93227B364C003898CF /* ChannelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1261BB92227B364C003898CF /* ChannelViewController.swift */; };
25+ 1261BB95227B3991003898CF /* AddToChannelVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1261BB94227B3991003898CF /* AddToChannelVC.swift */; };
26+ 1261BB9E227B793D003898CF /* ChatInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1261BB9D227B793D003898CF /* ChatInfoViewController.swift */; };
27+ 12758257227F1401001F291F /* DialogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12758256227F1401001F291F /* DialogViewController.swift */; };
2128 1288B5CE221F1158002BE6B1 /* DataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1288B5CD221F1158002BE6B1 /* DataStorage.swift */; };
2229 1291BE2D2221312D009D3F23 /* ChannelsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1291BE2C2221312C009D3F23 /* ChannelsCoordinator.swift */; };
2330 1291BE342221569B009D3F23 /* TabbarCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1291BE332221569B009D3F23 /* TabbarCoordinator.swift */; };
24− 1291BE3622218DBF009D3F23 /* LogInCoordinator1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1291BE3522218DBF009D3F23 /* LogInCoordinator1.swift */; };
31+ 129320042279B4270035C7B3 /* MessagesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 129320032279B4270035C7B3 /* MessagesViewController.swift */; };
32+ 129320072279B5690035C7B3 /* MessagesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 129320062279B5690035C7B3 /* MessagesCoordinator.swift */; };
33+ 129320092279C3B50035C7B3 /* PeopleToWriteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 129320082279C3B50035C7B3 /* PeopleToWriteViewController.swift */; };
34+ 1293200D2279D7310035C7B3 /* CompletionTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1293200C2279D7310035C7B3 /* CompletionTree.swift */; };
35+ 1298810B227F13840032ACA3 /* DialogCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1298810A227F13840032ACA3 /* DialogCell.swift */; };
2536 12BA4B9B224101A400DF93D7 /* ApplicationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12BA4B9A224101A400DF93D7 /* ApplicationCoordinator.swift */; };
2637 12BA4B9D224101E700DF93D7 /* BaseCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12BA4B9C224101E700DF93D7 /* BaseCoordinator.swift */; };
2738 12BA4B9F224102B700DF93D7 /* LogInCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12BA4B9E224102B700DF93D7 /* LogInCoordinator.swift */; };
39+ 12CE7D6F227B94B10024B6E8 /* TaggerKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12CE7D6E227B94B00024B6E8 /* TaggerKit.framework */; };
40+ 12CE7D71227B94B50024B6E8 /* TaggerKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12CE7D6E227B94B00024B6E8 /* TaggerKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
2841 12D7D133221C321600B35452 /* ChannelListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12D7D132221C321600B35452 /* ChannelListController.swift */; };
2942 12D7D135221C42B700B35452 /* ChannelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12D7D134221C42B700B35452 /* ChannelController.swift */; };
3043 12D7D137221D78E800B35452 /* Channel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12D7D136221D78E800B35452 /* Channel.swift */; };
@@ -38,53 +51,54 @@
3851 12E36DA1221424EA006FCDD7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 12E36D9F221424EA006FCDD7 /* Main.storyboard */; };
3952 12E36DA3221424ED006FCDD7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 12E36DA2221424ED006FCDD7 /* Assets.xcassets */; };
4053 12E36DA6221424ED006FCDD7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 12E36DA4221424ED006FCDD7 /* LaunchScreen.storyboard */; };
41− 12E36DAF22143D40006FCDD7 /* Cartography.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DAE22143D40006FCDD7 /* Cartography.framework */; };
42− 12E36DB322143D4E006FCDD7 /* Marklight.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DB222143D4E006FCDD7 /* Marklight.framework */; };
43− 12E36DB522143D54006FCDD7 /* TinyConstraints.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DB422143D54006FCDD7 /* TinyConstraints.framework */; };
4454 12E36DB72214400A006FCDD7 /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DB62214400A006FCDD7 /* Post.swift */; };
45− 12E36DB922144016006FCDD7 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DB822144016006FCDD7 /* User.swift */; };
46− 12E36DBD221441D5006FCDD7 /* TinyConstraints.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DB422143D54006FCDD7 /* TinyConstraints.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
47− 12E36DC0221441DB006FCDD7 /* Marklight.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DB222143D4E006FCDD7 /* Marklight.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
48− 12E36DC2221441E1006FCDD7 /* MarkdownKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DB022143D47006FCDD7 /* MarkdownKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
49− 12E36DC4221441EA006FCDD7 /* Cartography.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DAE22143D40006FCDD7 /* Cartography.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
5055 12E36DCA22144635006FCDD7 /* NewPostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DC922144635006FCDD7 /* NewPostViewController.swift */; };
5156 12E36DCC22144725006FCDD7 /* PostViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DCB22144725006FCDD7 /* PostViewCell.swift */; };
5257 12E36DCE22144756006FCDD7 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DCD22144756006FCDD7 /* Constants.swift */; };
5358 12E36DD122148122006FCDD7 /* FullPostController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DD022148122006FCDD7 /* FullPostController.swift */; };
5459 12E36DD522156559006FCDD7 /* MyStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DD422156559006FCDD7 /* MyStackView.swift */; };
55− 12E36DDB221594B1006FCDD7 /* MarkdownKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DB022143D47006FCDD7 /* MarkdownKit.framework */; };
5660 12F3D6B0224106F700A69214 /* TabbarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F3D6AF224106F700A69214 /* TabbarController.swift */; };
5761 12F3D6B22241097B00A69214 /* ProfileCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F3D6B12241097B00A69214 /* ProfileCoordinator.swift */; };
58− 12F3D6B4224112D600A69214 /* ReactiveMapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12F3D6B3224112D600A69214 /* ReactiveMapKit.framework */; };
59− 12F3D6B6224112E100A69214 /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12F3D6B5224112E000A69214 /* ReactiveSwift.framework */; };
60− 12F3D6B8224112E900A69214 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12F3D6B7224112E800A69214 /* Result.framework */; };
61− 12F3D6BB224112FD00A69214 /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12F3D6B9224112F500A69214 /* ReactiveCocoa.framework */; };
62− 12F3D6BC224112FD00A69214 /* ReactiveCocoa.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12F3D6B9224112F500A69214 /* ReactiveCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
63− 12F3D6BE2241130000A69214 /* Result.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12F3D6B7224112E800A69214 /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
64− 12F3D6C02241130200A69214 /* ReactiveSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12F3D6B5224112E000A69214 /* ReactiveSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
65− 12F3D6C22241130400A69214 /* ReactiveMapKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12F3D6B3224112D600A69214 /* ReactiveMapKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
66− 12F3D6C92242645800A69214 /* Pulley.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12F3D6C72242644D00A69214 /* Pulley.framework */; };
67− 12F3D6CA2242645800A69214 /* Pulley.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12F3D6C72242644D00A69214 /* Pulley.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
62+ 12FDAC7C226B5E2900995B4E /* Marklight.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC75226B5E2800995B4E /* Marklight.framework */; };
63+ 12FDAC7D226B5E2900995B4E /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC76226B5E2800995B4E /* Result.framework */; };
64+ 12FDAC7E226B5E2900995B4E /* Cartography.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC77226B5E2900995B4E /* Cartography.framework */; };
65+ 12FDAC81226B5E2900995B4E /* MarkdownKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC7A226B5E2900995B4E /* MarkdownKit.framework */; };
66+ 12FDAC84226B5E3700995B4E /* Alamofire.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC79226B5E2900995B4E /* Alamofire.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
67+ 12FDAC88226B5E4800995B4E /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC79226B5E2900995B4E /* Alamofire.framework */; };
68+ 12FDAC8A226B5E4B00995B4E /* Cartography.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC77226B5E2900995B4E /* Cartography.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
69+ 12FDAC8B226B5E4E00995B4E /* Hero.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC78226B5E2900995B4E /* Hero.framework */; };
70+ 12FDAC8C226B5E4E00995B4E /* Hero.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC78226B5E2900995B4E /* Hero.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
71+ 12FDAC8E226B5E5000995B4E /* MarkdownKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC7A226B5E2900995B4E /* MarkdownKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
72+ 12FDAC90226B5E5200995B4E /* Marklight.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC75226B5E2800995B4E /* Marklight.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
73+ 12FDAC91226B5E5400995B4E /* Pulley.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC7B226B5E2900995B4E /* Pulley.framework */; };
74+ 12FDAC92226B5E5400995B4E /* Pulley.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC7B226B5E2900995B4E /* Pulley.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
75+ 12FDAC94226B5E5600995B4E /* Result.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC76226B5E2800995B4E /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
76+ 12FDAC96226B61C400995B4E /* TinyConstraints.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC95226B61C400995B4E /* TinyConstraints.framework */; };
77+ 12FDAC98226B61CB00995B4E /* TinyConstraints.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC95226B61C400995B4E /* TinyConstraints.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
78+ 12FDAC9B226B652C00995B4E /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC99226B652400995B4E /* ReactiveSwift.framework */; };
79+ 12FDAC9C226B652C00995B4E /* ReactiveSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC99226B652400995B4E /* ReactiveSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
80+ 12FDAC9F226B9E7C00995B4E /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC9D226B9E7600995B4E /* ReactiveCocoa.framework */; };
81+ 12FDACA0226B9E7C00995B4E /* ReactiveCocoa.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC9D226B9E7600995B4E /* ReactiveCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
6882/* End PBXBuildFile section */
6983
7084/* Begin PBXCopyFilesBuildPhase section */
71− 12E36DBE221441D5006FCDD7 /* Embed Frameworks */ = {
85+ 12FDAC85226B5E3700995B4E /* Embed Frameworks */ = {
7286 isa = PBXCopyFilesBuildPhase;
7387 buildActionMask = 2147483647;
7488 dstPath = "";
7589 dstSubfolderSpec = 10;
7690 files = (
77− 12E36DC0221441DB006FCDD7 /* Marklight.framework in Embed Frameworks */,
78− 12F3D6C22241130400A69214 /* ReactiveMapKit.framework in Embed Frameworks */,
79− 124CC7072221C2C3009DF531 /* Alamofire.framework in Embed Frameworks */,
80− 12E36DC4221441EA006FCDD7 /* Cartography.framework in Embed Frameworks */,
81− 12F3D6BE2241130000A69214 /* Result.framework in Embed Frameworks */,
82− 12E36DBD221441D5006FCDD7 /* TinyConstraints.framework in Embed Frameworks */,
83− 12F3D6C02241130200A69214 /* ReactiveSwift.framework in Embed Frameworks */,
84− 12F3D6BC224112FD00A69214 /* ReactiveCocoa.framework in Embed Frameworks */,
85− 123AD0E5223C1C4300326173 /* WSTagsField.framework in Embed Frameworks */,
86− 12F3D6CA2242645800A69214 /* Pulley.framework in Embed Frameworks */,
87− 12E36DC2221441E1006FCDD7 /* MarkdownKit.framework in Embed Frameworks */,
91+ 12FDAC8E226B5E5000995B4E /* MarkdownKit.framework in Embed Frameworks */,
92+ 12FDAC98226B61CB00995B4E /* TinyConstraints.framework in Embed Frameworks */,
93+ 12FDAC94226B5E5600995B4E /* Result.framework in Embed Frameworks */,
94+ 12FDAC84226B5E3700995B4E /* Alamofire.framework in Embed Frameworks */,
95+ 12FDAC8A226B5E4B00995B4E /* Cartography.framework in Embed Frameworks */,
96+ 12FDAC9C226B652C00995B4E /* ReactiveSwift.framework in Embed Frameworks */,
97+ 12FDAC92226B5E5400995B4E /* Pulley.framework in Embed Frameworks */,
98+ 12FDAC8C226B5E4E00995B4E /* Hero.framework in Embed Frameworks */,
99+ 12FDAC90226B5E5200995B4E /* Marklight.framework in Embed Frameworks */,
100+ 12CE7D71227B94B50024B6E8 /* TaggerKit.framework in Embed Frameworks */,
101+ 12FDACA0226B9E7C00995B4E /* ReactiveCocoa.framework in Embed Frameworks */,
88102 );
89103 name = "Embed Frameworks";
90104 runOnlyForDeploymentPostprocessing = 0;
@@ -93,21 +107,35 @@
93107
94108/* Begin PBXFileReference section */
95109 1210A0BA223940310080686D /* SimplifiedChannelsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplifiedChannelsList.swift; sourceTree = "<group>"; };
96− 123AD0E2223C1C3900326173 /* WSTagsField.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WSTagsField.framework; path = Carthage/Build/iOS/WSTagsField.framework; sourceTree = "<group>"; };
110+ 121A8971226B1EAC005EE599 /* TagsSuggestionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagsSuggestionController.swift; sourceTree = "<group>"; };
111+ 1220FF93227C41270092C6BC /* TaggsSelectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaggsSelectionViewController.swift; sourceTree = "<group>"; };
112+ 123589FC22899339002F8028 /* MessageViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageViewCell.swift; sourceTree = "<group>"; };
97113 123E37A4221F1B3200F6E42A /* LogInViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogInViewController.swift; sourceTree = "<group>"; };
98114 123E37A6221F1DD700F6E42A /* MainController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainController.swift; sourceTree = "<group>"; };
99115 124CC7022221C00C009DF531 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = "<group>"; };
100− 124CC7042221C2BA009DF531 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = Carthage/Build/iOS/Alamofire.framework; sourceTree = "<group>"; };
116+ 125A9A892285C3DA00513E2A /* CodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeViewController.swift; sourceTree = "<group>"; };
117+ 125A9A8D2286068A00513E2A /* RegisterTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterTableViewController.swift; sourceTree = "<group>"; };
118+ 125A9A8F228609C500513E2A /* InititalsViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InititalsViewCell.swift; sourceTree = "<group>"; };
119+ 125A9A912286100800513E2A /* FacultyTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FacultyTableViewController.swift; sourceTree = "<group>"; };
101120 125BD57C22171D2A008A3575 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = "<group>"; };
102121 125BD57E22171D73008A3575 /* Extentions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extentions.swift; sourceTree = "<group>"; };
103122 125BD5802217314A008A3575 /* NewsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsVC.swift; sourceTree = "<group>"; };
123+ 1261BB92227B364C003898CF /* ChannelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelViewController.swift; sourceTree = "<group>"; };
124+ 1261BB94227B3991003898CF /* AddToChannelVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToChannelVC.swift; sourceTree = "<group>"; };
125+ 1261BB9D227B793D003898CF /* ChatInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInfoViewController.swift; sourceTree = "<group>"; };
126+ 12758256227F1401001F291F /* DialogViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DialogViewController.swift; sourceTree = "<group>"; };
104127 1288B5CD221F1158002BE6B1 /* DataStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStorage.swift; sourceTree = "<group>"; };
105128 1291BE2C2221312C009D3F23 /* ChannelsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelsCoordinator.swift; sourceTree = "<group>"; };
106129 1291BE332221569B009D3F23 /* TabbarCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabbarCoordinator.swift; sourceTree = "<group>"; };
107− 1291BE3522218DBF009D3F23 /* LogInCoordinator1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogInCoordinator1.swift; sourceTree = "<group>"; };
130+ 129320032279B4270035C7B3 /* MessagesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesViewController.swift; sourceTree = "<group>"; };
131+ 129320062279B5690035C7B3 /* MessagesCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesCoordinator.swift; sourceTree = "<group>"; };
132+ 129320082279C3B50035C7B3 /* PeopleToWriteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PeopleToWriteViewController.swift; path = GDproject/Controller/Messages/PeopleToWriteViewController.swift; sourceTree = SOURCE_ROOT; };
133+ 1293200C2279D7310035C7B3 /* CompletionTree.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionTree.swift; sourceTree = "<group>"; };
134+ 1298810A227F13840032ACA3 /* DialogCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DialogCell.swift; sourceTree = "<group>"; };
108135 12BA4B9A224101A400DF93D7 /* ApplicationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationCoordinator.swift; sourceTree = "<group>"; };
109136 12BA4B9C224101E700DF93D7 /* BaseCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseCoordinator.swift; sourceTree = "<group>"; };
110137 12BA4B9E224102B700DF93D7 /* LogInCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogInCoordinator.swift; sourceTree = "<group>"; };
138+ 12CE7D6E227B94B00024B6E8 /* TaggerKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TaggerKit.framework; path = Carthage/Build/iOS/TaggerKit.framework; sourceTree = "<group>"; };
111139 12D7D132221C321600B35452 /* ChannelListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelListController.swift; sourceTree = "<group>"; };
112140 12D7D134221C42B700B35452 /* ChannelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelController.swift; sourceTree = "<group>"; };
113141 12D7D136221D78E800B35452 /* Channel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Channel.swift; sourceTree = "<group>"; };
@@ -123,12 +151,7 @@
123151 12E36DA2221424ED006FCDD7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
124152 12E36DA5221424ED006FCDD7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
125153 12E36DA7221424ED006FCDD7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
126− 12E36DAE22143D40006FCDD7 /* Cartography.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cartography.framework; path = Carthage/Build/iOS/Cartography.framework; sourceTree = "<group>"; };
127− 12E36DB022143D47006FCDD7 /* MarkdownKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MarkdownKit.framework; path = Carthage/Build/iOS/MarkdownKit.framework; sourceTree = "<group>"; };
128− 12E36DB222143D4E006FCDD7 /* Marklight.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Marklight.framework; path = Carthage/Build/iOS/Marklight.framework; sourceTree = "<group>"; };
129− 12E36DB422143D54006FCDD7 /* TinyConstraints.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TinyConstraints.framework; path = Carthage/Build/iOS/TinyConstraints.framework; sourceTree = "<group>"; };
130154 12E36DB62214400A006FCDD7 /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = "<group>"; };
131− 12E36DB822144016006FCDD7 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
132155 12E36DC922144635006FCDD7 /* NewPostViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewPostViewController.swift; sourceTree = "<group>"; };
133156 12E36DCB22144725006FCDD7 /* PostViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostViewCell.swift; sourceTree = "<group>"; };
134157 12E36DCD22144756006FCDD7 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
@@ -136,11 +159,16 @@
136159 12E36DD422156559006FCDD7 /* MyStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyStackView.swift; sourceTree = "<group>"; };
137160 12F3D6AF224106F700A69214 /* TabbarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabbarController.swift; sourceTree = "<group>"; };
138161 12F3D6B12241097B00A69214 /* ProfileCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileCoordinator.swift; sourceTree = "<group>"; };
139− 12F3D6B3224112D600A69214 /* ReactiveMapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveMapKit.framework; path = Carthage/Build/iOS/ReactiveMapKit.framework; sourceTree = "<group>"; };
140− 12F3D6B5224112E000A69214 /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = Carthage/Build/iOS/ReactiveSwift.framework; sourceTree = "<group>"; };
141− 12F3D6B7224112E800A69214 /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = Carthage/Build/iOS/Result.framework; sourceTree = "<group>"; };
142− 12F3D6B9224112F500A69214 /* ReactiveCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveCocoa.framework; path = Carthage/Build/iOS/ReactiveCocoa.framework; sourceTree = "<group>"; };
143− 12F3D6C72242644D00A69214 /* Pulley.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Pulley.framework; path = Carthage/Build/iOS/Pulley.framework; sourceTree = "<group>"; };
162+ 12FDAC75226B5E2800995B4E /* Marklight.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Marklight.framework; path = Carthage/Build/iOS/Marklight.framework; sourceTree = "<group>"; };
163+ 12FDAC76226B5E2800995B4E /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = Carthage/Build/iOS/Result.framework; sourceTree = "<group>"; };
164+ 12FDAC77226B5E2900995B4E /* Cartography.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cartography.framework; path = Carthage/Build/iOS/Cartography.framework; sourceTree = "<group>"; };
165+ 12FDAC78226B5E2900995B4E /* Hero.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Hero.framework; path = Carthage/Build/iOS/Hero.framework; sourceTree = "<group>"; };
166+ 12FDAC79226B5E2900995B4E /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = Carthage/Build/iOS/Alamofire.framework; sourceTree = "<group>"; };
167+ 12FDAC7A226B5E2900995B4E /* MarkdownKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MarkdownKit.framework; path = Carthage/Build/iOS/MarkdownKit.framework; sourceTree = "<group>"; };
168+ 12FDAC7B226B5E2900995B4E /* Pulley.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Pulley.framework; path = Carthage/Build/iOS/Pulley.framework; sourceTree = "<group>"; };
169+ 12FDAC95226B61C400995B4E /* TinyConstraints.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TinyConstraints.framework; path = Carthage/Build/iOS/TinyConstraints.framework; sourceTree = "<group>"; };
170+ 12FDAC99226B652400995B4E /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = Carthage/Build/iOS/ReactiveSwift.framework; sourceTree = "<group>"; };
171+ 12FDAC9D226B9E7600995B4E /* ReactiveCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveCocoa.framework; path = Carthage/Build/iOS/ReactiveCocoa.framework; sourceTree = "<group>"; };
144172/* End PBXFileReference section */
145173
146174/* Begin PBXFrameworksBuildPhase section */
@@ -148,17 +176,17 @@
148176 isa = PBXFrameworksBuildPhase;
149177 buildActionMask = 2147483647;
150178 files = (
151− 12E36DB522143D54006FCDD7 /* TinyConstraints.framework in Frameworks */,
152− 12F3D6B4224112D600A69214 /* ReactiveMapKit.framework in Frameworks */,
153− 124CC7052221C2BB009DF531 /* Alamofire.framework in Frameworks */,
154− 12F3D6C92242645800A69214 /* Pulley.framework in Frameworks */,
155− 12F3D6BB224112FD00A69214 /* ReactiveCocoa.framework in Frameworks */,
156− 12F3D6B8224112E900A69214 /* Result.framework in Frameworks */,
157− 12F3D6B6224112E100A69214 /* ReactiveSwift.framework in Frameworks */,
158− 12E36DDB221594B1006FCDD7 /* MarkdownKit.framework in Frameworks */,
159− 12E36DAF22143D40006FCDD7 /* Cartography.framework in Frameworks */,
160− 123AD0E3223C1C3900326173 /* WSTagsField.framework in Frameworks */,
161− 12E36DB322143D4E006FCDD7 /* Marklight.framework in Frameworks */,
179+ 12FDAC7C226B5E2900995B4E /* Marklight.framework in Frameworks */,
180+ 12FDAC96226B61C400995B4E /* TinyConstraints.framework in Frameworks */,
181+ 12FDAC91226B5E5400995B4E /* Pulley.framework in Frameworks */,
182+ 12FDAC9F226B9E7C00995B4E /* ReactiveCocoa.framework in Frameworks */,
183+ 12FDAC88226B5E4800995B4E /* Alamofire.framework in Frameworks */,
184+ 12FDAC81226B5E2900995B4E /* MarkdownKit.framework in Frameworks */,
185+ 12FDAC7D226B5E2900995B4E /* Result.framework in Frameworks */,
186+ 12FDAC9B226B652C00995B4E /* ReactiveSwift.framework in Frameworks */,
187+ 12FDAC7E226B5E2900995B4E /* Cartography.framework in Frameworks */,
188+ 12CE7D6F227B94B10024B6E8 /* TaggerKit.framework in Frameworks */,
189+ 12FDAC8B226B5E4E00995B4E /* Hero.framework in Frameworks */,
162190 );
163191 runOnlyForDeploymentPostprocessing = 0;
164192 };
@@ -182,6 +210,7 @@
182210 isa = PBXGroup;
183211 children = (
184212 12BA4B9A224101A400DF93D7 /* ApplicationCoordinator.swift */,
213+ 129320062279B5690035C7B3 /* MessagesCoordinator.swift */,
185214 12F3D6AF224106F700A69214 /* TabbarController.swift */,
186215 12F3D6B12241097B00A69214 /* ProfileCoordinator.swift */,
187216 12BA4B9E224102B700DF93D7 /* LogInCoordinator.swift */,
@@ -195,26 +224,50 @@
195224 1291BE3722218DC4009D3F23 /* Log In */ = {
196225 isa = PBXGroup;
197226 children = (
227+ 125A9A912286100800513E2A /* FacultyTableViewController.swift */,
228+ 125A9A8F228609C500513E2A /* InititalsViewCell.swift */,
229+ 125A9A8D2286068A00513E2A /* RegisterTableViewController.swift */,
230+ 125A9A892285C3DA00513E2A /* CodeViewController.swift */,
198231 123E37A4221F1B3200F6E42A /* LogInViewController.swift */,
199− 1291BE3522218DBF009D3F23 /* LogInCoordinator1.swift */,
200232 );
201233 path = "Log In";
202234 sourceTree = "<group>";
203235 };
236+ 129320052279B4300035C7B3 /* Messages */ = {
237+ isa = PBXGroup;
238+ children = (
239+ 1261BB9D227B793D003898CF /* ChatInfoViewController.swift */,
240+ 129320082279C3B50035C7B3 /* PeopleToWriteViewController.swift */,
241+ 1298810C227F138C0032ACA3 /* Dialog */,
242+ 129320032279B4270035C7B3 /* MessagesViewController.swift */,
243+ 123589FC22899339002F8028 /* MessageViewCell.swift */,
244+ );
245+ path = Messages;
246+ sourceTree = "<group>";
247+ };
248+ 1298810C227F138C0032ACA3 /* Dialog */ = {
249+ isa = PBXGroup;
250+ children = (
251+ 12758256227F1401001F291F /* DialogViewController.swift */,
252+ 1298810A227F13840032ACA3 /* DialogCell.swift */,
253+ );
254+ path = Dialog;
255+ sourceTree = "<group>";
256+ };
204257 12E36D8F221424EA006FCDD7 = {
205258 isa = PBXGroup;
206259 children = (
207− 12F3D6C72242644D00A69214 /* Pulley.framework */,
208− 12F3D6B9224112F500A69214 /* ReactiveCocoa.framework */,
209− 12F3D6B7224112E800A69214 /* Result.framework */,
210− 12F3D6B5224112E000A69214 /* ReactiveSwift.framework */,
211− 12F3D6B3224112D600A69214 /* ReactiveMapKit.framework */,
212− 123AD0E2223C1C3900326173 /* WSTagsField.framework */,
213− 124CC7042221C2BA009DF531 /* Alamofire.framework */,
214− 12E36DB422143D54006FCDD7 /* TinyConstraints.framework */,
215− 12E36DB222143D4E006FCDD7 /* Marklight.framework */,
216− 12E36DB022143D47006FCDD7 /* MarkdownKit.framework */,
217− 12E36DAE22143D40006FCDD7 /* Cartography.framework */,
260+ 12CE7D6E227B94B00024B6E8 /* TaggerKit.framework */,
261+ 12FDAC9D226B9E7600995B4E /* ReactiveCocoa.framework */,
262+ 12FDAC99226B652400995B4E /* ReactiveSwift.framework */,
263+ 12FDAC95226B61C400995B4E /* TinyConstraints.framework */,
264+ 12FDAC79226B5E2900995B4E /* Alamofire.framework */,
265+ 12FDAC77226B5E2900995B4E /* Cartography.framework */,
266+ 12FDAC78226B5E2900995B4E /* Hero.framework */,
267+ 12FDAC7A226B5E2900995B4E /* MarkdownKit.framework */,
268+ 12FDAC75226B5E2800995B4E /* Marklight.framework */,
269+ 12FDAC7B226B5E2900995B4E /* Pulley.framework */,
270+ 12FDAC76226B5E2800995B4E /* Result.framework */,
218271 12E36D9A221424EA006FCDD7 /* GDproject */,
219272 12E36D99221424EA006FCDD7 /* Products */,
220273 12E36DDA221594B1006FCDD7 /* Frameworks */,
@@ -250,9 +303,9 @@
250303 isa = PBXGroup;
251304 children = (
252305 124CC7022221C00C009DF531 /* Model.swift */,
306+ 1293200C2279D7310035C7B3 /* CompletionTree.swift */,
253307 12E36DB62214400A006FCDD7 /* Post.swift */,
254308 12D7D136221D78E800B35452 /* Channel.swift */,
255− 12E36DB822144016006FCDD7 /* User.swift */,
256309 );
257310 path = "Simple model";
258311 sourceTree = "<group>";
@@ -271,6 +324,7 @@
271324 isa = PBXGroup;
272325 children = (
273326 1291BE32222154C3009D3F23 /* Coordinators */,
327+ 129320052279B4300035C7B3 /* Messages */,
274328 1291BE3722218DC4009D3F23 /* Log In */,
275329 1288B5CC221F0EFE002BE6B1 /* Profile */,
276330 12E36DD322156519006FCDD7 /* News and channels */,
@@ -281,7 +335,11 @@
281335 12E36DD322156519006FCDD7 /* News and channels */ = {
282336 isa = PBXGroup;
283337 children = (
338+ 1261BB92227B364C003898CF /* ChannelViewController.swift */,
339+ 1220FF93227C41270092C6BC /* TaggsSelectionViewController.swift */,
340+ 1261BB94227B3991003898CF /* AddToChannelVC.swift */,
284341 12D7D134221C42B700B35452 /* ChannelController.swift */,
342+ 121A8971226B1EAC005EE599 /* TagsSuggestionController.swift */,
285343 12D7D132221C321600B35452 /* ChannelListController.swift */,
286344 1210A0BA223940310080686D /* SimplifiedChannelsList.swift */,
287345 12E36DC922144635006FCDD7 /* NewPostViewController.swift */,
@@ -310,7 +368,7 @@
310368 12E36D94221424EA006FCDD7 /* Sources */,
311369 12E36D95221424EA006FCDD7 /* Frameworks */,
312370 12E36D96221424EA006FCDD7 /* Resources */,
313− 12E36DBE221441D5006FCDD7 /* Embed Frameworks */,
371+ 12FDAC85226B5E3700995B4E /* Embed Frameworks */,
314372 );
315373 buildRules = (
316374 );
@@ -377,32 +435,46 @@
377435 isa = PBXSourcesBuildPhase;
378436 buildActionMask = 2147483647;
379437 files = (
438+ 129320072279B5690035C7B3 /* MessagesCoordinator.swift in Sources */,
380439 124CC7032221C00C009DF531 /* Model.swift in Sources */,
440+ 125A9A90228609C500513E2A /* InititalsViewCell.swift in Sources */,
441+ 1220FF94227C41270092C6BC /* TaggsSelectionViewController.swift in Sources */,
381442 12F3D6B0224106F700A69214 /* TabbarController.swift in Sources */,
382443 12E36DD522156559006FCDD7 /* MyStackView.swift in Sources */,
383444 12D7D137221D78E800B35452 /* Channel.swift in Sources */,
384− 1291BE3622218DBF009D3F23 /* LogInCoordinator1.swift in Sources */,
385− 12E36DB922144016006FCDD7 /* User.swift in Sources */,
386445 1210A0BB223940310080686D /* SimplifiedChannelsList.swift in Sources */,
387446 125BD5812217314A008A3575 /* NewsVC.swift in Sources */,
388447 12E36DB72214400A006FCDD7 /* Post.swift in Sources */,
389448 12DB7FDF221877160096878E /* InviteViewController.swift in Sources */,
449+ 121A8972226B1EAC005EE599 /* TagsSuggestionController.swift in Sources */,
450+ 1293200D2279D7310035C7B3 /* CompletionTree.swift in Sources */,
390451 12E36DCA22144635006FCDD7 /* NewPostViewController.swift in Sources */,
452+ 1261BB9E227B793D003898CF /* ChatInfoViewController.swift in Sources */,
391453 12E36D9E221424EA006FCDD7 /* NewsController.swift in Sources */,
454+ 1298810B227F13840032ACA3 /* DialogCell.swift in Sources */,
455+ 125A9A922286100800513E2A /* FacultyTableViewController.swift in Sources */,
456+ 125A9A8E2286068A00513E2A /* RegisterTableViewController.swift in Sources */,
457+ 123589FD22899339002F8028 /* MessageViewCell.swift in Sources */,
392458 12E36DCC22144725006FCDD7 /* PostViewCell.swift in Sources */,
393459 123E37A5221F1B3200F6E42A /* LogInViewController.swift in Sources */,
394460 125BD57F22171D73008A3575 /* Extentions.swift in Sources */,
395461 12D7D133221C321600B35452 /* ChannelListController.swift in Sources */,
462+ 125A9A8A2285C3DA00513E2A /* CodeViewController.swift in Sources */,
396463 1291BE342221569B009D3F23 /* TabbarCoordinator.swift in Sources */,
464+ 129320092279C3B50035C7B3 /* PeopleToWriteViewController.swift in Sources */,
397465 12BA4B9B224101A400DF93D7 /* ApplicationCoordinator.swift in Sources */,
398466 1291BE2D2221312D009D3F23 /* ChannelsCoordinator.swift in Sources */,
399467 12E36DD122148122006FCDD7 /* FullPostController.swift in Sources */,
468+ 129320042279B4270035C7B3 /* MessagesViewController.swift in Sources */,
469+ 12758257227F1401001F291F /* DialogViewController.swift in Sources */,
400470 1288B5CE221F1158002BE6B1 /* DataStorage.swift in Sources */,
401471 12BA4B9F224102B700DF93D7 /* LogInCoordinator.swift in Sources */,
402472 12F3D6B22241097B00A69214 /* ProfileCoordinator.swift in Sources */,
403473 125BD57D22171D2A008A3575 /* ProfileViewController.swift in Sources */,
474+ 1261BB95227B3991003898CF /* AddToChannelVC.swift in Sources */,
404475 12DB7FDB2218590C0096878E /* InfoCell.swift in Sources */,
405476 12DB7FD922181E660096878E /* BasicInfoCell.swift in Sources */,
477+ 1261BB93227B364C003898CF /* ChannelViewController.swift in Sources */,
406478 12D7D135221C42B700B35452 /* ChannelController.swift in Sources */,
407479 12E36D9C221424EA006FCDD7 /* AppDelegate.swift in Sources */,
408480 12BA4B9D224101E700DF93D7 /* BaseCoordinator.swift in Sources */,
@@ -570,7 +642,7 @@
570642 PRODUCT_BUNDLE_IDENTIFIER = rednikina.com.drHSE.GDproject.sos;
571643 PRODUCT_NAME = "$(TARGET_NAME)";
572644 PROVISIONING_PROFILE_SPECIFIER = "";
573− SWIFT_VERSION = 4.2;
645+ SWIFT_VERSION = 5.0;
574646 TARGETED_DEVICE_FAMILY = "1,2";
575647 };
576648 name = Debug;
@@ -594,7 +666,7 @@
594666 PRODUCT_BUNDLE_IDENTIFIER = rednikina.com.drHSE.GDproject.sos;
595667 PRODUCT_NAME = "$(TARGET_NAME)";
596668 PROVISIONING_PROFILE_SPECIFIER = "";
597− SWIFT_VERSION = 4.2;
669+ SWIFT_VERSION = 5.0;
598670 TARGETED_DEVICE_FAMILY = "1,2";
599671 };
600672 name = Release;
GDproject/Base.lproj/Main.storyboard+632−74
@@ -1,16 +1,30 @@
11<?xml version="1.0" encoding="UTF-8"?>
2−<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
2+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
33 <device id="retina4_7" orientation="portrait">
44 <adaptation id="fullscreen"/>
55 </device>
66 <dependencies>
77 <deployment identifier="iOS"/>
8− <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
8+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
99 <capability name="Safe area layout guides" minToolsVersion="9.0"/>
1010 <capability name="Stack View standard spacing" minToolsVersion="9.0"/>
1111 <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
1212 </dependencies>
1313 <scenes>
14+ <!--Chats-->
15+ <scene sceneID="bZ2-fw-XBE">
16+ <objects>
17+ <navigationController id="gt4-mD-66Q" sceneMemberID="viewController">
18+ <tabBarItem key="tabBarItem" title="Chats" image="chat" id="ZPm-j1-3dy"/>
19+ <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="XiY-wd-750">
20+ <rect key="frame" x="0.0" y="20" width="375" height="44"/>
21+ <autoresizingMask key="autoresizingMask"/>
22+ </navigationBar>
23+ </navigationController>
24+ <placeholder placeholderIdentifier="IBFirstResponder" id="4yT-77-90q" userLabel="First Responder" sceneMemberID="firstResponder"/>
25+ </objects>
26+ <point key="canvasLocation" x="1089" y="481"/>
27+ </scene>
1428 <!--Profile View Controller-->
1529 <scene sceneID="rQ6-TM-V7T">
1630 <objects>
@@ -76,6 +90,9 @@
7690 <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yir-N6-Q6f">
7791 <rect key="frame" x="246" y="0.0" width="97" height="30"/>
7892 <state key="normal" title="New message"/>
93+ <connections>
94+ <action selector="sendMessage:" destination="R4D-Ao-uvG" eventType="touchUpInside" id="tQc-5f-SU3"/>
95+ </connections>
7996 </button>
8097 </subviews>
8198 </stackView>
@@ -131,7 +148,7 @@
131148 </viewController>
132149 <placeholder placeholderIdentifier="IBFirstResponder" id="sap-zV-B7l" userLabel="First Responder" sceneMemberID="firstResponder"/>
133150 </objects>
134− <point key="canvasLocation" x="2013.5999999999999" y="957.57121439280365"/>
151+ <point key="canvasLocation" x="1884" y="1216"/>
135152 </scene>
136153 <!--Settings View Controller-->
137154 <scene sceneID="WVn-Qy-9tO">
@@ -168,7 +185,7 @@
168185 </tableViewController>
169186 <placeholder placeholderIdentifier="IBFirstResponder" id="xdg-ZK-4Jv" userLabel="First Responder" sceneMemberID="firstResponder"/>
170187 </objects>
171− <point key="canvasLocation" x="1986" y="1756"/>
188+ <point key="canvasLocation" x="1980" y="1979"/>
172189 </scene>
173190 <!--Invite View Controller-->
174191 <scene sceneID="DcO-6Y-Iko">
@@ -212,7 +229,251 @@
212229 </viewController>
213230 <placeholder placeholderIdentifier="IBFirstResponder" id="t8a-Sv-mbh" userLabel="First Responder" sceneMemberID="firstResponder"/>
214231 </objects>
215− <point key="canvasLocation" x="2803" y="1756"/>
232+ <point key="canvasLocation" x="2799" y="1979"/>
233+ </scene>
234+ <!--Code View Controller-->
235+ <scene sceneID="VW5-ny-vaB">
236+ <objects>
237+ <viewController storyboardIdentifier="CodeViewController" id="Lz1-1J-ywG" customClass="CodeViewController" customModule="GDproject" customModuleProvider="target" sceneMemberID="viewController">
238+ <view key="view" contentMode="scaleToFill" id="Rud-1x-pKL">
239+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
240+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
241+ <subviews>
242+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Enter the code you received via mail" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hE6-cQ-NbO">
243+ <rect key="frame" x="50" y="47" width="275.5" height="20.5"/>
244+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
245+ <nil key="textColor"/>
246+ <nil key="highlightedColor"/>
247+ </label>
248+ <stackView opaque="NO" contentMode="scaleToFill" spacing="12" translatesAutoresizingMaskIntoConstraints="NO" id="2Gj-pC-RZ2">
249+ <rect key="frame" x="37.5" y="99" width="300" height="58"/>
250+ <subviews>
251+ <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="_" textAlignment="center" adjustsFontSizeToFit="NO" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="744-t1-YNn">
252+ <rect key="frame" x="0.0" y="0.0" width="40" height="58"/>
253+ <nil key="textColor"/>
254+ <fontDescription key="fontDescription" type="system" pointSize="45"/>
255+ <textInputTraits key="textInputTraits" keyboardType="numberPad"/>
256+ </textField>
257+ <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="_" textAlignment="center" adjustsFontSizeToFit="NO" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="jcR-jp-s7a">
258+ <rect key="frame" x="52" y="0.0" width="40" height="58"/>
259+ <nil key="textColor"/>
260+ <fontDescription key="fontDescription" type="system" pointSize="45"/>
261+ <textInputTraits key="textInputTraits" keyboardType="numberPad"/>
262+ </textField>
263+ <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="_" textAlignment="center" adjustsFontSizeToFit="NO" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="YKO-0M-KD8">
264+ <rect key="frame" x="104" y="0.0" width="40" height="58"/>
265+ <nil key="textColor"/>
266+ <fontDescription key="fontDescription" type="system" pointSize="45"/>
267+ <textInputTraits key="textInputTraits" keyboardType="numberPad"/>
268+ </textField>
269+ <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="_" textAlignment="center" adjustsFontSizeToFit="NO" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="SmG-Ae-oOs">
270+ <rect key="frame" x="156" y="0.0" width="40" height="58"/>
271+ <nil key="textColor"/>
272+ <fontDescription key="fontDescription" type="system" pointSize="45"/>
273+ <textInputTraits key="textInputTraits" keyboardType="numberPad"/>
274+ </textField>
275+ <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="_" textAlignment="center" adjustsFontSizeToFit="NO" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="vaP-rO-NZW">
276+ <rect key="frame" x="208" y="0.0" width="40" height="58"/>
277+ <nil key="textColor"/>
278+ <fontDescription key="fontDescription" type="system" pointSize="45"/>
279+ <textInputTraits key="textInputTraits" keyboardType="numberPad"/>
280+ </textField>
281+ <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="_" textAlignment="center" adjustsFontSizeToFit="NO" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="qDh-32-HSn">
282+ <rect key="frame" x="260" y="0.0" width="40" height="58"/>
283+ <nil key="textColor"/>
284+ <fontDescription key="fontDescription" type="system" pointSize="45"/>
285+ <textInputTraits key="textInputTraits" keyboardType="numberPad"/>
286+ </textField>
287+ </subviews>
288+ <constraints>
289+ <constraint firstAttribute="height" constant="58" id="5tl-bI-7tm"/>
290+ <constraint firstItem="YKO-0M-KD8" firstAttribute="width" secondItem="744-t1-YNn" secondAttribute="width" id="E7r-rk-4Mb"/>
291+ <constraint firstItem="SmG-Ae-oOs" firstAttribute="width" secondItem="744-t1-YNn" secondAttribute="width" id="Hkd-L6-qkf"/>
292+ <constraint firstItem="qDh-32-HSn" firstAttribute="width" secondItem="744-t1-YNn" secondAttribute="width" id="Koj-e5-Dof"/>
293+ <constraint firstItem="vaP-rO-NZW" firstAttribute="width" secondItem="744-t1-YNn" secondAttribute="width" id="Vzl-ZY-3yI"/>
294+ <constraint firstItem="jcR-jp-s7a" firstAttribute="width" secondItem="744-t1-YNn" secondAttribute="width" id="bDe-cA-CGi"/>
295+ <constraint firstAttribute="width" constant="300" id="gWy-t0-k14"/>
296+ </constraints>
297+ </stackView>
298+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="lCU-OF-giE">
299+ <rect key="frame" x="0.0" y="617" width="375" height="50"/>
300+ <subviews>
301+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="AMw-Yp-yMs">
302+ <rect key="frame" x="327" y="8" width="32" height="30"/>
303+ <state key="normal" title="Next"/>
304+ <connections>
305+ <action selector="whereToGoNext:" destination="Lz1-1J-ywG" eventType="touchUpInside" id="1OA-n8-Gky"/>
306+ </connections>
307+ </button>
308+ </subviews>
309+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
310+ <constraints>
311+ <constraint firstAttribute="bottom" secondItem="AMw-Yp-yMs" secondAttribute="bottom" constant="12" id="7qy-qR-6jm"/>
312+ <constraint firstItem="AMw-Yp-yMs" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="lCU-OF-giE" secondAttribute="leading" constant="20" symbolic="YES" id="Bqz-qr-CNW"/>
313+ <constraint firstAttribute="height" constant="50" id="Dyu-H3-S3f"/>
314+ <constraint firstAttribute="trailing" secondItem="AMw-Yp-yMs" secondAttribute="trailing" constant="16" id="N19-bz-XCw"/>
315+ <constraint firstItem="AMw-Yp-yMs" firstAttribute="top" secondItem="lCU-OF-giE" secondAttribute="top" constant="8" id="u6d-3F-1Y5"/>
316+ </constraints>
317+ </view>
318+ </subviews>
319+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
320+ <constraints>
321+ <constraint firstItem="hE6-cQ-NbO" firstAttribute="top" secondItem="xwd-51-foC" secondAttribute="top" constant="27" id="8Gx-SH-Pc0"/>
322+ <constraint firstItem="2Gj-pC-RZ2" firstAttribute="top" secondItem="hE6-cQ-NbO" secondAttribute="bottom" constant="31.5" id="DEY-u8-rFj"/>
323+ <constraint firstItem="lCU-OF-giE" firstAttribute="leading" secondItem="xwd-51-foC" secondAttribute="leading" id="HFt-0X-LMb"/>
324+ <constraint firstItem="lCU-OF-giE" firstAttribute="trailing" secondItem="xwd-51-foC" secondAttribute="trailing" id="Z71-jX-S5Q"/>
325+ <constraint firstItem="2Gj-pC-RZ2" firstAttribute="centerX" secondItem="xwd-51-foC" secondAttribute="centerX" id="aSr-QZ-LuB"/>
326+ <constraint firstItem="hE6-cQ-NbO" firstAttribute="centerX" secondItem="xwd-51-foC" secondAttribute="centerX" id="oFz-CS-SSk"/>
327+ <constraint firstItem="lCU-OF-giE" firstAttribute="bottom" secondItem="xwd-51-foC" secondAttribute="bottom" id="vMF-KP-eIa"/>
328+ </constraints>
329+ <viewLayoutGuide key="safeArea" id="xwd-51-foC"/>
330+ </view>
331+ <connections>
332+ <outlet property="bottomConstraint" destination="vMF-KP-eIa" id="a15-El-XZs"/>
333+ <outlet property="f1" destination="744-t1-YNn" id="kaQ-nt-ubz"/>
334+ <outlet property="f2" destination="jcR-jp-s7a" id="tjD-L6-nis"/>
335+ <outlet property="f3" destination="YKO-0M-KD8" id="QHg-UM-Bta"/>
336+ <outlet property="f4" destination="SmG-Ae-oOs" id="bwy-JB-My7"/>
337+ <outlet property="f5" destination="vaP-rO-NZW" id="rNh-iH-648"/>
338+ <outlet property="f6" destination="qDh-32-HSn" id="h4g-QY-abI"/>
339+ </connections>
340+ </viewController>
341+ <placeholder placeholderIdentifier="IBFirstResponder" id="y6E-3d-hPA" userLabel="First Responder" sceneMemberID="firstResponder"/>
342+ </objects>
343+ <point key="canvasLocation" x="4381.6000000000004" y="1961.4692653673164"/>
344+ </scene>
345+ <!--Register Table View Controller-->
346+ <scene sceneID="h6T-rh-iCS">
347+ <objects>
348+ <tableViewController storyboardIdentifier="RegisterTableViewController" id="gZg-8R-SiJ" customClass="RegisterTableViewController" customModule="GDproject" customModuleProvider="target" sceneMemberID="viewController">
349+ <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="yDm-gG-Zw0">
350+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
351+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
352+ <color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
353+ <prototypes>
354+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="cell111" textLabel="bby-bf-qyX" style="IBUITableViewCellStyleDefault" id="Ybe-Vg-GR0">
355+ <rect key="frame" x="0.0" y="55.5" width="375" height="44"/>
356+ <autoresizingMask key="autoresizingMask"/>
357+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Ybe-Vg-GR0" id="OZG-F0-QqJ">
358+ <rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
359+ <autoresizingMask key="autoresizingMask"/>
360+ <subviews>
361+ <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Add faculty" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="bby-bf-qyX">
362+ <rect key="frame" x="16" y="0.0" width="324" height="43.5"/>
363+ <autoresizingMask key="autoresizingMask"/>
364+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
365+ <nil key="textColor"/>
366+ <nil key="highlightedColor"/>
367+ </label>
368+ </subviews>
369+ </tableViewCellContentView>
370+ </tableViewCell>
371+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="facultyCell" textLabel="8Xf-Sx-eEe" detailTextLabel="Vjg-Il-F07" style="IBUITableViewCellStyleSubtitle" id="xJW-zY-MeC">
372+ <rect key="frame" x="0.0" y="99.5" width="375" height="44"/>
373+ <autoresizingMask key="autoresizingMask"/>
374+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="xJW-zY-MeC" id="hnt-oe-CHh">
375+ <rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
376+ <autoresizingMask key="autoresizingMask"/>
377+ <subviews>
378+ <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Add faculty" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="8Xf-Sx-eEe">
379+ <rect key="frame" x="16" y="5" width="87" height="20.5"/>
380+ <autoresizingMask key="autoresizingMask"/>
381+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
382+ <nil key="textColor"/>
383+ <nil key="highlightedColor"/>
384+ </label>
385+ <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Subtitle" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Vjg-Il-F07">
386+ <rect key="frame" x="16" y="25.5" width="44" height="14.5"/>
387+ <autoresizingMask key="autoresizingMask"/>
388+ <fontDescription key="fontDescription" type="system" pointSize="12"/>
389+ <nil key="textColor"/>
390+ <nil key="highlightedColor"/>
391+ </label>
392+ </subviews>
393+ </tableViewCellContentView>
394+ </tableViewCell>
395+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="nameCell" id="VPG-zq-xMh" customClass="InititalsViewCell" customModule="GDproject" customModuleProvider="target">
396+ <rect key="frame" x="0.0" y="143.5" width="375" height="44"/>
397+ <autoresizingMask key="autoresizingMask"/>
398+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VPG-zq-xMh" id="jrO-rB-x0c">
399+ <rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
400+ <autoresizingMask key="autoresizingMask"/>
401+ <subviews>
402+ <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Name" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="hwQ-St-DAp">
403+ <rect key="frame" x="16" y="7" width="343" height="30"/>
404+ <constraints>
405+ <constraint firstAttribute="height" constant="30" id="QXw-Sf-Hnk"/>
406+ </constraints>
407+ <nil key="textColor"/>
408+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
409+ <textInputTraits key="textInputTraits"/>
410+ </textField>
411+ </subviews>
412+ <constraints>
413+ <constraint firstAttribute="bottom" secondItem="hwQ-St-DAp" secondAttribute="bottom" constant="6.5" id="3Kb-PR-ZPu"/>
414+ <constraint firstItem="hwQ-St-DAp" firstAttribute="top" secondItem="jrO-rB-x0c" secondAttribute="top" constant="7" id="CyQ-kQ-mce"/>
415+ <constraint firstAttribute="trailing" secondItem="hwQ-St-DAp" secondAttribute="trailing" constant="16" id="HKq-mF-8h9"/>
416+ <constraint firstItem="hwQ-St-DAp" firstAttribute="leading" secondItem="jrO-rB-x0c" secondAttribute="leading" constant="16" id="NoW-FW-Q6L"/>
417+ </constraints>
418+ </tableViewCellContentView>
419+ <connections>
420+ <outlet property="initialsTextField" destination="hwQ-St-DAp" id="VU3-m6-FnI"/>
421+ </connections>
422+ </tableViewCell>
423+ </prototypes>
424+ <connections>
425+ <outlet property="dataSource" destination="gZg-8R-SiJ" id="OVm-5H-nyP"/>
426+ <outlet property="delegate" destination="gZg-8R-SiJ" id="khe-nG-0w4"/>
427+ </connections>
428+ </tableView>
429+ </tableViewController>
430+ <placeholder placeholderIdentifier="IBFirstResponder" id="0cS-ao-ZWH" userLabel="First Responder" sceneMemberID="firstResponder"/>
431+ </objects>
432+ <point key="canvasLocation" x="5925.6000000000004" y="2041.529235382309"/>
433+ </scene>
434+ <!--Faculty Table View Controller-->
435+ <scene sceneID="WcK-wN-L0e">
436+ <objects>
437+ <tableViewController storyboardIdentifier="FacultyTableViewController" id="wiz-jF-9HF" customClass="FacultyTableViewController" customModule="GDproject" customModuleProvider="target" sceneMemberID="viewController">
438+ <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="Iny-a7-zjv">
439+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
440+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
441+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
442+ <prototypes>
443+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="F" textLabel="mSQ-7c-C8A" detailTextLabel="YuF-0p-znr" style="IBUITableViewCellStyleSubtitle" id="Otu-BR-9o9">
444+ <rect key="frame" x="0.0" y="28" width="375" height="44"/>
445+ <autoresizingMask key="autoresizingMask"/>
446+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="Otu-BR-9o9" id="2Vm-f8-txH">
447+ <rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
448+ <autoresizingMask key="autoresizingMask"/>
449+ <subviews>
450+ <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="mSQ-7c-C8A">
451+ <rect key="frame" x="16" y="5" width="33.5" height="20.5"/>
452+ <autoresizingMask key="autoresizingMask"/>
453+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
454+ <nil key="textColor"/>
455+ <nil key="highlightedColor"/>
456+ </label>
457+ <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Subtitle" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="YuF-0p-znr">
458+ <rect key="frame" x="16" y="25.5" width="44" height="14.5"/>
459+ <autoresizingMask key="autoresizingMask"/>
460+ <fontDescription key="fontDescription" type="system" pointSize="12"/>
461+ <nil key="textColor"/>
462+ <nil key="highlightedColor"/>
463+ </label>
464+ </subviews>
465+ </tableViewCellContentView>
466+ </tableViewCell>
467+ </prototypes>
468+ <connections>
469+ <outlet property="dataSource" destination="wiz-jF-9HF" id="db0-qR-UJy"/>
470+ <outlet property="delegate" destination="wiz-jF-9HF" id="i3e-Uc-EfR"/>
471+ </connections>
472+ </tableView>
473+ </tableViewController>
474+ <placeholder placeholderIdentifier="IBFirstResponder" id="Kkr-Cr-ZiV" userLabel="First Responder" sceneMemberID="firstResponder"/>
475+ </objects>
476+ <point key="canvasLocation" x="6804" y="2044"/>
216477 </scene>
217478 <!--Simplified Channels List-->
218479 <scene sceneID="gAa-jy-vpG">
@@ -354,7 +615,7 @@
354615 </viewController>
355616 <placeholder placeholderIdentifier="IBFirstResponder" id="fzv-8a-15V" userLabel="First Responder" sceneMemberID="firstResponder"/>
356617 </objects>
357− <point key="canvasLocation" x="3560.8000000000002" y="-597.75112443778119"/>
618+ <point key="canvasLocation" x="3577" y="-598"/>
358619 </scene>
359620 <!--New Post View Controller-->
360621 <scene sceneID="TVK-xu-UdJ">
@@ -365,33 +626,35 @@
365626 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
366627 <subviews>
367628 <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="aRT-Du-PVE">
368− <rect key="frame" x="0.0" y="55" width="375" height="612"/>
629+ <rect key="frame" x="0.0" y="62" width="375" height="605"/>
369630 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
370631 </view>
371− <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="368-bf-QU6">
372− <rect key="frame" x="0.0" y="20" width="375" height="35"/>
373− <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
632+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="bHv-9b-9uA">
633+ <rect key="frame" x="16" y="32" width="343" height="30"/>
374634 <constraints>
375− <constraint firstAttribute="height" constant="35" id="5ER-su-0Yf"/>
635+ <constraint firstAttribute="height" constant="30" id="QPs-3f-AfA"/>
376636 </constraints>
377− </view>
637+ <state key="normal" title="Edit hashtags"/>
638+ <connections>
639+ <action selector="chooseTags:" destination="uED-wX-77j" eventType="touchUpInside" id="jvf-w2-K2f"/>
640+ </connections>
641+ </button>
378642 </subviews>
379643 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
380644 <constraints>
381645 <constraint firstItem="wxD-Ek-mgv" firstAttribute="trailing" secondItem="aRT-Du-PVE" secondAttribute="trailing" id="2ee-e8-G7A"/>
382− <constraint firstItem="368-bf-QU6" firstAttribute="leading" secondItem="wxD-Ek-mgv" secondAttribute="leading" id="9Rl-wh-kZf"/>
646+ <constraint firstItem="aRT-Du-PVE" firstAttribute="top" secondItem="bHv-9b-9uA" secondAttribute="bottom" id="6As-MU-bEh"/>
647+ <constraint firstItem="bHv-9b-9uA" firstAttribute="leading" secondItem="wxD-Ek-mgv" secondAttribute="leading" constant="16" id="OIV-hF-TBs"/>
648+ <constraint firstItem="bHv-9b-9uA" firstAttribute="top" secondItem="wxD-Ek-mgv" secondAttribute="top" constant="12" id="REt-Jv-IfH"/>
383649 <constraint firstItem="aRT-Du-PVE" firstAttribute="leading" secondItem="wxD-Ek-mgv" secondAttribute="leading" id="UBy-Ch-zSL"/>
384− <constraint firstItem="aRT-Du-PVE" firstAttribute="top" secondItem="368-bf-QU6" secondAttribute="bottom" id="WaE-kh-6w3"/>
385− <constraint firstItem="368-bf-QU6" firstAttribute="top" secondItem="wxD-Ek-mgv" secondAttribute="top" id="aI9-JQ-7dq"/>
386− <constraint firstItem="368-bf-QU6" firstAttribute="trailing" secondItem="wxD-Ek-mgv" secondAttribute="trailing" id="haz-0r-Cud"/>
650+ <constraint firstItem="wxD-Ek-mgv" firstAttribute="trailing" secondItem="bHv-9b-9uA" secondAttribute="trailing" constant="16" id="Ufp-mQ-Xeo"/>
387651 <constraint firstItem="wxD-Ek-mgv" firstAttribute="bottom" secondItem="aRT-Du-PVE" secondAttribute="bottom" id="x3f-eb-ACt"/>
388652 </constraints>
389653 <viewLayoutGuide key="safeArea" id="wxD-Ek-mgv"/>
390654 </view>
391655 <connections>
392656 <outlet property="bottomTextViewConstraint" destination="x3f-eb-ACt" id="ktT-im-InH"/>
393− <outlet property="view1" destination="aRT-Du-PVE" id="m3d-kW-vqW"/>
394− <outlet property="viewForTags" destination="368-bf-QU6" id="M5s-RZ-Fq7"/>
657+ <outlet property="view1" destination="aRT-Du-PVE" id="DXD-HR-NcE"/>
395658 </connections>
396659 </viewController>
397660 <placeholder placeholderIdentifier="IBFirstResponder" id="3fd-7N-vPg" userLabel="First Responder" sceneMemberID="firstResponder"/>
@@ -416,44 +679,352 @@
416679 </objects>
417680 <point key="canvasLocation" x="3444" y="129.98500749625188"/>
418681 </scene>
419− <!--Pulley View Controller-->
420− <scene sceneID="cBw-kf-fO5">
682+ <!--Taggs Selection View Controller-->
683+ <scene sceneID="74G-Lc-cqT">
421684 <objects>
422− <viewController id="IbM-Ix-dsI" customClass="PulleyViewController" customModule="Pulley" sceneMemberID="viewController">
423− <view key="view" contentMode="scaleToFill" id="hBO-ys-jBX">
685+ <viewController storyboardIdentifier="TaggsSelectionViewController" id="rQl-ri-9O8" customClass="TaggsSelectionViewController" customModule="GDproject" customModuleProvider="target" sceneMemberID="viewController">
686+ <view key="view" contentMode="scaleToFill" id="k3T-8B-NVf">
424687 <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
425688 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
426689 <subviews>
427− <containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="JAV-ei-FE9">
428− <rect key="frame" x="0.0" y="343.5" width="375" height="323.5"/>
429− <connections>
430− <segue destination="gBC-pa-4Y8" kind="embed" id="e5o-yX-vfJ"/>
431− </connections>
690+ <containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="yeR-J3-ysT">
691+ <rect key="frame" x="16" y="131" width="343" height="142"/>
692+ <constraints>
693+ <constraint firstAttribute="height" constant="142" id="1By-sC-rMT"/>
694+ </constraints>
432695 </containerView>
433− <containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="KD4-lL-5lI">
434− <rect key="frame" x="0.0" y="20" width="375" height="323.5"/>
435− <connections>
436− <segue destination="sPi-8s-ya2" kind="embed" id="0dA-aV-epC"/>
437− </connections>
696+ <containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="lEu-rZ-H6U">
697+ <rect key="frame" x="16" y="306" width="343" height="184"/>
698+ <constraints>
699+ <constraint firstAttribute="height" constant="184" id="F0W-3R-hCj"/>
700+ </constraints>
438701 </containerView>
702+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Add tags:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uXC-Rm-Hek">
703+ <rect key="frame" x="16" y="28" width="73" height="21"/>
704+ <constraints>
705+ <constraint firstAttribute="height" constant="21" id="KZW-ld-K8d"/>
706+ </constraints>
707+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
708+ <color key="textColor" red="0.43529411759999997" green="0.4431372549" blue="0.47450980390000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
709+ <nil key="highlightedColor"/>
710+ </label>
711+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Suggested tags:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hvS-Ix-A23">
712+ <rect key="frame" x="16" y="106" width="126" height="21"/>
713+ <constraints>
714+ <constraint firstAttribute="height" constant="21" id="ZJb-Aw-bLx"/>
715+ </constraints>
716+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
717+ <color key="textColor" red="0.43529411759999997" green="0.4431372549" blue="0.47450980390000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
718+ <nil key="highlightedColor"/>
719+ </label>
720+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Chosen tags:" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lxp-KC-Iua">
721+ <rect key="frame" x="16" y="281" width="101" height="21"/>
722+ <constraints>
723+ <constraint firstAttribute="height" constant="21" id="aT7-BK-i42"/>
724+ </constraints>
725+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
726+ <color key="textColor" red="0.43529411759999997" green="0.4431372549" blue="0.47450980390000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
727+ <nil key="highlightedColor"/>
728+ </label>
729+ <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="BgX-NT-7NG" customClass="TKTextField" customModule="TaggerKit">
730+ <rect key="frame" x="16" y="57" width="343" height="30"/>
731+ <constraints>
732+ <constraint firstAttribute="height" constant="30" id="dBr-rd-Cg8"/>
733+ </constraints>
734+ <nil key="textColor"/>
735+ <fontDescription key="fontDescription" type="system" pointSize="14"/>
736+ <textInputTraits key="textInputTraits"/>
737+ </textField>
439738 </subviews>
440739 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
441740 <constraints>
442− <constraint firstItem="AM9-XR-Q11" firstAttribute="trailing" secondItem="JAV-ei-FE9" secondAttribute="trailing" id="0nc-wd-359"/>
443− <constraint firstItem="JAV-ei-FE9" firstAttribute="top" secondItem="KD4-lL-5lI" secondAttribute="bottom" id="2ap-xA-MZc"/>
444− <constraint firstItem="KD4-lL-5lI" firstAttribute="top" secondItem="AM9-XR-Q11" secondAttribute="top" id="57N-k9-3Da"/>
445− <constraint firstItem="KD4-lL-5lI" firstAttribute="leading" secondItem="AM9-XR-Q11" secondAttribute="leading" id="KS0-2b-0Zn"/>
446− <constraint firstItem="KD4-lL-5lI" firstAttribute="height" secondItem="JAV-ei-FE9" secondAttribute="height" id="W9b-Gn-1ZT"/>
447− <constraint firstItem="AM9-XR-Q11" firstAttribute="trailing" secondItem="KD4-lL-5lI" secondAttribute="trailing" id="bxO-FD-qi7"/>
448− <constraint firstItem="AM9-XR-Q11" firstAttribute="bottom" secondItem="JAV-ei-FE9" secondAttribute="bottom" id="eCt-RJ-W5F"/>
449− <constraint firstItem="JAV-ei-FE9" firstAttribute="leading" secondItem="AM9-XR-Q11" secondAttribute="leading" id="tJO-om-OjN"/>
741+ <constraint firstItem="hvS-Ix-A23" firstAttribute="top" secondItem="BgX-NT-7NG" secondAttribute="bottom" constant="19" id="17U-ae-Gxa"/>
742+ <constraint firstItem="Oc2-oW-bST" firstAttribute="trailing" secondItem="yeR-J3-ysT" secondAttribute="trailing" constant="16" id="9gG-QD-LG4"/>
743+ <constraint firstItem="lxp-KC-Iua" firstAttribute="top" secondItem="yeR-J3-ysT" secondAttribute="bottom" constant="8" id="C3R-dM-Fzj"/>
744+ <constraint firstItem="uXC-Rm-Hek" firstAttribute="leading" secondItem="Oc2-oW-bST" secondAttribute="leading" constant="16" id="E6w-kw-7Uo"/>
745+ <constraint firstItem="Oc2-oW-bST" firstAttribute="trailing" secondItem="hvS-Ix-A23" secondAttribute="trailing" constant="233" id="GLM-Fj-UeZ"/>
746+ <constraint firstItem="Oc2-oW-bST" firstAttribute="trailing" secondItem="lxp-KC-Iua" secondAttribute="trailing" constant="258" id="Pbc-Ul-Bqf"/>
747+ <constraint firstItem="Oc2-oW-bST" firstAttribute="trailing" secondItem="uXC-Rm-Hek" secondAttribute="trailing" constant="286" id="Uc1-pS-uKg"/>
748+ <constraint firstItem="lEu-rZ-H6U" firstAttribute="top" secondItem="lxp-KC-Iua" secondAttribute="bottom" constant="4" id="aew-vr-F7L"/>
749+ <constraint firstItem="uXC-Rm-Hek" firstAttribute="top" secondItem="Oc2-oW-bST" secondAttribute="top" constant="8" id="dGW-ps-wNH"/>
750+ <constraint firstItem="lxp-KC-Iua" firstAttribute="leading" secondItem="Oc2-oW-bST" secondAttribute="leading" constant="16" id="gmr-z4-RMQ"/>
751+ <constraint firstItem="BgX-NT-7NG" firstAttribute="top" secondItem="uXC-Rm-Hek" secondAttribute="bottom" constant="8" id="jqp-Lv-waU"/>
752+ <constraint firstItem="yeR-J3-ysT" firstAttribute="top" secondItem="hvS-Ix-A23" secondAttribute="bottom" constant="4" id="nZ3-oH-0AF"/>
753+ <constraint firstItem="Oc2-oW-bST" firstAttribute="trailing" secondItem="BgX-NT-7NG" secondAttribute="trailing" constant="16" id="pPk-Ih-GDO"/>
754+ <constraint firstItem="yeR-J3-ysT" firstAttribute="leading" secondItem="Oc2-oW-bST" secondAttribute="leading" constant="16" id="qBn-xg-Ioc"/>
755+ <constraint firstItem="lEu-rZ-H6U" firstAttribute="leading" secondItem="Oc2-oW-bST" secondAttribute="leading" constant="16" id="qCJ-rc-qBq"/>
756+ <constraint firstItem="Oc2-oW-bST" firstAttribute="trailing" secondItem="lEu-rZ-H6U" secondAttribute="trailing" constant="16" id="vaR-Zr-5nI"/>
757+ <constraint firstItem="hvS-Ix-A23" firstAttribute="leading" secondItem="Oc2-oW-bST" secondAttribute="leading" constant="16" id="vcb-xj-bM8"/>
758+ <constraint firstItem="BgX-NT-7NG" firstAttribute="leading" secondItem="Oc2-oW-bST" secondAttribute="leading" constant="16" id="xOL-J4-JIV"/>
450759 </constraints>
451− <viewLayoutGuide key="safeArea" id="AM9-XR-Q11"/>
760+ <viewLayoutGuide key="safeArea" id="Oc2-oW-bST"/>
452761 </view>
762+ <connections>
763+ <outlet property="addTagsTextField" destination="BgX-NT-7NG" id="3vt-U9-NYI"/>
764+ <outlet property="searchContainerView" destination="yeR-J3-ysT" id="SzT-fM-A6H"/>
765+ <outlet property="testContainer" destination="lEu-rZ-H6U" id="DTe-9O-VAi"/>
766+ </connections>
453767 </viewController>
454− <placeholder placeholderIdentifier="IBFirstResponder" id="dJR-ry-ymY" userLabel="First Responder" sceneMemberID="firstResponder"/>
768+ <placeholder placeholderIdentifier="IBFirstResponder" id="eoH-1e-A6A" userLabel="First Responder" sceneMemberID="firstResponder"/>
455769 </objects>
456− <point key="canvasLocation" x="5055.1999999999998" y="757.87106446776613"/>
770+ <point key="canvasLocation" x="4293.6000000000004" y="132.68365817091455"/>
771+ </scene>
772+ <!--Messages View Controller-->
773+ <scene sceneID="WSW-fr-Vf8">
774+ <objects>
775+ <tableViewController storyboardIdentifier="MessagesViewController" id="KLf-CK-diQ" customClass="MessagesViewController" customModule="GDproject" customModuleProvider="target" sceneMemberID="viewController">
776+ <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="13B-QX-K78">
777+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
778+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
779+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
780+ <prototypes>
781+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="MessageViewCell" rowHeight="78" id="67C-fT-WEV" customClass="MessageViewCell" customModule="GDproject" customModuleProvider="target">
782+ <rect key="frame" x="0.0" y="28" width="375" height="78"/>
783+ <autoresizingMask key="autoresizingMask"/>
784+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="67C-fT-WEV" id="O3b-Xv-UNk">
785+ <rect key="frame" x="0.0" y="0.0" width="375" height="77.5"/>
786+ <autoresizingMask key="autoresizingMask"/>
787+ <subviews>
788+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xAY-IF-RpX">
789+ <rect key="frame" x="229" y="11" width="130" height="15"/>
790+ <constraints>
791+ <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="130" id="f99-ZF-f8h"/>
792+ </constraints>
793+ <fontDescription key="fontDescription" type="system" pointSize="12"/>
794+ <color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
795+ <nil key="highlightedColor"/>
796+ </label>
797+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="FG3-gU-0r1">
798+ <rect key="frame" x="16" y="11" width="46" height="22"/>
799+ <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="18"/>
800+ <nil key="textColor"/>
801+ <nil key="highlightedColor"/>
802+ </label>
803+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="nTy-rK-XTr">
804+ <rect key="frame" x="16" y="49" width="343" height="18"/>
805+ <fontDescription key="fontDescription" type="system" pointSize="15"/>
806+ <nil key="textColor"/>
807+ <nil key="highlightedColor"/>
808+ </label>
809+ </subviews>
810+ <constraints>
811+ <constraint firstItem="nTy-rK-XTr" firstAttribute="top" secondItem="xAY-IF-RpX" secondAttribute="bottom" constant="23" id="09Y-aK-nD9"/>
812+ <constraint firstItem="FG3-gU-0r1" firstAttribute="top" secondItem="O3b-Xv-UNk" secondAttribute="topMargin" id="4Hp-d5-9An"/>
813+ <constraint firstItem="xAY-IF-RpX" firstAttribute="top" secondItem="O3b-Xv-UNk" secondAttribute="topMargin" id="G4f-oJ-7ud"/>
814+ <constraint firstItem="nTy-rK-XTr" firstAttribute="trailing" secondItem="O3b-Xv-UNk" secondAttribute="trailingMargin" id="MaE-VM-KhL"/>
815+ <constraint firstItem="nTy-rK-XTr" firstAttribute="leading" secondItem="O3b-Xv-UNk" secondAttribute="leadingMargin" id="OIX-rY-Leo"/>
816+ <constraint firstItem="nTy-rK-XTr" firstAttribute="top" secondItem="FG3-gU-0r1" secondAttribute="bottom" constant="16" id="Qdj-s3-t60"/>
817+ <constraint firstItem="nTy-rK-XTr" firstAttribute="bottom" secondItem="O3b-Xv-UNk" secondAttribute="bottomMargin" id="Rv3-Mz-Aed"/>
818+ <constraint firstItem="FG3-gU-0r1" firstAttribute="leading" secondItem="O3b-Xv-UNk" secondAttribute="leadingMargin" id="iN1-KG-dYn"/>
819+ <constraint firstItem="xAY-IF-RpX" firstAttribute="trailing" secondItem="O3b-Xv-UNk" secondAttribute="trailingMargin" id="opV-4x-VCk"/>
820+ <constraint firstItem="xAY-IF-RpX" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="FG3-gU-0r1" secondAttribute="trailing" constant="4" id="x9r-sM-t4x"/>
821+ </constraints>
822+ </tableViewCellContentView>
823+ <connections>
824+ <outlet property="dateLabel" destination="xAY-IF-RpX" id="fcz-re-BXk"/>
825+ <outlet property="dialogName" destination="FG3-gU-0r1" id="gd7-ND-wCi"/>
826+ <outlet property="lastMessagePreview" destination="nTy-rK-XTr" id="Cmo-IJ-2r0"/>
827+ </connections>
828+ </tableViewCell>
829+ </prototypes>
830+ <connections>
831+ <outlet property="dataSource" destination="KLf-CK-diQ" id="LjF-Uw-Xhq"/>
832+ <outlet property="delegate" destination="KLf-CK-diQ" id="yJZ-wH-O3v"/>
833+ </connections>
834+ </tableView>
835+ </tableViewController>
836+ <placeholder placeholderIdentifier="IBFirstResponder" id="fu3-z4-EY7" userLabel="First Responder" sceneMemberID="firstResponder"/>
837+ </objects>
838+ <point key="canvasLocation" x="2681" y="918"/>
839+ </scene>
840+ <!--People To Write View Controller-->
841+ <scene sceneID="6Td-bq-Ebf">
842+ <objects>
843+ <tableViewController storyboardIdentifier="PeopleToWriteViewController" id="rUc-Tv-hWE" customClass="PeopleToWriteViewController" customModule="GDproject" customModuleProvider="target" sceneMemberID="viewController">
844+ <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" allowsSelectionDuringEditing="YES" allowsMultipleSelection="YES" allowsMultipleSelectionDuringEditing="YES" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="PDb-NB-Ey6">
845+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
846+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
847+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
848+ <prototypes>
849+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="peopleToWriteCell" textLabel="e4H-yo-W57" style="IBUITableViewCellStyleDefault" id="5ue-Sf-Blf">
850+ <rect key="frame" x="0.0" y="28" width="375" height="44"/>
851+ <autoresizingMask key="autoresizingMask"/>
852+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="5ue-Sf-Blf" id="8Dr-YE-x7M">
853+ <rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
854+ <autoresizingMask key="autoresizingMask"/>
855+ <subviews>
856+ <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="e4H-yo-W57">
857+ <rect key="frame" x="16" y="0.0" width="343" height="43.5"/>
858+ <autoresizingMask key="autoresizingMask"/>
859+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
860+ <nil key="textColor"/>
861+ <nil key="highlightedColor"/>
862+ </label>
863+ </subviews>
864+ </tableViewCellContentView>
865+ </tableViewCell>
866+ </prototypes>
867+ <connections>
868+ <outlet property="dataSource" destination="rUc-Tv-hWE" id="cuc-oZ-Sna"/>
869+ <outlet property="delegate" destination="rUc-Tv-hWE" id="hw6-6X-Rlx"/>
870+ </connections>
871+ </tableView>
872+ </tableViewController>
873+ <placeholder placeholderIdentifier="IBFirstResponder" id="Zp5-b5-nn2" userLabel="First Responder" sceneMemberID="firstResponder"/>
874+ </objects>
875+ <point key="canvasLocation" x="3614" y="938"/>
876+ </scene>
877+ <!--Chat Info View Controller-->
878+ <scene sceneID="1br-Xg-fR9">
879+ <objects>
880+ <tableViewController storyboardIdentifier="ChatInfoViewController" id="Rfd-Hp-Qqv" customClass="ChatInfoViewController" customModule="GDproject" customModuleProvider="target" sceneMemberID="viewController">
881+ <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="Rvg-ok-cnE">
882+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
883+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
884+ <color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
885+ <prototypes>
886+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="cell" textLabel="EdJ-of-2FG" style="IBUITableViewCellStyleDefault" id="hl5-Uc-R53">
887+ <rect key="frame" x="0.0" y="55.5" width="375" height="44"/>
888+ <autoresizingMask key="autoresizingMask"/>
889+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="hl5-Uc-R53" id="Oy5-4e-dPI">
890+ <rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
891+ <autoresizingMask key="autoresizingMask"/>
892+ <subviews>
893+ <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="EdJ-of-2FG">
894+ <rect key="frame" x="16" y="0.0" width="343" height="43.5"/>
895+ <autoresizingMask key="autoresizingMask"/>
896+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
897+ <nil key="textColor"/>
898+ <nil key="highlightedColor"/>
899+ </label>
900+ </subviews>
901+ </tableViewCellContentView>
902+ </tableViewCell>
903+ </prototypes>
904+ <sections/>
905+ <connections>
906+ <outlet property="dataSource" destination="Rfd-Hp-Qqv" id="ThC-G8-che"/>
907+ <outlet property="delegate" destination="Rfd-Hp-Qqv" id="1WG-zg-Ahf"/>
908+ </connections>
909+ </tableView>
910+ </tableViewController>
911+ <placeholder placeholderIdentifier="IBFirstResponder" id="KPW-41-EQd" userLabel="First Responder" sceneMemberID="firstResponder"/>
912+ </objects>
913+ <point key="canvasLocation" x="5057" y="938"/>
914+ </scene>
915+ <!--Tags Suggestion Controller-->
916+ <scene sceneID="gfS-IA-vBj">
917+ <objects>
918+ <tableViewController storyboardIdentifier="TagsSuggestionController" id="shX-JH-apo" customClass="TagsSuggestionController" customModule="GDproject" customModuleProvider="target" sceneMemberID="viewController">
919+ <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="jTj-mU-lli">
920+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
921+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
922+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
923+ <prototypes>
924+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="cell" textLabel="Lbu-Z7-fTN" style="IBUITableViewCellStyleDefault" id="pnl-jg-9DL">
925+ <rect key="frame" x="0.0" y="28" width="375" height="44"/>
926+ <autoresizingMask key="autoresizingMask"/>
927+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="pnl-jg-9DL" id="Vsm-Ic-k7e">
928+ <rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
929+ <autoresizingMask key="autoresizingMask"/>
930+ <subviews>
931+ <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Title" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Lbu-Z7-fTN">
932+ <rect key="frame" x="16" y="0.0" width="343" height="43.5"/>
933+ <autoresizingMask key="autoresizingMask"/>
934+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
935+ <nil key="textColor"/>
936+ <nil key="highlightedColor"/>
937+ </label>
938+ </subviews>
939+ </tableViewCellContentView>
940+ </tableViewCell>
941+ </prototypes>
942+ <connections>
943+ <outlet property="dataSource" destination="shX-JH-apo" id="a9b-xm-VIg"/>
944+ <outlet property="delegate" destination="shX-JH-apo" id="Cg4-aC-E7h"/>
945+ </connections>
946+ </tableView>
947+ </tableViewController>
948+ <placeholder placeholderIdentifier="IBFirstResponder" id="RsJ-xx-GJW" userLabel="First Responder" sceneMemberID="firstResponder"/>
949+ </objects>
950+ <point key="canvasLocation" x="5925" y="1339"/>
951+ </scene>
952+ <!--Channel View Controller-->
953+ <scene sceneID="myW-UY-uGB">
954+ <objects>
955+ <tableViewController storyboardIdentifier="ChannelViewController" id="M45-Em-JMM" customClass="ChannelViewController" customModule="GDproject" customModuleProvider="target" sceneMemberID="viewController">
956+ <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="poy-7L-m8s">
957+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
958+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
959+ <color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
960+ <prototypes>
961+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="titleCell" id="x0n-Is-bsF" customClass="TitleCell" customModule="GDproject" customModuleProvider="target">
962+ <rect key="frame" x="0.0" y="55.5" width="375" height="44"/>
963+ <autoresizingMask key="autoresizingMask"/>
964+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="x0n-Is-bsF" id="5aI-NN-BRQ">
965+ <rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
966+ <autoresizingMask key="autoresizingMask"/>
967+ <subviews>
968+ <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" placeholder="Name the channel" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="jTk-cs-cpn">
969+ <rect key="frame" x="16" y="7" width="343" height="30"/>
970+ <constraints>
971+ <constraint firstAttribute="height" constant="30" id="ZDL-Wx-W4e"/>
972+ </constraints>
973+ <nil key="textColor"/>
974+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
975+ <textInputTraits key="textInputTraits"/>
976+ </textField>
977+ </subviews>
978+ <constraints>
979+ <constraint firstAttribute="trailing" secondItem="jTk-cs-cpn" secondAttribute="trailing" constant="16" id="KLT-Mg-Igg"/>
980+ <constraint firstAttribute="bottom" secondItem="jTk-cs-cpn" secondAttribute="bottom" constant="6.5" id="Yuh-81-wLm"/>
981+ <constraint firstItem="jTk-cs-cpn" firstAttribute="leading" secondItem="5aI-NN-BRQ" secondAttribute="leading" constant="16" id="tx2-BD-LW1"/>
982+ <constraint firstItem="jTk-cs-cpn" firstAttribute="top" secondItem="5aI-NN-BRQ" secondAttribute="top" constant="7" id="ufD-Sr-H0c"/>
983+ </constraints>
984+ </tableViewCellContentView>
985+ <connections>
986+ <outlet property="titleLabel" destination="jTk-cs-cpn" id="ysU-SK-thv"/>
987+ </connections>
988+ </tableViewCell>
989+ </prototypes>
990+ <sections/>
991+ <connections>
992+ <outlet property="dataSource" destination="M45-Em-JMM" id="zIt-LL-u3B"/>
993+ <outlet property="delegate" destination="M45-Em-JMM" id="8oD-9d-gx0"/>
994+ </connections>
995+ </tableView>
996+ </tableViewController>
997+ <placeholder placeholderIdentifier="IBFirstResponder" id="W3K-lW-jpq" userLabel="First Responder" sceneMemberID="firstResponder"/>
998+ </objects>
999+ <point key="canvasLocation" x="5844" y="448"/>
1000+ </scene>
1001+ <!--Add To ChannelVC-->
1002+ <scene sceneID="Qir-mq-wma">
1003+ <objects>
1004+ <tableViewController storyboardIdentifier="AddToChannelVC" id="sZD-zJ-IzQ" customClass="AddToChannelVC" customModule="GDproject" customModuleProvider="target" sceneMemberID="viewController">
1005+ <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="yj4-wM-mw1">
1006+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
1007+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
1008+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
1009+ <prototypes>
1010+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" id="xMi-Gz-AGg">
1011+ <rect key="frame" x="0.0" y="28" width="375" height="44"/>
1012+ <autoresizingMask key="autoresizingMask"/>
1013+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="xMi-Gz-AGg" id="Lj0-2E-Ai9">
1014+ <rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
1015+ <autoresizingMask key="autoresizingMask"/>
1016+ </tableViewCellContentView>
1017+ </tableViewCell>
1018+ </prototypes>
1019+ <connections>
1020+ <outlet property="dataSource" destination="sZD-zJ-IzQ" id="RJA-hG-8Bd"/>
1021+ <outlet property="delegate" destination="sZD-zJ-IzQ" id="zlr-yK-CDk"/>
1022+ </connections>
1023+ </tableView>
1024+ </tableViewController>
1025+ <placeholder placeholderIdentifier="IBFirstResponder" id="kT3-kK-tSX" userLabel="First Responder" sceneMemberID="firstResponder"/>
1026+ </objects>
1027+ <point key="canvasLocation" x="6548" y="448"/>
4571028 </scene>
4581029 <!--Posts-->
4591030 <scene sceneID="RSd-UJ-34B">
@@ -469,7 +1040,7 @@
4691040 </navigationController>
4701041 <placeholder placeholderIdentifier="IBFirstResponder" id="POV-5T-jeQ" userLabel="First Responder" sceneMemberID="firstResponder"/>
4711042 </objects>
472− <point key="canvasLocation" x="1088.8" y="132.68365817091455"/>
1043+ <point key="canvasLocation" x="1089" y="-306"/>
4731044 </scene>
4741045 <!--Profile-->
4751046 <scene sceneID="dJj-Hp-Wa9">
@@ -485,7 +1056,7 @@
4851056 </navigationController>
4861057 <placeholder placeholderIdentifier="IBFirstResponder" id="Fvm-qf-2Ve" userLabel="First Responder" sceneMemberID="firstResponder"/>
4871058 </objects>
488− <point key="canvasLocation" x="1089" y="958"/>
1059+ <point key="canvasLocation" x="1015" y="1216"/>
4891060 </scene>
4901061 <!--Tabbar Controller-->
4911062 <scene sceneID="HCR-WO-p7g">
@@ -499,6 +1070,7 @@
4991070 </tabBar>
5001071 <connections>
5011072 <segue destination="NuV-ZG-ld2" kind="relationship" relationship="viewControllers" id="XuX-8s-fZU"/>
1073+ <segue destination="gt4-mD-66Q" kind="relationship" relationship="viewControllers" id="QM3-FF-LMu"/>
5021074 <segue destination="tae-5q-wvT" kind="relationship" relationship="viewControllers" id="wCJ-4N-lfC"/>
5031075 </connections>
5041076 </tabBarController>
@@ -507,53 +1079,39 @@
5071079 <point key="canvasLocation" x="-466" y="641"/>
5081080 </scene>
5091081 <!--News Controller-->
510− <scene sceneID="6SA-L8-PwW">
1082+ <scene sceneID="kf1-SJ-kcp">
5111083 <objects>
512− <viewController storyboardIdentifier="NewsController" id="sPi-8s-ya2" customClass="NewsController" customModule="GDproject" customModuleProvider="target" sceneMemberID="viewController">
513− <view key="view" contentMode="scaleToFill" id="Qwh-6O-iyN">
514− <rect key="frame" x="0.0" y="0.0" width="375" height="323.5"/>
1084+ <viewController storyboardIdentifier="NewsController" id="R7p-5c-Vzl" customClass="NewsController" customModule="GDproject" customModuleProvider="target" sceneMemberID="viewController">
1085+ <view key="view" contentMode="scaleToFill" id="Kx5-cu-FC2">
1086+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
5151087 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
5161088 <subviews>
517− <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="4pw-5n-XsC">
518− <rect key="frame" x="0.0" y="0.0" width="375" height="323.5"/>
1089+ <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="5IP-vK-phq">
1090+ <rect key="frame" x="0.0" y="20" width="375" height="647"/>
5191091 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
5201092 </tableView>
5211093 </subviews>
5221094 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
5231095 <constraints>
524− <constraint firstAttribute="bottomMargin" secondItem="4pw-5n-XsC" secondAttribute="bottom" id="iji-Cm-2Xx"/>
525− <constraint firstItem="4pw-5n-XsC" firstAttribute="top" secondItem="Qwh-6O-iyN" secondAttribute="topMargin" id="j2B-b5-dg7"/>
526− <constraint firstItem="4pw-5n-XsC" firstAttribute="leading" secondItem="Qwh-6O-iyN" secondAttribute="leading" id="nau-1I-Bh7"/>
527− <constraint firstAttribute="trailing" secondItem="4pw-5n-XsC" secondAttribute="trailing" id="vnT-kX-h1h"/>
1096+ <constraint firstItem="5IP-vK-phq" firstAttribute="top" secondItem="qGo-Sq-Oz2" secondAttribute="top" id="HvO-w8-yYd"/>
1097+ <constraint firstItem="5IP-vK-phq" firstAttribute="leading" secondItem="qGo-Sq-Oz2" secondAttribute="leading" id="NTk-fa-llk"/>
1098+ <constraint firstItem="qGo-Sq-Oz2" firstAttribute="bottom" secondItem="5IP-vK-phq" secondAttribute="bottom" id="mAj-tJ-R30"/>
1099+ <constraint firstItem="qGo-Sq-Oz2" firstAttribute="trailing" secondItem="5IP-vK-phq" secondAttribute="trailing" id="pcE-yb-fxC"/>
5281100 </constraints>
529− <viewLayoutGuide key="safeArea" id="uUX-ik-SiW"/>
1101+ <viewLayoutGuide key="safeArea" id="qGo-Sq-Oz2"/>
5301102 </view>
5311103 <connections>
532− <outlet property="tableView" destination="4pw-5n-XsC" id="usq-Vi-rXA"/>
1104+ <outlet property="tableView" destination="5IP-vK-phq" id="EQe-eC-W7P"/>
5331105 </connections>
5341106 </viewController>
535− <placeholder placeholderIdentifier="IBFirstResponder" id="3Pe-af-1NZ" userLabel="First Responder" sceneMemberID="firstResponder"/>
536− </objects>
537− <point key="canvasLocation" x="5890" y="482"/>
538− </scene>
539− <!--View Controller-->
540− <scene sceneID="LJw-Xf-aJT">
541− <objects>
542− <viewController storyboardIdentifier="DrawerContentViewController" id="gBC-pa-4Y8" sceneMemberID="viewController">
543− <view key="view" contentMode="scaleToFill" id="Z3E-He-aJI">
544− <rect key="frame" x="0.0" y="0.0" width="375" height="323.5"/>
545− <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
546− <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
547− <viewLayoutGuide key="safeArea" id="Vki-Ji-EOU"/>
548− </view>
549− </viewController>
550− <placeholder placeholderIdentifier="IBFirstResponder" id="KTe-P9-g8t" userLabel="First Responder" sceneMemberID="firstResponder"/>
1107+ <placeholder placeholderIdentifier="IBFirstResponder" id="6Lk-UL-mQL" userLabel="First Responder" sceneMemberID="firstResponder"/>
5511108 </objects>
552− <point key="canvasLocation" x="5888.8000000000002" y="999.40029985007504"/>
1109+ <point key="canvasLocation" x="5426" y="-235"/>
5531110 </scene>
5541111 </scenes>
5551112 <resources>
5561113 <image name="1" width="30" height="30"/>
5571114 <image name="5" width="30" height="30"/>
1115+ <image name="chat" width="30" height="30"/>
5581116 </resources>
5591117</document>
GDproject/Constants.swift+19
@@ -43,10 +43,29 @@ let newsController = "NewsController"
4343/// constant for switching to view controller for log in app
4444let logInController = "LogInController"
4545
46+/// constant for getting to messages
47+let messagesViewControllerId = "MessagesViewController"
48+
49+/// constant for displaying list of people to send message to
50+let peopleToWriteVC = "PeopleToWriteViewController"
51+
52+let dialogVC = "DialogViewController"
4653
4754let profileViewController = "ProfileViewController"
4855
4956let simplifiedChannelsList = "SimplifiedChannelsList"
5057
58+let channelViewControllerId = "ChannelViewController"
59+
60+let addToChannelVCId = "AddToChannelVC"
61+
62+let taggsSelectionViewController = "TaggsSelectionViewController"
63+
64+let dialogViewController = "DialogViewController"
65+
66+let tagsSuggestionController = "TagsSuggestionController"
67+
68+let chatInfoViewController = "ChatInfoViewController"
69+
5170let blueSystemColor = UIColor(red: 0, green: 137/255, blue: 249/255, alpha: 0.5)
5271
GDproject/Controller/ News and channels/AddToChannelVC.swiftnew file+132
@@ -0,0 +1,132 @@
1+//
2+// AddToChannelVC.swift
3+// GDproject
4+//
5+// Created by cstore on 02/05/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import UIKit
10+
11+enum DataSourse{
12+ case people, tags
13+}
14+
15+class AddToChannelVC: UITableViewController {
16+
17+ var channel: Model.Channels?
18+ weak var update: UpdatableChannel?
19+
20+ // data sources for people and hashtags
21+ var dataSourcePeople: [Model.Users] = Model.Channels.fullPeople
22+
23+ var reloadtable: Bool = false {
24+ didSet{
25+ tableView.reloadData()
26+ }
27+ }
28+
29+ var filteredDataSource = [Model.Users]()
30+
31+ func filterContentForSearchText(_ searchText: String, scope: String = "All")
32+ {
33+ filteredDataSource = dataSourcePeople.filter { $0.fullName().lowercased().contains(searchText.lowercased()) }
34+ tableView.reloadData()
35+ }
36+
37+ let searchC = UISearchController(searchResultsController: nil)
38+
39+ override func viewDidLoad() {
40+ super.viewDidLoad()
41+
42+ navigationItem.title = "People"
43+ tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
44+ self.navigationItem.searchController = searchC
45+ navigationItem.largeTitleDisplayMode = .never
46+ self.navigationItem.hidesSearchBarWhenScrolling = false
47+ definesPresentationContext = true
48+ searchC.searchResultsUpdater = self
49+ searchC.obscuresBackgroundDuringPresentation = false
50+ }
51+
52+ override func viewWillDisappear(_ animated: Bool) {
53+ super.viewWillDisappear(animated)
54+ update?.updateChannel(with: channel!)
55+ }
56+ // MARK: - Table view data source
57+
58+ override func numberOfSections(in tableView: UITableView) -> Int {
59+ return 1
60+ }
61+
62+ var isFiltering: Bool {
63+ return searchC.isActive && !searchBarIsEmpty()
64+ }
65+
66+ func searchBarIsEmpty() -> Bool {
67+ return searchC.searchBar.text?.isEmpty ?? true
68+ }
69+
70+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
71+ if isFiltering {
72+ return filteredDataSource.count
73+ } else {
74+ return dataSourcePeople.count
75+ }
76+ }
77+
78+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
79+ {
80+ let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
81+
82+ if isFiltering{
83+ cell.textLabel?.text = filteredDataSource[indexPath.row].fullName()
84+ if channel!.people.contains(filteredDataSource[indexPath.row].id) {
85+ cell.backgroundColor = #colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1)
86+ } else {
87+ cell.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
88+ }
89+ } else {
90+ cell.textLabel?.text = dataSourcePeople[indexPath.row].fullName()
91+ if channel!.people.contains(dataSourcePeople[indexPath.row].id) {
92+ cell.backgroundColor = #colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1)
93+ } else {
94+ cell.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
95+ }
96+ }
97+
98+ cell.selectionStyle = .none
99+
100+ return cell
101+ }
102+
103+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
104+ if let cell = tableView.cellForRow(at: indexPath) {
105+ if isFiltering{
106+ if cell.backgroundColor == #colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1) {
107+ let filtered = channel!.people.filter{ $0 != filteredDataSource[indexPath.row].id }
108+ channel?.people = filtered
109+ cell.backgroundColor = #colorLiteral(red: 0.9999960065, green: 1, blue: 1, alpha: 1)
110+ } else {
111+ channel?.people.append(filteredDataSource[indexPath.row].id)
112+ cell.backgroundColor = #colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1)
113+ }
114+ } else {
115+ if cell.backgroundColor == #colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1) {
116+ let filtered = channel!.people.filter{ $0 != dataSourcePeople[indexPath.row].id }
117+ channel?.people = filtered
118+ cell.backgroundColor = #colorLiteral(red: 0.9999960065, green: 1, blue: 1, alpha: 1)
119+ } else {
120+ channel?.people.append(dataSourcePeople[indexPath.row].id)
121+ cell.backgroundColor = #colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1)
122+ }
123+ }
124+ }
125+ }
126+}
127+
128+extension AddToChannelVC : UISearchResultsUpdating {
129+ func updateSearchResults(for searchController: UISearchController) {
130+ filterContentForSearchText(searchController.searchBar.text!)
131+ }
132+}
GDproject/Controller/ News and channels/ChannelController.swift+27−9
@@ -25,7 +25,7 @@ class ChannelController: UIViewController, UITableViewDelegate, UITableViewDataS
2525 var channel: Model.Channels?
2626 var myProtocol: DataDelegate?
2727
28− var fullTags: [String] = Array(Model.Channels.fullTags)
28+ var fullTags: [String] = CompletionTree.getCompletion(tree: Model.hashTagTree!, word: "")
2929
3030 var dataSourcePeople: [Model.Users] = []
3131
@@ -33,6 +33,9 @@ class ChannelController: UIViewController, UITableViewDelegate, UITableViewDataS
3333
3434 var activeDataSource: ActiveTable = .people
3535
36+ // func to show preview of the current channel
37+ var onShowingPreview: ((Model.Channels)->())?
38+
3639 @IBOutlet weak var viewww: UIView!
3740
3841 @IBOutlet weak var textField: UITextField!
@@ -61,16 +64,33 @@ class ChannelController: UIViewController, UITableViewDelegate, UITableViewDataS
6164
6265 // TODO: update channel
6366 override func viewWillDisappear(_ animated: Bool) {
64− super.viewWillDisappear(animated)
65− if let _ = channel?.id { print("update")
66− Model.updateChannel(with: channel!)
67− } else { print("create")
68− Model.createChannel(with: channel!)
67+
68+ defer {
69+ super.viewWillDisappear(animated)
70+ }
71+
72+ guard let _ = self.navigationController?.viewControllers.lastIndex(of: self) else {
73+ if let _ = channel?.id { print("update")
74+ Model.updateChannel(with: channel!)
75+ } else { print("create")
76+ //Model.createChannel(with: channel!)
77+ }
78+ return
79+ }
80+ // nou
81+ }
82+
83+ @objc func showPreview(){
84+ if let channel = channel {
85+ onShowingPreview?(channel)
6986 }
7087 }
7188
7289 func setUpController(){
7390
91+ // setting up the preview button for the editing or created channel
92+ navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Preview", style: .done, target: self, action: #selector(showPreview))
93+
7494 tableView.delegate = self
7595 tableView.dataSource = self
7696 tableView.register(UITableViewCell.self, forCellReuseIdentifier: itemCellId)
@@ -232,9 +252,7 @@ class ChannelController: UIViewController, UITableViewDelegate, UITableViewDataS
232252 }
233253 tableView.reloadData()
234254 case 1:
235− dataSourceTags = fullTags.filter({ (s) -> Bool in
236− s.lowercased().contains(text.lowercased())
237− })
255+ dataSourceTags = CompletionTree.getCompletion(tree: Model.hashTagTree!, word: text)
238256 tableView.reloadData()
239257 default:
240258 break
GDproject/Controller/ News and channels/ChannelListController.swift+14−15
@@ -13,11 +13,16 @@ struct ChannelData{
1313 var subtitle = String()
1414}
1515
16+protocol ChannelListData: class {
17+ func reloadData (with channels: [Model.Channels])
18+}
19+
1620// TODO: make search controller availiable
1721class ChannelListController: UITableViewController {
1822
1923 // MARK: - Output -
2024 var onChannelSelected: ((Model.Channels) -> Void)?
25+ var onEditingModeBegins: ((Model.Channels, IndexPath)->Void)?
2126
2227 // MARK: - filter search controller
2328 var filteredDataSource = [Model.Channels]()
@@ -25,7 +30,7 @@ class ChannelListController: UITableViewController {
2530 var displayingChannel: Model.Channels?
2631
2732 var toReload: Bool = false {
28− didSet{
33+ didSet {
2934 tableView.reloadData()
3035 }
3136 }
@@ -79,7 +84,7 @@ class ChannelListController: UITableViewController {
7984 askForUpdates()
8085 }
8186
82− private func askForUpdates(){
87+ func askForUpdates(){
8388 Model.channelsList { [weak self] (channels) in
8489 self?.dataSource = [ChannelListController.generalChannel] + channels
8590 self?.toReload = true
@@ -88,19 +93,17 @@ class ChannelListController: UITableViewController {
8893
8994 override func viewWillAppear(_ animated: Bool) {
9095 super.viewWillAppear(true)
91− askForUpdates()
96+ //askForUpdates()
97+ tabBarController?.tabBar.isHidden = false
9298 }
9399
94100 @objc func addChannel()
95101 {
96102 // editing mode is on automatically
97− let vc = storyboard?.instantiateViewController(withIdentifier: channelControllerId) as! ChannelController
98− vc.index = 1
99− vc.channel = Model.Channels(people: [], name: "Untitled", tags: [])
100− navigationController?.pushViewController(vc, animated: true)
103+ onEditingModeBegins?(Model.Channels(people: [], name: "Untitled", id: 0, tags: []),IndexPath(row: 1, section: 0))
101104 }
102105
103− static let generalChannel = Model.Channels(people: [], name: "General", tags: [])
106+ static let generalChannel = Model.Channels(people: [], name: "General", id: -1, tags: [])
104107
105108 var dataSource : [Model.Channels] = [ ChannelListController.generalChannel ]
106109
@@ -141,15 +144,11 @@ class ChannelListController: UITableViewController {
141144
142145 override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]?
143146 {
144−
145− let editButton = UITableViewRowAction(style: .normal, title: "Edit") { [weak self] (action, indexPath) in
146− let vc = self?.storyboard?.instantiateViewController(withIdentifier: channelControllerId) as! ChannelController
147− vc.index = indexPath.row
148− vc.channel = self?.dataSource[indexPath.row]
149− self?.navigationController?.pushViewController(vc, animated: true)
147+ let editButton = UITableViewRowAction(style: .normal, title: "Edit") { [unowned self] (rowAction, indexPath) in
148+ self.onEditingModeBegins?(self.dataSource[indexPath.row], indexPath)
150149 }
151− editButton.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
152150
151+ editButton.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
153152
154153 let deleteButton = UITableViewRowAction(style: .normal, title: "Delete") { [weak self] (action, indexPath) in
155154
GDproject/Controller/ News and channels/ChannelViewController.swiftnew file+208
@@ -0,0 +1,208 @@
1+//
2+// ChannelViewController.swift
3+// MessageApp
4+//
5+// Created by cstore on 02/05/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import UIKit
10+
11+protocol UpdatableName: class{
12+ func updateName(with name: String)
13+}
14+
15+protocol UpdatableChannel: class{
16+ func updateChannel(with channel: Model.Channels)
17+}
18+
19+class ChannelViewController: UITableViewController, UpdatableName, UpdatableChannel, TagsReceiver
20+{
21+ var onChoosingHashTags: (([String]?)->())?
22+
23+ var onChoosingPeople: ((Model.Channels?)->())?
24+
25+ func receiveTags(tags: [String]) {
26+ self.channel?.tags = tags
27+ }
28+
29+ func updateName(with name: String){
30+ channel?.name = name
31+ }
32+
33+ func updateChannel(with channel: Model.Channels) {
34+ self.channel = channel
35+ }
36+
37+ var channel: Model.Channels?
38+
39+ // func to show preview of the current channel
40+ var onShowingPreview: ((Model.Channels)->())?
41+
42+ override func viewDidLoad()
43+ {
44+ super.viewDidLoad()
45+ tableView.keyboardDismissMode = .onDrag
46+ navigationItem.rightBarButtonItems = [ UIBarButtonItem(barButtonSystemItem: .play, target: self, action: #selector(showPreview)), self.editButtonItem]
47+ navigationItem.largeTitleDisplayMode = .never
48+ tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
49+ tableView.register(UITableViewCell.self, forCellReuseIdentifier: "searchCell")
50+ }
51+
52+ override func viewWillAppear(_ animated: Bool) {
53+ super.viewWillAppear(animated)
54+ tableView.reloadData()
55+ }
56+
57+ override func viewWillDisappear(_ animated: Bool) {
58+
59+ defer {
60+ super.viewWillDisappear(animated)
61+ }
62+
63+ guard let _ = self.navigationController?.viewControllers.lastIndex(of: self) else
64+ {
65+ if let id = channel?.id, id != 0
66+ {
67+ Model.updateChannel(with: channel!)
68+ } else {
69+ Model.createChannel(with: channel!) {
70+ [weak self] in
71+ self?.showAlertOn(result: $0)
72+ return
73+ }
74+ }
75+ return
76+ }
77+ // nou
78+ }
79+
80+ @objc func showPreview()
81+ {
82+ if let channel = channel {
83+ onShowingPreview?(channel)
84+ }
85+ }
86+
87+ // MARK: - Table view data source
88+
89+ override func numberOfSections(in tableView: UITableView) -> Int {
90+ return 3
91+ }
92+
93+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
94+ switch section {
95+ case 1:
96+ return (channel?.people.count ?? 0) + 1
97+ case 2:
98+ return (channel?.tags.count ?? 0) + 1
99+ default:
100+ return 1
101+ }
102+ }
103+
104+ override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
105+ switch section {
106+ case 1:
107+ return "People"
108+ case 2:
109+ return "Tags"
110+ default:
111+ return "Title"
112+ }
113+ }
114+
115+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
116+
117+ switch indexPath.section{
118+ case 1 , 2:
119+ return whichCell(tableView, cellForRowAt: indexPath)
120+ default:
121+ let cell = tableView.dequeueReusableCell(withIdentifier: "titleCell") as! TitleCell
122+
123+ cell.fill(title: channel?.name)
124+ cell.update = self
125+
126+ return cell
127+ }
128+ }
129+
130+ private func whichCell(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
131+ {
132+ let row = indexPath.row
133+ let section = indexPath.section
134+
135+ if row == 0
136+ {
137+ let cell = tableView.dequeueReusableCell(withIdentifier: "searchCell")!
138+ cell.textLabel?.text = "Add more"
139+ cell.accessoryType = .disclosureIndicator
140+ return cell
141+ }
142+
143+ let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
144+
145+ if section == 1 {
146+ cell.textLabel?.text = "👤 \(Model.Channels.fullPeopleDict[channel?.people[row-1] ?? 0]!.fullName())"
147+ } else {
148+ cell.textLabel?.text = "# \(channel!.tags[row-1])"
149+ }
150+
151+ return cell
152+ }
153+
154+ override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath)
155+ {
156+ if editingStyle == UITableViewCell.EditingStyle.delete
157+ {
158+ if indexPath.section == 1 {
159+ channel?.people.remove(at: indexPath.row-1)
160+ } else {
161+ channel?.tags.remove(at: indexPath.row-1)
162+ }
163+ }
164+
165+ tableView.reloadData()
166+ }
167+
168+ override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
169+ if indexPath.row == 0 {
170+ return false
171+ }
172+
173+ return true
174+ }
175+
176+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
177+ {
178+ if indexPath.row == 0 && indexPath.section == 1
179+ {
180+ onChoosingPeople?(channel)
181+ }
182+
183+ if indexPath.row == 0 && indexPath.section == 2 {
184+ onChoosingHashTags?(channel?.tags)
185+ }
186+ }
187+}
188+
189+
190+class TitleCell: UITableViewCell, UITextFieldDelegate {
191+
192+ @IBOutlet weak var titleLabel: UITextField!
193+
194+ weak var update: UpdatableName?
195+
196+ override func awakeFromNib() {
197+ super.awakeFromNib()
198+ }
199+
200+ func fill(title: String?){
201+ titleLabel.text = title
202+ titleLabel.addTarget(self, action: #selector(changedText(_:)), for: .editingChanged)
203+ }
204+
205+ @objc func changedText(_ textField: UITextField){
206+ update?.updateName(with: textField.text!)
207+ }
208+}
GDproject/Controller/ News and channels/FullPostController.swift+3−19
@@ -25,27 +25,10 @@ class FullPostController: UITableViewController {
2525
2626 func setUpNavigationBar(){
2727 navigationItem.largeTitleDisplayMode = .never
28− navigationItem.title = "\(post?.authorId ?? 0)"
29− navigationItem.rightBarButtonItems = [UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(self.options))]
28+ navigationItem.title = "Post"
3029 }
3130
32− @objc func options(){
33− // drafts
34− // saved
35−
36− let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
37− let editAction = UIAlertAction(title: "Send via message", style: .default)
38− let shareAction = UIAlertAction(title: "Send via project", style: .default)
39− let settingsAction = UIAlertAction(title: "Copy link", style: .default)
40− let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
41−
42− optionMenu.addAction(editAction)
43− optionMenu.addAction(shareAction)
44− optionMenu.addAction(settingsAction)
45− optionMenu.addAction(cancelAction)
46−
47− self.present(optionMenu, animated: true, completion: nil)
48− }
31+
4932 // MARK: - Table view data source
5033
5134 override func numberOfSections(in tableView: UITableView) -> Int {
@@ -102,6 +85,7 @@ class FullPostController: UITableViewController {
10285 cell.onUserDisplay = { [weak self] (id) in
10386 let vc = self?.storyboard!.instantiateViewController(withIdentifier: profileViewController) as! ProfileViewController
10487 vc.idProfile = id
88+
10589 self?.navigationController?.pushViewController(vc, animated: true)
10690 }
10791 default:
GDproject/Controller/ News and channels/NewPostViewController.swift+56−20
@@ -10,20 +10,38 @@ import UIKit
1010import Cartography
1111import Marklight
1212import TinyConstraints
13−import WSTagsField
13+import TaggerKit
14+// import WSTagsField
1415
15−class NewPostViewController: UIViewController, UITextViewDelegate {
16+class NewPostViewController: UIViewController, UITextViewDelegate, TagsReceiver
17+{
18+ func receiveTags(tags: [String]) {
19+ currentTags = tags
20+ }
1621
1722 @IBOutlet weak var view1: UIView!
1823
19− @IBOutlet weak var viewForTags: UIView!
24+ var currentTags: [String] = []
25+
26+ // We want the whole experience, let's create two TKCollectionViews
27+ let productTags = TKCollectionView()
2028
21− fileprivate let tagsField = WSTagsField()
29+ @IBAction func chooseTags(_ sender: UIButton) {
30+ let vc = storyboard?.instantiateViewController(withIdentifier: taggsSelectionViewController) as! TaggsSelectionViewController
31+
32+ NewPostViewController.draft = textView.text
33+ vc.receiver = self
34+ vc.currentTags = currentTags
35+
36+ navigationController?.pushViewController(vc, animated: true)
37+ }
38+
39+ // fileprivate let tagsField = WSTagsField()
2240 // Keep strong instance of the `NSTextStorage` subclass
2341 let textStorage = MarklightTextStorage()
2442
2543 static var draft: String = ""
26− static var hashTagsDraft: [String] = []
44+ static var hashTagsDraft: [String] = [ ]
2745
2846 var textView: UITextView!
2947
@@ -54,11 +72,11 @@ class NewPostViewController: UIViewController, UITextViewDelegate {
5472
5573 setUpMD()
5674 setUpTextView()
57− setUpAccessoryView()
58− setUpTagsView()
75+ // setUpAccessoryView()
76+ //setUpTagsView()
5977 }
6078
61− func setUpTagsView(){
79+ /* func setUpTagsView(){
6280 tagsField.frame = viewForTags.bounds
6381 viewForTags.addSubview(tagsField)
6482
@@ -86,15 +104,16 @@ class NewPostViewController: UIViewController, UITextViewDelegate {
86104 tagsField.acceptTagOption = .space
87105
88106 textFieldEvents()
89− }
107+ }*/
90108
91109 override func viewWillAppear(_ animated: Bool) {
92110 super.viewWillAppear(animated)
93111
94− navigationController?.navigationBar.prefersLargeTitles = false
112+ //navigationController?.navigationBar.prefersLargeTitles = false
113+ navigationItem.largeTitleDisplayMode = .never
95114 navigationItem.title = "New post"
96115 textView.text = NewPostViewController.draft
97− tagsField.addTags(NewPostViewController.hashTagsDraft)
116+ // tagsField.addTags(NewPostViewController.hashTagsDraft)
98117
99118 }
100119
@@ -122,7 +141,7 @@ class NewPostViewController: UIViewController, UITextViewDelegate {
122141 func setUpTextView(){
123142 view.addConstraint(bottomTextViewConstraint)
124143 view1.addSubview(textView)
125− textView.edgesToSuperview(insets: .top(8) + .left(8) + .bottom(40+8) + .right(8))
144+ textView.edgesToSuperview(insets: .top(8) + .left(8) + .bottom(8) + .right(8))
126145
127146 if #available(iOS 11.0, *) {
128147 textView.smartDashesType = .no
@@ -191,7 +210,7 @@ class NewPostViewController: UIViewController, UITextViewDelegate {
191210 }
192211
193212 func setUpMD(){
194− textStorage.marklightTextProcessor.codeColor = UIColor.orange
213+ textStorage.marklightTextProcessor.codeColor = UIColor.gray
195214 textStorage.marklightTextProcessor.quoteColor = UIColor.darkGray
196215 textStorage.marklightTextProcessor.syntaxColor = UIColor.blue
197216 textStorage.marklightTextProcessor.codeFontName = "Courier"
@@ -213,10 +232,27 @@ class NewPostViewController: UIViewController, UITextViewDelegate {
213232 var indexOfPost = 0
214233 // MARK:- new post
215234 @objc func newPost(){
216− Model.createAndPublish(body: [Model.Attachments(markdown: textView!.text)], tags: tagsField.tags.map { $0.text })
235+ self.navigationItem.title = "Sending..."
236+ Model.createAndPublish(body: [Model.Attachments(markdown: textView!.text)], tags: currentTags) {
237+ [weak self] in
238+
239+ switch $0 {
240+ case .success1, .success:
241+ NewPostViewController.draft = ""
242+ NewPostViewController.hashTagsDraft = []
243+ self?.moveBackToParentVC?()
244+ default:
245+ self?.showAlertOn(result: $0)
246+ self?.navigationItem.title = "New post"
247+ }
248+
249+ }
250+
251+ // NewPostViewController.draft = ""
252+ // NewPostViewController.hashTagsDraft = []
217253 // adding row to uiTableView after adding new post
218254 // myProtocol?.addPost(post: p)
219− moveBackToParentVC?()
255+ // moveBackToParentVC?()
220256 // somewhere here i will be sending server notifications about new post arrival
221257 }
222258
@@ -228,7 +264,7 @@ class NewPostViewController: UIViewController, UITextViewDelegate {
228264 [weak self]
229265 _ in
230266 NewPostViewController.draft = self?.textView.text ?? ""
231− NewPostViewController.hashTagsDraft = self?.tagsField.tags.map { $0.text } ?? []
267+ NewPostViewController.hashTagsDraft = self?.currentTags ?? []
232268 self?.moveBackToParentVC?()
233269 }
234270
@@ -269,7 +305,7 @@ class NewPostViewController: UIViewController, UITextViewDelegate {
269305 textView.scrollRectToVisible(rect, animated: animated)
270306 }
271307
272− fileprivate func textFieldEvents() {
308+ /*fileprivate func textFieldEvents() {
273309 tagsField.onDidAddTag = { _, _ in
274310 print("onDidAddTag")
275311 }
@@ -293,16 +329,16 @@ class NewPostViewController: UIViewController, UITextViewDelegate {
293329 tagsField.onDidUnselectTagView = { _, tagView in
294330 print("Unselect \(tagView)")
295331 }
296− }
332+ }*/
297333}
298334
299335
300336extension NewPostViewController: UITextFieldDelegate {
301337
302338 func textFieldShouldReturn(_ textField: UITextField) -> Bool {
303− if textField == tagsField {
339+ /*if textField == tagsField {
304340 textView.becomeFirstResponder()
305− }
341+ }*/
306342 return true
307343 }
308344
GDproject/Controller/ News and channels/NewsController.swift+51−124
@@ -12,52 +12,33 @@ import Cartography
1212
1313protocol UpdateableWithChannel: class {
1414 var channel: Model.Channels? { get set }
15+ func updateChannel(on channel: Model.Channels)
1516}
16−protocol NewPostDelegate {
17− func addPost(post: Post)
18−}
17+
1918protocol UpdateableWithUser: class {
2019 var user: Model.Users? { get set }
2120}
2221// MARK:- Controller with posts and channels availiable.
2322// Search is availiable within every table (posts and channels). Has button-functionality for boths post and chnnels
24−class NewsController: UIViewController, UISearchControllerDelegate, NewPostDelegate, UpdateableWithChannel
23+class NewsController: UIViewController, UISearchControllerDelegate, UpdateableWithChannel, UISearchResultsUpdating
2524{
25+ func updateChannel(on channel: Model.Channels) {
26+ self.channel = channel
27+ news.currChannel = channel
28+ decideWhatChannelDisplay()
29+ }
30+
2631 var changedChannelName: ((String)->())?
2732
2833 @IBOutlet weak var tableView: UITableView!
2934
30− var dictionary: [Int: Model.Users]? {
31− didSet {
32−
33− var newPosts: [Model.Posts] = []
34−
35− posts!.forEach({ (post) in
36−
37− newPosts.append(Model.Posts(body: post.body, authorId: post.authorId, id: post.id, user: dictionary![post.authorId]!, date: post.updated, tags: post.tags))
38−
39− post.tags.forEach { Model.Channels.fullTags.insert($0) }
40−
41− })
42−
43− news.dataSourse = newPosts
44− tableView.reloadData()
45− }
46− }
47−
48− var posts: [Model.Posts]?
49−
5035 var channel: Model.Channels?
51− var anonymousChannel: (users: [Int: Model.Users],posts: [Model.Posts])?
36+ var anonymousChannel: (users: [Int: Model.Users], posts: [Model.Posts])?
5237
5338 // MARK: - Output -
5439 var onSelectChannel: (() -> Void)?
55−
56− func addPost(post: Post) {
57− //news.dataSourse.insert(post, at: 0)
58− }
5940
60− var searchController = UISearchController(searchResultsController: nil)
41+ var searchController: UISearchController?
6142
6243 var news = NewsVC()
6344 var type: HeaderType? = .NEWS
@@ -67,6 +48,13 @@ class NewsController: UIViewController, UISearchControllerDelegate, NewPostDeleg
6748 override func viewDidLoad()
6849 {
6950 super.viewDidLoad()
51+
52+ let updater = TagsSuggestionController()
53+ updater.delegate = self
54+ searchController = UISearchController(searchResultsController: updater)
55+ navigationItem.largeTitleDisplayMode = .always
56+
57+ navigationController?.navigationBar.prefersLargeTitles = true
7058 navigationItem.title = "Loading ..."
7159 tableView.refreshControl = refreshContr
7260 // Configure Refresh Control
@@ -75,25 +63,26 @@ class NewsController: UIViewController, UISearchControllerDelegate, NewPostDeleg
7563 tableView.register(PostViewCell.self, forCellReuseIdentifier: postCellId)
7664 tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
7765
78− // setUpSearchContr()
66+ setUpSearchContr()
7967
68+ // navigationItem.
8069 news.viewController = self
8170 news.type = type == .NEWS ? .NEWS : type!
71+ news.currChannel = channel
8272
8373 setUpNavigationItemsforPosts()
84−
85− //setUpBanner()
8674 }
8775
8876 func setUpSearchContr(){
89− searchController.delegate = self
90− searchController.obscuresBackgroundDuringPresentation = false
77+ searchController?.delegate = self
78+ searchController?.obscuresBackgroundDuringPresentation = false
9179 navigationItem.searchController = searchController
9280 definesPresentationContext = true
93− searchController.searchBar.placeholder = "Search anything"
81+ searchController?.searchResultsUpdater = self
82+ searchController?.searchBar.placeholder = "Search tags"
9483 }
9584
96− @objc func refreshPostsData( _ ff: UIRefreshControl){
85+ @objc func refreshPostsData( _ ff: UIRefreshControl) {
9786 decideWhatChannelDisplay()
9887 }
9988
@@ -101,19 +90,9 @@ class NewsController: UIViewController, UISearchControllerDelegate, NewPostDeleg
10190 print("news clear")
10291 }
10392
104− let label : UILabel = {
105− let label = UILabel()
106− label.text = "No posts to display yet!"
107− label.font = UIFont.systemFont(ofSize: 18)
108− label.textColor = .black
109− label.isHidden = true
110− label.numberOfLines = 0
111− return label
112− }()
113−
11493 override func viewWillAppear(_ animated: Bool) {
11594 super.viewWillAppear(animated)
116− searchController.isActive = false
95+ searchController?.isActive = false
11796 // TODO:- display something if no posts are availiable
11897
11998 decideWhatChannelDisplay()
@@ -122,34 +101,31 @@ class NewsController: UIViewController, UISearchControllerDelegate, NewPostDeleg
122101 }
123102
124103 func decideWhatChannelDisplay(){
104+
125105 switch type! {
126106 case .NEWS, .NONE:
127− if let channel = channel, let id = channel.id {
107+ if let channel = channel, let id = channel.id, id != -1 {
128108
129− Model.getChannel(with: id) { [weak self] in
130− self?.posts = $0.posts
131− self?.dictionary = $0.users
109+ Model.getAnonymousChannel(by: channel) { [weak self] in
110+ self?.news.dataSourse = $0.posts
111+ self?.news.dictionary = $0.users
132112 self?.refreshContr.endRefreshing()
133113 self?.changedChannelName?(channel.name)
134114 }
135115
136− } else if let _ = posts {
137− print("here!")
138116 } else {
139117 Model.getLast { [weak self] in
140− self?.posts = $0.posts
141− self?.dictionary = $0.users
118+ self?.news.dataSourse = $0.posts
119+ self?.news.dictionary = $0.users
142120 self?.refreshContr.endRefreshing()
143121 self?.changedChannelName?("General")
144122 }
145123 }
146124 default:
147− if let anonChannel = anonymousChannel {
148− posts = anonChannel.posts
149− dictionary = anonChannel.users
150− changedChannelName?("Anonymous")
151− }
125+ refreshContr.endRefreshing()
126+ break
152127 }
128+
153129 }
154130
155131 func setUpNavigationItemsforPosts(){
@@ -158,75 +134,26 @@ class NewsController: UIViewController, UISearchControllerDelegate, NewPostDeleg
158134 tableView.reloadData()
159135 }
160136
161−
162− // for animating the banner
163− var topConstraint: NSLayoutConstraint?
164−
165− let bannerView: UIView = {
166− let view = UIView()
167− view.backgroundColor = .blue
168− view.layer.cornerRadius = 25.0
169− view.clipsToBounds = true
170− return view
171− }()
172−
173− let statusLabel: UILabel = {
174− let label = UILabel()
175− label.text = "-"
176− label.textColor = .white
177− label.textAlignment = .center
178− return label
179− }()
180−
181− // MARK:- banner
182− private func setUpBanner()
137+ func updateSearchResults(for searchController: UISearchController)
183138 {
184− view.addSubview(bannerView)
185− bannerView.addSubview(statusLabel)
186− statusLabel.edgesToSuperview()
187−
188− let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
189−
190− bannerView.addGestureRecognizer(tap)
191− topConstraint = NSLayoutConstraint(item: bannerView, attribute: .top, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .top, multiplier: 1, constant: -300)
192− view.addConstraint(topConstraint!)
193−
194− bannerView.edgesToSuperview(excluding: [.bottom, .top, .left], insets: .right(20), usingSafeArea: true)
195− bannerView.height(50)
196− bannerView.width(50)
139+ let filteredData = CompletionTree.getCompletion(tree: Model.hashTagTree!, word: searchController.searchBar.text?.lowercased() ?? "")
140+
141+ // Apply the filtered results to the search results table.
142+ if let resultsController = searchController.searchResultsController as? TagsSuggestionController {
143+ resultsController.suggestions = filteredData
144+ resultsController.tableView.reloadData()
145+ }
197146 }
198147
199− // when table is scrolling no deletion is availiable
200− @objc func handleTap(sender: UITapGestureRecognizer? = nil) {
201− let indexPath = IndexPath(row: 0, section: 0)
202− self.tableView.scrollToRow(at: indexPath, at: .top, animated: true)
203−// isBannerVisible = false
204−// changeConstraint(isVisible: false)
148+ func willPresentSearchController(_ searchController: UISearchController) {
149+ searchController.searchResultsController?.view.isHidden = false;
205150 }
206151
207− // animation for banner
208− func changeConstraint(isVisible: Bool){
209− topConstraint?.constant = isVisible ? 50 : -300
210−
211− UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: {
212− self.view.layoutIfNeeded()
213− }) { (completed) in
214−
215− }
152+ func didPresentSearchController(_ searchController: UISearchController) {
153+ searchController.searchResultsController?.view.isHidden = false;
216154 }
217155
218− var isBannerVisible: Bool = false
219−
220− func scrollViewDidScroll(_ scrollView: UIScrollView) {
221− print(scrollView.contentOffset.y)
222− if scrollView.contentOffset.y >= 50 && !isBannerVisible{
223− isBannerVisible = true
224− changeConstraint(isVisible: true)
225− }
226−
227− if isBannerVisible && scrollView.contentOffset.y == 0{
228− isBannerVisible = false
229− changeConstraint(isVisible: false)
230− }
156+ func didDismissSearchController(_ searchController: UISearchController) {
157+ searchController.searchBar.text = ""
231158 }
232159}
GDproject/Controller/ News and channels/NewsVC.swift+55−15
@@ -19,24 +19,40 @@ struct PostCellData{
1919
2020 let markdownParser = MarkdownParser(font: UIFont.systemFont(ofSize: 16))
2121 markdownParser.enabledElements = .disabledAutomaticLink
22− markdownParser.code.textBackgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
22+ markdownParser.code.font = UIFont(name: "Menlo", size: 16)
23+ markdownParser.code.textBackgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
24+ markdownParser.code.color = #colorLiteral(red: 0.2549019754, green: 0.2745098174, blue: 0.3019607961, alpha: 1)
2325
2426 return markdownParser.parse(markdown)
2527 }
2628}
2729
28−class NewsVC: UIViewController, UITableViewDelegate, UITableViewDataSource{
30+class NewsVC: UIViewController, UITableViewDelegate, UITableViewDataSource {
31+
2932
3033 var onChannelDidChange: ((([Int: Model.Users],[Model.Posts]))->())?
3134
3235 var onFullPost: ((HeaderType, Model.Posts)->())?
3336
34− var dataSourse: [Model.Posts] = [] {
37+ var dictionary: [Int: Model.Users] = [:] {
3538 didSet {
36− cellDataSourse = []
37− dataSourse.forEach { (item) in
38− cellDataSourse.append(PostCellData(attributedData: PostCellData.create(with: item.body)))
39+
40+ dataSourse = dataSourse.map {
41+ var copy = $0
42+ copy.user = dictionary[$0.authorId]
43+ return copy
3944 }
45+
46+ (viewController as? NewsController)?.tableView.reloadData()
47+ (viewController as? ProfileViewController)?.tableView.reloadData()
48+ }
49+ }
50+
51+ var currChannel : Model.Channels?
52+
53+ var dataSourse: [Model.Posts] = [] {
54+ didSet {
55+ cellDataSourse = dataSourse.map { PostCellData(attributedData: PostCellData.create(with: $0.body)) }
4056 }
4157 }
4258
@@ -72,19 +88,17 @@ class NewsVC: UIViewController, UITableViewDelegate, UITableViewDataSource{
7288 vc.idProfile = id
7389 self?.viewController!.navigationController!.pushViewController(vc, animated: true)
7490 }
91+
92+ cell.onAnonymousChannelDisplay = {
93+ [weak self] in
94+ (self?.viewController as? UpdateableWithChannel)?.updateChannel(on: Model.Channels(people: [], name: $0, id: 0, tags: [$0]))
95+ }
7596 default:
7697 cell.onUserDisplay = { (id) in
77− print("tapped when profile is open already \(id)")
98+ print("tapped when profile is opened already \(id)")
7899 }
79100 }
80101
81− cell.onAnonymousChannelDisplay = {
82− [weak self] (tag) in
83− Model.getAnonymousChannel(by: Model.AnonymousChannel(people: [], tags: [tag]),
84− completion: { (tuple) in self?.onChannelDidChange?(tuple) }
85− )
86− }
87−
88102 cell.selectionStyle = .none
89103 return cell
90104 }
@@ -96,8 +110,34 @@ class NewsVC: UIViewController, UITableViewDelegate, UITableViewDataSource{
96110 func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
97111 return 100.0
98112 }
99−}
113+
114+ var prevLast = -1
115+
116+ func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
117+ {
118+ // pagination
119+ if indexPath.row == cellDataSourse.count - 1 && prevLast != indexPath.row
120+ {
121+ if let ch = currChannel, let id = ch.id, id != -1 {
122+ // check this!
123+ Model.getAnonymousChannel(by: ch, exclusiveFrom: dataSourse.last?.id) { [weak self] in
124+ self?.dataSourse.append(contentsOf: $0.posts)
125+ $0.users.forEach { self?.dictionary[$0.key] = $0.value }
126+ }
127+
128+ } else {
129+ // check this!
130+ Model.getLast(on: 10, from: dataSourse.last?.id )
131+ { [weak self] in
132+ self?.dataSourse.append(contentsOf: $0.posts)
133+ $0.users.forEach { self?.dictionary[$0.key] = $0.value }
134+ }
135+ }
100136
137+ prevLast = indexPath.row
138+ }
139+ }
140+}
101141
102142enum HeaderType {
103143 case NONE
GDproject/Controller/ News and channels/PostViewCell.swift+7−3
@@ -8,11 +8,11 @@
88
99import UIKit
1010import Cartography
11−import MarkdownKit
1211import TinyConstraints
1312
1413class PostViewCell: UITableViewCell
1514{
15+
1616 var onUserDisplay: ((Int)->())?
1717
1818 var onAnonymousChannelDisplay: ((String)->())?
@@ -42,6 +42,7 @@ class PostViewCell: UITableViewCell
4242
4343 let shareButton: UIButton = {
4444 let button = UIButton(type: UIButton.ButtonType.detailDisclosure)
45+ button.isHidden = true
4546 return button
4647 }()
4748
@@ -86,7 +87,7 @@ class PostViewCell: UITableViewCell
8687 nameLabel.setTitle("\(post.user?.firstName ?? "") \(post.user?.lastName ?? "")", for: .normal)
8788 nameLabel.addTarget(self, action: #selector(displayProfile), for: .touchUpInside)
8889 if let user = post.user{
89− fullNameLabel.text = "\(user.firstName) \(user.middleName) \(user.lastName)"
90+ fullNameLabel.text = "\(user.faculty.name)"
9091 }
9192 else {
9293 fullNameLabel.text = "\(post.authorId)"
@@ -123,6 +124,7 @@ class PostViewCell: UITableViewCell
123124 let button = UIButton()
124125 button.setTitle("#" + hash, for: .normal)
125126 button.addTarget(self, action: #selector(setAnonymousChannel(on:)), for: .touchUpInside)
127+ // button.addGestureRecognizer(longPressRecognizer)
126128 button.titleLabel?.font = UIFont.monospacedDigitSystemFont(ofSize: 15, weight: .semibold)
127129 button.setTitleColor(#colorLiteral(red: 0, green: 0.4784313725, blue: 1, alpha: 1), for: .normal)
128130 buttons.append(button)
@@ -180,7 +182,9 @@ class PostViewCell: UITableViewCell
180182 }
181183
182184 @objc func displayProfile(){
183− onUserDisplay?(post!.authorId)
185+ if let id = post?.authorId {
186+ onUserDisplay?(id)
187+ }
184188 }
185189
186190 @objc func setAnonymousChannel(on button: UIButton){
GDproject/Controller/ News and channels/TaggsSelectionViewController.swiftnew file+106
@@ -0,0 +1,106 @@
1+//
2+// TaggsSelectionViewController.swift
3+// GDproject
4+//
5+// Created by cstore on 03/05/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import UIKit
10+import TaggerKit
11+
12+protocol TagsReceiver: class {
13+ func receiveTags(tags: [String])
14+}
15+
16+class TaggsSelectionViewController: UIViewController {
17+
18+ @IBOutlet weak var addTagsTextField: TKTextField!
19+
20+ @IBOutlet weak var searchContainerView: UIView!
21+
22+ @IBOutlet weak var testContainer: UIView!
23+
24+ // We want the whole experience, let's create two TKCollectionViews
25+ let productTags = TKCollectionView()
26+ let allTags = TKCollectionView()
27+
28+ var currentTags: [String]?
29+ weak var receiver: TagsReceiver?
30+
31+ // MARK: - Lifecycle Methods
32+ override func viewDidLoad() {
33+ super.viewDidLoad()
34+
35+ navigationItem.title = "Hashtags"
36+ addTagsTextField.delegate = self
37+ // Customisation example
38+ // testCollection.customFont = UIFont.boldSystemFont(ofSize: 14) // Custom font
39+ // testCollection.customCornerRadius = 14.0 // Corner radius of tags
40+ // testCollection.customSpacing = 20.0 // Spacing between cells
41+ // testCollection.customBackgroundColor = UIColor.red // Background of cells
42+
43+
44+ // These are the tags already added by the user, give an aray of strings to the collection
45+ if let tags = currentTags {
46+ productTags.tags = tags
47+ }
48+ // These are intended to be all the tags the user has added in the app, which are going to be filtered
49+ allTags.tags = CompletionTree.getCompletion(tree: Model.hashTagTree!, word: "")
50+
51+ /*
52+ We set this collection's action to .removeTag,
53+ becasue these are supposed to be the tags the user has already added
54+ */
55+ productTags.action = .removeTag
56+
57+
58+ // Set the current controller as the delegate of both collections
59+ productTags.delegate = self
60+ allTags.delegate = self
61+
62+ // "testCollection" takes the tags sent by "searchCollection"
63+ allTags.receiver = productTags
64+
65+ // The tags in "searchCollection" are going to be added, so we set the action to addTag
66+ allTags.action = .addTag
67+
68+
69+ // Set the sender and receiver of the TextField
70+ addTagsTextField.sender = allTags
71+ addTagsTextField.receiver = productTags
72+
73+ add(productTags, toView: testContainer)
74+ add(allTags, toView: searchContainerView)
75+ }
76+
77+ override func viewWillDisappear(_ animated: Bool) {
78+ super.viewWillDisappear(animated)
79+ if let receiver = receiver{
80+ receiver.receiveTags(tags: productTags.tags)
81+ }
82+ }
83+
84+ /*
85+ These methods come from UIViewController now conforming to TKCollectionViewDelegate,
86+ You use these to do whatever you want when a tag is added or removed (e.g. save to file, etc)
87+ */
88+ override func tagIsBeingAdded(name: String?) {
89+ // Example: save testCollection.tags to UserDefault
90+ print("added \(name!)")
91+ }
92+
93+ override func tagIsBeingRemoved(name: String?) {
94+ print("removed \(name!)")
95+ }
96+}
97+
98+// textField deletage to close keyboard on return
99+extension TaggsSelectionViewController: UITextFieldDelegate
100+{
101+ func textFieldShouldReturn(_ textField: UITextField) -> Bool
102+ {
103+ self.view.endEditing(true)
104+ return false
105+ }
106+}
GDproject/Controller/ News and channels/TagsSuggestionController.swiftnew file+52
@@ -0,0 +1,52 @@
1+//
2+// AnonimousChannelController.swift
3+// GDproject
4+//
5+// Created by cstore on 20/04/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class TagsSuggestionController: UITableViewController
12+{
13+ weak var delegate: UpdateableWithChannel?
14+
15+ var suggestions = [String]()
16+ var channel: Model.Channels?
17+
18+ override func viewDidLoad()
19+ {
20+ super.viewDidLoad()
21+ if let tree = Model.hashTagTree {
22+ suggestions = CompletionTree.getCompletion(tree: tree, word: "")
23+ print(suggestions)
24+ }
25+ tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
26+ tableView.reloadData()
27+ }
28+
29+ override func numberOfSections(in tableView: UITableView) -> Int {
30+ return 1
31+ }
32+
33+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
34+ return suggestions.count
35+ }
36+
37+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
38+ {
39+ let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
40+ cell.textLabel?.text = suggestions[indexPath.row]
41+
42+ return cell
43+ }
44+
45+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
46+ {
47+ channel = Model.Channels(people: [], name: "\(self.suggestions[indexPath.row])", id: 0, tags: [self.suggestions[indexPath.row]])
48+
49+ delegate?.updateChannel(on: channel!)
50+ self.dismiss(animated: true)
51+ }
52+}
GDproject/Controller/Coordinators/BaseCoordinator.swift+1−1
@@ -23,7 +23,7 @@ class BaseCoordinator: Coordinator {
2323
2424 // add only unique object
2525 func addDependency(_ coordinator: Coordinator) {
26− guard !childCoordinators.contains(where: { $0 === coordinator }) else { return }
26+ guard !childCoordinators.contains(where: { $0 === coordinator }) else { return }
2727 childCoordinators.append(coordinator)
2828 }
2929
GDproject/Controller/Coordinators/ChannelsCoordinator.swift+65−32
@@ -27,53 +27,86 @@ class ChannelsCoordinator: BaseCoordinator{
2727
2828 let channels = storyboard.instantiateViewController(withIdentifier: channelListControllerId) as! ChannelListController
2929
30− channels.onChannelSelected = { [weak self]
30+ channels.onChannelSelected = { [unowned self]
3131 (channel) in
32− self?.navigationController?.pushViewController(self!.presentNewsController(with: channel), animated: true)
32+ self.navigationController?.pushViewController(self.presentNewsController(with: channel), animated: true)
3333 }
34− navigationController?.setViewControllers([channels,presentNewsController()], animated: false)
34+
35+ channels.askForUpdates()
36+
37+ channels.onEditingModeBegins = { [unowned self] (channel, indexPath) in
38+ let vc = self.presentChannelController(with: channel)
39+ self.navigationController?.pushViewController(vc, animated: true)
40+ vc.tabBarController?.tabBar.isHidden = true
41+ }
42+
43+ let nc = presentNewsController()
44+ navigationController?.setViewControllers([channels, nc], animated: false)
3545 }
3646
37− func presentNewsController(with channel: Model.Channels? = nil) -> PulleyViewController {
47+ /// Function that presents channel controller
48+ ///
49+ /// - Parameter channel: channel that needs to be displayed
50+ func presentChannelController(with channel: Model.Channels? = nil) -> ChannelViewController {
51+ let mainContentVC = storyboard.instantiateViewController(withIdentifier: channelViewControllerId) as! ChannelViewController
3852
39− let mainContentVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: newsController) as! NewsController
40− let drawerContentVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DrawerContentViewController")
53+ // to show preview we need to instantiate newsController but with different functionality
54+ mainContentVC.onShowingPreview = { [weak mainContentVC, unowned self] ch in
55+ let vc = self.presentNewsController(with: ch, previewMode: true)
56+ mainContentVC?.navigationController?.pushViewController(vc, animated: true)
57+ }
4158
42− mainContentVC.channel = channel
43− mainContentVC.news.onFullPost = {
44− [weak self] (type,post) in
59+ // to go for choosing hashtags
60+ mainContentVC.onChoosingHashTags = { [weak mainContentVC, unowned self] ch in
61+ let vc = self.storyboard.instantiateViewController(withIdentifier: taggsSelectionViewController) as! TaggsSelectionViewController
4562
46− let vc = self?.storyboard.instantiateViewController(withIdentifier: fullPostControllerId) as! FullPostController
47− vc.type = type
48− vc.post = post
49− self?.navigationController!.pushViewController(vc, animated: true)
63+ vc.currentTags = ch
64+ vc.receiver = mainContentVC
65+
66+ self.navigationController?.pushViewController(vc, animated: true)
5067 }
5168
52− mainContentVC.news.onChannelDidChange = {
53− [weak self, weak mainContentVC] (tuple) in print("hep")
54− switch mainContentVC!.news.type {
55− case .ANONYMOUS:
56− mainContentVC!.posts = tuple.1
57− mainContentVC!.dictionary = tuple.0
58− default:
59− let vc = self!.presentNewsController()
60− (vc.primaryContentViewController as! NewsController).anonymousChannel = tuple
61− (vc.primaryContentViewController as! NewsController).type = .ANONYMOUS
62− self?.navigationController?.pushViewController(vc, animated: true)
63− }
69+ mainContentVC.onChoosingPeople = { [weak mainContentVC, unowned self] channel in
70+ let vc = self.storyboard.instantiateViewController(withIdentifier: addToChannelVCId) as! AddToChannelVC
71+
72+ vc.channel = channel
73+ vc.update = mainContentVC
74+
75+ self.navigationController?.pushViewController(vc, animated: true)
6476 }
6577
66− let pulleyDrawerVC = PulleyViewController(contentViewController: mainContentVC, drawerViewController: drawerContentVC)
78+ mainContentVC.channel = channel
79+ return mainContentVC
80+ }
81+
82+ func presentNewsController(with channel: Model.Channels? = nil, previewMode: Bool = false) -> NewsController
83+ {
84+ let mainContentVC = storyboard.instantiateViewController(withIdentifier: newsController) as! NewsController
85+ mainContentVC.channel = channel
86+
87+ if !previewMode {
88+ mainContentVC.news.onFullPost = {
89+ [weak self] (type,post) in
90+
91+ let vc = self?.storyboard.instantiateViewController(withIdentifier: fullPostControllerId) as! FullPostController
92+ vc.type = type
93+ vc.post = post
94+ self?.navigationController!.pushViewController(vc, animated: true)
95+ }
96+
97+ mainContentVC.news.onChannelDidChange = {
98+ print("anon with \($0.0.count) users")
99+ }
100+
101+ mainContentVC.navigationItem.rightBarButtonItems = [UIBarButtonItem(barButtonSystemItem: .compose, target: self, action: #selector(writePost(_:)))
102+ ]
103+ }
67104
68− pulleyDrawerVC.initialDrawerPosition = .collapsed
69105 mainContentVC.changedChannelName = {
70− [weak pulleyDrawerVC] (title) in pulleyDrawerVC?.navigationItem.title = title
106+ [weak mainContentVC] (title) in mainContentVC?.navigationItem.title = title
71107 }
72108
73− pulleyDrawerVC.navigationItem.rightBarButtonItems = [UIBarButtonItem(barButtonSystemItem: .compose, target: self, action: #selector(writePost(_:)))
74− ]
75−
76− return pulleyDrawerVC
109+ return mainContentVC
77110 }
78111
79112 @objc private func writePost(_ barItem: UIBarButtonItem)
GDproject/Controller/Coordinators/LogInCoordinator.swift+37−8
@@ -10,6 +10,8 @@ import UIKit
1010
1111class LogInCoordinator: BaseCoordinator {
1212
13+ let storyboard = UIStoryboard(name: "Main", bundle: nil)
14+
1315 var didEndFlow: (()->())?
1416 var window: UIWindow!
1517 var navigationController: UINavigationController?
@@ -22,20 +24,47 @@ class LogInCoordinator: BaseCoordinator {
2224
2325 override func start(){
2426 let logInVC = LogInViewController()
25− logInVC.authenticate = { [weak self] (id) in
27+ logInVC.authenticate = { [weak self, weak logInVC] (email) in
2628
27− Model.authenticate(with: id) {
28− (res) in
29+ DataStorage.standard.setEmail(email: email)
30+ Model.authenticate(with: email) { [weak self]
31+ (authStatus) in
32+
33+ print(authStatus.userStatus)
2934
30− if (res) {
31− DataStorage.standard.setUserKey(with: id)
32− self?.didEndFlow?()
35+ if (authStatus.userStatus == "invalid") {
36+ logInVC?.presentAlertInvalidCode()
3337 }
34− else {
35− DataStorage.standard.setUserKey(with: 0)
38+ else if (authStatus.userStatus == "canRegister") { // register form
39+ self?.presentRegisterVC()
40+ } else { // validation code
41+ self?.presentViewControllerForCodeInput()
3642 }
3743 }
3844 }
45+
3946 navigationController?.pushViewController(logInVC, animated: false)
4047 }
48+
49+ func presentViewControllerForCodeInput()
50+ {
51+ let vc = storyboard.instantiateViewController(withIdentifier: "CodeViewController") as! CodeViewController
52+ self.navigationController?.pushViewController(vc, animated: true)
53+ vc.onSuccessLogIn = {
54+ [weak self] in self?.didEndFlow?()
55+ }
56+ }
57+
58+ func presentRegisterVC(){
59+
60+ let vc1 = storyboard.instantiateViewController(withIdentifier: "RegisterTableViewController") as! RegisterTableViewController
61+
62+ vc1.onRegistration = { [weak self] in
63+ self?.presentViewControllerForCodeInput()
64+ }
65+
66+ self.navigationController?.pushViewController(vc1, animated: true)
67+ }
68+
69+
4170}
GDproject/Controller/Coordinators/MessagesCoordinator.swiftnew file+87
@@ -0,0 +1,87 @@
1+//
2+// MessagesCoordinator.swift
3+// GDproject
4+//
5+// Created by cstore on 01/05/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+
10+import Foundation
11+import UIKit
12+
13+class MessagesCoordinator: BaseCoordinator {
14+
15+ var didEndSession: (()->())?
16+
17+ let storyboard = UIStoryboard(name: "Main", bundle: nil)
18+ private weak var navigationController: UINavigationController?
19+
20+ init(nc: UINavigationController) {
21+ self.navigationController = nc
22+ }
23+
24+ override func start() {
25+ show()
26+ }
27+
28+ private func show(){
29+ let vc = storyboard.instantiateViewController(withIdentifier: messagesViewControllerId) as! MessagesViewController
30+
31+ // choose person to write message to
32+ vc.onUserDisplayList = { [weak vc, unowned self] in
33+
34+ let newVC = self.storyboard.instantiateViewController(withIdentifier: peopleToWriteVC) as! PeopleToWriteViewController
35+
36+ newVC.whatToDoWithSelection = { [weak newVC, weak self] people in
37+ newVC?.navigationController?.popViewController(animated: true)
38+
39+ // detect is it a user or group dialog
40+ let count = people.count
41+ if count == 1 {
42+
43+ let user = people.first!.key
44+ let createdDialog = Model.Dialog.userChat(Model.UserChat(user: user))
45+
46+ let vc = DialogViewController()
47+ vc.users = Model.Channels.fullPeopleDict
48+ vc.dialog = createdDialog
49+ self?.navigationController?.pushViewController(vc, animated: true)
50+
51+ } else {
52+ var group = Model.Group(users: people, name: "Untitled", id: 0)
53+ group.users[DataStorage.standard.getUserId()] = Model.UserPermission(isAdmin: true)
54+
55+ Model.createGroupChat(from: group, completion: { [weak self] id in
56+
57+ let newVC1 = DialogViewController()
58+ group.id = id
59+ newVC1.users = Model.Channels.fullPeopleDict
60+ newVC1.dialog = Model.Dialog.groupChat(Model.GroupChat(group: group))
61+ self?.navigationController?.pushViewController(newVC1, animated: true)
62+ })
63+ }
64+ }
65+
66+ vc?.navigationController?.pushViewController(newVC, animated: true)
67+ }
68+
69+ vc.onDialogDisplay = { [weak vc] in
70+
71+ let newVC = DialogViewController()
72+ newVC.dialog = $0.dialog
73+ newVC.users = $0.users
74+ newVC.onUserDisplay = { [weak self] id in
75+
76+ let vc = self?.storyboard.instantiateViewController(withIdentifier: profileViewController) as! ProfileViewController
77+ vc.idProfile = id
78+ self?.navigationController!.pushViewController(vc, animated: true)
79+
80+ }
81+
82+ vc?.navigationController?.pushViewController(newVC, animated: true)
83+ }
84+
85+ navigationController?.viewControllers = [vc]
86+ }
87+}
GDproject/Controller/Coordinators/ProfileCoordinator.swift+22−16
@@ -27,28 +27,34 @@ class ProfileCoordinator: BaseCoordinator {
2727 let storyboard = UIStoryboard(name: "Main", bundle: nil)
2828 let profile = storyboard.instantiateViewController(withIdentifier: "ProfileViewController") as! ProfileViewController
2929
30− profile.logOut = { [weak self] in
31− DataStorage.standard.setIsLoggedIn(value: false, with: 0)
32− self?.didEndSession?()
30+ profile.logOut = {
31+ Model.logout() {
32+ [weak self] in
33+
34+ DataStorage.standard.setIsLoggedIn(value: false, with: 0)
35+ self?.didEndSession?()
36+ }
3337 }
3438
35− profile.onSettings = { [weak self, weak storyboard] in
36− let vc = storyboard?.instantiateViewController(withIdentifier: "SettingsViewController") as! SettingsViewController
37− self?.navigationController?.pushViewController(vc, animated: true)
39+ profile.deleteAllSessions = {
40+ Model.deactivateAll() {
41+ [weak self] in
42+
43+ DataStorage.standard.setIsLoggedIn(value: false, with: 0)
44+ self?.didEndSession?()
45+ }
3846 }
3947
40− profile.onChannelsListToAddAPerson = { [weak self, weak storyboard] (user) in
41− let vc = storyboard?.instantiateViewController(withIdentifier: simplifiedChannelsList) as! SimplifiedChannelsList
42− vc.user = user
48+ profile.onSettings = { [weak self, weak storyboard, weak profile] in
49+
50+ let vc = storyboard?.instantiateViewController(withIdentifier: "RegisterTableViewController") as! RegisterTableViewController
51+ vc.delegate = profile
52+ vc.userActive = profile?.user
4353
44− let transition = CATransition()
45− transition.duration = 0.5
46− transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
47− transition.type = CATransitionType.moveIn
48− transition.subtype = CATransitionSubtype.fromTop
49− self?.navigationController?.view.layer.add(transition, forKey: nil)
50− self?.navigationController?.pushViewController(vc, animated: false)
54+ self?.navigationController?.pushViewController(vc, animated: true)
5155 }
56+
57+
5258 navigationController?.setViewControllers([profile], animated: false)
5359 }
5460}
GDproject/Controller/Coordinators/TabbarController.swift+6−1
@@ -12,6 +12,7 @@ import UIKit
1212protocol TabbarView: class {
1313 var onChannelsFlowSelect: ((UINavigationController) -> ())? { get set }
1414 var onProfileFlowSelect: ((UINavigationController) -> ())? { get set }
15+ var onMessagesFlowSelect: ((UINavigationController) -> ())? { get set }
1516 var onViewDidLoad: ((UINavigationController) -> ())? { get set }
1617}
1718
@@ -20,6 +21,8 @@ final class TabbarController: UITabBarController, UITabBarControllerDelegate, Ta
2021
2122 var onChannelsFlowSelect: ((UINavigationController) -> ())?
2223 var onProfileFlowSelect: ((UINavigationController) -> ())?
24+ var onMessagesFlowSelect: ((UINavigationController) -> ())?
25+
2326 var onViewDidLoad: ((UINavigationController) -> ())?
2427
2528 override func viewDidLoad() {
@@ -38,8 +41,10 @@ final class TabbarController: UITabBarController, UITabBarControllerDelegate, Ta
3841
3942 if selectedIndex == 0 {
4043 onChannelsFlowSelect?(controller)
41− } else {
44+ } else if selectedIndex == 2 {
4245 onProfileFlowSelect?(controller)
46+ } else {
47+ onMessagesFlowSelect?(controller)
4348 }
4449 }
4550}
GDproject/Controller/Coordinators/TabbarCoordinator.swift+12−1
@@ -15,7 +15,7 @@ class TabBarCoordinator: BaseCoordinator {
1515 var didEndFlow: (()->())?
1616
1717 private let tabbarView: TabbarView
18− private let window: UIWindow?
18+ private weak var window: UIWindow?
1919
2020 init(tabbarView: TabbarView, window: UIWindow) {
2121 self.tabbarView = tabbarView
@@ -26,6 +26,7 @@ class TabBarCoordinator: BaseCoordinator {
2626 tabbarView.onViewDidLoad = runChannelsFlow()
2727 tabbarView.onChannelsFlowSelect = runChannelsFlow()
2828 tabbarView.onProfileFlowSelect = runProfileFlow()
29+ tabbarView.onMessagesFlowSelect = runMessagesFlow()
2930 window?.rootViewController = tabbarView as! TabbarController
3031 }
3132
@@ -40,6 +41,16 @@ class TabBarCoordinator: BaseCoordinator {
4041 }
4142 }
4243
44+ private func runMessagesFlow() -> ((UINavigationController) -> ()){
45+ return { [unowned self] navController in
46+ if navController.viewControllers.isEmpty == true {
47+ let messagesCoordinator = MessagesCoordinator(nc: navController)
48+ self.addDependency(messagesCoordinator)
49+ messagesCoordinator.start()
50+ }
51+ }
52+ }
53+
4354 private func runProfileFlow() -> ((UINavigationController) -> ())
4455 {
4556 return { [unowned self] navController in
GDproject/Controller/Log In/CodeViewController.swiftnew file+144
@@ -0,0 +1,144 @@
1+//
2+// CodeViewController.swift
3+// GDproject
4+//
5+// Created by cstore on 10/05/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import UIKit
10+import TinyConstraints
11+
12+class CodeViewController: UIViewController {
13+
14+ @IBOutlet weak var f1: UITextField!
15+ @IBOutlet weak var f2: UITextField!
16+ @IBOutlet weak var f3: UITextField!
17+ @IBOutlet weak var f4: UITextField!
18+ @IBOutlet weak var f5: UITextField!
19+ @IBOutlet weak var f6: UITextField!
20+
21+ var loading = UIActivityIndicatorView(style: .gray)
22+
23+ var onSuccessLogIn: (()->())?
24+
25+ override func viewDidLoad() {
26+ super.viewDidLoad()
27+ setUpConstraint()
28+
29+ navigationItem.title = DataStorage.standard.getEmail()
30+
31+ f1.addTarget(self, action: #selector(textFiledDidChange(textField:)), for: .editingChanged)
32+ f2.addTarget(self, action: #selector(textFiledDidChange(textField:)), for: .editingChanged)
33+ f3.addTarget(self, action: #selector(textFiledDidChange(textField:)), for: .editingChanged)
34+ f4.addTarget(self, action: #selector(textFiledDidChange(textField:)), for: .editingChanged)
35+ f5.addTarget(self, action: #selector(textFiledDidChange(textField:)), for: .editingChanged)
36+ f6.addTarget(self, action: #selector(textFiledDidChange(textField:)), for: .editingChanged)
37+ }
38+
39+ override func viewWillDisappear(_ animated: Bool) {
40+ super.viewWillDisappear(animated)
41+
42+ loading.stopAnimating()
43+ }
44+
45+ override func viewWillAppear(_ animated: Bool) {
46+ super.viewWillAppear(animated)
47+
48+ f1.becomeFirstResponder()
49+ }
50+
51+ @objc func textFiledDidChange(textField: UITextField) {
52+ let text = textField.text
53+
54+ // only 1 char
55+ if text?.utf16.count == 1 {
56+ switch textField {
57+ case f1:
58+ f2.becomeFirstResponder()
59+ case f2:
60+ f3.becomeFirstResponder()
61+ case f3:
62+ f4.becomeFirstResponder()
63+ case f4:
64+ f5.becomeFirstResponder()
65+ case f5:
66+ f6.becomeFirstResponder()
67+ case f6:
68+ f6.resignFirstResponder()
69+ default:
70+ break
71+ }
72+ }
73+ }
74+
75+
76+ func presentAlertInvalidData(with text: String)
77+ {
78+ let alert = UIAlertController(title: "Invalid code", message: text, preferredStyle: .alert)
79+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
80+ self.present(alert, animated: true, completion: nil)
81+
82+ loading.stopAnimating()
83+ }
84+
85+ @IBOutlet weak var bottomConstraint: NSLayoutConstraint!
86+
87+ @IBAction func whereToGoNext(_ sender: UIButton)
88+ {
89+ guard let f1t = f1.text, let f2t = f2.text, let f3t = f3.text, let f4t = f4.text, let f5t = f5.text, let f6t = f6.text else {
90+ presentAlertInvalidData(with: "Some fields are missing");
91+ return
92+ }
93+
94+ if f1t.isEmpty || f2t.isEmpty || f3t.isEmpty || f4t.isEmpty || f5t.isEmpty || f6t.isEmpty {
95+ presentAlertInvalidData(with: "Some fields are missing");
96+ return
97+ }
98+
99+ let code = "\(f1.text!)\(f2.text!)\(f3.text!)\(f4.text!)\(f5.text!)\(f6.text!)"
100+ if let codeToInt = Int(code)
101+ {
102+ loading.startAnimating()
103+ Model.verify(with: codeToInt) { [weak self] in
104+ // if everything is okay we can authemticicate
105+ if !$0 {
106+ self?.presentAlertInvalidData(with: "Wrong code. Try again!")
107+ self?.loading.stopAnimating()
108+ return
109+ } else {
110+ // Model.authemticiateMe
111+ Model.authenticateMe { [weak self] (res) in
112+ if res {
113+ self?.onSuccessLogIn?()
114+ }
115+ }
116+ return
117+ }
118+ }
119+ }
120+ }
121+
122+ func setUpConstraint()
123+ {
124+ self.view.addSubview(loading)
125+ loading.centerInSuperview()
126+ // for keyboard notifications
127+ NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotifications), name: UIResponder.keyboardWillShowNotification, object: nil)
128+
129+ NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotifications), name: UIResponder.keyboardWillHideNotification, object: nil)
130+ }
131+
132+ @objc func handleKeyboardNotifications(notification: NSNotification){
133+ if let userInfo = notification.userInfo {
134+ // UIKeyboardFrameEndUserInfoKey
135+ let keyBoardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
136+ bottomConstraint.constant = notification.name == UIResponder.keyboardWillShowNotification ? keyBoardFrame.height - self.view.safeAreaInsets.bottom : 0
137+
138+ UIView.animate(withDuration: 0, delay: 0, options: .curveEaseOut, animations: {
139+ self.view.layoutIfNeeded()
140+ })
141+ }
142+ }
143+
144+}
GDproject/Controller/Log In/FacultyTableViewController.swiftnew file+88
@@ -0,0 +1,88 @@
1+//
2+// FacultyTableViewController.swift
3+// GDproject
4+//
5+// Created by cstore on 10/05/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import UIKit
10+
11+protocol ChosenFactulty: class {
12+ func onChooseFaculty(faculty: Model.Faculty)
13+}
14+
15+class FacultyTableViewController: UITableViewController {
16+
17+ weak var delegate: ChosenFactulty?
18+
19+ var currentFaculties: [Model.Faculty] = []{
20+ didSet {
21+ tableView.reloadData()
22+ }
23+ }
24+
25+ var isFiltering: Bool {
26+ return searchController.isActive && !searchBarIsEmpty()
27+ }
28+
29+ func searchBarIsEmpty() -> Bool {
30+ return searchController.searchBar.text?.isEmpty ?? true
31+ }
32+
33+ var searchController = UISearchController(searchResultsController: nil)
34+
35+ override func viewDidLoad() {
36+ super.viewDidLoad()
37+
38+ self.navigationItem.title = "Faculties"
39+ self.navigationItem.searchController = searchController
40+ self.navigationItem.hidesSearchBarWhenScrolling = false
41+ searchController.obscuresBackgroundDuringPresentation = false
42+
43+ setUpTimer()
44+ }
45+
46+ func setUpTimer(){
47+ Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { (timer) in
48+ DispatchQueue.main.async { [weak self] in
49+
50+ if self!.isFiltering {
51+ Model.searchFaculty(string: (self?.searchController.searchBar.text)!) { [weak self] in
52+ self?.currentFaculties = $0
53+ }
54+ }
55+ }
56+ }
57+ }
58+
59+
60+ // MARK: - Table view data source
61+
62+ override func numberOfSections(in tableView: UITableView) -> Int {
63+ return 1
64+ }
65+
66+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
67+ return currentFaculties.count
68+ }
69+
70+
71+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
72+ {
73+ let cell = tableView.dequeueReusableCell(withIdentifier: "F", for: indexPath)
74+
75+ cell.textLabel?.text = currentFaculties[indexPath.row].name
76+ cell.detailTextLabel?.text = currentFaculties[indexPath.row].campusName
77+ cell.selectionStyle = .none
78+
79+ return cell
80+ }
81+
82+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
83+ delegate?.onChooseFaculty(faculty: currentFaculties[indexPath.row])
84+ searchController.dismiss(animated: true, completion: nil)
85+ self.navigationController?.popViewController(animated: true)
86+ }
87+
88+}
GDproject/Controller/Log In/InititalsViewCell.swiftnew file+23
@@ -0,0 +1,23 @@
1+//
2+// InititalsViewCell.swift
3+// GDproject
4+//
5+// Created by cstore on 10/05/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class InititalsViewCell: UITableViewCell {
12+
13+ @IBOutlet weak var initialsTextField: UITextField!
14+
15+ override func awakeFromNib() {
16+ super.awakeFromNib()
17+ // Initialization code
18+ }
19+
20+ func fill(with name: String){
21+ initialsTextField.text = name
22+ }
23+}
GDproject/Controller/Log In/LogInCoordinator1.swiftdeleted−48
@@ -1,48 +0,0 @@
1−////
2−//// LogInCoordinator.swift
3−//// GDproject
4−////
5−//// Created by cstore on 23/02/2019.
6−//// Copyright © 2019 drHSE. All rights reserved.
7−////
8−//
9−//import UIKit
10−//
11−//class LogInCoordinator{
12−//
13−// private weak var navigationController: UINavigationController?
14−// private var window: UIWindow!
15−// // MARK:- Init
16−// init(navigationController: UINavigationController, window: UIWindow) {
17−// self.navigationController = navigationController
18−// self.window = window
19−// }
20−//
21−// func start(){
22−// showLogInPage()
23−// }
24−//
25−// // MARK: - Private implementation
26−//// private func showStatusPage(){
27−//// let controller = TabbarCoordinator(window: window)
28−//// controller.start()
29−//// }
30−//
31−// private func showLogInPage(){
32−// let controller = UIStoryboard.makeLogIn()
33−//
34−// controller.onLogIn = {
35−// (id) in Model.authenticate(with: id) {
36−// (res) in
37−//
38−// if (res) { DataStorage.standard.setUserKey(with: id) }
39−// else { DataStorage.standard.setUserKey(with: 0) }
40−//
41−// controller.authenticateSucceeded = res
42−// }
43−// }
44−//
45−// navigationController?.pushViewController(controller, animated: false)
46−// }
47−//
48−//}
GDproject/Controller/Log In/LogInViewController.swift+30−5
@@ -14,7 +14,24 @@ import Result
1414
1515class LogInViewController: UIViewController {
1616
17− var authenticate: ((Int)->())?
17+ var loading = UIActivityIndicatorView(style: .gray)
18+
19+ var authenticate: ((String)->())?
20+
21+ func presentAlertInvalidCode()
22+ {
23+ let alert = UIAlertController(title: "Invalid email", message: "Try again. Use @hse.ru mail.", preferredStyle: .alert)
24+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
25+ self.present(alert, animated: true, completion: nil)
26+ mailTextField.text = ""
27+ loading.stopAnimating()
28+ }
29+
30+ override func viewWillDisappear(_ animated: Bool) {
31+ super.viewWillDisappear(animated)
32+
33+ loading.stopAnimating()
34+ }
1835
1936 let logInLabel: UILabel = {
2037 let label = UILabel()
@@ -30,6 +47,8 @@ class LogInViewController: UIViewController {
3047 textField.placeholder = "Mail"
3148 textField.borderStyle = .roundedRect
3249 textField.textColor = .black
50+ textField.autocorrectionType = UITextAutocorrectionType.no
51+ textField.autocapitalizationType = UITextAutocapitalizationType.none
3352 textField.clearButtonMode = .always
3453 return textField
3554 }()
@@ -45,7 +64,8 @@ class LogInViewController: UIViewController {
4564 }()
4665
4766 @objc func handleTap(){
48− authenticate?(Int(mailTextField.text!)!)
67+ loading.startAnimating()
68+ authenticate?(mailTextField.text!)
4969 }
5070
5171 private lazy var keyboardBar = UIView()
@@ -55,6 +75,10 @@ class LogInViewController: UIViewController {
5575 func setUpView(){
5676 // logIn stack with textField and label
5777 view.addSubview(contentView)
78+ view.addSubview(loading)
79+
80+ loading.centerInSuperview()
81+
5882 contentView.edgesToSuperview(excluding: .bottom, insets: .left(16) + .right(16) + .top(80), usingSafeArea: true)
5983 let views = [logInLabel, mailTextField]
6084 contentView.stack(views, axis: .vertical, spacing: 10)
@@ -62,6 +86,7 @@ class LogInViewController: UIViewController {
6286
6387 func configureKeyboard(){
6488
89+ mailTextField.keyboardType = .emailAddress
6590 // configure keyboardBar setUp
6691 view.addSubview(keyboardBar)
6792 keyboardBar.height(50)
@@ -72,14 +97,14 @@ class LogInViewController: UIViewController {
7297 // configure keyboardBar components
7398 keyboardBar.addSubview(logInButton)
7499 logInButton.height(50)
75− logInButton.leftToSuperview(view.leftAnchor, offset: 16, relation: .equal, isActive: true)
100+ logInButton.rightToSuperview(view.rightAnchor, offset: -16, relation: .equal, isActive: true)
76101
77102 }
78103
79104 func logicOfLogInInputValidation() -> ((String?)->()) {
80105
81106 let logic: ((String?)->()) = { [weak self] (someText) in
82− if let text = someText, !text.isEmpty, let id = Int(text) {
107+ if let text = someText, !text.isEmpty, text.contains("@") {
83108 self?.logInButton.isEnabled = true
84109 } else {
85110 self?.logInButton.isEnabled = false
@@ -95,7 +120,7 @@ class LogInViewController: UIViewController {
95120 setUpView()
96121 configureKeyboard()
97122
98− let mailFieldValuesSignal: Signal<String?, NoError> = mailTextField.reactive.continuousTextValues
123+ let mailFieldValuesSignal: Signal<String, NoError> = mailTextField.reactive.continuousTextValues
99124
100125 mailFieldValuesSignal.observeValues(logicOfLogInInputValidation())
101126
GDproject/Controller/Log In/RegisterTableViewController.swiftnew file+198
@@ -0,0 +1,198 @@
1+//
2+// RegisterTableViewController.swift
3+// GDproject
4+//
5+// Created by cstore on 10/05/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class RegisterTableViewController: UITableViewController, ChosenFactulty
12+{
13+
14+ func presentAlertInvalidData(message: String)
15+ {
16+ let alert = UIAlertController(title: "Invalid data", message: message, preferredStyle: .alert)
17+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
18+ self.present(alert, animated: true, completion: nil)
19+ }
20+
21+ func onChooseFaculty(faculty: Model.Faculty) {
22+ self.faculty = faculty
23+ tableView.reloadData()
24+ }
25+
26+ weak var delegate: UpdateUser?
27+
28+ var onRegistration: (()->())?
29+
30+ var faculty: Model.Faculty?
31+ var user: Model.NewRegistration = Model.NewRegistration(email: DataStorage.standard.getEmail()!, firstName: nil, middleName: nil, lastName: nil, faculty: nil)
32+
33+ var userActive: Model.Users? {
34+ didSet {
35+ if let userA = userActive {
36+ user.firstName = userA.firstName
37+ user.middleName = userA.middleName
38+ user.lastName = userA.lastName
39+
40+ faculty = userA.faculty
41+ self.updateUserIfCan = true
42+ tableView.reloadData()
43+ }
44+ }
45+ }
46+
47+ var updateUserIfCan: Bool = false
48+
49+ override func viewDidLoad()
50+ {
51+ super.viewDidLoad()
52+
53+ navigationItem.largeTitleDisplayMode = .never
54+ tableView.keyboardDismissMode = .interactive
55+ navigationItem.title = user.email
56+ navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(endRegistration))
57+ }
58+
59+
60+ @objc func endRegistration()
61+ {
62+ let fieldsMissing = "Some fields are missing. Add more information"
63+ updateUser()
64+ guard let faculty = faculty else { presentAlertInvalidData(message: fieldsMissing); return }
65+
66+ user.faculty = faculty.url
67+
68+ guard let name = user.firstName else { presentAlertInvalidData(message: fieldsMissing); return }
69+ guard let middle = user.middleName else { presentAlertInvalidData(message: fieldsMissing); return }
70+ guard let last = user.lastName else { presentAlertInvalidData(message: fieldsMissing); return }
71+
72+ if updateUserIfCan {
73+ guard let delegate = delegate else { return }
74+ guard var userA = userActive else { return }
75+
76+ // updating current
77+ userA.firstName = name
78+ userA.faculty = faculty
79+ userA.middleName = middle
80+ userA.lastName = last
81+
82+ Model.userUpdate(with: user) { [weak self] in
83+
84+ if $0 {
85+ delegate.updateUserObj(with: userA)
86+ self?.navigationController?.popViewController(animated: true)
87+ } else {
88+ self?.presentAlertInvalidData(message: "Something went wrong! Try again")
89+ }
90+
91+ }
92+ } else {
93+
94+ Model.register(object: user)
95+ { [weak self] in
96+ self?.onRegistration?()
97+ }
98+ }
99+ }
100+ // MARK: - Table view data source
101+
102+ override func numberOfSections(in tableView: UITableView) -> Int {
103+ return 2
104+ }
105+
106+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
107+
108+ switch section {
109+ case 0:
110+ return 3
111+ default:
112+ if let _ = faculty {
113+ return 2
114+ } else {
115+ return 1
116+ }
117+ }
118+ }
119+
120+ override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
121+ {
122+ switch section {
123+ case 0:
124+ return "Initials"
125+ default:
126+ return "Faculty"
127+ }
128+ }
129+
130+
131+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
132+ {
133+ switch indexPath.section {
134+ case 0:
135+ let cell = tableView.dequeueReusableCell(withIdentifier: "nameCell", for: indexPath) as! InititalsViewCell
136+ switch indexPath.row{
137+ case 0:
138+ if let name = user.firstName {
139+ cell.fill(with: name)
140+ } else {
141+ cell.initialsTextField.placeholder = "First name"
142+ }
143+ case 1:
144+ if let name = user.middleName {
145+ cell.fill(with: name)
146+ } else {
147+ cell.initialsTextField.placeholder = "Middle name"
148+ }
149+ default:
150+ if let name = user.lastName {
151+ cell.fill(with: name)
152+ } else {
153+ cell.initialsTextField.placeholder = "Last name"
154+ }
155+ }
156+ cell.selectionStyle = .none
157+ return cell
158+ default:
159+ switch indexPath.row {
160+ case 0:
161+ let cell = tableView.dequeueReusableCell(withIdentifier: "cell111", for: indexPath)
162+ cell.selectionStyle = .none
163+ return cell
164+ default:
165+ let cell = tableView.dequeueReusableCell(withIdentifier: "facultyCell", for: indexPath)
166+ cell.textLabel?.text = faculty!.name
167+ cell.detailTextLabel?.text = faculty!.campusName
168+ cell.selectionStyle = .none
169+ return cell
170+ }
171+ }
172+ }
173+
174+
175+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
176+ if indexPath.section == 1 && indexPath.row == 0 {
177+
178+ updateUser()
179+
180+ let vc = storyboard?.instantiateViewController(withIdentifier: "FacultyTableViewController") as! FacultyTableViewController
181+
182+ vc.delegate = self
183+
184+ self.navigationController?.pushViewController(vc, animated: true)
185+ }
186+ }
187+
188+ func updateUser(){
189+ guard let cell = tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? InititalsViewCell else {return}
190+ guard let cell1 = tableView.cellForRow(at: IndexPath(row: 1, section: 0)) as? InititalsViewCell else {return}
191+ guard let cell2 = tableView.cellForRow(at: IndexPath(row: 2, section: 0)) as? InititalsViewCell else {return}
192+
193+ user.firstName = cell.initialsTextField.text!
194+ user.middleName = cell1.initialsTextField.text!
195+ user.lastName = cell2.initialsTextField.text!
196+ }
197+
198+}
GDproject/Controller/Messages/ChatInfoViewController.swiftnew file+308
@@ -0,0 +1,308 @@
1+//
2+// ChatInfoViewController.swift
3+// GDproject
4+//
5+// Created by cstore on 02/05/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import UIKit
10+
11+/// Class for displaying chat info
12+///
13+class ChatInfoViewController: UITableViewController {
14+
15+ enum PersonStatus{
16+ case admin
17+ case ordinary
18+ }
19+
20+ weak var delegate: UpdatableGroup?
21+
22+ var groupChat: Model.Group? {
23+ didSet{
24+ if let groupChat = groupChat {
25+ usersArray = groupChat.users.map { $0.key }
26+ }
27+ }
28+ }
29+
30+ var usersArray: [Int] = [] {
31+ didSet {
32+ tableView.reloadData()
33+ }
34+ }
35+
36+ var users: [Int: Model.Users] = [:]
37+
38+ func canIEditThisChat() -> PersonStatus
39+ {
40+ let myId = UserDefaults.standard.integer(forKey: UserDefaultsKeys.id.rawValue)
41+
42+ if let groupChatUserPermission = groupChat?.users[myId]?.isAdmin {
43+ return groupChatUserPermission ? .admin : .ordinary
44+ }
45+
46+ return .ordinary
47+ }
48+
49+ var myPermissions: PersonStatus = .ordinary
50+
51+
52+
53+ override func viewDidLoad() {
54+ super.viewDidLoad()
55+
56+ tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
57+ myPermissions = canIEditThisChat()
58+ tableView.reloadData()
59+
60+ switch myPermissions {
61+ case .admin:
62+ navigationItem.rightBarButtonItem = self.editButtonItem
63+ default:
64+ return
65+ }
66+ }
67+
68+ // MARK: - Table view data source
69+
70+ override func numberOfSections(in tableView: UITableView) -> Int {
71+ return 3
72+ }
73+
74+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
75+ switch section {
76+ case 0:
77+ return 1
78+ case 1:
79+ return 1
80+ default:
81+ return usersArray.count + (myPermissions == .ordinary ? 0 : 1)
82+ }
83+ }
84+
85+
86+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
87+ {
88+ let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
89+
90+ if indexPath.row == 0 {
91+ switch indexPath.section {
92+ case 0:
93+ cell.textLabel?.text = groupChat!.name
94+ cell.selectionStyle = .none
95+ return cell
96+ case 1:
97+ cell.textLabel?.text = "Leave chat"
98+ cell.textLabel?.textColor = #colorLiteral(red: 1, green: 0.1491314173, blue: 0, alpha: 1)
99+ cell.selectionStyle = .none
100+ return cell
101+ default:
102+ switch myPermissions{
103+ case .admin:
104+ cell.textLabel?.text = "Add participants"
105+ cell.accessoryType = .disclosureIndicator
106+ case .ordinary:
107+ cell.textLabel?.text = name(for: users[usersArray[indexPath.row]])
108+ }
109+ cell.selectionStyle = .none
110+ return cell
111+ }
112+ }
113+
114+ switch myPermissions{
115+ case .admin:
116+ cell.textLabel?.text = name(for: users[usersArray[indexPath.row-1]])
117+ case .ordinary:
118+ cell.textLabel?.text = name(for: users[usersArray[indexPath.row]])
119+ }
120+ cell.selectionStyle = .none
121+ return cell
122+ }
123+
124+ private func name(for user: Model.Users?) -> String {
125+ if let user = user, let perm = groupChat?.users[user.id]?.isAdmin {
126+ if perm {
127+ return "🤴🏻 \(user.fullName())"
128+ }
129+ return "👤 \(user.fullName())"
130+ }
131+
132+ return "left"
133+ }
134+
135+ override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
136+ {
137+ switch section {
138+ case 0:
139+ return "Title"
140+ case 1:
141+ return "Opportunities"
142+ default:
143+ return "Participants"
144+ }
145+ }
146+
147+
148+ func editName(for cell: UITableViewCell){
149+ let alert = UIAlertController(title: "Are you sure?", message: "Title:", preferredStyle: .alert)
150+
151+ let action1 = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
152+
153+ alert.addTextField { [weak self] (tf) in
154+ tf.textColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1)
155+ tf.text = self?.groupChat?.name
156+ }
157+
158+ let action2 = UIAlertAction(title: "OK", style: .default) { [unowned self] _ in
159+ let name = alert.textFields?.first?.text
160+ cell.textLabel?.text = name
161+ self.groupChat?.name = name!
162+ Model.updateGroupChat(with: self.groupChat!)
163+ self.delegate?.updateGroup(with: self.groupChat!)
164+ }
165+
166+ alert.addAction(action1)
167+ alert.addAction(action2)
168+
169+ present(alert, animated: true, completion: nil)
170+ }
171+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
172+ {
173+ guard let groupChat = groupChat else {
174+ return
175+ }
176+
177+ guard let cell = tableView.cellForRow(at: indexPath) else {
178+ return
179+ }
180+
181+ if indexPath.row == 0 {
182+ switch indexPath.section{
183+ case 0:
184+ switch myPermissions{
185+ case .admin:
186+ editName(for: cell)
187+ default:
188+ break
189+ }
190+ case 1:
191+ Model.leaveGroupChat(id: groupChat.id) {
192+ [weak self] in
193+ self?.navigationController?.popViewController(animated: true)
194+ }
195+ default:
196+ switch myPermissions{
197+ case .admin:
198+ showUserChoiceVC()
199+ default:
200+ showParticipantPage(with: usersArray[indexPath.row])
201+ }
202+ }
203+ }
204+
205+
206+ if indexPath.section == 2 && indexPath.row != 0 {
207+ switch myPermissions{
208+ case .admin:
209+ showParticipantPage(with: usersArray[indexPath.row-1])
210+ default:
211+ showParticipantPage(with: usersArray[indexPath.row])
212+ }
213+ }
214+ }
215+
216+ var onUserDiplay: ((Int)->())?
217+
218+ func showParticipantPage(with user: Int?) {
219+ if let user = user {
220+ onUserDiplay?(user)
221+ }
222+ }
223+
224+ func showUserChoiceVC()
225+ {
226+ let vc = storyboard?.instantiateViewController(withIdentifier: peopleToWriteVC) as! PeopleToWriteViewController
227+
228+ vc.whatToDoWithSelection = { [weak self, weak vc] mapa in
229+
230+ mapa.forEach { self?.users[$0.key] = Model.Channels.fullPeopleDict[$0.key] }
231+ mapa.forEach { self?.groupChat?.users[$0.key] = $0.value }
232+ vc?.navigationController?.popViewController(animated: true)
233+ }
234+
235+ navigationController?.pushViewController(vc, animated: true)
236+ }
237+
238+ override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath)
239+ {
240+ if editingStyle == UITableViewCell.EditingStyle.delete {
241+ groupChat?.users[usersArray[indexPath.row-1]] = nil
242+ }
243+
244+ tableView.reloadData()
245+ }
246+
247+ override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
248+ if indexPath.row == 0 {
249+ return false
250+ }
251+
252+ return true
253+ }
254+
255+ override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]?
256+ {
257+ let editButton = UITableViewRowAction(style: .normal, title: "Promote") { [unowned self] (rowAction, indexPath) in
258+
259+ self.tableView.beginUpdates()
260+ self.groupChat?.users[self.usersArray[indexPath.row-1]]?.isAdmin = true
261+ self.tableView.reloadRows(at: [indexPath], with: .none)
262+ self.tableView.endUpdates()
263+ }
264+
265+ editButton.backgroundColor = #colorLiteral(red: 0.476841867, green: 0.5048075914, blue: 1, alpha: 1)
266+
267+ let restrictButton = UITableViewRowAction(style: .normal, title: "Restrict") { [unowned self] (rowAction, indexPath) in
268+
269+ self.tableView.beginUpdates()
270+ self.groupChat?.users[self.usersArray[indexPath.row-1]]?.isAdmin = false
271+ self.tableView.reloadRows(at: [indexPath], with: .none)
272+ self.tableView.endUpdates()
273+ }
274+
275+ restrictButton.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
276+
277+ let deleteButton = UITableViewRowAction(style: .normal, title: "Delete") { [unowned self] (action, indexPath) in
278+
279+ self.tableView.beginUpdates()
280+ self.groupChat?.users[self.usersArray[indexPath.row-1]] = nil
281+ self.tableView.deleteRows(at: [indexPath], with: .none)
282+ self.tableView.endUpdates()
283+ }
284+
285+ deleteButton.backgroundColor = #colorLiteral(red: 1, green: 0.1491314173, blue: 0, alpha: 1)
286+
287+ return [editButton, restrictButton, deleteButton]
288+ }
289+
290+ /// update chatInfo
291+ override func viewWillDisappear(_ animated: Bool) {
292+
293+ defer {
294+ super.viewWillDisappear(animated)
295+ }
296+
297+ guard let _ = self.navigationController?.viewControllers.lastIndex(of: self) else {
298+ switch myPermissions {
299+ case .ordinary:
300+ return
301+ case .admin:
302+ Model.updateGroupChat(with: groupChat!)
303+ delegate?.updateGroup(with: groupChat!)
304+ }
305+ return
306+ }
307+ }
308+}
GDproject/Controller/Messages/Dialog/DialogCell.swiftnew file+132
@@ -0,0 +1,132 @@
1+//
2+// DialogCell.swift
3+// GDproject
4+//
5+// Created by cstore on 05/05/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import UIKit
10+import TinyConstraints
11+import MarkdownKit
12+
13+class DialogCell: UITableViewCell {
14+
15+ let nameLabel: UIButton = {
16+ let button = UIButton()
17+ button.setTitleColor(.black, for: .normal)
18+ button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
19+ button.contentHorizontalAlignment = UIControl.ContentHorizontalAlignment.left
20+ return button
21+ }()
22+
23+ @objc func displayProfile()
24+ {
25+ if let id = self.user?.id {
26+ print("diplay")
27+ onUserDisplay?(id)
28+ }
29+ }
30+
31+ var onUserDisplay: ((Int)->())?
32+
33+
34+ let timeLabel: UILabel = {
35+ let label = UILabel()
36+ label.font = UIFont.systemFont(ofSize: 9)
37+ label.textAlignment = NSTextAlignment.right
38+ label.textColor = #colorLiteral(red: 0.4352941176, green: 0.4431372549, blue: 0.4745098039, alpha: 1)
39+ return label
40+ }()
41+
42+ func createTextView(with text: NSAttributedString, _ isSelectable: Bool) -> UITextView
43+ {
44+ let textView = UITextView()
45+ textView.isScrollEnabled = false
46+ textView.isEditable = false
47+ textView.sizeToFit()
48+
49+ if isSelectable {
50+ textView.isSelectable = true
51+ } else {
52+ textView.isUserInteractionEnabled = false
53+ }
54+
55+ textView.attributedText = text
56+ return textView
57+ }
58+
59+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?)
60+ {
61+ super.init(style: style, reuseIdentifier: reuseIdentifier)
62+ }
63+
64+ required init?(coder aDecoder: NSCoder)
65+ {
66+ fatalError("init(coder:) has not been implemented")
67+ }
68+
69+ var user: Model.Users? {
70+ didSet{
71+ nameLabel.setTitle(user!.fullName(), for: .normal)
72+ }
73+ }
74+
75+ var textView: UITextView!
76+
77+ func fill(with markdownText: NSAttributedString, byUser: Model.Users, when: String)
78+ {
79+ let myId = DataStorage.standard.getUserId()
80+ // important
81+ contentView.subviews.forEach({ $0.removeFromSuperview() })
82+
83+ nameLabel.addTarget(self, action: #selector(displayProfile), for: .touchUpInside)
84+
85+ self.user = byUser
86+ textView = createTextView(with: markdownText, true)
87+ textView.layer.cornerRadius = 10
88+ textView.clipsToBounds = true
89+
90+ self.contentView.addSubview(textView)
91+ self.contentView.addSubview(timeLabel)
92+
93+ timeLabel.text = when.getDate()
94+ textView.width(min: 50, max: self.contentView.bounds.width-70, priority: .required, isActive: true)
95+
96+ if myId == byUser.id
97+ {
98+ layoutMyMessage()
99+ } else {
100+ layOutOtherMessage()
101+ }
102+ }
103+
104+ func layOutOtherMessage(){
105+ self.contentView.addSubview(nameLabel)
106+
107+ nameLabel.setTitle(user!.fullName(), for: .normal)
108+
109+ nameLabel.edgesToSuperview(excluding: .bottom, insets: .top(4) + .left(8))
110+ textView.edgesToSuperview(excluding: [.top, .right] , insets: .left(8) + .bottom(20))
111+ textView.topToBottom(of: nameLabel)
112+
113+ textView.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
114+ textView.textColor = .white
115+ timeLabel.edgesToSuperview(excluding: .top, insets: .right(70) + .left(8))
116+ timeLabel.topToBottom(of: textView, offset: 4)
117+ timeLabel.textAlignment = .left
118+ }
119+
120+ func layoutMyMessage()
121+ {
122+ textView.edgesToSuperview(excluding: .left, insets: .right(8) + .bottom(20) + .top(4))
123+ textView.backgroundColor = UIColor(red:0.08, green:0.49, blue:0.98, alpha:1.0)
124+ textView.textColor = .white
125+ timeLabel.edgesToSuperview(excluding: .top, insets: .left(70) + .right(8))
126+ timeLabel.topToBottom(of: textView, offset: 4)
127+ timeLabel.textAlignment = .right
128+ }
129+}
130+
131+
132+
GDproject/Controller/Messages/Dialog/DialogViewController.swiftnew file+340
@@ -0,0 +1,340 @@
1+//
2+// DialogViewController.swift
3+// GDproject
4+//
5+// Created by cstore on 01/05/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import UIKit
10+import TinyConstraints
11+import Marklight
12+
13+protocol UpdatableGroup:class {
14+ func updateGroup(with group: Model.Group)
15+}
16+
17+class DialogViewController: UIViewController, UpdatableGroup, UITableViewDelegate, UITableViewDataSource
18+{
19+ var onUserDisplay: ((Int)->())?
20+
21+ var tableView: UITableView = UITableView()
22+
23+ func updateGroup(with group: Model.Group){
24+ self.groupChat?.group = group
25+ setTitleForGroup(groupChat: groupChat!)
26+ }
27+
28+ let cellId = "cell3"
29+ var onInfoShow: (()->())?
30+
31+ var dialog: Model.Dialog? {
32+ didSet{
33+ switch dialog! {
34+ case .groupChat(let chat):
35+ self.groupChat = chat
36+ case .userChat(let chat):
37+ self.userChat = chat
38+ }
39+ }
40+ }
41+
42+ var groupChat: Model.GroupChat?
43+ var userChat: Model.UserChat?
44+
45+ var users: [Int: Model.Users]?
46+
47+ var groupId: Int?
48+
49+ var cellData: [PostCellData] = [] {
50+ didSet {
51+ tableView.reloadData()
52+ canBePaginated = true
53+ }
54+ }
55+
56+ override func viewDidLoad() {
57+ super.viewDidLoad()
58+
59+ setConstraints()
60+ magicForKeyboardChanges()
61+
62+ self.tableView.delegate = self
63+ self.tableView.dataSource = self
64+
65+ tableView.setContentOffset(CGPoint(x: 0, y: 50), animated: false)
66+
67+ navigationItem.largeTitleDisplayMode = .never
68+
69+ tableView.keyboardDismissMode = .onDrag
70+ tableView.separatorStyle = .none
71+ tableView.register(DialogCell.self, forCellReuseIdentifier: cellId)
72+ tableView.transform = CGAffineTransform(scaleX: 1, y: -1)
73+ tableView.contentInsetAdjustmentBehavior = .never
74+ tableView.contentOffset = CGPoint(x: 0, y: 30)
75+
76+ if let groupChat = groupChat {
77+ setTitleForGroup(groupChat: groupChat)
78+ } else if let userChat = userChat{
79+ setTitleForChat(userChat: userChat)
80+ }
81+ }
82+
83+ var messageSendView: UIView = {
84+ let view = UIView()
85+ view.backgroundColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
86+ return view
87+ }()
88+
89+ var messageTextView: UITextView =
90+ {
91+ let textStorage = MarklightTextStorage()
92+ textStorage.marklightTextProcessor.codeColor = UIColor.orange
93+ textStorage.marklightTextProcessor.quoteColor = UIColor.darkGray
94+ textStorage.marklightTextProcessor.syntaxColor = UIColor.blue
95+ textStorage.marklightTextProcessor.codeFontName = "Courier"
96+ textStorage.marklightTextProcessor.fontTextStyle = UIFont.TextStyle.subheadline.rawValue
97+ textStorage.marklightTextProcessor.hideSyntax = false
98+
99+ let layoutManager = NSLayoutManager()
100+
101+ // Assign the `UITextView`'s `NSLayoutManager` to the `NSTextStorage` subclass
102+ //textStorage.addLayoutManager(textView.layoutManager)
103+ textStorage.addLayoutManager(layoutManager)
104+
105+ let textContainer = NSTextContainer()
106+ layoutManager.addTextContainer(textContainer)
107+
108+ let textView = UITextView(frame: CGRect.zero, textContainer: textContainer)
109+ textView.isEditable = true
110+ textView.isScrollEnabled = true
111+ textView.backgroundColor = .white
112+ return textView
113+ }()
114+
115+ var sendButton: UIButton = {
116+ let button = UIButton()
117+ button.setTitle("Send", for: .normal)
118+ button.setTitleColor(.blue, for: .normal)
119+ button.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold)
120+ button.addTarget(self, action: #selector(sendMessage), for: .touchUpInside)
121+ return button
122+ }()
123+
124+ @objc func sendMessage()
125+ {
126+ var destination: Model.MessageDestination?
127+
128+ if let group = groupChat {
129+ destination = Model.MessageDestination.groupChatDestination(group.group.id)
130+ } else if let user = userChat {
131+ destination = Model.MessageDestination.userChatDestination(user.user)
132+ }
133+
134+ if let destination = destination
135+ {
136+ Model.sendMessage(message: Model.SendMessage(body: Model.Attachments(markdown: messageTextView.text), destination: destination)) { [unowned self] in
137+
138+ switch $0 {
139+ case .success, .success1:
140+ self.getMessagesNew(for: self.dialog!)
141+ self.messageTextView.text = ""
142+ default:
143+ self.showAlertOn(result: $0)
144+ }
145+ }
146+
147+ prevLast = -1
148+ }
149+ }
150+
151+ var lineView: UIView = {
152+ let view = UIView()
153+ view.backgroundColor = #colorLiteral(red: 0.6000000238, green: 0.6000000238, blue: 0.6000000238, alpha: 1)
154+ return view
155+ }()
156+
157+ var bottomConstraint: NSLayoutConstraint!
158+
159+ func setConstraints(){
160+ self.view.addSubview(tableView)
161+ self.view.addSubview(messageSendView)
162+
163+ messageSendView.addSubview(messageTextView)
164+ messageSendView.addSubview(sendButton)
165+ messageSendView.addSubview(lineView)
166+
167+ messageSendView.edgesToSuperview(excluding: [.top,.bottom])
168+
169+ bottomConstraint = NSLayoutConstraint(item: messageSendView, attribute: .bottom, relatedBy: .equal, toItem: self.view.safeAreaLayoutGuide, attribute: .bottom, multiplier: 1, constant: 0)
170+ view.addConstraint(bottomConstraint)
171+
172+ messageSendView.height(60)
173+
174+ sendButton.edgesToSuperview(excluding: .left)
175+ sendButton.width(60)
176+
177+ lineView.edgesToSuperview(excluding: .bottom)
178+ lineView.height(0.5)
179+
180+ messageTextView.edgesToSuperview(excluding: .right)
181+ messageTextView.rightToLeft(of: sendButton)
182+
183+ tableView.edgesToSuperview(excluding: .bottom)
184+ tableView.bottomToTop(of: messageSendView)
185+
186+ self.view.layoutSubviews()
187+ }
188+
189+ func magicForKeyboardChanges()
190+ {
191+ NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotifications), name: UIResponder.keyboardWillShowNotification, object: nil)
192+
193+ NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotifications), name: UIResponder.keyboardWillHideNotification, object: nil)
194+ }
195+
196+ @objc func handleKeyboardNotifications(notification: NSNotification){
197+ if let userInfo = notification.userInfo{
198+ // UIKeyboardFrameEndUserInfoKey
199+
200+ let keyBoardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
201+
202+ bottomConstraint.constant = notification.name == UIResponder.keyboardWillShowNotification ? -(keyBoardFrame.height) + self.view.safeAreaInsets.bottom : 0
203+
204+ UIView.animate(withDuration: 0, delay: 0, options: .curveEaseOut, animations: {
205+ self.view.layoutIfNeeded()
206+ }) { (completed) in
207+
208+ }
209+ }
210+ }
211+
212+ func setTitleForChat(userChat: Model.UserChat){
213+ navigationItem.title = "\(users![userChat.user]!.fullName())"
214+ navigationItem.rightBarButtonItem = UIBarButtonItem(title: "👤", style: .plain, target: self, action: #selector(showPersonPage))
215+ }
216+
217+ @objc func showPersonPage(){
218+ if let id = userChat?.user{
219+ onUserDisplay?(id)
220+ }
221+ }
222+
223+ func setTitleForGroup(groupChat: Model.GroupChat){
224+ navigationItem.title = "\(groupChat.group.name)"
225+ navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Info", style: .plain, target: self, action: #selector(moveToInfoVC))
226+ }
227+
228+ // onInfoShow
229+ @objc func moveToInfoVC(){
230+ let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: chatInfoViewController) as! ChatInfoViewController
231+
232+ vc.delegate = self
233+ vc.users = users!
234+ vc.onUserDiplay = onUserDisplay
235+
236+ if let groupChat = groupChat {
237+ vc.groupChat = groupChat.group
238+ }
239+
240+ navigationController?.pushViewController(vc, animated: true)
241+ }
242+
243+ var currentMessagesInChat: [Model.LastMessage] = [] {
244+ didSet
245+ {
246+ cellData = currentMessagesInChat.map { PostCellData(attributedData: PostCellData.create(with: [$0.body])) }
247+ }
248+ }
249+
250+ // MARK: - Table view data source
251+
252+ func numberOfSections(in tableView: UITableView) -> Int {
253+ return 1
254+ }
255+
256+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
257+ return cellData.count
258+ }
259+
260+ override func viewWillAppear(_ animated: Bool)
261+ {
262+ super.viewWillAppear(animated)
263+ tabBarController?.tabBar.isHidden = true
264+
265+ if let dialog = dialog {
266+ getMessagesNew(for: dialog)
267+ }
268+ }
269+
270+ func getMessages(for dialog: Model.Dialog, starting from: Int? = nil)
271+ {
272+ switch dialog {
273+ case .groupChat(let groupChat):
274+ Model.getMessagesFor(typeOfChat: Model.Dialog.groupChat(groupChat), chat: groupChat.group.id, exclusiveFrom: from)
275+ { [weak self] in
276+ self?.currentMessagesInChat.append(contentsOf: $0)
277+ }
278+ case .userChat(let userChat):
279+ Model.getMessagesFor(typeOfChat: Model.Dialog.userChat(userChat), chat: userChat.user, exclusiveFrom: from)
280+ { [weak self] in
281+ self?.currentMessagesInChat.append(contentsOf: $0)
282+ }
283+ }
284+ }
285+
286+
287+ func getMessagesNew(for dialog: Model.Dialog, starting from: Int? = nil)
288+ {
289+ switch dialog {
290+ case .groupChat(let groupChat):
291+ Model.getMessagesFor(typeOfChat: Model.Dialog.groupChat(groupChat), chat: groupChat.group.id, exclusiveFrom: from)
292+ { [weak self] in
293+ self?.currentMessagesInChat = $0
294+ }
295+ case .userChat(let userChat):
296+ Model.getMessagesFor(typeOfChat: Model.Dialog.userChat(userChat), chat: userChat.user, exclusiveFrom: from)
297+ { [weak self] in
298+ self?.currentMessagesInChat = $0
299+ }
300+ }
301+ }
302+
303+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
304+ {
305+ let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! DialogCell
306+
307+ //In cellForRowAtIndexPath
308+ cell.transform = CGAffineTransform(scaleX: 1, y: -1)
309+ cell.selectionStyle = .none
310+
311+
312+ if let user = users?[currentMessagesInChat[indexPath.row].author]
313+ {
314+ cell.fill(with: cellData[indexPath.row].attributedData, byUser: user, when: currentMessagesInChat[indexPath.row].time)
315+
316+ cell.onUserDisplay = onUserDisplay
317+ }
318+
319+ return cell
320+ }
321+
322+ var prevLast = -1
323+ var canBePaginated = false
324+ func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
325+ {
326+ if indexPath.row == cellData.count - 1 && prevLast != indexPath.row && canBePaginated && cellData.count >= 24
327+ {
328+ print("exclusiveFrom \(currentMessagesInChat.last?.id ?? 0)")
329+ if let dialog = dialog
330+ {
331+ getMessages(for: dialog, starting: currentMessagesInChat.last?.id)
332+ }
333+
334+ prevLast = indexPath.row
335+ }
336+ }
337+}
338+
339+
340+
GDproject/Controller/Messages/MessageViewCell.swiftnew file+38
@@ -0,0 +1,38 @@
1+//
2+// MessageViewCell.swift
3+// GDproject
4+//
5+// Created by cstore on 13/05/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class MessageViewCell: UITableViewCell {
12+
13+ @IBOutlet weak var dialogName: UILabel!
14+
15+ @IBOutlet weak var lastMessagePreview: UILabel!
16+
17+ @IBOutlet weak var dateLabel: UILabel!
18+
19+ override func awakeFromNib() {
20+ super.awakeFromNib()
21+ // Initialization code
22+ }
23+
24+ func fill(with dialog: Model.Dialog, user: Model.Users? = nil)
25+ {
26+ switch dialog {
27+
28+ case .groupChat(let group):
29+ dialogName.text = group.group.name
30+ lastMessagePreview.text = group.lastMessage?.body.markdown
31+ dateLabel.text = group.lastMessage?.time.getDate()
32+ case .userChat(let userChat):
33+ dialogName.text = "👤 \(user!.fullName())"
34+ lastMessagePreview.text = userChat.lastMessage?.body.markdown
35+ dateLabel.text = userChat.lastMessage?.time.getDate()
36+ }
37+ }
38+}
GDproject/Controller/Messages/MessagesViewController.swiftnew file+181
@@ -0,0 +1,181 @@
1+//
2+// MessagesViewController.swift
3+// GDproject
4+//
5+// Created by cstore on 01/05/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import UIKit
10+
11+protocol SelectableDialog: class {
12+ func openDialog(with dialog: Model.Dialog)
13+}
14+
15+class MessagesViewController: UITableViewController, UISearchControllerDelegate, UISearchResultsUpdating, SelectableDialog {
16+
17+ func openDialog(with dialog: Model.Dialog) {
18+ onDialogDisplay?((dialog, users))
19+ }
20+
21+ func updateSearchResults(for searchController: UISearchController) {
22+
23+ let filteredData = currentActiveDialogs
24+ let inputText = searchController.searchBar.text ?? ""
25+
26+ let filteredResults = filteredData.filter {
27+
28+ switch $0 {
29+ case .groupChat(let gropuChat):
30+ return gropuChat.group.name.contains(inputText)
31+
32+ case .userChat(let userChat):
33+ let user = users[userChat.user]
34+ guard let u = user else { return false }
35+ return u.fullName().contains(inputText)
36+
37+ }
38+
39+ }
40+
41+ // Apply the filtered results to the search results table.
42+ if let resultsController = searchController.searchResultsController as? SuggestedDialogsTableViewController {
43+ resultsController.users = users
44+ resultsController.currentDialogs = filteredResults
45+ resultsController.tableView.reloadData()
46+ }
47+ }
48+
49+ // curreent Active which can be displayed
50+ var currentActiveDialogs: [Model.Dialog] = [] {
51+ didSet {
52+ tableView.reloadData()
53+ }
54+ }
55+
56+ func setUpSearchContr(){
57+
58+ searchController?.delegate = self
59+ searchController?.obscuresBackgroundDuringPresentation = true
60+ navigationItem.searchController = searchController
61+ definesPresentationContext = true
62+ searchController?.searchResultsUpdater = self
63+ searchController?.searchBar.placeholder = "Search dialogs"
64+
65+ }
66+
67+ // curreent users
68+ var users: [Int: Model.Users] = [:]
69+
70+ var onUserDisplayList: (()->())?
71+
72+ var onDialogDisplay: (((dialog: Model.Dialog, users: [Int:Model.Users]))->())?
73+
74+ var searchController: UISearchController?
75+
76+ override func viewDidLoad() {
77+ super.viewDidLoad()
78+
79+ let updater = SuggestedDialogsTableViewController()
80+
81+ updater.delegate = self
82+ searchController = UISearchController(searchResultsController: updater)
83+
84+ // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
85+ self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Write", style: .plain, target: self, action: #selector(choosePerson))
86+
87+ self.navigationItem.title = "Messages"
88+ // self.navigationItem.largeTitleDisplayMode = .always
89+ setUpSearchContr()
90+ navigationController?.navigationBar.prefersLargeTitles = true
91+ self.navigationItem.hidesSearchBarWhenScrolling = false
92+ }
93+
94+ @objc func choosePerson(){
95+ onUserDisplayList?()
96+ }
97+
98+ override func viewWillAppear(_ animated: Bool) {
99+ super.viewWillAppear(animated)
100+ tabBarController?.tabBar.isHidden = false
101+ Model.getChatAll { [weak self] in
102+ self?.currentActiveDialogs = $0.0
103+ self?.users = $0.1
104+ }
105+ }
106+
107+ // MARK: - Table view data source
108+ override func numberOfSections(in tableView: UITableView) -> Int {
109+ return 1
110+ }
111+
112+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
113+ return currentActiveDialogs.count
114+ }
115+
116+
117+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
118+ {
119+ let cell = tableView.dequeueReusableCell(withIdentifier: "MessageViewCell", for: indexPath) as! MessageViewCell
120+
121+ switch currentActiveDialogs[indexPath.row].self {
122+ case .groupChat:
123+ cell.fill(with: currentActiveDialogs[indexPath.row])
124+ case .userChat(let userChat):
125+ cell.fill(with: currentActiveDialogs[indexPath.row], user: users[userChat.user])
126+ }
127+
128+ return cell
129+ }
130+
131+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
132+ let tuple = (currentActiveDialogs[indexPath.row],users)
133+ onDialogDisplay?(tuple)
134+ }
135+}
136+
137+
138+class SuggestedDialogsTableViewController: UITableViewController {
139+
140+ var idCell = "cell"
141+
142+ var users: [Int: Model.Users] = [:]
143+
144+ weak var delegate: SelectableDialog?
145+
146+ var currentDialogs: [Model.Dialog] = []
147+
148+ override func viewDidLoad() {
149+ super.viewDidLoad()
150+ tableView.register(UITableViewCell.self, forCellReuseIdentifier: idCell)
151+ tableView.reloadData()
152+ }
153+
154+ override func numberOfSections(in tableView: UITableView) -> Int {
155+ return 1
156+ }
157+
158+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
159+ return currentDialogs.count
160+ }
161+
162+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
163+ {
164+ let cell = tableView.dequeueReusableCell(withIdentifier: idCell, for: indexPath)
165+
166+ switch currentDialogs[indexPath.row] {
167+ case .groupChat(let groupChat):
168+ cell.textLabel?.text = groupChat.group.name
169+ case .userChat(let userChat):
170+ cell.textLabel?.text = users[userChat.user]?.fullName()
171+ }
172+
173+ cell.accessoryType = .disclosureIndicator
174+ return cell
175+ }
176+
177+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
178+ delegate?.openDialog(with: currentDialogs[indexPath.row])
179+ // self.dismiss(animated: true)
180+ }
181+}
GDproject/Controller/Messages/PeopleToWriteViewController.swiftnew file+67
@@ -0,0 +1,67 @@
1+//
2+// PeopleToWriteViewController.swift
3+// GDproject
4+//
5+// Created by cstore on 01/05/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class PeopleToWriteViewController: UITableViewController {
12+
13+ // TODO: - edit button when it's used for selection
14+ var whatToDoWithSelection: (([Int: Model.UserPermission])->())?
15+
16+ let searchC = UISearchController(searchResultsController: nil)
17+
18+ let users = Model.Channels.fullPeople
19+
20+ var chosenUsers: [Int: Model.UserPermission] = [:]
21+
22+ override func viewDidLoad() {
23+ super.viewDidLoad()
24+
25+ tableView.isEditing = true
26+ self.navigationItem.title = "People"
27+
28+ self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(newMessage))
29+
30+ self.navigationItem.largeTitleDisplayMode = .never
31+ self.navigationItem.searchController = searchC
32+ self.navigationItem.hidesSearchBarWhenScrolling = false
33+ }
34+
35+ override func setEditing(_ editing: Bool, animated: Bool) {
36+ super.setEditing(true, animated: animated)
37+ }
38+
39+ override func numberOfSections(in tableView: UITableView) -> Int {
40+ return 1
41+ }
42+
43+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
44+ return users.count
45+ }
46+
47+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
48+ chosenUsers[users[indexPath.row].id] = Model.UserPermission(isAdmin: false)
49+ }
50+
51+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
52+ {
53+ let cell = tableView.dequeueReusableCell(withIdentifier: "peopleToWriteCell", for: indexPath)
54+
55+ cell.textLabel?.text = users[indexPath.row].fullName()
56+
57+ return cell
58+ }
59+
60+ @objc func newMessage()
61+ {
62+ if chosenUsers.count != 0
63+ {
64+ whatToDoWithSelection?(chosenUsers)
65+ }
66+ }
67+}
GDproject/Controller/Profile/BasicInfoController.swift+16−11
@@ -16,17 +16,20 @@ struct CellData{
1616
1717class BasicInfoController: UIViewController, UITableViewDelegate, UITableViewDataSource {
1818
19− var dataSourse: [CellData] = [
20− CellData(opened: false, title: "Phone", sectionData: ["+7 901 733 01 79"]),
21− CellData(opened: false, title: "Mail", sectionData: ["vbogomazova@edu.hse.ru"]),
22− CellData(opened: false, title: "Research ID", sectionData: ["4567834336789456737"]),
23− CellData(opened: false, title: "Published", sectionData: ["Это будет очень длинное название книги 1, чтобы сделать проверку", "Книга 2", "Книга 3"]),
24− CellData(opened: false, title: "Courses", sectionData: ["Курс 1", "Курс 2"]),
25− CellData(opened: false, title: "Link", sectionData: ["https://wwww.hse.ru"])
26− ]
19+ var userInfo: Model.Users? {
20+ didSet {
21+ if let userInfo = userInfo {
22+ dataSourse = [CellData(opened: false, title: "Contacts", sectionData: [userInfo.email, userInfo.faculty.address]),
23+ CellData(opened: false, title: "Faculty", sectionData: [userInfo.faculty.campusName, userInfo.faculty.name, userInfo.faculty.address, userInfo.faculty.path]),
24+ CellData(opened: false, title: "Interests", sectionData: userInfo.faculty.tags)]
25+ }
26+ }
27+ }
28+
29+ var dataSourse: [CellData] = [ ]
2730
2831 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
29− if (dataSourse[section].opened){
32+ if (dataSourse[section].opened) {
3033 return dataSourse[section].sectionData.count + 1
3134 } else {
3235 return 1
@@ -45,8 +48,10 @@ class BasicInfoController: UIViewController, UITableViewDelegate, UITableViewDat
4548 cell.selectionStyle = .none
4649 return cell
4750 } else {
48− let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
49− cell.textLabel?.text = dataSourse[indexPath.section].sectionData[indexPath.row - 1]
51+ let cell = tableView.dequeueReusableCell(withIdentifier: infoCellId, for: indexPath) as! InfoCell
52+ cell.fill(title: dataSourse[indexPath.section].sectionData[indexPath.row - 1])
53+ cell.accessoryType = .none
54+ cell.selectionStyle = .none
5055 return cell
5156 }
5257 }
GDproject/Controller/Profile/InfoCell.swift+2−1
@@ -16,8 +16,9 @@ class InfoCell: UITableViewCell{
1616 label.font = UIFont.systemFont(ofSize: 16)
1717 label.sizeToFit()
1818 label.isScrollEnabled = false
19− label.isUserInteractionEnabled = false
19+ // label.isUserInteractionEnabled = false
2020 label.isEditable = false
21+ label.dataDetectorTypes = .all
2122 label.textColor = .black
2223 return label
2324 }()
GDproject/Controller/Profile/ProfileViewController.swift+139−55
@@ -9,8 +9,17 @@
99import UIKit
1010import TinyConstraints
1111
12−class ProfileViewController: UIViewController
12+protocol UpdateUser: class {
13+ func updateUserObj(with user: Model.Users)
14+}
15+
16+class ProfileViewController: UIViewController, UpdateUser
1317{
18+ func updateUserObj(with user: Model.Users)
19+ {
20+ self.user = user
21+ Model.Channels.fullPeopleDict[user.id] = user
22+ }
1423
1524 @IBOutlet weak var segmentedControl: UISegmentedControl!
1625
@@ -32,18 +41,31 @@ class ProfileViewController: UIViewController
3241
3342 var logOut: (()->())?
3443
44+ var deleteAllSessions: (()->())?
45+
3546 var onSettings: (()->())?
3647
37− var onChannelsListToAddAPerson: ((Model.Users)->())?
48+ @IBAction func sendMessage(_ sender: UIButton)
49+ {
50+ if let userId = idProfile
51+ {
52+ let createdDialog = Model.Dialog.userChat(Model.UserChat(user: userId))
53+ let vc = DialogViewController()
54+ vc.users = Model.Channels.fullPeopleDict
55+ vc.dialog = createdDialog
56+ self.navigationController?.pushViewController(vc, animated: true)
57+ }
58+ }
3859
39− var protoDictionary: [Int: UIImage] = [9: #imageLiteral(resourceName: "9"), 5051: #imageLiteral(resourceName: "5051"), 69: #imageLiteral(resourceName: "69"), 42: #imageLiteral(resourceName: "42")]
60+ var protoDictionary: [String: UIImage] = ["135213": #imageLiteral(resourceName: "9"), "135288": #imageLiteral(resourceName: "5051"), "22723" : #imageLiteral(resourceName: "69"), "135083": #imageLiteral(resourceName: "42")]
4061
41− func fill(with user: Model.Users){
42− self.facultyLabel.text = "Студент: Факультет Компьютерных Наук"
62+ func fill(with user: Model.Users)
63+ {
64+ self.facultyLabel.text = user.faculty.name
4365 self.nameLabel.text = "\(user.firstName) \(user.middleName)"
4466 self.surnameLabel.text = "\(user.lastName)"
45− self.profileImageView.image = protoDictionary[user.id]?.roundedImage
46− self.placeLabel.text = "📍Москва, Кочновский пр.3"
67+ self.profileImageView.image = protoDictionary[user.faculty.campusCode]?.roundedImage
68+ self.placeLabel.text = "📍\(user.faculty.address)"
4769 if user.id == DataStorage.standard.getUserId() {
4870 newMessageButton.isHidden = true
4971 } else {
@@ -53,44 +75,61 @@ class ProfileViewController: UIViewController
5375
5476 var user: Model.Users? {
5577 didSet {
56− self.fill(with: user!)
57− Model.getPostsForUser(with: user!.id) { [weak self] (posts) in
58− self?.dataSourse = posts
78+ if let user = user {
79+ self.fill(with: user)
80+ navigationItem.title = "\(user.firstName) \(user.lastName)"
81+ } else if let id = idProfile {
82+ Model.getUsers(for: [id]) { [unowned self ] in
83+ self.user = $0[id]
84+ }
5985 }
60− navigationItem.title = "\(user!.firstName) \(user!.lastName)"
6186 }
6287 }
6388
64− var basicInfo = BasicInfoController()
65− var posts = NewsVC()
89+ var channel: Model.Channels? {
90+ didSet {
91+ self.update()
92+ }
93+ }
6694
67− var dataSourse: [Model.Posts]?{
68− didSet{
69−
70− var newPosts: [Model.Posts] = []
71−
72− dataSourse?.forEach({ (post) in
73− newPosts.append(Model.Posts(body: post.body, authorId: post.authorId, id: post.id, user: user!, date: post.updated, tags: post.tags))
74− })
75−
76− self.posts.dataSourse = newPosts
77− tableView.reloadData()
95+ func update()
96+ {
97+ Model.getAnonymousChannel(by: channel!) { [unowned self] in
98+ self.posts.dataSourse = $0.posts
99+ self.posts.dictionary = $0.users
100+ self.user = $0.users[self.idProfile!]
78101 }
79102 }
80103
104+ var basicInfo = BasicInfoController()
105+ var posts = NewsVC()
106+
81107 override func viewDidLoad()
82108 {
83109 super.viewDidLoad()
84−
110+ if idProfile == nil {
111+ idProfile = DataStorage.standard.getUserId()
112+ }
113+
85114 posts.viewController = self
86115 posts.type = .NONE
116+ posts.currChannel = channel
117+
118+ posts.onFullPost = {
119+ [weak self] (type,post) in
120+
121+ let vc = self?.storyboard?.instantiateViewController(withIdentifier: fullPostControllerId) as! FullPostController
122+ vc.type = type
123+ vc.post = post
124+ self?.navigationController!.pushViewController(vc, animated: true)
125+ }
87126
88127 tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
89128
90129 tableView.register(PostViewCell.self, forCellReuseIdentifier: postCellId)
91130
92131 tableView.register(BasicInfoCell.self, forCellReuseIdentifier: basicInfoCellId)
93−
132+
94133 tableView.register(InfoCell.self, forCellReuseIdentifier: infoCellId)
95134
96135 posts.viewController = self
@@ -100,29 +139,27 @@ class ProfileViewController: UIViewController
100139 tableView.reloadData()
101140 }
102141
103− var idProfile: Int?
142+ var idProfile: Int? {
143+ didSet {
144+ channel = Model.Channels(people: [idProfile!], name: "", id: 0, tags: [])
145+ }
146+ }
104147
105148 override func viewWillAppear(_ animated: Bool) {
149+ tabBarController?.tabBar.isHidden = false
106150
107151 if idProfile == nil {
108152 idProfile = DataStorage.standard.getUserId()
109153 }
154+ user = Model.Channels.fullPeopleDict[idProfile!]
110155
111− if let id = idProfile {
112− if let user = Model.idUser[id] {
113− self.user = user
114− } else {
115− Model.getUsers(for: [id]) { [weak self] (dic) in
116− self?.user = dic[id]
117− }
118− }
119− }
156+ update()
120157
121158 setUpNavigarionBar()
122159 }
123160
124161 func setUpNavigarionBar(){
125− navigationController?.navigationBar.prefersLargeTitles = true
162+ // navigationController?.navigationBar.prefersLargeTitles = true
126163 let uibarbutton = UIBarButtonItem(title: "More", style: .plain, target: self, action: #selector(showInformation))
127164 navigationItem.rightBarButtonItems = [uibarbutton]
128165 navigationItem.largeTitleDisplayMode = .always
@@ -132,29 +169,75 @@ class ProfileViewController: UIViewController
132169
133170 let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
134171
135− let channelAction = UIAlertAction(title: "Add to a channel", style: .default){
136− [weak self] (_) in
137− self?.onChannelsListToAddAPerson?(self!.user!)
138− }
139−
140− let settingsAction = UIAlertAction(title: "Setting", style: .default)
141− { [weak self] (_) in
142− self?.onSettings?()
143− }
144−
172+
145173 let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
146174
147− let logoutAction = UIAlertAction(title: "Log out", style: .destructive)
148− { [weak self]
149− (_) in
150− self?.logOut?()
175+
176+ if idProfile == DataStorage.standard.getUserId() {
177+ let logoutAction = UIAlertAction(title: "Log out", style: .destructive)
178+ { [weak self] (_) in
179+
180+ if let logOut = self?.logOut {
181+ logOut()
182+ } else {
183+ Model.logout() {
184+ DataStorage.standard.setIsLoggedIn(value: false, with: 0)
185+ (UIApplication.shared.delegate as! AppDelegate).relaunch()
186+ }
187+ }
188+ }
189+
190+ let deleetAllSessionsAction = UIAlertAction(title: "Delete all sessions", style: .destructive)
191+ { [weak self] _ in
192+
193+ if let delete = self?.deleteAllSessions {
194+ delete()
195+ } else {
196+ Model.deactivateAll() {
197+ DataStorage.standard.setIsLoggedIn(value: false, with: 0)
198+ (UIApplication.shared.delegate as! AppDelegate).relaunch()
199+ }
200+ }
201+ }
202+
203+ let settingsAction = UIAlertAction(title: "Edit profile", style: .default)
204+ { [weak self] (_) in
205+
206+ if let settings = self?.onSettings{
207+ settings()
208+ } else {
209+ let vc = self?.storyboard?.instantiateViewController(withIdentifier: "RegisterTableViewController") as! RegisterTableViewController
210+ vc.delegate = self
211+ vc.userActive = self?.user
212+
213+ self?.navigationController?.pushViewController(vc, animated: true)
214+ }
215+ }
216+
217+ optionMenu.addAction(settingsAction)
218+ optionMenu.addAction(logoutAction)
219+ optionMenu.addAction(deleetAllSessionsAction)
220+ } else {
221+ let channelAction = UIAlertAction(title: "Add to a channel", style: .default)
222+ {
223+ [weak self] (_) in
224+
225+ let vc = self?.storyboard?.instantiateViewController(withIdentifier: simplifiedChannelsList) as! SimplifiedChannelsList
226+ vc.user = self?.user
227+
228+ let transition = CATransition()
229+ transition.duration = 0.5
230+ transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
231+ transition.type = CATransitionType.moveIn
232+ transition.subtype = CATransitionSubtype.fromTop
233+ self?.navigationController?.view.layer.add(transition, forKey: nil)
234+ self?.navigationController?.pushViewController(vc, animated: false)
235+ }
236+
237+ optionMenu.addAction(channelAction)
151238 }
152239
153− optionMenu.addAction(channelAction)
154− optionMenu.addAction(settingsAction)
155− optionMenu.addAction(logoutAction)
156240 optionMenu.addAction(cancelAction)
157−
158241 self.present(optionMenu, animated: true, completion: nil)
159242 }
160243
@@ -184,6 +267,7 @@ class ProfileViewController: UIViewController
184267 func changeToBasicInfo(){
185268 tableView.delegate = basicInfo
186269 tableView.dataSource = basicInfo
270+ basicInfo.userInfo = self.user
187271 tableView.reloadData()
188272 }
189273}
GDproject/DataStorage.swift+8
@@ -37,6 +37,13 @@ class DataStorage{
3737 return UserDefaults.standard.integer(forKey: UserDefaultsKeys.id.rawValue)
3838 }
3939
40+ func setEmail(email: String) {
41+ UserDefaults.standard.set(email, forKey: UserDefaultsKeys.email.rawValue)
42+ }
43+
44+ func getEmail() -> String? {
45+ return UserDefaults.standard.string(forKey: UserDefaultsKeys.email.rawValue)
46+ }
4047 /**
4148 Function to determine is user logged in already or not
4249 */
@@ -49,4 +56,5 @@ class DataStorage{
4956enum UserDefaultsKeys: String{
5057 case loggedIn
5158 case id
59+ case email
5260}
GDproject/Extentions.swift+47
@@ -60,3 +60,50 @@ extension UIStoryboard {
6060 return UIStoryboard.userEdit.instantiateViewController(withIdentifier: logInController) as! LogInViewController
6161 }
6262}
63+
64+
65+extension String {
66+ func getDate() -> String
67+ {
68+ let dateFormatter = DateFormatter()
69+ dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"//this your string date format
70+ dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
71+ let date = dateFormatter.date(from: self)
72+
73+ dateFormatter.dateFormat = "MMM d, yyyy HH:mm" ///this is what you want to convert format
74+ dateFormatter.timeZone = NSTimeZone.local
75+ let timeStamp = dateFormatter.string(from: date!)
76+
77+ return timeStamp
78+ }
79+}
80+
81+extension UIViewController {
82+ func showAlertOn(result: ResultR) {
83+
84+ let message: String
85+
86+ switch result {
87+ case .impossibleContent:
88+ message = "Impossible content"
89+ case .exceededContent:
90+ message = "The content exceeded"
91+ case .longContent:
92+ message = "The content is too long"
93+ case .badAccess:
94+ message = "Try reloading the page again"
95+ case .alreadyRegistered:
96+ message = "User is already registered"
97+ case .tooMuchToAdd:
98+ message = "Limit of channels exceeded"
99+ case .success, .success1:
100+ return
101+ default:
102+ message = "Something went wrong"
103+ }
104+
105+ let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
106+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
107+ self.present(alert, animated: true, completion: nil)
108+ }
109+}
GDproject/Simple model/CompletionTree.swiftnew file+44
@@ -0,0 +1,44 @@
1+//
2+// CompletionTree.swift
3+// GDproject
4+//
5+// Created by cstore on 01/05/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import Foundation
10+
11+
12+struct CompletionTree: Codable {
13+ var value: String?
14+ var subtree: [String : CompletionTree]
15+
16+ static func getCompletion(tree: CompletionTree, word: String) -> [String] {
17+ if word == "" {
18+ return getValues(tree: tree)
19+ }
20+
21+ let character = String(word.first!)
22+
23+ if tree.subtree[character] == nil {
24+ return []
25+ } else {
26+ return getCompletion(tree: tree.subtree[character]!, word: String(word.dropFirst()))
27+ }
28+ }
29+
30+ static func getValues(tree: CompletionTree) -> [String]
31+ {
32+ var out = [String]()
33+
34+ if let treeVal = tree.value {
35+ out.append(treeVal)
36+ }
37+
38+ for (_, subtree) in tree.subtree {
39+ out += getValues(tree: subtree)
40+ }
41+
42+ return out
43+ }
44+}
GDproject/Simple model/Model.swift+697−49
@@ -7,42 +7,85 @@
77//
88
99import Foundation
10+import UIKit
1011import Alamofire
1112
13+enum ResultR: Int {
14+ case internalServerError = 500
15+ case exceededContent = 470
16+ case longContent = 471
17+ case incorrectContent = 472
18+ case impossibleContent = 473
19+ case invalidTocken = 498
20+ case alreadyRegistered = 409
21+ case invalidCode = 403
22+ case badAccess = 400
23+ case tooMuchToAdd = 406
24+ case notFound = 404
25+ case success1 = 204
26+ case success = 200
27+}
28+
1229class Model{
1330
1431 static let invalidTocken = 498
1532
33+ static var hashTagTree: CompletionTree?
34+
1635 private static var isValidTocken: ((Int)->())? = { responce in
1736 print(responce)
1837 if responce == invalidTocken {
38+
1939 DataStorage.standard.setIsLoggedIn(value: false, with: 0)
40+ (UIApplication.shared.delegate as! AppDelegate).relaunch()
41+
42+ Model.logout {
43+ print("Logout Is Successful")
44+ }
2045 }
2146 }
2247
2348 private static let baseUrl = "https://valera-denis.herokuapp.com"
2449 static let decoder = JSONDecoder()
25−
50+
51+ static let authMeURL = URL(string: "\(baseUrl)/authentication/me")!
52+ static let deactivateURL = URL(string: "\(baseUrl)/deactivateAll")!
53+ static let registerMeURL = URL(string: "\(baseUrl)/authentication/register")!
54+ static let authVerifyURL = URL(string: "\(baseUrl)/authentication/verify")!
2655 static let authenticationURL = URL(string:"\(baseUrl)/authentication/login")!
56+ static let logOutURL = URL(string:"\(baseUrl)/authentication/logout")!
2757 static let postsLastURL = URL(string:"\(baseUrl)/posts/last")!
2858 static let postsForUserURL = URL(string:"\(baseUrl)/posts/forUser")!
2959 static let postsPublishURL = URL(string:"\(baseUrl)/posts/publish")!
3060 static let usersURL = URL(string:"\(baseUrl)/users")!
3161 static let usersAllURL = URL(string:"\(baseUrl)/users/all")!
62+ static let usersUpdateURL = URL(string:"\(baseUrl)/users/update")!
3263 static let channelsGetURL = URL(string: "\(baseUrl)/channels/get")!
3364 static let channelsUpdateURL = URL(string: "\(baseUrl)/channels/update")!
3465 static let channelsListURL = URL(string: "\(baseUrl)/channels")!
3566 static let channelsCreateURL = URL(string: "\(baseUrl)/channels/create")!
3667 static let channelsDeleteURL = URL(string: "\(baseUrl)/channels/delete")!
3768 static let channelsGetAnonURL = URL(string: "\(baseUrl)/channels/getAnonymous")!
69+ static let complexURL = URL(string: "\(baseUrl)/complex")!
70+ static let hashTagTreeURL = URL(string: "\(baseUrl)/tags/completions")!
71+ static let createGroupChatURL = URL(string: "\(baseUrl)/chats/createGroupChat")!
72+ static let chatsGetAllURL = URL(string: "\(baseUrl)/chats/getAll")!
73+ static let getGroupChatURL = URL(string: "\(baseUrl)/chats/getGroupChat")!
74+ static let leaveGroupChatURL = URL(string: "\(baseUrl)/chats/leaveGroupChat")!
75+ static let updateGroupChatURL = URL(string: "\(baseUrl)/chats/updateGroupChat")!
76+ static let messagesGetGroupChatURL = URL(string: "\(baseUrl)/messages/get/groupChat")! //r
77+ static let messagesSendURL = URL(string: "\(baseUrl)/messages/send")!
78+ static let messagesGetUserChatURL = URL(string: "\(baseUrl)/messages/get/userChat")!
79+ static let facultySearchURL = URL(string: "\(baseUrl)/faculty/search")!
3880
3981
40− struct QueryPosts: Codable{
82+ struct QueryPosts<T: Codable>: Codable {
4183 var users: [Int: Users]
42− var posts: [Posts]
84+ var response: [T]
4385 }
4486
4587 struct Posts: Codable {
88+
4689 var body: [Attachments]
4790 var authorId: Int
4891 var id: Int
@@ -105,7 +148,12 @@ class Model{
105148 var lastName: String
106149 var firstName: String
107150 var id: Int
151+ var faculty: Faculty
152+ var email: String
108153
154+ func fullName() -> String {
155+ return "\(firstName) \(lastName)"
156+ }
109157 }
110158
111159 struct Attachments: Codable {
@@ -127,7 +175,7 @@ class Model{
127175
128176 struct Channels: Codable {
129177
130− static var fullTags = Set<String>()
178+ // static var fullTags = Set<String>()
131179 static var fullPeople = [Users]()
132180 static var fullPeopleDict = [Int:Users]()
133181
@@ -165,24 +213,108 @@ class Model{
165213 }
166214 }
167215
168− static func authenticate(with id: Int, completion: @escaping ((Bool)->())) {
216+ struct AuthStatus: Codable {
217+ var userStatus: String
169218
170− let json: [String:Any] = ["authenticationId" : id]
171− let jsonData = try? JSONSerialization.data(withJSONObject: json)
219+ init(){
220+ userStatus = "invalid"
221+ }
222+ }
223+
224+ static func logout(completion: @escaping (()->())){
225+ var request = URLRequest(url: logOutURL)
226+ request.httpMethod = "POST"
227+
228+ AF.request(request).response { (responce) in
229+ isValidTocken?(responce.response?.statusCode ?? 498)
230+
231+ if let code = responce.response?.statusCode, code == 498 {
232+ return
233+ }
234+
235+ completion()
236+ }
237+ }
238+
239+ /*
240+ "email": "vchernyshev@hse.ru",
241+ "middleName": "Algebrovich",
242+ "lastName": "Leonidov",
243+ "faculty": "cs.hse.ru/dse",
244+ "firstName": "Seva"
245+ */
246+
247+ struct NewRegistration: Codable{
248+ var email: String
249+ var firstName: String?
250+ var middleName: String?
251+ var lastName: String?
252+ var faculty: String?
253+ }
254+
255+ static func register(object: NewRegistration, completion: @escaping (()->())) {
256+ var request = URLRequest(url: registerMeURL)
257+ request.httpBody = try? JSONEncoder().encode(object)
172258
173− var request = URLRequest(url: authenticationURL)
174259 request.httpMethod = "POST"
260+ request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
175261
176− // insert json data to the request
177− request.httpBody = jsonData
262+ AF.request(request).response { (responce) in
263+
264+ guard let code = responce.response?.statusCode else { return }
265+ isValidTocken?(code)
266+
267+ if let code = responce.response?.statusCode, code == 498 {
268+ return
269+ }
270+
271+ if code == 204 {
272+
273+ let fields = responce.response?.allHeaderFields as? [String :String]
274+ let cookies = HTTPCookie.cookies(withResponseHeaderFields: fields!, for: responce.response!.url!)
275+ HTTPCookieStorage.shared.setCookie(cookies[0])
276+
277+ completion()
278+ }
279+ }
280+ }
281+
282+ // true only if everything is good
283+ static func verify(with code: Int, completion: @escaping ((Bool)->())){
284+ var request = URLRequest(url: authVerifyURL)
285+ request.httpMethod = "POST"
286+ request.httpBody = "\(code)".data(using: .utf8)
178287 request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
179288
180289 AF.request(request).response { (responce) in
181290
182− guard let realResponse = responce.response, realResponse.statusCode == 200 else
183− {
184− print("Not a 200 response")
291+ guard let statusCode = responce.response?.statusCode else { completion(false); return }
292+ isValidTocken?(statusCode)
293+
294+ if let code = responce.response?.statusCode, code == 498 {
295+ return
296+ }
297+
298+ if statusCode == 204 {
299+ // at this point cookies are set
300+ completion(true)
301+ } else {
185302 completion(false)
303+ }
304+ }
305+ }
306+
307+ // yes or no
308+ static func authenticateMe(completion: @escaping ((Bool)->())){
309+ var request = URLRequest(url: authMeURL)
310+ request.httpMethod = "POST"
311+
312+ AF.request(request).response { (responce) in
313+
314+ guard let statusCode = responce.response?.statusCode else { completion(false); return }
315+ isValidTocken?(statusCode)
316+
317+ if let code = responce.response?.statusCode, code == 498 {
186318 return
187319 }
188320
@@ -192,26 +324,87 @@ class Model{
192324 return
193325 }
194326
195− let fields = realResponse.allHeaderFields as? [String :String]
327+ DataStorage.standard.setIsLoggedIn(value: true, with: personId)
328+ completion(true)
329+ }
330+ }
331+
332+ // register, ok or invalid
333+ static func authenticate(with email: String, completion: @escaping ((AuthStatus)->())) {
334+
335+ let json: [String:Any] = ["authenticationEmail" : email]
336+ let jsonData = try? JSONSerialization.data(withJSONObject: json)
337+
338+ var request = URLRequest(url: authenticationURL)
339+ request.httpMethod = "POST"
340+
341+ // insert json data to the request
342+ request.httpBody = jsonData
343+ request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
344+
345+ AF.request(request).response { (response) in
196346
197− let cookies = HTTPCookie.cookies(withResponseHeaderFields: fields!, for: realResponse.url!)
347+ guard let realResponse = response.response, realResponse.statusCode == 200 else
348+ {
349+ print("Not a 200 response")
350+ completion(AuthStatus())
351+ return
352+ }
353+
354+ guard let json = response.data else { return }
355+ guard let res = try? decoder.decode(AuthStatus.self, from: json) else { return }
198356
199− HTTPCookieStorage.shared.setCookie(cookies[0])
357+ if realResponse.statusCode == 200 && res.userStatus == "registered"
358+ {
359+ // at this point cookies are set
360+ let fields = realResponse.allHeaderFields as? [String :String]
361+ let cookies = HTTPCookie.cookies(withResponseHeaderFields: fields!, for: realResponse.url!)
362+ HTTPCookieStorage.shared.setCookie(cookies[0])
363+ }
200364
201− DataStorage.standard.setIsLoggedIn(value: true, with: personId)
202− completion(true)
365+ completion(res)
203366 }
204367 }
205368
369+ struct GeneralRequest<T: Codable>: Codable
370+ {
371+ var direction: String
372+ var limit: Int
373+ var exclusiveFrom: Int?
374+ var request: T
375+
376+ init(direction: String = "backward", limit: Int, exclusiveFrom: Int?, request: T) {
377+ self.direction = direction
378+ self.limit = limit
379+ self.exclusiveFrom = exclusiveFrom
380+ self.request = request
381+ }
382+
383+ enum CodingKeys: String, CodingKey {
384+ case limit
385+ case exclusiveFrom
386+ case request
387+ case direction
388+ }
389+
390+ func encode(to encoder: Encoder) throws {
391+ var container = encoder.container(keyedBy: CodingKeys.self)
392+ try container.encode(limit, forKey: .limit)
393+ try container.encode(exclusiveFrom, forKey: .exclusiveFrom)
394+ try container.encode(request, forKey: .request)
395+ try container.encode(direction, forKey: .direction)
396+ }
397+ }
206398
207− static func getLast(completion: @escaping (((users:[Int: Users], posts:[Posts]))->())){
208− let jsonString = "30"
209− var request = URLRequest(url: postsLastURL)
399+ static func getLast(on limit: Int = 10, from pointInTime: Int? = nil, completion: @escaping (((users:[Int: Users], posts:[Posts]))->()))
400+ {
401+ let postRequest = GeneralRequest<[Int]>(limit: limit, exclusiveFrom: pointInTime, request: [])
210402
403+ var request = URLRequest(url: postsLastURL)
404+ request.httpBody = try? JSONEncoder().encode(postRequest)
211405 request.httpMethod = "POST"
212406
213407 // insert json data to the request
214− request.httpBody = jsonString.data(using: .utf8)
215408 request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
216409
217410 AF.request(request).responseJSON {
@@ -219,18 +412,22 @@ class Model{
219412
220413 isValidTocken?(response.response?.statusCode ?? 498)
221414
415+ if let code = response.response?.statusCode, code == 498 {
416+ return
417+ }
418+
222419 guard let json = response.data else { return }
223420
224− guard let newQueery = try? decoder.decode(QueryPosts.self, from: json) else { print("no")
421+ guard let newQueery = try? decoder.decode(QueryPosts<Posts>.self, from: json) else { print("no")
225422 return }
226423
227424 idUser = newQueery.users
228− completion((newQueery.users, newQueery.posts))
425+ completion((newQueery.users, newQueery.response))
229426 }
230427 }
231428
232429
233− static func createAndPublish(body: [Attachments], tags: [String]){
430+ static func createAndPublish(body: [Attachments], tags: [String], completion: @escaping ((ResultR)->())){
234431 let jsonUpd = CreatedPost(body: body, tags: tags)
235432 var request = URLRequest(url: postsPublishURL)
236433
@@ -244,17 +441,30 @@ class Model{
244441 (response) in
245442
246443 isValidTocken?(response.response?.statusCode ?? 498)
444+
445+ if let code = response.response?.statusCode, code == 498 {
446+ return
447+ }
448+
449+ if let code = response.response?.statusCode,
450+ let result = ResultR(rawValue: code)
451+ {
452+ completion(result)
453+ } else {
454+ completion(.internalServerError)
455+ }
247456 }
248457 }
249458
459+// static func requestForPosts(limit: Int = 10, ){
460+//
461+// }
250462
251− static func getPostsForUser(with id: Int, completion: @escaping (([Posts])->())){
252− let json = [
253− "request" : id,
254− "limit": 10
255− ]
463+ static func getPostsForUser(for limit: Int = 10, from pointInTime: Int? = nil, with id: Int, completion: @escaping (([Posts])->()))
464+ {
465+ let postsRequest = GeneralRequest<Int>(limit: limit, exclusiveFrom: pointInTime, request: id)
256466
257− let jsonData = try? JSONSerialization.data(withJSONObject: json)
467+ let jsonData = try? JSONEncoder().encode(postsRequest)
258468
259469 var request = URLRequest(url: postsForUserURL)
260470 request.httpMethod = "POST"
@@ -268,11 +478,15 @@ class Model{
268478
269479 isValidTocken?(response.response?.statusCode ?? 498)
270480
481+ if let code = response.response?.statusCode, code == 498 {
482+ return
483+ }
484+
271485 guard let json = response.data else { return }
272486
273− guard let newPost = try? decoder.decode(QueryPosts.self, from: json) else { return }
487+ guard let newPost = try? decoder.decode(QueryPosts<Posts>.self, from: json) else { return }
274488
275− completion(newPost.posts)
489+ completion(newPost.response)
276490 }
277491 }
278492
@@ -280,7 +494,7 @@ class Model{
280494
281495 static func getUsers(for ids: [Int], completion: @escaping (([Int:Users])->())){
282496 let json = "\(Set(ids))"
283− print(json)
497+
284498 var request = URLRequest(url: usersURL)
285499
286500 request.httpMethod = "POST"
@@ -294,11 +508,16 @@ class Model{
294508
295509 isValidTocken?(response.response?.statusCode ?? 498)
296510
511+ if let code = response.response?.statusCode, code == 498 {
512+ return
513+ }
514+
297515 guard let json = response.data else { return }
298516
299517 guard let users = try? decoder.decode([Users].self, from: json) else { return }
300518
301519 var dict: [Int:Users] = [:]
520+
302521 users.forEach({ (user) in
303522 dict[user.id] = user
304523 })
@@ -308,13 +527,10 @@ class Model{
308527 }
309528
310529 // get channel (with id): in responce -- PostQuery
311− static func getChannel(with channelId: Int, completion: @escaping (((users:[Int: Users], posts:[Posts]))->())){
312− let json = [
313− "request" : channelId,
314− "limit": 10
315− ]
316−
317− let jsonData = try? JSONSerialization.data(withJSONObject: json)
530+ static func getChannel(with channelId: Int, on limit: Int = 10, from pointInTime: Int? = nil, completion: @escaping (((users:[Int: Users], posts:[Posts]))->()))
531+ {
532+ let postRequest = GeneralRequest<Int>(limit: limit, exclusiveFrom: pointInTime, request: channelId)
533+ let jsonData = try? JSONEncoder().encode(postRequest)
318534
319535 var request = URLRequest(url: channelsGetURL)
320536 request.httpMethod = "POST"
@@ -328,18 +544,21 @@ class Model{
328544
329545 isValidTocken?(response.response?.statusCode ?? 498)
330546
547+ if let code = response.response?.statusCode, code == 498 {
548+ return
549+ }
331550
332551 guard let json = response.data else { return }
333552
334− guard let newQueery = try? decoder.decode(QueryPosts.self, from: json) else { return }
553+ guard let newQueery = try? decoder.decode(QueryPosts<Posts>.self, from: json) else { return }
335554
336555 idUser = newQueery.users
337− completion((newQueery.users, newQueery.posts))
556+ completion((newQueery.users, newQueery.response))
338557 }
339558 }
340559
341560
342− static func createChannel(with channel: Channels) {
561+ static func createChannel(with channel: Channels, completion: @escaping ((ResultR)->())) {
343562
344563 var request = URLRequest(url: channelsCreateURL)
345564 request.httpMethod = "POST"
@@ -350,6 +569,13 @@ class Model{
350569 (response) in
351570
352571 isValidTocken?(response.response?.statusCode ?? 498)
572+
573+ if let code = response.response?.statusCode, let result = ResultR(rawValue: code) {
574+ completion(result)
575+ return
576+ }
577+
578+ completion(ResultR.internalServerError)
353579 }
354580
355581 }
@@ -362,6 +588,9 @@ class Model{
362588 AF.request(request).responseJSON { (response) in
363589
364590 isValidTocken?(response.response?.statusCode ?? 498)
591+ if let code = response.response?.statusCode, code == 498 {
592+ return
593+ }
365594
366595 guard let json = response.data else { return }
367596
@@ -380,6 +609,9 @@ class Model{
380609
381610 AF.request(request).response { (response) in
382611 isValidTocken?(response.response?.statusCode ?? 498)
612+ if let code = response.response?.statusCode, code == 498 {
613+ return
614+ }
383615 }
384616 completion()
385617 }
@@ -395,6 +627,9 @@ class Model{
395627 (response) in
396628
397629 isValidTocken?(response.response?.statusCode ?? 498)
630+ if let code = response.response?.statusCode, code == 498 {
631+ return
632+ }
398633 }
399634 }
400635
@@ -417,6 +652,9 @@ class Model{
417652 }
418653
419654 isValidTocken?(response.response?.statusCode ?? 498)
655+ if let code = response.response?.statusCode, code == 498 {
656+ return
657+ }
420658 }
421659 }
422660
@@ -479,24 +717,434 @@ class Model{
479717 }
480718 }
481719 */
482− static func getAnonymousChannel(by anonymousChannel: Model.AnonymousChannel, completion: @escaping (((users:[Int: Users], posts:[Posts]))->())){
720+ static func getAnonymousChannel(by anonymousChannel: Model.Channels, exclusiveFrom: Int? = nil, completion: @escaping (((users:[Int: Users], posts:[Posts]))->())){
483721
722+ let req = GeneralRequest<Model.Channels>(limit: 10, exclusiveFrom: exclusiveFrom, request: anonymousChannel)
723+
484724 var request = URLRequest(url: channelsGetAnonURL)
485725 request.httpMethod = "POST"
486− request.httpBody = try? JSONEncoder().encode(anonymousChannel)
726+ request.httpBody = try? JSONEncoder().encode(req)
487727
488728 request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
489729 AF.request(request).response {
490730 (response) in
491731
492732 isValidTocken?(response.response?.statusCode ?? 498)
733+ if let code = response.response?.statusCode, code == 498 {
734+ return
735+ }
736+
737+ guard let json = response.data else { return }
738+
739+ guard let newQueery = try? decoder.decode(QueryPosts<Posts>.self, from: json) else { return }
740+ completion((newQueery.users, newQueery.response))
741+ }
742+ }
743+
744+ static func getCompl(completion: @escaping ((CompletionTree)->())) {
745+
746+ AF.request(URLRequest(url: hashTagTreeURL)).responseJSON {
747+ (response) in
748+ isValidTocken?(response.response?.statusCode ?? 498)
749+ if let code = response.response?.statusCode, code == 498 {
750+ return
751+ }
752+ guard let json = response.data else { return }
753+ guard let tree = try? decoder.decode(CompletionTree.self, from: json) else { return }
754+ completion(tree)
755+ }
756+ }
757+
758+ static func getChatAll(limit: Int = 10, exclusiveFrom: Int? = nil, request: [Int] = [], completion: @escaping ((([Dialog],[Int:Model.Users]))->()))
759+ {
760+ let req = GeneralRequest<[Int]>(limit: limit, exclusiveFrom: exclusiveFrom, request: request)
761+ var request = URLRequest(url: chatsGetAllURL)
762+ request.httpMethod = "POST"
763+ request.httpBody = try? JSONEncoder().encode(req)
764+ request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
765+
766+ AF.request(request).responseJSON { response in
767+ isValidTocken?(response.response?.statusCode ?? 498)
768+
769+ if let code = response.response?.statusCode, code == 498 {
770+ return
771+ }
772+
773+ guard let json = response.data else { return }
774+ let dialogs = try! decoder.decode(QueryPosts<Dialog>.self, from: json)
775+
776+ completion((dialogs.response,dialogs.users))
777+ }
778+ }
779+
780+ enum Dialog: Codable
781+ {
782+ case groupChat(GroupChat)
783+ case userChat(UserChat)
784+
785+ private enum DialogKeys: CodingKey{
786+ case groupChat
787+ case userChat
788+ }
789+
790+ init(from decoder: Decoder) throws {
791+ let container = try decoder.container(keyedBy: DialogKeys.self)
792+ if container.contains(.groupChat){
793+ self = .groupChat(try GroupChat(from: container.superDecoder(forKey: .groupChat)))
794+ } else if container.contains(.userChat){
795+ self = .userChat(try UserChat(from: container.superDecoder(forKey: .userChat)))
796+ } else {
797+ throw DecodingError.keyNotFound(DialogKeys.groupChat, DecodingError.Context(codingPath: container.codingPath, debugDescription: "hz"))
798+ }
799+ }
800+
801+ func encode(to encoder: Encoder) throws {
802+ var container = encoder.container(keyedBy: DialogKeys.self)
803+ switch self{
804+ case .groupChat(let chat):
805+ try chat.encode(to: container.superEncoder(forKey: .groupChat))
806+ case .userChat(let chat):
807+ try chat.encode(to: container.superEncoder(forKey: .userChat))
808+ }
809+ }
810+ }
811+
812+ struct GroupChat: Codable {
813+ var group: Group
814+ var lastMessage: LastMessage?
815+
816+
817+ init(group: Group, lastMessage: LastMessage? = nil) {
818+ self.group = group
819+ self.lastMessage = lastMessage
820+ }
821+ }
822+
823+ struct UserChat: Codable {
824+ var user: Int
825+ var lastMessage: LastMessage?
826+
827+ init(user: Int, lastMessage: LastMessage? = nil) {
828+ self.user = user
829+ self.lastMessage = lastMessage
830+ }
831+ }
832+
833+ struct Group: Codable {
834+ var users: [Int: UserPermission]
835+ var name: String
836+ var id: Int
837+
838+ init(users: [Int: UserPermission] = [:], name: String = "", id: Int) {
839+ self.id = id
840+ self.users = users
841+ self.name = name
842+ }
843+ }
844+
845+ struct UserPermission: Codable {
846+ var isAdmin: Bool
847+ }
848+
849+ struct LastMessage: Codable {
850+ var body: Attachments
851+ var destination: MessageDestination
852+ var time: String
853+ var author: Int
854+ var id: Int
855+
856+ enum MessageCodingKeys: CodingKey{
857+ case user
858+ case group
859+ case body
860+ case time
861+ case author
862+ case id
863+ }
864+
865+ init(from decoder: Decoder) throws
866+ {
867+ let container = try decoder.container(keyedBy: MessageCodingKeys.self)
868+ time = try container.decode(String.self, forKey: .time)
869+ body = try container.decode(Attachments.self, forKey: .body)
870+ author = try container.decode(Int.self, forKey: .author)
871+ id = try container.decode(Int.self, forKey: .id)
872+
873+ if container.contains(.user){
874+ destination = MessageDestination.userChatDestination(try Int(from: container.superDecoder(forKey: .user)))
875+ } else if container.contains(.group){
876+ destination = MessageDestination.groupChatDestination(try Int(from: container.superDecoder(forKey: .group)))
877+ } else {
878+ throw DecodingError.keyNotFound(MessageCodingKeys.group, DecodingError.Context(codingPath: container.codingPath, debugDescription: "hz gr"))
879+ }
880+ }
881+
882+ func encode(to encoder: Encoder) throws {
883+ var container = encoder.container(keyedBy: MessageCodingKeys.self)
884+
885+ try container.encode(time, forKey: .time)
886+ try container.encode(id, forKey: .id)
887+ try container.encode(author, forKey: .author)
888+ try container.encode(body, forKey: .body)
889+
890+ switch destination {
891+ case .userChatDestination(let uId):
892+ try uId.encode(to: container.superEncoder(forKey: .user))
893+ case .groupChatDestination(let gId):
894+ try gId.encode(to: container.superEncoder(forKey: .group))
895+ }
896+ }
897+ }
898+
899+ enum MessageDestination: Codable
900+ {
901+ func encode(to encoder: Encoder) throws
902+ {
903+ var container = encoder.container(keyedBy: MessCodingKeys.self)
904+ switch self {
905+ case .userChatDestination(let uId):
906+ try uId.encode(to: container.superEncoder(forKey: .user))
907+ case .groupChatDestination(let gId):
908+ try gId.encode(to: container.superEncoder(forKey: .group))
909+ }
910+ }
911+
912+ init(from decoder: Decoder) throws
913+ {
914+ let container = try decoder.container(keyedBy: MessCodingKeys.self)
915+ if container.contains(.user){
916+ self = .userChatDestination(try Int(from: container.superDecoder(forKey: .user)))
917+ } else if container.contains(.group){
918+ self = .groupChatDestination(try Int(from: container.superDecoder(forKey: .group)))
919+ } else {
920+ throw DecodingError.keyNotFound(MessCodingKeys.group, DecodingError.Context(codingPath: container.codingPath, debugDescription: "hz gr"))
921+ }
922+ }
923+
924+ enum MessCodingKeys: CodingKey{
925+ case user
926+ case group
927+ }
928+
929+ case userChatDestination(Int)
930+ case groupChatDestination(Int)
931+
932+ }
933+
934+ static func createGroupChat(from group: Group, completion: @escaping ((Int)->())) {
935+ let req = group
936+ var request = URLRequest(url: createGroupChatURL)
937+ request.httpMethod = "POST"
938+ request.httpBody = try? JSONEncoder().encode(req)
939+ request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
940+
941+ AF.request(request).response { (response) in
942+
943+ isValidTocken?(response.response?.statusCode ?? 498)
944+ if let code = response.response?.statusCode, code == 498 {
945+ return
946+ }
947+
948+ guard let json = response.data else { return }
949+ let stringInt = String.init(data: json, encoding: String.Encoding.utf8)
950+ let intId = Int.init(stringInt!)
951+
952+ completion(intId!)
953+ }
954+ }
955+
956+ static func getMessagesFor(typeOfChat: Model.Dialog, chat id: Int, exclusiveFrom: Int? = nil, limit l: Int = 25, direction: String = "backward", completion: @escaping (([LastMessage])->()))
957+ {
958+ let req = GeneralRequest<Int>(direction: direction, limit: l, exclusiveFrom: exclusiveFrom, request: id)
959+ var request: URLRequest?
960+
961+ switch typeOfChat {
962+ case .groupChat:
963+ request = URLRequest(url: messagesGetGroupChatURL)
964+ case .userChat:
965+ request = URLRequest(url: messagesGetUserChatURL)
966+ }
967+
968+ request!.httpMethod = "POST"
969+ request!.httpBody = try? JSONEncoder().encode(req)
970+ request!.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
971+
972+ AF.request(request!).response { (response) in
973+
974+ isValidTocken?(response.response?.statusCode ?? 498)
975+ if let code = response.response?.statusCode, code == 498 {
976+ return
977+ }
493978
494979 guard let json = response.data else { return }
980+ guard let messages = try? decoder.decode([LastMessage].self, from: json) else { return }
981+
982+ completion(messages)
983+ }
984+ }
985+
986+ static func leaveGroupChat(id: Int, completion: @escaping (()->()))
987+ {
988+ var request = URLRequest(url: leaveGroupChatURL)
989+ request.httpMethod = "POST"
990+ request.httpBody = "\(id)".data(using: .utf8)
991+ request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
992+
993+ AF.request(request).response { (response) in
994+ isValidTocken?(response.response?.statusCode ?? 498)
495995
496− guard let newQueery = try? decoder.decode(QueryPosts.self, from: json) else { return }
996+ if let code = response.response?.statusCode, code == 498 {
997+ return
998+ }
999+ }
1000+
1001+ completion()
1002+ }
1003+
1004+ static func updateGroupChat(with group: Model.Group)
1005+ {
1006+ var request = URLRequest(url: updateGroupChatURL)
1007+ request.httpMethod = "POST"
1008+ request.httpBody = try? JSONEncoder().encode(group)
1009+ request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
1010+
1011+ AF.request(request).response { (response) in
1012+ isValidTocken?(response.response?.statusCode ?? 498)
1013+
1014+ if let code = response.response?.statusCode, code == 498 {
1015+ return
1016+ }
1017+ }
1018+
1019+ // completion()
1020+ }
1021+
1022+ struct SendMessage: Codable {
1023+ var body: Attachments
1024+ var destination: MessageDestination
1025+
1026+ init(from decoder: Decoder) throws
1027+ {
1028+ let container = try decoder.container(keyedBy: LastMessage.MessageCodingKeys.self)
1029+ body = try container.decode(Attachments.self, forKey: .body)
1030+
1031+ if container.contains(.user){
1032+ destination = MessageDestination.userChatDestination(try Int(from: container.superDecoder(forKey: .user)))
1033+ } else if container.contains(.group){
1034+ destination = MessageDestination.groupChatDestination(try Int(from: container.superDecoder(forKey: .group)))
1035+ } else {
1036+ throw DecodingError.keyNotFound(LastMessage.MessageCodingKeys.group, DecodingError.Context(codingPath: container.codingPath, debugDescription: "hz gr1"))
1037+ }
1038+ }
1039+
1040+ func encode(to encoder: Encoder) throws {
1041+ var container = encoder.container(keyedBy: LastMessage.MessageCodingKeys.self)
1042+ try container.encode(body, forKey: .body)
1043+
1044+ switch destination {
1045+ case .userChatDestination(let uId):
1046+ try uId.encode(to: container.superEncoder(forKey: .user))
1047+ case .groupChatDestination(let gId):
1048+ try gId.encode(to: container.superEncoder(forKey: .group))
1049+ }
1050+ }
1051+
1052+ init(body: Attachments, destination: MessageDestination)
1053+ {
1054+ self.body = body
1055+ self.destination = destination
1056+ }
1057+ }
1058+
1059+ static func sendMessage(message: SendMessage, completion: @escaping ((ResultR)->())){
1060+
1061+ var request = URLRequest(url: messagesSendURL)
1062+ request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
1063+ request.httpMethod = "POST"
1064+ request.httpBody = try? JSONEncoder().encode(message)
1065+
1066+ AF.request(request).response { (response) in
1067+ isValidTocken?(response.response?.statusCode ?? 498)
1068+
1069+ if let code = response.response?.statusCode, code == 498 {
1070+ return
1071+ }
1072+
1073+ if let result = ResultR(rawValue: response.response!.statusCode){
1074+ completion(result)
1075+ } else {
1076+ completion(.internalServerError)
1077+ }
1078+ }
1079+ }
1080+
1081+ struct Faculty: Codable
1082+ {
1083+
1084+ var path: String
1085+ var url: String
1086+ var campusName: String
1087+ var campusCode: String
1088+ var name: String
1089+ var tags: [String]
1090+ var address: String
1091+
1092+ }
1093+
1094+ static func searchFaculty(string: String, completion: @escaping (([Model.Faculty])->()))
1095+ {
1096+ var request = URLRequest(url: facultySearchURL)
1097+ request.httpMethod = "POST"
1098+ request.httpBody = string.data(using: .utf8)
1099+ request.setValue("text/plain;charset=utf-8", forHTTPHeaderField: "Content-Type")
1100+
1101+ AF.request(request).response { (response) in
1102+ isValidTocken?(response.response?.statusCode ?? 498)
1103+
1104+ if let code = response.response?.statusCode, code == 498 {
1105+ return
1106+ }
1107+
1108+ guard let json = response.data else { return }
1109+ guard let faculties = try? decoder.decode([Faculty].self, from: json) else { return }
1110+
1111+ completion(faculties)
1112+ }
1113+ }
1114+
1115+ static func userUpdate(with newUser: NewRegistration, completion: @escaping ((Bool)->())) {
1116+ var request = URLRequest(url: usersUpdateURL)
1117+ request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
1118+ request.httpMethod = "POST"
1119+ request.httpBody = try? JSONEncoder().encode(newUser)
1120+
1121+ AF.request(request).response { (response) in
1122+ isValidTocken?(response.response?.statusCode ?? 498)
1123+
1124+ if let code = response.response?.statusCode, code == 498 {
1125+ return
1126+ }
1127+
1128+ if let code = response.response?.statusCode, code != 204{
1129+ completion (false)
1130+ } else {
1131+ completion(true)
1132+ }
1133+ }
1134+ }
1135+
1136+ static func deactivateAll(completion: @escaping (()->())){
1137+ var request = URLRequest(url: deactivateURL)
1138+ request.httpMethod = "DELETE"
1139+
1140+ AF.request(request).response { (responce) in
1141+ isValidTocken?(responce.response?.statusCode ?? 498)
1142+
1143+ if let code = responce.response?.statusCode, code == 498 {
1144+ return
1145+ }
4971146
498− // idUser = newQueery.users
499− completion((newQueery.users, newQueery.posts))
1147+ completion()
5001148 }
5011149 }
5021150}
GDproject/Simple model/Post.swift−26
@@ -9,31 +9,6 @@
99import Foundation
1010import UIKit
1111
12−struct Post {
13− var dataArray: [Media]
14− var fromUser: User
15− var atDate: String
16− var comments: [Comment] = []
17−
18− var hashtags: [String] = ["ФКН", "Подбельский", "НИУВШЭ", "Шершаков", "ПАД", "Интересное","Мемы","Забавное","Учеба","Наука"]
19−
20− init(dataArray: [Media], from: User, date: String, comments: [Comment] = [])
21− {
22− self.dataArray = dataArray
23− fromUser = from
24− atDate = date
25− self.comments = comments
26− }
27−
28− init(dataArray: [Media]) {
29− self.dataArray = dataArray
30− fromUser = User(name: "vbogomazova", id: 2, fullName: "Богомазова Вероника Львовна")
31− atDate = "23.03.19 в 23:33"
32− }
33−
34−}
35−
36−
3712enum Media{
3813 case text(String)
3914 case image([UIImage])
@@ -41,7 +16,6 @@ enum Media{
4116}
4217
4318struct Comment {
44− var from: User
4519 var atTime: String
4620 var withData: String
4721}
GDproject/Simple model/User.swiftdeleted−41
@@ -1,41 +0,0 @@
1−//
2−// User.swift
3−// NewsFeed
4−//
5−// Created by cstore on 12/01/2019.
6−// Copyright © 2019 drHSE. All rights reserved.
7−//
8−
9−import Foundation
10−import UIKit
11−
12−struct User {
13− //var photo: UIImage? = #imageLiteral(resourceName: "image")
14− var login: String
15− var id: Int
16− var fullName: String
17− var initials: (surname: String, name: String, optional: String?) = ("Богомазова","Вероника","Львовна")
18− var placeOfWork: String?
19− var faculty: String?
20− let posts: [Post] = []
21−
22−
23− init(name: String, id: Int, fullName: String) {
24− self.login = name
25− self.id = id
26− self.fullName = fullName
27− }
28−
29− init(surname: String, name: String, optional: String?, emailName: String, id: Int, place: String?, faculty: String?)
30− {
31− self.id = id
32− self.login = emailName
33− self.initials = (surname, name, optional)
34−
35− self.fullName = "\(surname) \(name) \(optional ?? "")"
36− self.placeOfWork = place
37− if let f = faculty {
38− self.faculty = f
39− }
40− }
41−}
GDproject/Supporting files/AppDelegate.swift+7
@@ -22,11 +22,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
2222 window?.makeKeyAndVisible()
2323
2424 // window?.rootViewController = UINavigationController()
25+ Model.getCompl { (complTree) in
26+ Model.hashTagTree = complTree
27+ }
2528
2629 appCoordinator = ApplicationCoordinator(window: window!)
2730 appCoordinator.start()
2831
2932 return true
3033 }
34+
35+ func relaunch(){
36+ appCoordinator.start()
37+ }
3138}
3239
GDproject/Supporting files/Assets.xcassets/chat.imageset/Contents.jsonnew file+21
@@ -0,0 +1,21 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "chat.pdf",
6+ "scale" : "1x"
7+ },
8+ {
9+ "idiom" : "universal",
10+ "scale" : "2x"
11+ },
12+ {
13+ "idiom" : "universal",
14+ "scale" : "3x"
15+ }
16+ ],
17+ "info" : {
18+ "version" : 1,
19+ "author" : "xcode"
20+ }
21+}
GDproject/Supporting files/Assets.xcassets/chat.imageset/chat.pdfnew file

Binary file not shown.