git.iko.soy/dashared/GDproject
on master help sign in
50 files changed+4883−963
since splitsnapshots
unifiedsplit
Cartfile+5−3 GDproject.xcodeproj/project.pbxproj+185−64 GDproject/Base.lproj/Main.storyboard+651−97 GDproject/Constants.swift+23 GDproject/Controller/ News and channels/AddToChannelVC.swift+132 GDproject/Controller/ News and channels/ChannelController.swift+27−9 GDproject/Controller/ News and channels/ChannelListController.swift+17−30 GDproject/Controller/ News and channels/ChannelViewController.swift+208 GDproject/Controller/ News and channels/ChannelsCoordinator.swift−55 GDproject/Controller/ News and channels/FullPostController.swift+3−19 GDproject/Controller/ News and channels/NewPostViewController.swift+59−35 GDproject/Controller/ News and channels/NewsController.swift+67−168 GDproject/Controller/ News and channels/NewsVC.swift+62−14 GDproject/Controller/ News and channels/PostViewCell.swift+15−6 GDproject/Controller/ News and channels/TaggsSelectionViewController.swift+106 GDproject/Controller/ News and channels/TagsSuggestionController.swift+52 GDproject/Controller/Coordinator tab bar?/TabbarCoordinator.swift−14 GDproject/Controller/Coordinators/ApplicationCoordinator.swift+69 GDproject/Controller/Coordinators/BaseCoordinator.swift+47 GDproject/Controller/Coordinators/ChannelsCoordinator.swift+139 GDproject/Controller/Coordinators/LogInCoordinator.swift+70 GDproject/Controller/Coordinators/MessagesCoordinator.swift+87 GDproject/Controller/Coordinators/ProfileCoordinator.swift+60 GDproject/Controller/Coordinators/TabbarController.swift+50 GDproject/Controller/Coordinators/TabbarCoordinator.swift+68 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/LogInCoordinator.swift−48 GDproject/Controller/Log In/LogInViewController.swift+92−90 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+144−78 GDproject/Controller/RepeatingTimer.swift−59 GDproject/DataStorage.swift+9−9 GDproject/Extentions.swift+47 GDproject/Simple model/CompletionTree.swift+44 GDproject/Simple model/Model.swift+774−44 GDproject/Simple model/Post.swift−26 GDproject/Simple model/User.swift−41 GDproject/Supporting files/AppDelegate.swift+13−42 GDproject/Supporting files/Assets.xcassets/chat.imageset/Contents.json+21 GDproject/Supporting files/Assets.xcassets/chat.imageset/chat.pdf
Cartfile+5−3
@@ -2,6 +2,8 @@ github "robb/Cartography"
22github "ivanbruel/MarkdownKit"
33github "roberthein/TinyConstraints"
44github "macteo/Marklight"
5−github "ElaWorkshop/TagListView"
6−github "Alamofire/Alamofire" "5.0.0-beta.2"
7−github "whitesmith/WSTagsField"
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+185−64
@@ -8,21 +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, ); }; };
18− 1253867D2223DC9F00CC5EA7 /* RepeatingTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1253867C2223DC9F00CC5EA7 /* RepeatingTimer.swift */; };
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 */; };
1921 125BD57D22171D2A008A3575 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 125BD57C22171D2A008A3575 /* ProfileViewController.swift */; };
2022 125BD57F22171D73008A3575 /* Extentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 125BD57E22171D73008A3575 /* Extentions.swift */; };
2123 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 */; };
2228 1288B5CE221F1158002BE6B1 /* DataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1288B5CD221F1158002BE6B1 /* DataStorage.swift */; };
2329 1291BE2D2221312D009D3F23 /* ChannelsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1291BE2C2221312C009D3F23 /* ChannelsCoordinator.swift */; };
2430 1291BE342221569B009D3F23 /* TabbarCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1291BE332221569B009D3F23 /* TabbarCoordinator.swift */; };
25− 1291BE3622218DBF009D3F23 /* LogInCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1291BE3522218DBF009D3F23 /* LogInCoordinator.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 */; };
36+ 12BA4B9B224101A400DF93D7 /* ApplicationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12BA4B9A224101A400DF93D7 /* ApplicationCoordinator.swift */; };
37+ 12BA4B9D224101E700DF93D7 /* BaseCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12BA4B9C224101E700DF93D7 /* BaseCoordinator.swift */; };
38+ 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, ); }; };
2641 12D7D133221C321600B35452 /* ChannelListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12D7D132221C321600B35452 /* ChannelListController.swift */; };
2742 12D7D135221C42B700B35452 /* ChannelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12D7D134221C42B700B35452 /* ChannelController.swift */; };
2843 12D7D137221D78E800B35452 /* Channel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12D7D136221D78E800B35452 /* Channel.swift */; };
@@ -36,39 +51,54 @@
3651 12E36DA1221424EA006FCDD7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 12E36D9F221424EA006FCDD7 /* Main.storyboard */; };
3752 12E36DA3221424ED006FCDD7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 12E36DA2221424ED006FCDD7 /* Assets.xcassets */; };
3853 12E36DA6221424ED006FCDD7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 12E36DA4221424ED006FCDD7 /* LaunchScreen.storyboard */; };
39− 12E36DAF22143D40006FCDD7 /* Cartography.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DAE22143D40006FCDD7 /* Cartography.framework */; };
40− 12E36DB322143D4E006FCDD7 /* Marklight.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DB222143D4E006FCDD7 /* Marklight.framework */; };
41− 12E36DB522143D54006FCDD7 /* TinyConstraints.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DB422143D54006FCDD7 /* TinyConstraints.framework */; };
4254 12E36DB72214400A006FCDD7 /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DB62214400A006FCDD7 /* Post.swift */; };
43− 12E36DB922144016006FCDD7 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DB822144016006FCDD7 /* User.swift */; };
44− 12E36DBD221441D5006FCDD7 /* TinyConstraints.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DB422143D54006FCDD7 /* TinyConstraints.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
45− 12E36DC0221441DB006FCDD7 /* Marklight.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DB222143D4E006FCDD7 /* Marklight.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
46− 12E36DC2221441E1006FCDD7 /* MarkdownKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DB022143D47006FCDD7 /* MarkdownKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
47− 12E36DC4221441EA006FCDD7 /* Cartography.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DAE22143D40006FCDD7 /* Cartography.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
4855 12E36DCA22144635006FCDD7 /* NewPostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DC922144635006FCDD7 /* NewPostViewController.swift */; };
4956 12E36DCC22144725006FCDD7 /* PostViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DCB22144725006FCDD7 /* PostViewCell.swift */; };
5057 12E36DCE22144756006FCDD7 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DCD22144756006FCDD7 /* Constants.swift */; };
5158 12E36DD122148122006FCDD7 /* FullPostController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DD022148122006FCDD7 /* FullPostController.swift */; };
5259 12E36DD522156559006FCDD7 /* MyStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DD422156559006FCDD7 /* MyStackView.swift */; };
53− 12E36DD8221594A9006FCDD7 /* TagListView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DD622159493006FCDD7 /* TagListView.framework */; };
54− 12E36DD9221594A9006FCDD7 /* TagListView.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DD622159493006FCDD7 /* TagListView.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
55− 12E36DDB221594B1006FCDD7 /* MarkdownKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DB022143D47006FCDD7 /* MarkdownKit.framework */; };
60+ 12F3D6B0224106F700A69214 /* TabbarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F3D6AF224106F700A69214 /* TabbarController.swift */; };
61+ 12F3D6B22241097B00A69214 /* ProfileCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F3D6B12241097B00A69214 /* ProfileCoordinator.swift */; };
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, ); }; };
5682/* End PBXBuildFile section */
5783
5884/* Begin PBXCopyFilesBuildPhase section */
59− 12E36DBE221441D5006FCDD7 /* Embed Frameworks */ = {
85+ 12FDAC85226B5E3700995B4E /* Embed Frameworks */ = {
6086 isa = PBXCopyFilesBuildPhase;
6187 buildActionMask = 2147483647;
6288 dstPath = "";
6389 dstSubfolderSpec = 10;
6490 files = (
65− 12E36DC0221441DB006FCDD7 /* Marklight.framework in Embed Frameworks */,
66− 124CC7072221C2C3009DF531 /* Alamofire.framework in Embed Frameworks */,
67− 12E36DC4221441EA006FCDD7 /* Cartography.framework in Embed Frameworks */,
68− 12E36DD9221594A9006FCDD7 /* TagListView.framework in Embed Frameworks */,
69− 12E36DBD221441D5006FCDD7 /* TinyConstraints.framework in Embed Frameworks */,
70− 123AD0E5223C1C4300326173 /* WSTagsField.framework in Embed Frameworks */,
71− 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 */,
72102 );
73103 name = "Embed Frameworks";
74104 runOnlyForDeploymentPostprocessing = 0;
@@ -77,19 +107,35 @@
77107
78108/* Begin PBXFileReference section */
79109 1210A0BA223940310080686D /* SimplifiedChannelsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplifiedChannelsList.swift; sourceTree = "<group>"; };
80− 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>"; };
81113 123E37A4221F1B3200F6E42A /* LogInViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogInViewController.swift; sourceTree = "<group>"; };
82114 123E37A6221F1DD700F6E42A /* MainController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainController.swift; sourceTree = "<group>"; };
83115 124CC7022221C00C009DF531 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = "<group>"; };
84− 124CC7042221C2BA009DF531 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = Carthage/Build/iOS/Alamofire.framework; sourceTree = "<group>"; };
85− 1253867C2223DC9F00CC5EA7 /* RepeatingTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepeatingTimer.swift; 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>"; };
86120 125BD57C22171D2A008A3575 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = "<group>"; };
87121 125BD57E22171D73008A3575 /* Extentions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extentions.swift; sourceTree = "<group>"; };
88122 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>"; };
89127 1288B5CD221F1158002BE6B1 /* DataStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStorage.swift; sourceTree = "<group>"; };
90128 1291BE2C2221312C009D3F23 /* ChannelsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelsCoordinator.swift; sourceTree = "<group>"; };
91129 1291BE332221569B009D3F23 /* TabbarCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabbarCoordinator.swift; sourceTree = "<group>"; };
92− 1291BE3522218DBF009D3F23 /* LogInCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogInCoordinator.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>"; };
135+ 12BA4B9A224101A400DF93D7 /* ApplicationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationCoordinator.swift; sourceTree = "<group>"; };
136+ 12BA4B9C224101E700DF93D7 /* BaseCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseCoordinator.swift; sourceTree = "<group>"; };
137+ 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>"; };
93139 12D7D132221C321600B35452 /* ChannelListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelListController.swift; sourceTree = "<group>"; };
94140 12D7D134221C42B700B35452 /* ChannelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelController.swift; sourceTree = "<group>"; };
95141 12D7D136221D78E800B35452 /* Channel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Channel.swift; sourceTree = "<group>"; };
@@ -105,18 +151,24 @@
105151 12E36DA2221424ED006FCDD7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
106152 12E36DA5221424ED006FCDD7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
107153 12E36DA7221424ED006FCDD7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
108− 12E36DAE22143D40006FCDD7 /* Cartography.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cartography.framework; path = Carthage/Build/iOS/Cartography.framework; sourceTree = "<group>"; };
109− 12E36DB022143D47006FCDD7 /* MarkdownKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MarkdownKit.framework; path = Carthage/Build/iOS/MarkdownKit.framework; sourceTree = "<group>"; };
110− 12E36DB222143D4E006FCDD7 /* Marklight.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Marklight.framework; path = Carthage/Build/iOS/Marklight.framework; sourceTree = "<group>"; };
111− 12E36DB422143D54006FCDD7 /* TinyConstraints.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TinyConstraints.framework; path = Carthage/Build/iOS/TinyConstraints.framework; sourceTree = "<group>"; };
112154 12E36DB62214400A006FCDD7 /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = "<group>"; };
113− 12E36DB822144016006FCDD7 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
114155 12E36DC922144635006FCDD7 /* NewPostViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewPostViewController.swift; sourceTree = "<group>"; };
115156 12E36DCB22144725006FCDD7 /* PostViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostViewCell.swift; sourceTree = "<group>"; };
116157 12E36DCD22144756006FCDD7 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
117158 12E36DD022148122006FCDD7 /* FullPostController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullPostController.swift; sourceTree = "<group>"; };
118159 12E36DD422156559006FCDD7 /* MyStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyStackView.swift; sourceTree = "<group>"; };
119− 12E36DD622159493006FCDD7 /* TagListView.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TagListView.framework; path = Carthage/Build/iOS/TagListView.framework; sourceTree = "<group>"; };
160+ 12F3D6AF224106F700A69214 /* TabbarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabbarController.swift; sourceTree = "<group>"; };
161+ 12F3D6B12241097B00A69214 /* ProfileCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileCoordinator.swift; 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>"; };
120172/* End PBXFileReference section */
121173
122174/* Begin PBXFrameworksBuildPhase section */
@@ -124,13 +176,17 @@
124176 isa = PBXFrameworksBuildPhase;
125177 buildActionMask = 2147483647;
126178 files = (
127− 12E36DB522143D54006FCDD7 /* TinyConstraints.framework in Frameworks */,
128− 124CC7052221C2BB009DF531 /* Alamofire.framework in Frameworks */,
129− 12E36DDB221594B1006FCDD7 /* MarkdownKit.framework in Frameworks */,
130− 12E36DD8221594A9006FCDD7 /* TagListView.framework in Frameworks */,
131− 12E36DAF22143D40006FCDD7 /* Cartography.framework in Frameworks */,
132− 123AD0E3223C1C3900326173 /* WSTagsField.framework in Frameworks */,
133− 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 */,
134190 );
135191 runOnlyForDeploymentPostprocessing = 0;
136192 };
@@ -150,33 +206,68 @@
150206 path = Profile;
151207 sourceTree = "<group>";
152208 };
153− 1291BE32222154C3009D3F23 /* Coordinator tab bar? */ = {
209+ 1291BE32222154C3009D3F23 /* Coordinators */ = {
154210 isa = PBXGroup;
155211 children = (
212+ 12BA4B9A224101A400DF93D7 /* ApplicationCoordinator.swift */,
213+ 129320062279B5690035C7B3 /* MessagesCoordinator.swift */,
214+ 12F3D6AF224106F700A69214 /* TabbarController.swift */,
215+ 12F3D6B12241097B00A69214 /* ProfileCoordinator.swift */,
216+ 12BA4B9E224102B700DF93D7 /* LogInCoordinator.swift */,
217+ 12BA4B9C224101E700DF93D7 /* BaseCoordinator.swift */,
218+ 1291BE2C2221312C009D3F23 /* ChannelsCoordinator.swift */,
156219 1291BE332221569B009D3F23 /* TabbarCoordinator.swift */,
157220 );
158− path = "Coordinator tab bar?";
221+ path = Coordinators;
159222 sourceTree = "<group>";
160223 };
161224 1291BE3722218DC4009D3F23 /* Log In */ = {
162225 isa = PBXGroup;
163226 children = (
227+ 125A9A912286100800513E2A /* FacultyTableViewController.swift */,
228+ 125A9A8F228609C500513E2A /* InititalsViewCell.swift */,
229+ 125A9A8D2286068A00513E2A /* RegisterTableViewController.swift */,
230+ 125A9A892285C3DA00513E2A /* CodeViewController.swift */,
164231 123E37A4221F1B3200F6E42A /* LogInViewController.swift */,
165− 1291BE3522218DBF009D3F23 /* LogInCoordinator.swift */,
166232 );
167233 path = "Log In";
168234 sourceTree = "<group>";
169235 };
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+ };
170257 12E36D8F221424EA006FCDD7 = {
171258 isa = PBXGroup;
172259 children = (
173− 123AD0E2223C1C3900326173 /* WSTagsField.framework */,
174− 124CC7042221C2BA009DF531 /* Alamofire.framework */,
175− 12E36DD622159493006FCDD7 /* TagListView.framework */,
176− 12E36DB422143D54006FCDD7 /* TinyConstraints.framework */,
177− 12E36DB222143D4E006FCDD7 /* Marklight.framework */,
178− 12E36DB022143D47006FCDD7 /* MarkdownKit.framework */,
179− 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 */,
180271 12E36D9A221424EA006FCDD7 /* GDproject */,
181272 12E36D99221424EA006FCDD7 /* Products */,
182273 12E36DDA221594B1006FCDD7 /* Frameworks */,
@@ -212,9 +303,9 @@
212303 isa = PBXGroup;
213304 children = (
214305 124CC7022221C00C009DF531 /* Model.swift */,
306+ 1293200C2279D7310035C7B3 /* CompletionTree.swift */,
215307 12E36DB62214400A006FCDD7 /* Post.swift */,
216308 12D7D136221D78E800B35452 /* Channel.swift */,
217− 12E36DB822144016006FCDD7 /* User.swift */,
218309 );
219310 path = "Simple model";
220311 sourceTree = "<group>";
@@ -232,8 +323,8 @@
232323 12E36DCF22145923006FCDD7 /* Controller */ = {
233324 isa = PBXGroup;
234325 children = (
235− 1253867C2223DC9F00CC5EA7 /* RepeatingTimer.swift */,
236− 1291BE32222154C3009D3F23 /* Coordinator tab bar? */,
326+ 1291BE32222154C3009D3F23 /* Coordinators */,
327+ 129320052279B4300035C7B3 /* Messages */,
237328 1291BE3722218DC4009D3F23 /* Log In */,
238329 1288B5CC221F0EFE002BE6B1 /* Profile */,
239330 12E36DD322156519006FCDD7 /* News and channels */,
@@ -244,13 +335,16 @@
244335 12E36DD322156519006FCDD7 /* News and channels */ = {
245336 isa = PBXGroup;
246337 children = (
338+ 1261BB92227B364C003898CF /* ChannelViewController.swift */,
339+ 1220FF93227C41270092C6BC /* TaggsSelectionViewController.swift */,
340+ 1261BB94227B3991003898CF /* AddToChannelVC.swift */,
247341 12D7D134221C42B700B35452 /* ChannelController.swift */,
342+ 121A8971226B1EAC005EE599 /* TagsSuggestionController.swift */,
248343 12D7D132221C321600B35452 /* ChannelListController.swift */,
249344 1210A0BA223940310080686D /* SimplifiedChannelsList.swift */,
250345 12E36DC922144635006FCDD7 /* NewPostViewController.swift */,
251346 12E36DD022148122006FCDD7 /* FullPostController.swift */,
252347 12E36D9D221424EA006FCDD7 /* NewsController.swift */,
253− 1291BE2C2221312C009D3F23 /* ChannelsCoordinator.swift */,
254348 125BD5802217314A008A3575 /* NewsVC.swift */,
255349 12E36DCB22144725006FCDD7 /* PostViewCell.swift */,
256350 );
@@ -274,7 +368,7 @@
274368 12E36D94221424EA006FCDD7 /* Sources */,
275369 12E36D95221424EA006FCDD7 /* Frameworks */,
276370 12E36D96221424EA006FCDD7 /* Resources */,
277− 12E36DBE221441D5006FCDD7 /* Embed Frameworks */,
371+ 12FDAC85226B5E3700995B4E /* Embed Frameworks */,
278372 );
279373 buildRules = (
280374 );
@@ -297,6 +391,11 @@
297391 TargetAttributes = {
298392 12E36D97221424EA006FCDD7 = {
299393 CreatedOnToolsVersion = 10.1;
394+ SystemCapabilities = {
395+ com.apple.Keychain = {
396+ enabled = 0;
397+ };
398+ };
300399 };
301400 };
302401 };
@@ -336,31 +435,49 @@
336435 isa = PBXSourcesBuildPhase;
337436 buildActionMask = 2147483647;
338437 files = (
438+ 129320072279B5690035C7B3 /* MessagesCoordinator.swift in Sources */,
339439 124CC7032221C00C009DF531 /* Model.swift in Sources */,
440+ 125A9A90228609C500513E2A /* InititalsViewCell.swift in Sources */,
441+ 1220FF94227C41270092C6BC /* TaggsSelectionViewController.swift in Sources */,
442+ 12F3D6B0224106F700A69214 /* TabbarController.swift in Sources */,
340443 12E36DD522156559006FCDD7 /* MyStackView.swift in Sources */,
341444 12D7D137221D78E800B35452 /* Channel.swift in Sources */,
342− 1291BE3622218DBF009D3F23 /* LogInCoordinator.swift in Sources */,
343− 12E36DB922144016006FCDD7 /* User.swift in Sources */,
344445 1210A0BB223940310080686D /* SimplifiedChannelsList.swift in Sources */,
345446 125BD5812217314A008A3575 /* NewsVC.swift in Sources */,
346447 12E36DB72214400A006FCDD7 /* Post.swift in Sources */,
347448 12DB7FDF221877160096878E /* InviteViewController.swift in Sources */,
449+ 121A8972226B1EAC005EE599 /* TagsSuggestionController.swift in Sources */,
450+ 1293200D2279D7310035C7B3 /* CompletionTree.swift in Sources */,
348451 12E36DCA22144635006FCDD7 /* NewPostViewController.swift in Sources */,
452+ 1261BB9E227B793D003898CF /* ChatInfoViewController.swift in Sources */,
349453 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 */,
350458 12E36DCC22144725006FCDD7 /* PostViewCell.swift in Sources */,
351459 123E37A5221F1B3200F6E42A /* LogInViewController.swift in Sources */,
352460 125BD57F22171D73008A3575 /* Extentions.swift in Sources */,
353461 12D7D133221C321600B35452 /* ChannelListController.swift in Sources */,
462+ 125A9A8A2285C3DA00513E2A /* CodeViewController.swift in Sources */,
354463 1291BE342221569B009D3F23 /* TabbarCoordinator.swift in Sources */,
464+ 129320092279C3B50035C7B3 /* PeopleToWriteViewController.swift in Sources */,
465+ 12BA4B9B224101A400DF93D7 /* ApplicationCoordinator.swift in Sources */,
355466 1291BE2D2221312D009D3F23 /* ChannelsCoordinator.swift in Sources */,
356467 12E36DD122148122006FCDD7 /* FullPostController.swift in Sources */,
468+ 129320042279B4270035C7B3 /* MessagesViewController.swift in Sources */,
469+ 12758257227F1401001F291F /* DialogViewController.swift in Sources */,
357470 1288B5CE221F1158002BE6B1 /* DataStorage.swift in Sources */,
471+ 12BA4B9F224102B700DF93D7 /* LogInCoordinator.swift in Sources */,
472+ 12F3D6B22241097B00A69214 /* ProfileCoordinator.swift in Sources */,
358473 125BD57D22171D2A008A3575 /* ProfileViewController.swift in Sources */,
359− 1253867D2223DC9F00CC5EA7 /* RepeatingTimer.swift in Sources */,
474+ 1261BB95227B3991003898CF /* AddToChannelVC.swift in Sources */,
360475 12DB7FDB2218590C0096878E /* InfoCell.swift in Sources */,
361476 12DB7FD922181E660096878E /* BasicInfoCell.swift in Sources */,
477+ 1261BB93227B364C003898CF /* ChannelViewController.swift in Sources */,
362478 12D7D135221C42B700B35452 /* ChannelController.swift in Sources */,
363479 12E36D9C221424EA006FCDD7 /* AppDelegate.swift in Sources */,
480+ 12BA4B9D224101E700DF93D7 /* BaseCoordinator.swift in Sources */,
364481 12DB7FD722181CD20096878E /* BasicInfoController.swift in Sources */,
365482 12E36DCE22144756006FCDD7 /* Constants.swift in Sources */,
366483 12DB7FDD221872F80096878E /* SettingsViewController.swift in Sources */,
@@ -510,6 +627,7 @@
510627 isa = XCBuildConfiguration;
511628 buildSettings = {
512629 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
630+ CODE_SIGN_IDENTITY = "iPhone Developer";
513631 CODE_SIGN_STYLE = Automatic;
514632 DEVELOPMENT_TEAM = 9Z6LFGKA9G;
515633 FRAMEWORK_SEARCH_PATHS = (
@@ -521,9 +639,10 @@
521639 "$(inherited)",
522640 "@executable_path/Frameworks",
523641 );
524− PRODUCT_BUNDLE_IDENTIFIER = rednikina.com.drHSE.GDproject;
642+ PRODUCT_BUNDLE_IDENTIFIER = rednikina.com.drHSE.GDproject.sos;
525643 PRODUCT_NAME = "$(TARGET_NAME)";
526− SWIFT_VERSION = 4.2;
644+ PROVISIONING_PROFILE_SPECIFIER = "";
645+ SWIFT_VERSION = 5.0;
527646 TARGETED_DEVICE_FAMILY = "1,2";
528647 };
529648 name = Debug;
@@ -532,6 +651,7 @@
532651 isa = XCBuildConfiguration;
533652 buildSettings = {
534653 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
654+ CODE_SIGN_IDENTITY = "iPhone Developer";
535655 CODE_SIGN_STYLE = Automatic;
536656 DEVELOPMENT_TEAM = 9Z6LFGKA9G;
537657 FRAMEWORK_SEARCH_PATHS = (
@@ -543,9 +663,10 @@
543663 "$(inherited)",
544664 "@executable_path/Frameworks",
545665 );
546− PRODUCT_BUNDLE_IDENTIFIER = rednikina.com.drHSE.GDproject;
666+ PRODUCT_BUNDLE_IDENTIFIER = rednikina.com.drHSE.GDproject.sos;
547667 PRODUCT_NAME = "$(TARGET_NAME)";
548− SWIFT_VERSION = 4.2;
668+ PROVISIONING_PROFILE_SPECIFIER = "";
669+ SWIFT_VERSION = 5.0;
549670 TARGETED_DEVICE_FAMILY = "1,2";
550671 };
551672 name = Release;
GDproject/Base.lproj/Main.storyboard+651−97
@@ -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" initialViewController="609-2j-gc9">
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>
@@ -20,7 +34,7 @@
2034 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
2135 <subviews>
2236 <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="cay-ea-rTl">
23− <rect key="frame" x="0.0" y="116" width="375" height="502"/>
37+ <rect key="frame" x="0.0" y="20" width="375" height="647"/>
2438 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
2539 <view key="tableHeaderView" contentMode="scaleToFill" id="kFZ-3c-OFY">
2640 <rect key="frame" x="0.0" y="0.0" width="375" height="199"/>
@@ -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,54 +679,352 @@
416679 </objects>
417680 <point key="canvasLocation" x="3444" y="129.98500749625188"/>
418681 </scene>
419− <!--Log In View Controller-->
420− <scene sceneID="Pkd-pB-bQs">
682+ <!--Taggs Selection View Controller-->
683+ <scene sceneID="74G-Lc-cqT">
421684 <objects>
422− <viewController storyboardIdentifier="LogInController" id="ZCz-70-hDh" customClass="LogInViewController" customModule="GDproject" customModuleProvider="target" sceneMemberID="viewController">
423− <view key="view" contentMode="scaleToFill" id="1q2-4g-KZs">
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− <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="12" translatesAutoresizingMaskIntoConstraints="NO" id="UwC-m5-jA5">
428− <rect key="frame" x="16" y="100" width="343" height="83"/>
429− <subviews>
430− <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Log In" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="efn-ze-jca">
431− <rect key="frame" x="0.0" y="0.0" width="343" height="41"/>
432− <fontDescription key="fontDescription" type="boldSystem" pointSize="34"/>
433− <nil key="textColor"/>
434− <nil key="highlightedColor"/>
435− </label>
436− <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Mail" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="mFP-WG-jY5">
437− <rect key="frame" x="0.0" y="53" width="343" height="30"/>
438− <color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
439− <nil key="textColor"/>
440− <fontDescription key="fontDescription" type="system" pointSize="14"/>
441− <textInputTraits key="textInputTraits"/>
442− </textField>
443− </subviews>
444− </stackView>
445− <activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" style="gray" translatesAutoresizingMaskIntoConstraints="NO" id="K3b-gv-JPp">
446− <rect key="frame" x="177.5" y="333.5" width="20" height="20"/>
447− </activityIndicatorView>
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>
695+ </containerView>
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>
701+ </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>
448738 </subviews>
449739 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
450740 <constraints>
451− <constraint firstItem="UwC-m5-jA5" firstAttribute="leading" secondItem="LOR-Yw-0FU" secondAttribute="leading" constant="16" id="M6z-nU-H40"/>
452− <constraint firstItem="K3b-gv-JPp" firstAttribute="centerY" secondItem="LOR-Yw-0FU" secondAttribute="centerY" id="eOp-yQ-n28"/>
453− <constraint firstItem="K3b-gv-JPp" firstAttribute="centerX" secondItem="LOR-Yw-0FU" secondAttribute="centerX" id="gdW-VU-pE8"/>
454− <constraint firstItem="LOR-Yw-0FU" firstAttribute="trailing" secondItem="UwC-m5-jA5" secondAttribute="trailing" constant="16" id="jST-rr-MMX"/>
455− <constraint firstItem="UwC-m5-jA5" firstAttribute="top" secondItem="LOR-Yw-0FU" secondAttribute="top" constant="80" id="rvy-Ii-bwS"/>
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"/>
456759 </constraints>
457− <viewLayoutGuide key="safeArea" id="LOR-Yw-0FU"/>
760+ <viewLayoutGuide key="safeArea" id="Oc2-oW-bST"/>
458761 </view>
459762 <connections>
460− <outlet property="indicatorView" destination="K3b-gv-JPp" id="mSR-QU-VZQ"/>
461− <outlet property="mailTextField" destination="mFP-WG-jY5" id="7SN-qe-DDm"/>
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"/>
462766 </connections>
463767 </viewController>
464− <placeholder placeholderIdentifier="IBFirstResponder" id="6e8-oo-bbc" userLabel="First Responder" sceneMemberID="firstResponder"/>
768+ <placeholder placeholderIdentifier="IBFirstResponder" id="eoH-1e-A6A" userLabel="First Responder" sceneMemberID="firstResponder"/>
465769 </objects>
466− <point key="canvasLocation" x="-466" y="-821"/>
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"/>
4671028 </scene>
4681029 <!--Posts-->
4691030 <scene sceneID="RSd-UJ-34B">
@@ -476,33 +1037,10 @@
4761037 <autoresizingMask key="autoresizingMask"/>
4771038 </navigationBar>
4781039 <nil name="viewControllers"/>
479− <connections>
480− <segue destination="Nqc-TI-ueQ" kind="relationship" relationship="rootViewController" id="2BZ-i4-uIE"/>
481− </connections>
4821040 </navigationController>
4831041 <placeholder placeholderIdentifier="IBFirstResponder" id="POV-5T-jeQ" userLabel="First Responder" sceneMemberID="firstResponder"/>
4841042 </objects>
485− <point key="canvasLocation" x="1088.8" y="132.68365817091455"/>
486− </scene>
487− <!--News Controller-->
488− <scene sceneID="eR7-97-LRL">
489− <objects>
490− <tableViewController storyboardIdentifier="NewsController" id="Nqc-TI-ueQ" customClass="NewsController" customModule="GDproject" customModuleProvider="target" sceneMemberID="viewController">
491− <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="hwA-SE-xpS">
492− <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
493− <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
494− <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
495− <color key="sectionIndexBackgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
496− <connections>
497− <outlet property="dataSource" destination="Nqc-TI-ueQ" id="bu5-tQ-7lb"/>
498− <outlet property="delegate" destination="Nqc-TI-ueQ" id="kTM-Be-8qb"/>
499− </connections>
500− </tableView>
501− <navigationItem key="navigationItem" largeTitleDisplayMode="always" id="b8E-6f-hLM"/>
502− </tableViewController>
503− <placeholder placeholderIdentifier="IBFirstResponder" id="c9d-Kh-itI" userLabel="First Responder" sceneMemberID="firstResponder"/>
504− </objects>
505− <point key="canvasLocation" x="1880" y="136"/>
1043+ <point key="canvasLocation" x="1089" y="-306"/>
5061044 </scene>
5071045 <!--Profile-->
5081046 <scene sceneID="dJj-Hp-Wa9">
@@ -515,31 +1053,15 @@
5151053 <autoresizingMask key="autoresizingMask"/>
5161054 </navigationBar>
5171055 <nil name="viewControllers"/>
518− <connections>
519− <segue destination="R4D-Ao-uvG" kind="relationship" relationship="rootViewController" id="aF6-zK-hCB"/>
520− </connections>
5211056 </navigationController>
5221057 <placeholder placeholderIdentifier="IBFirstResponder" id="Fvm-qf-2Ve" userLabel="First Responder" sceneMemberID="firstResponder"/>
5231058 </objects>
524− <point key="canvasLocation" x="1089" y="958"/>
1059+ <point key="canvasLocation" x="1015" y="1216"/>
5251060 </scene>
526− <!--Navigation Controller-->
527− <scene sceneID="r0M-xH-UwU">
528− <objects>
529− <navigationController storyboardIdentifier="root" id="609-2j-gc9" sceneMemberID="viewController">
530− <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="Ah8-NU-AsD">
531− <rect key="frame" x="0.0" y="20" width="375" height="44"/>
532− <autoresizingMask key="autoresizingMask"/>
533− </navigationBar>
534− </navigationController>
535− <placeholder placeholderIdentifier="IBFirstResponder" id="Raj-78-jlT" userLabel="First Responder" sceneMemberID="firstResponder"/>
536− </objects>
537− <point key="canvasLocation" x="-2242" y="68"/>
538− </scene>
539− <!--Tab Bar Controller-->
1061+ <!--Tabbar Controller-->
5401062 <scene sceneID="HCR-WO-p7g">
5411063 <objects>
542− <tabBarController storyboardIdentifier="TabBar" automaticallyAdjustsScrollViewInsets="NO" id="IeP-4W-8LT" sceneMemberID="viewController">
1064+ <tabBarController storyboardIdentifier="TabbarController" automaticallyAdjustsScrollViewInsets="NO" id="IeP-4W-8LT" customClass="TabbarController" customModule="GDproject" customModuleProvider="target" sceneMemberID="viewController">
5431065 <toolbarItems/>
5441066 <tabBar key="tabBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="3pl-mU-U30">
5451067 <rect key="frame" x="0.0" y="0.0" width="1000" height="1000"/>
@@ -548,6 +1070,7 @@
5481070 </tabBar>
5491071 <connections>
5501072 <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"/>
5511074 <segue destination="tae-5q-wvT" kind="relationship" relationship="viewControllers" id="wCJ-4N-lfC"/>
5521075 </connections>
5531076 </tabBarController>
@@ -555,9 +1078,40 @@
5551078 </objects>
5561079 <point key="canvasLocation" x="-466" y="641"/>
5571080 </scene>
1081+ <!--News Controller-->
1082+ <scene sceneID="kf1-SJ-kcp">
1083+ <objects>
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"/>
1087+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
1088+ <subviews>
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"/>
1091+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
1092+ </tableView>
1093+ </subviews>
1094+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
1095+ <constraints>
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"/>
1100+ </constraints>
1101+ <viewLayoutGuide key="safeArea" id="qGo-Sq-Oz2"/>
1102+ </view>
1103+ <connections>
1104+ <outlet property="tableView" destination="5IP-vK-phq" id="EQe-eC-W7P"/>
1105+ </connections>
1106+ </viewController>
1107+ <placeholder placeholderIdentifier="IBFirstResponder" id="6Lk-UL-mQL" userLabel="First Responder" sceneMemberID="firstResponder"/>
1108+ </objects>
1109+ <point key="canvasLocation" x="5426" y="-235"/>
1110+ </scene>
5581111 </scenes>
5591112 <resources>
5601113 <image name="1" width="30" height="30"/>
5611114 <image name="5" width="30" height="30"/>
1115+ <image name="chat" width="30" height="30"/>
5621116 </resources>
5631117</document>
GDproject/Constants.swift+23
@@ -6,6 +6,7 @@
66// Copyright © 2019 drHSE. All rights reserved.
77//
88
9+import UIKit
910/// constant for cell in posts NewsController.swift
1011let postCellId = "PostCell"
1112
@@ -42,7 +43,29 @@ let newsController = "NewsController"
4243/// constant for switching to view controller for log in app
4344let logInController = "LogInController"
4445
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"
4553
4654let profileViewController = "ProfileViewController"
4755
4856let simplifiedChannelsList = "SimplifiedChannelsList"
57+
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+
70+let blueSystemColor = UIColor(red: 0, green: 137/255, blue: 249/255, alpha: 0.5)
71+
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+17−30
@@ -13,20 +13,24 @@ 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
17−class ChannelListController: UITableViewController, DataDelegate {
21+class 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]()
2429
25− var myProtocol: DataDelegate?
2630 var displayingChannel: Model.Channels?
2731
2832 var toReload: Bool = false {
29− didSet{
33+ didSet {
3034 tableView.reloadData()
3135 }
3236 }
@@ -80,7 +84,7 @@ class ChannelListController: UITableViewController, DataDelegate {
8084 askForUpdates()
8185 }
8286
83− private func askForUpdates(){
87+ func askForUpdates(){
8488 Model.channelsList { [weak self] (channels) in
8589 self?.dataSource = [ChannelListController.generalChannel] + channels
8690 self?.toReload = true
@@ -89,17 +93,14 @@ class ChannelListController: UITableViewController, DataDelegate {
8993
9094 override func viewWillAppear(_ animated: Bool) {
9195 super.viewWillAppear(true)
92− askForUpdates()
96+ //askForUpdates()
97+ tabBarController?.tabBar.isHidden = false
9398 }
9499
95100 @objc func addChannel()
96101 {
97102 // editing mode is on automatically
98− let vc = storyboard?.instantiateViewController(withIdentifier: channelControllerId) as! ChannelController
99− vc.index = 1
100− vc.myProtocol = self
101− vc.channel = Model.Channels(people: [], name: "Untitled", tags: [])
102− navigationController?.pushViewController(vc, animated: true)
103+ onEditingModeBegins?(Model.Channels(people: [], name: "Untitled", id: 0, tags: []),IndexPath(row: 1, section: 0))
103104 }
104105
105106 static let generalChannel = Model.Channels(people: [], name: "General", id: -1, tags: [])
@@ -143,27 +144,18 @@ class ChannelListController: UITableViewController, DataDelegate {
143144
144145 override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]?
145146 {
146−
147− let editButton = UITableViewRowAction(style: .normal, title: "Edit") { [weak self] (action, indexPath) in
148− let vc = self?.storyboard?.instantiateViewController(withIdentifier: channelControllerId) as! ChannelController
149− vc.index = indexPath.row
150− vc.myProtocol = self!
151− vc.channel = self?.dataSource[indexPath.row]
152− 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)
153149 }
154− editButton.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
155150
151+ editButton.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
156152
157153 let deleteButton = UITableViewRowAction(style: .normal, title: "Delete") { [weak self] (action, indexPath) in
158154
159155 Model.channelsDelete(by: self!.dataSource[indexPath.row].id!, completion: {
160156 print("хз что тут делать при успехе")
161157 })
162−
163− if (self!.dataSource[indexPath.row].id == self!.displayingChannel?.id ?? -1)
164− {
165− self?.myProtocol?.passData(for: 0, channel: self!.dataSource[0])
166− }
158+
167159 self?.tableView.beginUpdates()
168160 self?.dataSource.remove(at: indexPath.row)
169161 self?.tableView.deleteRows(at: [indexPath], with: .none)
@@ -175,17 +167,12 @@ class ChannelListController: UITableViewController, DataDelegate {
175167 return [editButton, deleteButton]
176168 }
177169
178− // TODO: remove popping
179170 override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
180171 {
181172 if isFiltering {
182− //onChannelSelected?(filteredDataSource[indexPath.row])
183− myProtocol?.passData(for: 0, channel: filteredDataSource[indexPath.row])
184− navigationController?.popViewController(animated: true)
173+ onChannelSelected?(filteredDataSource[indexPath.row])
185174 } else {
186− myProtocol?.passData(for: 0, channel: dataSource[indexPath.row])
187− navigationController?.popViewController(animated: true)
188− //onChannelSelected?(ChannelListController.dataSource[indexPath.row])
175+ onChannelSelected?(dataSource[indexPath.row])
189176 }
190177 }
191178}
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/ChannelsCoordinator.swiftdeleted−55
@@ -1,55 +0,0 @@
1−//
2−// ChannelsCoordinator.swift
3−// GDproject
4−//
5−// Created by cstore on 23/02/2019.
6−// Copyright © 2019 drHSE. All rights reserved.
7−//
8−
9−import Foundation
10−import UIKit
11−
12−final class ChannelsCoordinator{
13−
14− // MARK: - Properties
15− private var channel: Model.Channels { didSet { updateInterfaces() } }
16− private weak var navigationController: UINavigationController?
17−
18− // MARK:- Init
19− init(currentChannel: Model.Channels, navigationController: UINavigationController) {
20− self.channel = currentChannel
21− self.navigationController = navigationController
22− }
23−
24− func start(){
25− showCurrentChannel()
26− }
27−
28− // MARK: - Private implementation
29− private func showListOfChannels(){
30− let controller = UIStoryboard.makeChannelsListController()
31− controller.onChannelSelected = { [weak self] channel
32− in
33− self?.channel = channel
34− _ = self?.navigationController?.popViewController(animated: true)
35− }
36− navigationController?.pushViewController(controller, animated: true)
37− }
38−
39− private func showCurrentChannel(){
40− let controller = UIStoryboard.makeNewsController()
41− controller.channel = channel
42− controller.onSelectChannel = { [weak self] in
43− self?.showListOfChannels()
44− }
45− navigationController?.setViewControllers([controller], animated: false)
46− //navigationController?.pushViewController(controller, animated: false)
47− }
48−
49− // MARK:- update only viewControllers which are depandable on chosen channel
50− private func updateInterfaces() {
51− navigationController?.viewControllers.forEach {
52− ($0 as? UpdateableWithChannel)?.channel = channel
53− }
54− }
55−}
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+59−35
@@ -10,23 +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()
28+
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+ }
2038
21− fileprivate let tagsField = WSTagsField()
39+ // fileprivate let tagsField = WSTagsField()
2240 // Keep strong instance of the `NSTextStorage` subclass
2341 let textStorage = MarklightTextStorage()
2442
25− weak var parentVC: NewsController?
26− var myProtocol: NewPostDelegate?
27−
2843 static var draft: String = ""
29− static var hashTagsDraft: [String] = []
44+ static var hashTagsDraft: [String] = [ ]
3045
3146 var textView: UITextView!
3247
@@ -57,11 +72,11 @@ class NewPostViewController: UIViewController, UITextViewDelegate {
5772
5873 setUpMD()
5974 setUpTextView()
60− setUpAccessoryView()
61− setUpTagsView()
75+ // setUpAccessoryView()
76+ //setUpTagsView()
6277 }
6378
64− func setUpTagsView(){
79+ /* func setUpTagsView(){
6580 tagsField.frame = viewForTags.bounds
6681 viewForTags.addSubview(tagsField)
6782
@@ -89,15 +104,16 @@ class NewPostViewController: UIViewController, UITextViewDelegate {
89104 tagsField.acceptTagOption = .space
90105
91106 textFieldEvents()
92− }
107+ }*/
93108
94109 override func viewWillAppear(_ animated: Bool) {
95110 super.viewWillAppear(animated)
96111
97− navigationController?.navigationBar.prefersLargeTitles = false
112+ //navigationController?.navigationBar.prefersLargeTitles = false
113+ navigationItem.largeTitleDisplayMode = .never
98114 navigationItem.title = "New post"
99115 textView.text = NewPostViewController.draft
100− tagsField.addTags(NewPostViewController.hashTagsDraft)
116+ // tagsField.addTags(NewPostViewController.hashTagsDraft)
101117
102118 }
103119
@@ -125,7 +141,7 @@ class NewPostViewController: UIViewController, UITextViewDelegate {
125141 func setUpTextView(){
126142 view.addConstraint(bottomTextViewConstraint)
127143 view1.addSubview(textView)
128− textView.edgesToSuperview(insets: .top(8) + .left(8) + .bottom(40+8) + .right(8))
144+ textView.edgesToSuperview(insets: .top(8) + .left(8) + .bottom(8) + .right(8))
129145
130146 if #available(iOS 11.0, *) {
131147 textView.smartDashesType = .no
@@ -194,7 +210,7 @@ class NewPostViewController: UIViewController, UITextViewDelegate {
194210 }
195211
196212 func setUpMD(){
197− textStorage.marklightTextProcessor.codeColor = UIColor.orange
213+ textStorage.marklightTextProcessor.codeColor = UIColor.gray
198214 textStorage.marklightTextProcessor.quoteColor = UIColor.darkGray
199215 textStorage.marklightTextProcessor.syntaxColor = UIColor.blue
200216 textStorage.marklightTextProcessor.codeFontName = "Courier"
@@ -216,10 +232,27 @@ class NewPostViewController: UIViewController, UITextViewDelegate {
216232 var indexOfPost = 0
217233 // MARK:- new post
218234 @objc func newPost(){
219− 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 = []
220253 // adding row to uiTableView after adding new post
221254 // myProtocol?.addPost(post: p)
222− moveBackToParentVC()
255+ // moveBackToParentVC?()
223256 // somewhere here i will be sending server notifications about new post arrival
224257 }
225258
@@ -231,8 +264,8 @@ class NewPostViewController: UIViewController, UITextViewDelegate {
231264 [weak self]
232265 _ in
233266 NewPostViewController.draft = self?.textView.text ?? ""
234− NewPostViewController.hashTagsDraft = self?.tagsField.tags.map { $0.text } ?? []
235− self?.moveBackToParentVC()
267+ NewPostViewController.hashTagsDraft = self?.currentTags ?? []
268+ self?.moveBackToParentVC?()
236269 }
237270
238271 let deleteAction = UIAlertAction(title: "Delete draft", style: .destructive)
@@ -241,7 +274,7 @@ class NewPostViewController: UIViewController, UITextViewDelegate {
241274 in
242275 NewPostViewController.draft = ""
243276 NewPostViewController.hashTagsDraft = []
244− self?.moveBackToParentVC()
277+ self?.moveBackToParentVC?()
245278 }
246279
247280 let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
@@ -258,16 +291,7 @@ class NewPostViewController: UIViewController, UITextViewDelegate {
258291 actionSaveDraft()
259292 }
260293
261− func moveBackToParentVC(){
262− let transition = CATransition()
263− transition.duration = 0.5
264− transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
265− transition.type = CATransitionType.reveal
266− transition.subtype = CATransitionSubtype.fromBottom
267− navigationController?.view.layer.add(transition, forKey: nil)
268− navigationController?.popViewController(animated: false)
269− textView!.resignFirstResponder()
270− }
294+ var moveBackToParentVC: (()->())?
271295
272296
273297 func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
@@ -281,7 +305,7 @@ class NewPostViewController: UIViewController, UITextViewDelegate {
281305 textView.scrollRectToVisible(rect, animated: animated)
282306 }
283307
284− fileprivate func textFieldEvents() {
308+ /*fileprivate func textFieldEvents() {
285309 tagsField.onDidAddTag = { _, _ in
286310 print("onDidAddTag")
287311 }
@@ -305,16 +329,16 @@ class NewPostViewController: UIViewController, UITextViewDelegate {
305329 tagsField.onDidUnselectTagView = { _, tagView in
306330 print("Unselect \(tagView)")
307331 }
308− }
332+ }*/
309333}
310334
311335
312336extension NewPostViewController: UITextFieldDelegate {
313337
314338 func textFieldShouldReturn(_ textField: UITextField) -> Bool {
315− if textField == tagsField {
339+ /*if textField == tagsField {
316340 textView.becomeFirstResponder()
317− }
341+ }*/
318342 return true
319343 }
320344
GDproject/Controller/ News and channels/NewsController.swift+67−168
@@ -12,72 +12,49 @@ 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: UITableViewController, UISearchControllerDelegate, NewPostDelegate, UpdateableWithChannel, DataDelegate
23+class NewsController: UIViewController, UISearchControllerDelegate, UpdateableWithChannel, UISearchResultsUpdating
2524{
26− func passData(for row: Int, channel: Model.Channels) {
27− if channel.id == -1{
28− self.channel = nil
29− } else {
30− self.channel = channel
31− }
25+ func updateChannel(on channel: Model.Channels) {
26+ self.channel = channel
27+ news.currChannel = channel
28+ decideWhatChannelDisplay()
3229 }
3330
34− var dictionary: [Int: Model.Users]? {
35− didSet {
36−
37− var newPosts: [Model.Posts] = []
38−
39− posts!.forEach({ (post) in
40−
41− newPosts.append(Model.Posts(body: post.body, authorId: post.authorId, id: post.id, user: dictionary![post.authorId]!, date: post.updated, tags: post.tags))
42−
43− post.tags.forEach { Model.Channels.fullTags.insert($0) }
44−
45− })
46−
47−
48− news.dataSourse = newPosts
49− tableView.reloadData()
50− }
51− }
31+ var changedChannelName: ((String)->())?
5232
53− var posts: [Model.Posts]? {
54− didSet{
55−
56− }
57− }
33+ @IBOutlet weak var tableView: UITableView!
5834
59− var channel: Model.Channels? {
60− didSet {
61− navigationItem.title = channel?.name ?? ""
62− }
63− }
35+ var channel: Model.Channels?
36+ var anonymousChannel: (users: [Int: Model.Users], posts: [Model.Posts])?
6437
6538 // MARK: - Output -
6639 var onSelectChannel: (() -> Void)?
67−
68− func addPost(post: Post) {
69− //news.dataSourse.insert(post, at: 0)
70− }
7140
72− var searchController = UISearchController(searchResultsController: nil)
41+ var searchController: UISearchController?
7342
7443 var news = NewsVC()
44+ var type: HeaderType? = .NEWS
7545
7646 var refreshContr = UIRefreshControl()
7747
7848 override func viewDidLoad()
7949 {
8050 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
8158 navigationItem.title = "Loading ..."
8259 tableView.refreshControl = refreshContr
8360 // Configure Refresh Control
@@ -86,25 +63,26 @@ class NewsController: UITableViewController, UISearchControllerDelegate, NewPost
8663 tableView.register(PostViewCell.self, forCellReuseIdentifier: postCellId)
8764 tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
8865
89− // setUpSearchContr()
66+ setUpSearchContr()
9067
68+ // navigationItem.
9169 news.viewController = self
92− news.type = .NEWS
70+ news.type = type == .NEWS ? .NEWS : type!
71+ news.currChannel = channel
9372
9473 setUpNavigationItemsforPosts()
95−
96− //setUpBanner()
9774 }
9875
9976 func setUpSearchContr(){
100− searchController.delegate = self
101− searchController.obscuresBackgroundDuringPresentation = false
77+ searchController?.delegate = self
78+ searchController?.obscuresBackgroundDuringPresentation = false
10279 navigationItem.searchController = searchController
10380 definesPresentationContext = true
104− searchController.searchBar.placeholder = "Search anything"
81+ searchController?.searchResultsUpdater = self
82+ searchController?.searchBar.placeholder = "Search tags"
10583 }
10684
107− @objc func refreshPostsData( _ ff: UIRefreshControl){
85+ @objc func refreshPostsData( _ ff: UIRefreshControl) {
10886 decideWhatChannelDisplay()
10987 }
11088
@@ -112,19 +90,9 @@ class NewsController: UITableViewController, UISearchControllerDelegate, NewPost
11290 print("news clear")
11391 }
11492
115− let label : UILabel = {
116− let label = UILabel()
117− label.text = "No posts to display yet!"
118− label.font = UIFont.systemFont(ofSize: 18)
119− label.textColor = .black
120− label.isHidden = true
121− label.numberOfLines = 0
122− return label
123− }()
124−
12593 override func viewWillAppear(_ animated: Bool) {
12694 super.viewWillAppear(animated)
127− searchController.isActive = false
95+ searchController?.isActive = false
12896 // TODO:- display something if no posts are availiable
12997
13098 decideWhatChannelDisplay()
@@ -133,128 +101,59 @@ class NewsController: UITableViewController, UISearchControllerDelegate, NewPost
133101 }
134102
135103 func decideWhatChannelDisplay(){
136− if let channel = channel, let id = channel.id {
137−
138− Model.getChannel(with: id) { [weak self] in
139− self?.posts = $0.posts
140− self?.dictionary = $0.users
141− self?.refreshContr.endRefreshing()
142− }
143−
144− } else {
145−
146− Model.getLast { [weak self] in
147− self?.posts = $0.posts
148− self?.dictionary = $0.users
149− self?.refreshContr.endRefreshing()
150− self?.navigationItem.title = "General"
104+
105+ switch type! {
106+ case .NEWS, .NONE:
107+ if let channel = channel, let id = channel.id, id != -1 {
108+
109+ Model.getAnonymousChannel(by: channel) { [weak self] in
110+ self?.news.dataSourse = $0.posts
111+ self?.news.dictionary = $0.users
112+ self?.refreshContr.endRefreshing()
113+ self?.changedChannelName?(channel.name)
114+ }
115+
116+ } else {
117+ Model.getLast { [weak self] in
118+ self?.news.dataSourse = $0.posts
119+ self?.news.dictionary = $0.users
120+ self?.refreshContr.endRefreshing()
121+ self?.changedChannelName?("General")
122+ }
151123 }
152−
124+ default:
125+ refreshContr.endRefreshing()
126+ break
153127 }
128+
154129 }
155130
156131 func setUpNavigationItemsforPosts(){
157− navigationItem.rightBarButtonItems = [UIBarButtonItem(barButtonSystemItem: .compose, target: self, action: #selector(self.writePost(_:))),UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(self.showChannels(_:)))
158− ]
159132 tableView.delegate = news
160133 tableView.dataSource = news
161134 tableView.reloadData()
162135 }
163136
164− // MARK:- attempt with coordinator
165− @objc func showChannels(_ barItem: UIBarButtonItem){
166− let vc = UIStoryboard.makeChannelsListController()
167− vc.myProtocol = self
168− vc.displayingChannel = channel
169− self.navigationController?.pushViewController(vc, animated: true)
170− }
171−
172− @objc func writePost(_ barItem: UIBarButtonItem)
173− {
174− let vc = storyboard?.instantiateViewController(withIdentifier: "NewPostViewController") as! NewPostViewController
175−
176− vc.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Post", style: .plain, target: vc, action: #selector(vc.newPost))
177− vc.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Close", style: .plain, target: vc, action: #selector(vc.closeView))
178−
179− vc.myProtocol = self
180−
181− let transition = CATransition()
182− transition.duration = 0.5
183− transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
184− transition.type = CATransitionType.moveIn
185− transition.subtype = CATransitionSubtype.fromTop
186− navigationController?.view.layer.add(transition, forKey: nil)
187− navigationController?.pushViewController(vc, animated: false)
188− }
189−
190− // for animating the banner
191− var topConstraint: NSLayoutConstraint?
192−
193− let bannerView: UIView = {
194− let view = UIView()
195− view.backgroundColor = .blue
196− view.layer.cornerRadius = 25.0
197− view.clipsToBounds = true
198− return view
199− }()
200−
201− let statusLabel: UILabel = {
202− let label = UILabel()
203− label.text = "-"
204− label.textColor = .white
205− label.textAlignment = .center
206− return label
207− }()
208−
209− // MARK:- banner
210− private func setUpBanner()
137+ func updateSearchResults(for searchController: UISearchController)
211138 {
212− view.addSubview(bannerView)
213− bannerView.addSubview(statusLabel)
214− statusLabel.edgesToSuperview()
215−
216− let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
217−
218− bannerView.addGestureRecognizer(tap)
219− topConstraint = NSLayoutConstraint(item: bannerView, attribute: .top, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .top, multiplier: 1, constant: -300)
220− view.addConstraint(topConstraint!)
221−
222− bannerView.edgesToSuperview(excluding: [.bottom, .top, .left], insets: .right(20), usingSafeArea: true)
223− bannerView.height(50)
224− 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+ }
225146 }
226147
227− // when table is scrolling no deletion is availiable
228− @objc func handleTap(sender: UITapGestureRecognizer? = nil) {
229− let indexPath = IndexPath(row: 0, section: 0)
230− self.tableView.scrollToRow(at: indexPath, at: .top, animated: true)
231−// isBannerVisible = false
232−// changeConstraint(isVisible: false)
148+ func willPresentSearchController(_ searchController: UISearchController) {
149+ searchController.searchResultsController?.view.isHidden = false;
233150 }
234151
235− // animation for banner
236− func changeConstraint(isVisible: Bool){
237− topConstraint?.constant = isVisible ? 50 : -300
238−
239− UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: {
240− self.view.layoutIfNeeded()
241− }) { (completed) in
242−
243− }
152+ func didPresentSearchController(_ searchController: UISearchController) {
153+ searchController.searchResultsController?.view.isHidden = false;
244154 }
245155
246− var isBannerVisible: Bool = false
247−
248− override func scrollViewDidScroll(_ scrollView: UIScrollView) {
249− print(scrollView.contentOffset.y)
250− if scrollView.contentOffset.y >= 50 && !isBannerVisible{
251− isBannerVisible = true
252− changeConstraint(isVisible: true)
253− }
254−
255− if isBannerVisible && scrollView.contentOffset.y == 0{
256− isBannerVisible = false
257− changeConstraint(isVisible: false)
258− }
156+ func didDismissSearchController(_ searchController: UISearchController) {
157+ searchController.searchBar.text = ""
259158 }
260159}
GDproject/Controller/ News and channels/NewsVC.swift+62−14
@@ -19,20 +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 {
2931
30− var dataSourse: [Model.Posts] = [] {
32+
33+ var onChannelDidChange: ((([Int: Model.Users],[Model.Posts]))->())?
34+
35+ var onFullPost: ((HeaderType, Model.Posts)->())?
36+
37+ var dictionary: [Int: Model.Users] = [:] {
3138 didSet {
32− cellDataSourse = []
33− dataSourse.forEach { (item) in
34− 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
3544 }
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)) }
3656 }
3757 }
3858
@@ -48,11 +68,7 @@ class NewsVC: UIViewController, UITableViewDelegate, UITableViewDataSource{
4868
4969 func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
5070 {
51− tableView.deselectRow(at: indexPath, animated: true)
52− let vc = viewController!.storyboard!.instantiateViewController(withIdentifier: fullPostControllerId) as! FullPostController
53− vc.type = type
54− vc.post = dataSourse[indexPath.row]
55− viewController!.navigationController!.pushViewController(vc, animated: true)
71+ onFullPost?(type, dataSourse[indexPath.row])
5672 }
5773
5874 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
@@ -72,9 +88,14 @@ class NewsVC: UIViewController, UITableViewDelegate, UITableViewDataSource{
7288 vc.idProfile = id
7389 self?.viewController!.navigationController!.pushViewController(vc, animated: true)
7490 }
75− case .NONE:
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+ }
96+ 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
@@ -89,10 +110,37 @@ class NewsVC: UIViewController, UITableViewDelegate, UITableViewDataSource{
89110 func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
90111 return 100.0
91112 }
92−}
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+ }
93136
137+ prevLast = indexPath.row
138+ }
139+ }
140+}
94141
95142enum HeaderType {
96143 case NONE
97144 case NEWS
145+ case ANONYMOUS
98146}
GDproject/Controller/ News and channels/PostViewCell.swift+15−6
@@ -8,13 +8,15 @@
88
99import UIKit
1010import Cartography
11−import MarkdownKit
1211import TinyConstraints
1312
1413class PostViewCell: UITableViewCell
1514{
15+
1616 var onUserDisplay: ((Int)->())?
1717
18+ var onAnonymousChannelDisplay: ((String)->())?
19+
1820 let nameLabel: UIButton = {
1921 let button = UIButton()
2022 button.setTitleColor(.black, for: .normal)
@@ -40,6 +42,7 @@ class PostViewCell: UITableViewCell
4042
4143 let shareButton: UIButton = {
4244 let button = UIButton(type: UIButton.ButtonType.detailDisclosure)
45+ button.isHidden = true
4346 return button
4447 }()
4548
@@ -84,7 +87,7 @@ class PostViewCell: UITableViewCell
8487 nameLabel.setTitle("\(post.user?.firstName ?? "") \(post.user?.lastName ?? "")", for: .normal)
8588 nameLabel.addTarget(self, action: #selector(displayProfile), for: .touchUpInside)
8689 if let user = post.user{
87− fullNameLabel.text = "\(user.firstName) \(user.middleName) \(user.lastName)"
90+ fullNameLabel.text = "\(user.faculty.name)"
8891 }
8992 else {
9093 fullNameLabel.text = "\(post.authorId)"
@@ -120,8 +123,8 @@ class PostViewCell: UITableViewCell
120123 for hash in hashtags {
121124 let button = UIButton()
122125 button.setTitle("#" + hash, for: .normal)
123− //button.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
124− //button.layer.cornerRadius = 10
126+ button.addTarget(self, action: #selector(setAnonymousChannel(on:)), for: .touchUpInside)
127+ // button.addGestureRecognizer(longPressRecognizer)
125128 button.titleLabel?.font = UIFont.monospacedDigitSystemFont(ofSize: 15, weight: .semibold)
126129 button.setTitleColor(#colorLiteral(red: 0, green: 0.4784313725, blue: 1, alpha: 1), for: .normal)
127130 buttons.append(button)
@@ -179,7 +182,13 @@ class PostViewCell: UITableViewCell
179182 }
180183
181184 @objc func displayProfile(){
182− print("buttonTapped")
183− onUserDisplay?(post!.authorId)
185+ if let id = post?.authorId {
186+ onUserDisplay?(id)
187+ }
188+ }
189+
190+ @objc func setAnonymousChannel(on button: UIButton){
191+ print("\(button.titleLabel?.text ?? "nothing")")
192+ onAnonymousChannelDisplay?(String(button.titleLabel!.text!.dropFirst()))
184193 }
185194}
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/Coordinator tab bar?/TabbarCoordinator.swiftdeleted−14
@@ -1,14 +0,0 @@
1−import UIKit
2−
3−class TabbarCoordinator{
4− var window: UIWindow!
5−
6− func start(){
7− let tabbar = UIStoryboard.tabBarController()
8− window.rootViewController = tabbar
9− }
10−
11− init(window: UIWindow) {
12− self.window = window
13− }
14−}
GDproject/Controller/Coordinators/ApplicationCoordinator.swiftnew file+69
@@ -0,0 +1,69 @@
1+//
2+// ApplicationCoordinator.swift
3+// RxSwift
4+//
5+// Created by cstore on 04/03/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import Foundation
10+import UIKit
11+
12+
13+
14+fileprivate enum LaunchInstructor {
15+ case main, auth
16+
17+ static func configure(
18+ isAutorized: Bool = DataStorage.standard.isLoggedIn) -> LaunchInstructor {
19+
20+ if isAutorized{
21+ return .main
22+ } else {
23+ return .auth
24+ }
25+ }
26+}
27+
28+final class ApplicationCoordinator: BaseCoordinator{
29+ private var window: UIWindow!
30+
31+ init(window: UIWindow) {
32+ self.window = window
33+ }
34+
35+ private var instructor: LaunchInstructor {
36+ return LaunchInstructor.configure()
37+ }
38+
39+ override func start() {
40+ switch instructor {
41+ case .auth: runAuthFlow()
42+ case .main: runMainFlow()
43+ }
44+ }
45+
46+ private func runAuthFlow() {
47+ let coordinator = LogInCoordinator(window: window)
48+ coordinator.didEndFlow = { [weak self, weak coordinator] in
49+ self?.start()
50+ self?.removeDependency(coordinator)
51+ }
52+ addDependency(coordinator)
53+ coordinator.start()
54+ }
55+
56+ private func runMainFlow() {
57+ let storyboard = UIStoryboard(name: "Main", bundle: nil)
58+ let tabBar = storyboard.instantiateViewController(withIdentifier: "TabbarController") as! TabbarController
59+ let coordinator = TabBarCoordinator(tabbarView: tabBar, window: window!)
60+
61+ coordinator.didEndFlow = { [weak self, weak coordinator] in
62+ self?.start()
63+ self?.removeDependency(coordinator)
64+ }
65+
66+ addDependency(coordinator)
67+ coordinator.start()
68+ }
69+}
GDproject/Controller/Coordinators/BaseCoordinator.swiftnew file+47
@@ -0,0 +1,47 @@
1+//
2+// BaseCoordinator.swift
3+// RxSwift
4+//
5+// Created by cstore on 02/03/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import Foundation
10+import UIKit
11+
12+protocol Coordinator: class {
13+ func start()
14+}
15+
16+class BaseCoordinator: Coordinator {
17+
18+ var childCoordinators: [Coordinator] = []
19+
20+ func start() {
21+
22+ }
23+
24+ // add only unique object
25+ func addDependency(_ coordinator: Coordinator) {
26+ guard !childCoordinators.contains(where: { $0 === coordinator }) else { return }
27+ childCoordinators.append(coordinator)
28+ }
29+
30+ func removeDependency(_ coordinator: Coordinator?) {
31+ guard
32+ childCoordinators.isEmpty == false,
33+ let coordinator = coordinator
34+ else { return }
35+
36+ // Clear child-coordinators recursively
37+ if let coordinator = coordinator as? BaseCoordinator, !coordinator.childCoordinators.isEmpty {
38+ coordinator.childCoordinators
39+ .filter({ $0 !== coordinator })
40+ .forEach({ coordinator.removeDependency($0) })
41+ }
42+ for (index, element) in childCoordinators.enumerated() where element === coordinator {
43+ childCoordinators.remove(at: index)
44+ break
45+ }
46+ }
47+}
GDproject/Controller/Coordinators/ChannelsCoordinator.swiftnew file+139
@@ -0,0 +1,139 @@
1+//
2+// ChannelsCoordinator.swift
3+// RxSwift
4+//
5+// Created by cstore on 03/03/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import Foundation
10+import UIKit
11+import Pulley
12+
13+class ChannelsCoordinator: BaseCoordinator{
14+ let storyboard = UIStoryboard(name: "Main", bundle: nil)
15+ private weak var navigationController: UINavigationController?
16+
17+ init(nc: UINavigationController) {
18+ self.navigationController = nc
19+ navigationController?.navigationItem.largeTitleDisplayMode = .always
20+ }
21+
22+ override func start() {
23+ show()
24+ }
25+
26+ private func show() {
27+
28+ let channels = storyboard.instantiateViewController(withIdentifier: channelListControllerId) as! ChannelListController
29+
30+ channels.onChannelSelected = { [unowned self]
31+ (channel) in
32+ self.navigationController?.pushViewController(self.presentNewsController(with: channel), animated: true)
33+ }
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)
45+ }
46+
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
52+
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+ }
58+
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
62+
63+ vc.currentTags = ch
64+ vc.receiver = mainContentVC
65+
66+ self.navigationController?.pushViewController(vc, animated: true)
67+ }
68+
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)
76+ }
77+
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+ }
104+
105+ mainContentVC.changedChannelName = {
106+ [weak mainContentVC] (title) in mainContentVC?.navigationItem.title = title
107+ }
108+
109+ return mainContentVC
110+ }
111+
112+ @objc private func writePost(_ barItem: UIBarButtonItem)
113+ {
114+ let vc = storyboard.instantiateViewController(withIdentifier: "NewPostViewController") as! NewPostViewController
115+
116+ vc.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Post", style: .plain, target: vc, action: #selector(vc.newPost))
117+ vc.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Close", style: .plain, target: vc, action: #selector(vc.closeView))
118+
119+ let transition = CATransition()
120+ transition.duration = 0.5
121+ transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
122+ transition.type = CATransitionType.moveIn
123+ transition.subtype = CATransitionSubtype.fromTop
124+ navigationController?.view.layer.add(transition, forKey: nil)
125+ navigationController?.pushViewController(vc, animated: false)
126+
127+ vc.moveBackToParentVC = {
128+ [weak self] in
129+
130+ let transition = CATransition()
131+ transition.duration = 0.5
132+ transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
133+ transition.type = CATransitionType.reveal
134+ transition.subtype = CATransitionSubtype.fromBottom
135+ self?.navigationController?.view.layer.add(transition, forKey: nil)
136+ self?.navigationController?.popViewController(animated: false)
137+ }
138+ }
139+}
GDproject/Controller/Coordinators/LogInCoordinator.swiftnew file+70
@@ -0,0 +1,70 @@
1+//
2+// LogInCoordinator.swift
3+// RxSwift
4+//
5+// Created by cstore on 01/03/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class LogInCoordinator: BaseCoordinator {
12+
13+ let storyboard = UIStoryboard(name: "Main", bundle: nil)
14+
15+ var didEndFlow: (()->())?
16+ var window: UIWindow!
17+ var navigationController: UINavigationController?
18+
19+ init(window: UIWindow) {
20+ self.window = window
21+ self.window?.rootViewController = UINavigationController()
22+ self.navigationController = window.rootViewController as? UINavigationController
23+ }
24+
25+ override func start(){
26+ let logInVC = LogInViewController()
27+ logInVC.authenticate = { [weak self, weak logInVC] (email) in
28+
29+ DataStorage.standard.setEmail(email: email)
30+ Model.authenticate(with: email) { [weak self]
31+ (authStatus) in
32+
33+ print(authStatus.userStatus)
34+
35+ if (authStatus.userStatus == "invalid") {
36+ logInVC?.presentAlertInvalidCode()
37+ }
38+ else if (authStatus.userStatus == "canRegister") { // register form
39+ self?.presentRegisterVC()
40+ } else { // validation code
41+ self?.presentViewControllerForCodeInput()
42+ }
43+ }
44+ }
45+
46+ navigationController?.pushViewController(logInVC, animated: false)
47+ }
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+
70+}
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.swiftnew file+60
@@ -0,0 +1,60 @@
1+//
2+// ProfileCoordinator.swift
3+// RxSwift
4+//
5+// Created by cstore on 02/03/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import Foundation
10+import UIKit
11+
12+class ProfileCoordinator: BaseCoordinator {
13+
14+ var didEndSession: (()->())?
15+
16+ private weak var navigationController: UINavigationController?
17+
18+ init(nc: UINavigationController) {
19+ self.navigationController = nc
20+ }
21+
22+ override func start() {
23+ show()
24+ }
25+
26+ private func show() {
27+ let storyboard = UIStoryboard(name: "Main", bundle: nil)
28+ let profile = storyboard.instantiateViewController(withIdentifier: "ProfileViewController") as! ProfileViewController
29+
30+ profile.logOut = {
31+ Model.logout() {
32+ [weak self] in
33+
34+ DataStorage.standard.setIsLoggedIn(value: false, with: 0)
35+ self?.didEndSession?()
36+ }
37+ }
38+
39+ profile.deleteAllSessions = {
40+ Model.deactivateAll() {
41+ [weak self] in
42+
43+ DataStorage.standard.setIsLoggedIn(value: false, with: 0)
44+ self?.didEndSession?()
45+ }
46+ }
47+
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
53+
54+ self?.navigationController?.pushViewController(vc, animated: true)
55+ }
56+
57+
58+ navigationController?.setViewControllers([profile], animated: false)
59+ }
60+}
GDproject/Controller/Coordinators/TabbarController.swiftnew file+50
@@ -0,0 +1,50 @@
1+//
2+// TabbarController.swift
3+// RxSwift
4+//
5+// Created by cstore on 03/03/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import Foundation
10+import UIKit
11+
12+protocol TabbarView: class {
13+ var onChannelsFlowSelect: ((UINavigationController) -> ())? { get set }
14+ var onProfileFlowSelect: ((UINavigationController) -> ())? { get set }
15+ var onMessagesFlowSelect: ((UINavigationController) -> ())? { get set }
16+ var onViewDidLoad: ((UINavigationController) -> ())? { get set }
17+}
18+
19+final class TabbarController: UITabBarController, UITabBarControllerDelegate, TabbarView {
20+
21+
22+ var onChannelsFlowSelect: ((UINavigationController) -> ())?
23+ var onProfileFlowSelect: ((UINavigationController) -> ())?
24+ var onMessagesFlowSelect: ((UINavigationController) -> ())?
25+
26+ var onViewDidLoad: ((UINavigationController) -> ())?
27+
28+ override func viewDidLoad() {
29+ super.viewDidLoad()
30+
31+ delegate = self
32+ if let controller = customizableViewControllers?.first as? UINavigationController {
33+ onViewDidLoad?(controller)
34+ }
35+ }
36+
37+ func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController)
38+ {
39+
40+ guard let controller = viewControllers?[selectedIndex] as? UINavigationController else { return }
41+
42+ if selectedIndex == 0 {
43+ onChannelsFlowSelect?(controller)
44+ } else if selectedIndex == 2 {
45+ onProfileFlowSelect?(controller)
46+ } else {
47+ onMessagesFlowSelect?(controller)
48+ }
49+ }
50+}
GDproject/Controller/Coordinators/TabbarCoordinator.swiftnew file+68
@@ -0,0 +1,68 @@
1+//
2+// TabBarCoordinator.swift
3+// RxSwift
4+//
5+// Created by cstore on 02/03/2019.
6+// Copyright © 2019 drHSE. All rights reserved.
7+//
8+
9+import Foundation
10+import UIKit
11+
12+
13+class TabBarCoordinator: BaseCoordinator {
14+
15+ var didEndFlow: (()->())?
16+
17+ private let tabbarView: TabbarView
18+ private weak var window: UIWindow?
19+
20+ init(tabbarView: TabbarView, window: UIWindow) {
21+ self.tabbarView = tabbarView
22+ self.window = window
23+ }
24+
25+ override func start() {
26+ tabbarView.onViewDidLoad = runChannelsFlow()
27+ tabbarView.onChannelsFlowSelect = runChannelsFlow()
28+ tabbarView.onProfileFlowSelect = runProfileFlow()
29+ tabbarView.onMessagesFlowSelect = runMessagesFlow()
30+ window?.rootViewController = tabbarView as! TabbarController
31+ }
32+
33+ private func runChannelsFlow() -> ((UINavigationController) -> ())
34+ {
35+ return { [unowned self] navController in
36+ if navController.viewControllers.isEmpty == true {
37+ let channelCoordinator = ChannelsCoordinator(nc: navController)
38+ self.addDependency(channelCoordinator)
39+ channelCoordinator.start()
40+ }
41+ }
42+ }
43+
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+
54+ private func runProfileFlow() -> ((UINavigationController) -> ())
55+ {
56+ return { [unowned self] navController in
57+ if navController.viewControllers.isEmpty == true {
58+ let profileCoordinator = ProfileCoordinator(nc: navController)
59+ profileCoordinator.didEndSession = { [weak self, weak profileCoordinator] in
60+ self?.removeDependency(profileCoordinator)
61+ self?.didEndFlow?()
62+ }
63+ self.addDependency(profileCoordinator)
64+ profileCoordinator.start()
65+ }
66+ }
67+ }
68+}
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/LogInCoordinator.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+92−90
@@ -1,128 +1,135 @@
11//
22// LogInViewController.swift
3−// NewsFeed
3+// RxSwift
44//
5−// Created by cstore on 20/01/2019.
5+// Created by cstore on 01/03/2019.
66// Copyright © 2019 drHSE. All rights reserved.
77//
88
99import UIKit
10+import TinyConstraints
11+import ReactiveSwift
12+import ReactiveCocoa
13+import Result
1014
1115class LogInViewController: UIViewController {
1216
13− @IBOutlet weak var mailTextField: UITextField!
17+ var loading = UIActivityIndicatorView(style: .gray)
1418
15− @IBOutlet weak var indicatorView: UIActivityIndicatorView!
19+ var authenticate: ((String)->())?
1620
17− var onLogIn: ((Int)->())?
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+ }
1829
19− var authenticateSucceeded: Bool? {
20− didSet {
21− if !authenticateSucceeded! {
22− indicatorView.stopAnimating()
23− indicatorView.isHidden = true
24− }
25− }
30+ override func viewWillDisappear(_ animated: Bool) {
31+ super.viewWillDisappear(animated)
32+
33+ loading.stopAnimating()
2634 }
2735
28− static let titleColor = UIColor(red: 0, green: 137/255, blue: 249/255, alpha: 0.5)
36+ let logInLabel: UILabel = {
37+ let label = UILabel()
38+ label.text = "Log In"
39+ label.textColor = .black
40+ label.font = UIFont.boldSystemFont(ofSize: 34)
41+ return label
42+ }()
2943
30− var bottomConstraint: NSLayoutConstraint?
44+ let mailTextField: UITextField = {
45+ let textField = UITextField()
46+ textField.backgroundColor = #colorLiteral(red: 0.937254902, green: 0.937254902, blue: 0.9568627451, alpha: 1)
47+ textField.placeholder = "Mail"
48+ textField.borderStyle = .roundedRect
49+ textField.textColor = .black
50+ textField.autocorrectionType = UITextAutocorrectionType.no
51+ textField.autocapitalizationType = UITextAutocapitalizationType.none
52+ textField.clearButtonMode = .always
53+ return textField
54+ }()
3155
3256 let logInButton: UIButton = {
3357 let button = UIButton(type: .system)
3458 button.setTitle("Log In", for: .normal)
35− button.setTitleColor(titleColor, for: .normal)
59+ button.setTitleColor(blueSystemColor, for: .normal)
3660 button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16)
61+ button.addTarget(self, action: #selector(handleTap), for: .touchUpInside)
3762 button.isEnabled = false
38− button.addTarget(self, action: #selector(activateLogInProcess), for: .touchUpInside)
3963 return button
4064 }()
4165
42− let keyboardBar: UIView = {
43− let view = UIView()
44− view.backgroundColor = .white
45− return view
46− }()
47−
48− override func viewDidLoad() {
49− super.viewDidLoad()
50− setUpView()
51− configureTapgesture()
66+ @objc func handleTap(){
67+ loading.startAnimating()
68+ authenticate?(mailTextField.text!)
5269 }
5370
54− override func viewWillAppear(_ animated: Bool) {
55− super.viewWillAppear(animated)
56− }
71+ private lazy var keyboardBar = UIView()
72+ private lazy var contentView = UIView()
73+ private var bottomConstraint: NSLayoutConstraint?
5774
58− override func viewWillDisappear(_ animated: Bool) {
59− super.viewWillDisappear(animated)
60− }
61−
62− private func configureTapgesture(){
63− let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap))
64− view.addGestureRecognizer(tapGesture)
75+ func setUpView(){
76+ // logIn stack with textField and label
77+ view.addSubview(contentView)
78+ view.addSubview(loading)
79+
80+ loading.centerInSuperview()
81+
82+ contentView.edgesToSuperview(excluding: .bottom, insets: .left(16) + .right(16) + .top(80), usingSafeArea: true)
83+ let views = [logInLabel, mailTextField]
84+ contentView.stack(views, axis: .vertical, spacing: 10)
6585 }
6686
67− @objc func handleTap(){
68− view.endEditing(true)
87+ func configureKeyboard(){
88+
89+ mailTextField.keyboardType = .emailAddress
90+ // configure keyboardBar setUp
91+ view.addSubview(keyboardBar)
92+ keyboardBar.height(50)
93+ keyboardBar.edgesToSuperview(excluding: [.top,.bottom])
94+ bottomConstraint = NSLayoutConstraint(item: keyboardBar, attribute: .bottom, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .bottom, multiplier: 1, constant: 0)
95+ view.addConstraint(bottomConstraint!)
96+
97+ // configure keyboardBar components
98+ keyboardBar.addSubview(logInButton)
99+ logInButton.height(50)
100+ logInButton.rightToSuperview(view.rightAnchor, offset: -16, relation: .equal, isActive: true)
101+
69102 }
70103
71− @objc func activateLogInProcess(){
72− if logInButton.isEnabled {
73− // MARK:- when log in is succeeded do I need to go there?
74− if let id = Int(mailTextField.text!){
75− indicatorView.isHidden = false
76− indicatorView.startAnimating()
77− onLogIn?(id)
78− view.endEditing(true)
104+ func logicOfLogInInputValidation() -> ((String?)->()) {
105+
106+ let logic: ((String?)->()) = { [weak self] (someText) in
107+ if let text = someText, !text.isEmpty, text.contains("@") {
108+ self?.logInButton.isEnabled = true
79109 } else {
80− logInButton.isEnabled = false
81− logInButton.setTitleColor(LogInViewController.titleColor.withAlphaComponent(0.5), for: .normal)
110+ self?.logInButton.isEnabled = false
82111 }
83112 }
113+
114+ return logic
84115 }
85116
86− func setUpView(){
87− indicatorView.isHidden = true
88− mailTextField.delegate = self
89− view.addSubview(keyboardBar)
117+ override func viewDidLoad() {
118+ super.viewDidLoad()
119+ self.view.backgroundColor = .white
120+ setUpView()
121+ configureKeyboard()
90122
91− view.addConstraintsWithFormat(format: "H:|[v0]|", views: keyboardBar)
92− view.addConstraintsWithFormat(format: "V:[v0(50)]", views: keyboardBar)
123+ let mailFieldValuesSignal: Signal<String, NoError> = mailTextField.reactive.continuousTextValues
93124
94− setUpBarComponents()
95−
96− configureKeyboardBehavior()
97− }
98−
99− func configureKeyboardBehavior(){
100− bottomConstraint = NSLayoutConstraint(item: keyboardBar, attribute: .bottom, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .bottom, multiplier: 1, constant: 0)
101− view.addConstraint(bottomConstraint!)
125+ mailFieldValuesSignal.observeValues(logicOfLogInInputValidation())
102126
103127 // for keyboard notifications
104128 NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotifications), name: UIResponder.keyboardWillShowNotification, object: nil)
105129
106130 NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotifications), name: UIResponder.keyboardWillHideNotification, object: nil)
107−
108− // for log in button notifications
109− NotificationCenter.default.addObserver(self, selector: #selector(inputDidChanged), name: UITextField.textDidChangeNotification, object: mailTextField)
110−
111− NotificationCenter.default.addObserver(self, selector: #selector(inputDidChanged), name: UITextField.textDidBeginEditingNotification, object: mailTextField)
112131 }
113132
114− @objc func inputDidChanged(notification: NSNotification){
115− if mailTextField.text?.isEmpty ?? true
116− {
117− logInButton.isEnabled = false
118− logInButton.setTitleColor(LogInViewController.titleColor.withAlphaComponent(0.5), for: .normal)
119− }
120− else
121− {
122− logInButton.isEnabled = true
123− logInButton.setTitleColor(LogInViewController.titleColor.withAlphaComponent(1), for: .normal)
124− }
125− }
126133
127134 @objc func handleKeyboardNotifications(notification: NSNotification){
128135 if let userInfo = notification.userInfo{
@@ -136,18 +143,13 @@ class LogInViewController: UIViewController {
136143 }
137144 }
138145
139− func setUpBarComponents(){
140− keyboardBar.addSubview(logInButton)
141− keyboardBar.addConstraintsWithFormat(format: "H:[v0(60)]-16-|", views: logInButton)
142− keyboardBar.addConstraintsWithFormat(format: "V:|[v0]|", views: logInButton)
143− }
144146}
145147
146−
147−extension LogInViewController: UITextFieldDelegate{
148− func textFieldShouldReturn(_ textField: UITextField) -> Bool
149− {
150− textField.resignFirstResponder()
151− return true
148+extension UIButton {
149+ override open var isEnabled: Bool {
150+ didSet {
151+ let color = isEnabled ? self.titleLabel?.textColor.withAlphaComponent(1) : self.titleLabel?.textColor.withAlphaComponent(0.5)
152+ self.setTitleColor(color, for: .normal)
153+ }
152154 }
153155}
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+144−78
@@ -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
@@ -30,14 +39,33 @@ class ProfileViewController: UIViewController
3039
3140 @IBOutlet weak var newMessageButton: UIButton!
3241
33− var protoDictionary: [Int: UIImage] = [9: #imageLiteral(resourceName: "9"), 5051: #imageLiteral(resourceName: "5051"), 69: #imageLiteral(resourceName: "69"), 42: #imageLiteral(resourceName: "42")]
42+ var logOut: (()->())?
43+
44+ var deleteAllSessions: (()->())?
3445
35− func fill(with user: Model.Users){
36− self.facultyLabel.text = "Студент: Факультет Компьютерных Наук"
46+ var onSettings: (()->())?
47+
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+ }
59+
60+ var protoDictionary: [String: UIImage] = ["135213": #imageLiteral(resourceName: "9"), "135288": #imageLiteral(resourceName: "5051"), "22723" : #imageLiteral(resourceName: "69"), "135083": #imageLiteral(resourceName: "42")]
61+
62+ func fill(with user: Model.Users)
63+ {
64+ self.facultyLabel.text = user.faculty.name
3765 self.nameLabel.text = "\(user.firstName) \(user.middleName)"
3866 self.surnameLabel.text = "\(user.lastName)"
39− self.profileImageView.image = protoDictionary[user.id]?.roundedImage
40− self.placeLabel.text = "📍Москва, Кочновский пр.3"
67+ self.profileImageView.image = protoDictionary[user.faculty.campusCode]?.roundedImage
68+ self.placeLabel.text = "📍\(user.faculty.address)"
4169 if user.id == DataStorage.standard.getUserId() {
4270 newMessageButton.isHidden = true
4371 } else {
@@ -47,46 +75,61 @@ class ProfileViewController: UIViewController
4775
4876 var user: Model.Users? {
4977 didSet {
50− self.fill(with: user!)
51− Model.getPostsForUser(with: user!.id) { [weak self] (posts) in
52− 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+ }
5385 }
54− navigationItem.title = "\(user!.firstName) \(user!.lastName)"
5586 }
5687 }
5788
58− var basicInfo = BasicInfoController()
59− var posts = NewsVC()
89+ var channel: Model.Channels? {
90+ didSet {
91+ self.update()
92+ }
93+ }
6094
61− var dataSourse: [Model.Posts]?{
62− didSet{
63−
64− var newPosts: [Model.Posts] = []
65−
66− dataSourse?.forEach({ (post) in
67− newPosts.append(Model.Posts(body: post.body, authorId: post.authorId, id: post.id, user: user!, date: post.updated, tags: post.tags))
68− })
69−
70− self.posts.dataSourse = newPosts
71− 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!]
72101 }
73102 }
74103
104+ var basicInfo = BasicInfoController()
105+ var posts = NewsVC()
106+
75107 override func viewDidLoad()
76108 {
77109 super.viewDidLoad()
78−
79−
80−
110+ if idProfile == nil {
111+ idProfile = DataStorage.standard.getUserId()
112+ }
113+
81114 posts.viewController = self
82115 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+ }
83126
84127 tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
85128
86129 tableView.register(PostViewCell.self, forCellReuseIdentifier: postCellId)
87130
88131 tableView.register(BasicInfoCell.self, forCellReuseIdentifier: basicInfoCellId)
89−
132+
90133 tableView.register(InfoCell.self, forCellReuseIdentifier: infoCellId)
91134
92135 posts.viewController = self
@@ -96,83 +139,105 @@ class ProfileViewController: UIViewController
96139 tableView.reloadData()
97140 }
98141
99− var idProfile: Int?
142+ var idProfile: Int? {
143+ didSet {
144+ channel = Model.Channels(people: [idProfile!], name: "", id: 0, tags: [])
145+ }
146+ }
100147
101148 override func viewWillAppear(_ animated: Bool) {
149+ tabBarController?.tabBar.isHidden = false
150+
102151 if idProfile == nil {
103152 idProfile = DataStorage.standard.getUserId()
104153 }
154+ user = Model.Channels.fullPeopleDict[idProfile!]
105155
106− if let id = idProfile {
107− if let user = Model.idUser[id] {
108− self.user = user
109− } else {
110− Model.getUsers(for: [id]) { [weak self] (dic) in
111− self?.user = dic[id]
112− }
113− }
114−
115− // requst for postsforuser was here. moved because of concrr
116− }
156+ update()
117157
118158 setUpNavigarionBar()
119159 }
120160
121161 func setUpNavigarionBar(){
122− navigationController?.navigationBar.prefersLargeTitles = true
123− //navigationItem.title = "\(user?.id ?? 0)"
162+ // navigationController?.navigationBar.prefersLargeTitles = true
124163 let uibarbutton = UIBarButtonItem(title: "More", style: .plain, target: self, action: #selector(showInformation))
125164 navigationItem.rightBarButtonItems = [uibarbutton]
126165 navigationItem.largeTitleDisplayMode = .always
127166 }
128167
129−
130− let copyLink: UIAlertAction = {
131− let b = UIAlertAction(title: "Copy link", style: .default)
132− return b
133− }()
134−
135168 @objc func showInformation(){
136169
137− // drafts
138− // saved
139−
140170 let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
141− let channelAction = UIAlertAction(title: "Add to a channel", style: .default){
142− [weak self] (_) in
171+
172+
173+ let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
174+
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+ }
143189
144− let vc = self?.storyboard?.instantiateViewController(withIdentifier: simplifiedChannelsList) as! SimplifiedChannelsList
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+ }
145202
146− vc.user = self?.user!
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+ }
147216
148− let transition = CATransition()
149− transition.duration = 0.5
150− transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
151− transition.type = CATransitionType.moveIn
152− transition.subtype = CATransitionSubtype.fromTop
153− self?.navigationController?.view.layer.add(transition, forKey: nil)
154− self?.navigationController?.pushViewController(vc, animated: false)
155− }
156− let settingsAction = UIAlertAction(title: "Setting", style: .default)
157− { [weak self] (_) in
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+ }
158236
159− let vc = self?.storyboard?.instantiateViewController(withIdentifier: "SettingsViewController") as! SettingsViewController
160− self?.navigationController?.pushViewController(vc, animated: true)
161− }
162−
163− let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
164− let logoutAction = UIAlertAction(title: "Log out", style: .destructive)
165− {
166− (_) in
167− DataStorage.standard.setIsLoggedIn(value: false, with: 0)
237+ optionMenu.addAction(channelAction)
168238 }
169239
170− optionMenu.addAction(channelAction)
171− optionMenu.addAction(settingsAction)
172− optionMenu.addAction(copyLink)
173− optionMenu.addAction(logoutAction)
174240 optionMenu.addAction(cancelAction)
175−
176241 self.present(optionMenu, animated: true, completion: nil)
177242 }
178243
@@ -202,6 +267,7 @@ class ProfileViewController: UIViewController
202267 func changeToBasicInfo(){
203268 tableView.delegate = basicInfo
204269 tableView.dataSource = basicInfo
270+ basicInfo.userInfo = self.user
205271 tableView.reloadData()
206272 }
207273}
GDproject/Controller/RepeatingTimer.swiftdeleted−59
@@ -1,59 +0,0 @@
1−/// RepeatingTimer mimics the API of DispatchSourceTimer but in a way that prevents
2−/// crashes that occur from calling resume multiple times on a timer that is
3−/// already resumed (noted by https://github.com/SiftScience/sift-ios/issues/52
4−
5−import Foundation
6−
7−class RepeatingTimer {
8−
9− let timeInterval: TimeInterval
10−
11− init(timeInterval: TimeInterval) {
12− self.timeInterval = timeInterval
13− }
14−
15− private lazy var timer: DispatchSourceTimer = {
16− let t = DispatchSource.makeTimerSource()
17− t.schedule(deadline: .now() + self.timeInterval, repeating: self.timeInterval)
18− t.setEventHandler(handler: { [weak self] in
19− self?.eventHandler?()
20− })
21− return t
22− }()
23−
24− var eventHandler: (() -> Void)?
25−
26− private enum State {
27− case suspended
28− case resumed
29− }
30−
31− private var state: State = .suspended
32−
33− deinit {
34− timer.setEventHandler {}
35− timer.cancel()
36− /*
37− If the timer is suspended, calling cancel without resuming
38− triggers a crash. This is documented here https://forums.developer.apple.com/thread/15902
39− */
40− resume()
41− eventHandler = nil
42− }
43−
44− func resume() {
45− if state == .resumed {
46− return
47− }
48− state = .resumed
49− timer.resume()
50− }
51−
52− func suspend() {
53− if state == .suspended {
54− return
55− }
56− state = .suspended
57− timer.suspend()
58− }
59−}
GDproject/DataStorage.swift+9−9
@@ -37,18 +37,17 @@ 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 */
43− var isLoggedIn: Bool = UserDefaults.standard.bool(forKey: UserDefaultsKeys.loggedIn.rawValue) {
44− didSet{
45− if isLoggedIn && getUserId() != 0 {
46− (UIApplication.shared.delegate as? AppDelegate)?.tabCoordinator.start()
47− } else {
48− (UIApplication.shared.delegate as? AppDelegate)?.logInAgain()
49− }
50− }
51− }
50+ var isLoggedIn: Bool = UserDefaults.standard.bool(forKey: UserDefaultsKeys.loggedIn.rawValue)
5251}
5352
5453/**
@@ -57,4 +56,5 @@ class DataStorage{
5756enum UserDefaultsKeys: String{
5857 case loggedIn
5958 case id
59+ case email
6060}
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+774−44
@@ -7,40 +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
36+ print(responce)
1737 if responce == invalidTocken {
38+
1839 DataStorage.standard.setIsLoggedIn(value: false, with: 0)
40+ (UIApplication.shared.delegate as! AppDelegate).relaunch()
41+
42+ Model.logout {
43+ print("Logout Is Successful")
44+ }
1945 }
2046 }
2147
2248 private static let baseUrl = "https://valera-denis.herokuapp.com"
2349 static let decoder = JSONDecoder()
24−
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")!
2555 static let authenticationURL = URL(string:"\(baseUrl)/authentication/login")!
56+ static let logOutURL = URL(string:"\(baseUrl)/authentication/logout")!
2657 static let postsLastURL = URL(string:"\(baseUrl)/posts/last")!
2758 static let postsForUserURL = URL(string:"\(baseUrl)/posts/forUser")!
2859 static let postsPublishURL = URL(string:"\(baseUrl)/posts/publish")!
2960 static let usersURL = URL(string:"\(baseUrl)/users")!
3061 static let usersAllURL = URL(string:"\(baseUrl)/users/all")!
62+ static let usersUpdateURL = URL(string:"\(baseUrl)/users/update")!
3163 static let channelsGetURL = URL(string: "\(baseUrl)/channels/get")!
3264 static let channelsUpdateURL = URL(string: "\(baseUrl)/channels/update")!
3365 static let channelsListURL = URL(string: "\(baseUrl)/channels")!
3466 static let channelsCreateURL = URL(string: "\(baseUrl)/channels/create")!
3567 static let channelsDeleteURL = URL(string: "\(baseUrl)/channels/delete")!
68+ 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")!
3680
3781
38− struct QueryPosts: Codable{
82+ struct QueryPosts<T: Codable>: Codable {
3983 var users: [Int: Users]
40− var posts: [Posts]
84+ var response: [T]
4185 }
4286
4387 struct Posts: Codable {
88+
4489 var body: [Attachments]
4590 var authorId: Int
4691 var id: Int
@@ -103,7 +148,12 @@ class Model{
103148 var lastName: String
104149 var firstName: String
105150 var id: Int
151+ var faculty: Faculty
152+ var email: String
106153
154+ func fullName() -> String {
155+ return "\(firstName) \(lastName)"
156+ }
107157 }
108158
109159 struct Attachments: Codable {
@@ -125,7 +175,7 @@ class Model{
125175
126176 struct Channels: Codable {
127177
128− static var fullTags = Set<String>()
178+ // static var fullTags = Set<String>()
129179 static var fullPeople = [Users]()
130180 static var fullPeopleDict = [Int:Users]()
131181
@@ -163,24 +213,108 @@ class Model{
163213 }
164214 }
165215
166− static func authenticate(with id: Int, completion: @escaping ((Bool)->())) {
216+ struct AuthStatus: Codable {
217+ var userStatus: String
167218
168− let json: [String:Any] = ["authenticationId" : id]
169− 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)
170258
171− var request = URLRequest(url: authenticationURL)
172259 request.httpMethod = "POST"
260+ request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
173261
174− // insert json data to the request
175− 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)
176287 request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
177288
178289 AF.request(request).response { (responce) in
179290
180− guard let realResponse = responce.response, realResponse.statusCode == 200 else
181− {
182− 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 {
183302 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 {
184318 return
185319 }
186320
@@ -190,26 +324,87 @@ class Model{
190324 return
191325 }
192326
193− 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
194346
195− 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 }
196356
197− 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+ }
198364
199− DataStorage.standard.setIsLoggedIn(value: true, with: personId)
200− completion(true)
365+ completion(res)
201366 }
202367 }
203368
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+ }
204398
205− static func getLast(completion: @escaping (((users:[Int: Users], posts:[Posts]))->())){
206− let jsonString = "30"
207− 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: [])
208402
403+ var request = URLRequest(url: postsLastURL)
404+ request.httpBody = try? JSONEncoder().encode(postRequest)
209405 request.httpMethod = "POST"
210406
211407 // insert json data to the request
212− request.httpBody = jsonString.data(using: .utf8)
213408 request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
214409
215410 AF.request(request).responseJSON {
@@ -217,18 +412,22 @@ class Model{
217412
218413 isValidTocken?(response.response?.statusCode ?? 498)
219414
415+ if let code = response.response?.statusCode, code == 498 {
416+ return
417+ }
418+
220419 guard let json = response.data else { return }
221420
222− 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")
223422 return }
224423
225424 idUser = newQueery.users
226− completion((newQueery.users, newQueery.posts))
425+ completion((newQueery.users, newQueery.response))
227426 }
228427 }
229428
230429
231− static func createAndPublish(body: [Attachments], tags: [String]){
430+ static func createAndPublish(body: [Attachments], tags: [String], completion: @escaping ((ResultR)->())){
232431 let jsonUpd = CreatedPost(body: body, tags: tags)
233432 var request = URLRequest(url: postsPublishURL)
234433
@@ -242,17 +441,30 @@ class Model{
242441 (response) in
243442
244443 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+ }
245456 }
246457 }
247458
459+// static func requestForPosts(limit: Int = 10, ){
460+//
461+// }
248462
249− static func getPostsForUser(with id: Int, completion: @escaping (([Posts])->())){
250− let json = [
251− "request" : id,
252− "limit": 10
253− ]
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)
254466
255− let jsonData = try? JSONSerialization.data(withJSONObject: json)
467+ let jsonData = try? JSONEncoder().encode(postsRequest)
256468
257469 var request = URLRequest(url: postsForUserURL)
258470 request.httpMethod = "POST"
@@ -266,11 +478,15 @@ class Model{
266478
267479 isValidTocken?(response.response?.statusCode ?? 498)
268480
481+ if let code = response.response?.statusCode, code == 498 {
482+ return
483+ }
484+
269485 guard let json = response.data else { return }
270486
271− 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 }
272488
273− completion(newPost.posts)
489+ completion(newPost.response)
274490 }
275491 }
276492
@@ -278,7 +494,7 @@ class Model{
278494
279495 static func getUsers(for ids: [Int], completion: @escaping (([Int:Users])->())){
280496 let json = "\(Set(ids))"
281− print(json)
497+
282498 var request = URLRequest(url: usersURL)
283499
284500 request.httpMethod = "POST"
@@ -292,11 +508,16 @@ class Model{
292508
293509 isValidTocken?(response.response?.statusCode ?? 498)
294510
511+ if let code = response.response?.statusCode, code == 498 {
512+ return
513+ }
514+
295515 guard let json = response.data else { return }
296516
297517 guard let users = try? decoder.decode([Users].self, from: json) else { return }
298518
299519 var dict: [Int:Users] = [:]
520+
300521 users.forEach({ (user) in
301522 dict[user.id] = user
302523 })
@@ -306,13 +527,10 @@ class Model{
306527 }
307528
308529 // get channel (with id): in responce -- PostQuery
309− static func getChannel(with channelId: Int, completion: @escaping (((users:[Int: Users], posts:[Posts]))->())){
310− let json = [
311− "request" : channelId,
312− "limit": 10
313− ]
314−
315− 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)
316534
317535 var request = URLRequest(url: channelsGetURL)
318536 request.httpMethod = "POST"
@@ -326,18 +544,21 @@ class Model{
326544
327545 isValidTocken?(response.response?.statusCode ?? 498)
328546
547+ if let code = response.response?.statusCode, code == 498 {
548+ return
549+ }
329550
330551 guard let json = response.data else { return }
331552
332− 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 }
333554
334555 idUser = newQueery.users
335− completion((newQueery.users, newQueery.posts))
556+ completion((newQueery.users, newQueery.response))
336557 }
337558 }
338559
339560
340− static func createChannel(with channel: Channels) {
561+ static func createChannel(with channel: Channels, completion: @escaping ((ResultR)->())) {
341562
342563 var request = URLRequest(url: channelsCreateURL)
343564 request.httpMethod = "POST"
@@ -348,6 +569,13 @@ class Model{
348569 (response) in
349570
350571 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)
351579 }
352580
353581 }
@@ -360,6 +588,9 @@ class Model{
360588 AF.request(request).responseJSON { (response) in
361589
362590 isValidTocken?(response.response?.statusCode ?? 498)
591+ if let code = response.response?.statusCode, code == 498 {
592+ return
593+ }
363594
364595 guard let json = response.data else { return }
365596
@@ -378,6 +609,9 @@ class Model{
378609
379610 AF.request(request).response { (response) in
380611 isValidTocken?(response.response?.statusCode ?? 498)
612+ if let code = response.response?.statusCode, code == 498 {
613+ return
614+ }
381615 }
382616 completion()
383617 }
@@ -393,6 +627,9 @@ class Model{
393627 (response) in
394628
395629 isValidTocken?(response.response?.statusCode ?? 498)
630+ if let code = response.response?.statusCode, code == 498 {
631+ return
632+ }
396633 }
397634 }
398635
@@ -415,6 +652,499 @@ class Model{
415652 }
416653
417654 isValidTocken?(response.response?.statusCode ?? 498)
655+ if let code = response.response?.statusCode, code == 498 {
656+ return
657+ }
658+ }
659+ }
660+
661+ struct AnonymousChannel: Codable {
662+
663+ var limit = 10
664+ var request: RequestPeopleTags
665+
666+ enum CodingKeys: String, CodingKey {
667+ case limit
668+ case request
669+ }
670+
671+ init(people: [Int], tags: [String]) {
672+ self.request = RequestPeopleTags(people: people, tags: tags)
673+ }
674+
675+ func encode(to encoder: Encoder) throws {
676+ var container = encoder.container(keyedBy: CodingKeys.self)
677+ try container.encode(limit, forKey: .limit)
678+ try container.encode(request, forKey: .request)
679+ }
680+
681+ struct RequestPeopleTags: Codable {
682+
683+ var people: [Int]
684+ var tags: [String]
685+
686+ init(people: [Int], tags: [String]) {
687+ self.people = people
688+ self.tags = tags
689+ }
690+
691+ func encode(to encoder: Encoder) throws {
692+ var container = encoder.container(keyedBy: CodingKeys.self)
693+ try container.encode(people, forKey: .people)
694+ try container.encode(tags, forKey: .tags)
695+ }
696+
697+ enum CodingKeys: String, CodingKey {
698+ case people
699+ case tags
700+ }
701+
702+ }
703+ }
704+ /*
705+ {
706+ "limit": 11,
707+ "request": {
708+ "people": [
709+ 2,
710+ 7,
711+ 8
712+ ],
713+ "tags": [
714+ "thisIsHashTag",
715+ "thisIsAlsoHashTag"
716+ ]
717+ }
718+ }
719+ */
720+ static func getAnonymousChannel(by anonymousChannel: Model.Channels, exclusiveFrom: Int? = nil, completion: @escaping (((users:[Int: Users], posts:[Posts]))->())){
721+
722+ let req = GeneralRequest<Model.Channels>(limit: 10, exclusiveFrom: exclusiveFrom, request: anonymousChannel)
723+
724+ var request = URLRequest(url: channelsGetAnonURL)
725+ request.httpMethod = "POST"
726+ request.httpBody = try? JSONEncoder().encode(req)
727+
728+ request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
729+ AF.request(request).response {
730+ (response) in
731+
732+ 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+ }
978+
979+ 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)
995+
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+ }
1146+
1147+ completion()
4181148 }
4191149 }
4201150}
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+13−42
@@ -12,57 +12,28 @@ import UIKit
1212class AppDelegate: UIResponder, UIApplicationDelegate {
1313
1414
15− var coordinator: LogInCoordinator!
16− var tabCoordinator: TabbarCoordinator!
17−
1815 var window: UIWindow?
19− var rootController: UINavigationController? {
20− return self.window!.rootViewController as? UINavigationController
21− }
22−
23− func logInAgain(){
24− let root = UIStoryboard.navRoot()
25− window?.rootViewController = root
26− window?.makeKeyAndVisible()
27− coordinator = LogInCoordinator(navigationController: root, window: UIApplication.shared.keyWindow!)
28− coordinator!.start()
29− }
16+ var appCoordinator: ApplicationCoordinator!
3017
3118
3219 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
3320 {
34− coordinator = LogInCoordinator(navigationController: rootController!, window: window!)
35− tabCoordinator = TabbarCoordinator(window: window!)
36− // TODO:- determine what to do here: log in or tabbar
37− if DataStorage.standard.isLoggedIn {
38− tabCoordinator.start()
39− } else {
40− coordinator.start()
21+ window = UIWindow(frame: UIScreen.main.bounds)
22+ window?.makeKeyAndVisible()
23+
24+ // window?.rootViewController = UINavigationController()
25+ Model.getCompl { (complTree) in
26+ Model.hashTagTree = complTree
4127 }
4228
29+ appCoordinator = ApplicationCoordinator(window: window!)
30+ appCoordinator.start()
31+
4332 return true
4433 }
45−
46− func applicationWillResignActive(_ application: UIApplication) {
47− // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
48− // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
49− }
50−
51− func applicationDidEnterBackground(_ application: UIApplication) {
52− // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
53− // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
54− }
55−
56− func applicationWillEnterForeground(_ application: UIApplication) {
57− // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
58− }
59−
60− func applicationDidBecomeActive(_ application: UIApplication) {
61− // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
62− }
63−
64− func applicationWillTerminate(_ application: UIApplication) {
65− // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
34+
35+ func relaunch(){
36+ appCoordinator.start()
6637 }
6738}
6839
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.