diff --git a/Cartfile b/Cartfile index 72919cd..fdc304e 100644 --- a/Cartfile +++ b/Cartfile @@ -2,6 +2,8 @@ github "robb/Cartography" github "ivanbruel/MarkdownKit" github "roberthein/TinyConstraints" github "macteo/Marklight" -github "ElaWorkshop/TagListView" -github "Alamofire/Alamofire" "5.0.0-beta.2" -github "whitesmith/WSTagsField" +github "Alamofire/Alamofire" "5.0.0-beta.5" +github "52inc/Pulley" +github "HeroTransitions/Hero" +github "ReactiveCocoa/ReactiveCocoa" ~> 9.0 +github "ZaidSA/TaggerKit" "bb826a7" \ No newline at end of file diff --git a/GDproject.xcodeproj/project.pbxproj b/GDproject.xcodeproj/project.pbxproj index 8079f0c..ffd97b9 100644 --- a/GDproject.xcodeproj/project.pbxproj +++ b/GDproject.xcodeproj/project.pbxproj @@ -8,21 +8,36 @@ /* Begin PBXBuildFile section */ 1210A0BB223940310080686D /* SimplifiedChannelsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1210A0BA223940310080686D /* SimplifiedChannelsList.swift */; }; - 123AD0E3223C1C3900326173 /* WSTagsField.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 123AD0E2223C1C3900326173 /* WSTagsField.framework */; }; - 123AD0E5223C1C4300326173 /* WSTagsField.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 123AD0E2223C1C3900326173 /* WSTagsField.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 121A8972226B1EAC005EE599 /* TagsSuggestionController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 121A8971226B1EAC005EE599 /* TagsSuggestionController.swift */; }; + 1220FF94227C41270092C6BC /* TaggsSelectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1220FF93227C41270092C6BC /* TaggsSelectionViewController.swift */; }; + 123589FD22899339002F8028 /* MessageViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 123589FC22899339002F8028 /* MessageViewCell.swift */; }; 123E37A5221F1B3200F6E42A /* LogInViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 123E37A4221F1B3200F6E42A /* LogInViewController.swift */; }; 123E37A7221F1DD700F6E42A /* MainController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 123E37A6221F1DD700F6E42A /* MainController.swift */; }; 124CC7032221C00C009DF531 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 124CC7022221C00C009DF531 /* Model.swift */; }; - 124CC7052221C2BB009DF531 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 124CC7042221C2BA009DF531 /* Alamofire.framework */; }; - 124CC7072221C2C3009DF531 /* Alamofire.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 124CC7042221C2BA009DF531 /* Alamofire.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 1253867D2223DC9F00CC5EA7 /* RepeatingTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1253867C2223DC9F00CC5EA7 /* RepeatingTimer.swift */; }; + 125A9A8A2285C3DA00513E2A /* CodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 125A9A892285C3DA00513E2A /* CodeViewController.swift */; }; + 125A9A8E2286068A00513E2A /* RegisterTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 125A9A8D2286068A00513E2A /* RegisterTableViewController.swift */; }; + 125A9A90228609C500513E2A /* InititalsViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 125A9A8F228609C500513E2A /* InititalsViewCell.swift */; }; + 125A9A922286100800513E2A /* FacultyTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 125A9A912286100800513E2A /* FacultyTableViewController.swift */; }; 125BD57D22171D2A008A3575 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 125BD57C22171D2A008A3575 /* ProfileViewController.swift */; }; 125BD57F22171D73008A3575 /* Extentions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 125BD57E22171D73008A3575 /* Extentions.swift */; }; 125BD5812217314A008A3575 /* NewsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 125BD5802217314A008A3575 /* NewsVC.swift */; }; + 1261BB93227B364C003898CF /* ChannelViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1261BB92227B364C003898CF /* ChannelViewController.swift */; }; + 1261BB95227B3991003898CF /* AddToChannelVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1261BB94227B3991003898CF /* AddToChannelVC.swift */; }; + 1261BB9E227B793D003898CF /* ChatInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1261BB9D227B793D003898CF /* ChatInfoViewController.swift */; }; + 12758257227F1401001F291F /* DialogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12758256227F1401001F291F /* DialogViewController.swift */; }; 1288B5CE221F1158002BE6B1 /* DataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1288B5CD221F1158002BE6B1 /* DataStorage.swift */; }; 1291BE2D2221312D009D3F23 /* ChannelsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1291BE2C2221312C009D3F23 /* ChannelsCoordinator.swift */; }; 1291BE342221569B009D3F23 /* TabbarCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1291BE332221569B009D3F23 /* TabbarCoordinator.swift */; }; - 1291BE3622218DBF009D3F23 /* LogInCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1291BE3522218DBF009D3F23 /* LogInCoordinator.swift */; }; + 129320042279B4270035C7B3 /* MessagesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 129320032279B4270035C7B3 /* MessagesViewController.swift */; }; + 129320072279B5690035C7B3 /* MessagesCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 129320062279B5690035C7B3 /* MessagesCoordinator.swift */; }; + 129320092279C3B50035C7B3 /* PeopleToWriteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 129320082279C3B50035C7B3 /* PeopleToWriteViewController.swift */; }; + 1293200D2279D7310035C7B3 /* CompletionTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1293200C2279D7310035C7B3 /* CompletionTree.swift */; }; + 1298810B227F13840032ACA3 /* DialogCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1298810A227F13840032ACA3 /* DialogCell.swift */; }; + 12BA4B9B224101A400DF93D7 /* ApplicationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12BA4B9A224101A400DF93D7 /* ApplicationCoordinator.swift */; }; + 12BA4B9D224101E700DF93D7 /* BaseCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12BA4B9C224101E700DF93D7 /* BaseCoordinator.swift */; }; + 12BA4B9F224102B700DF93D7 /* LogInCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12BA4B9E224102B700DF93D7 /* LogInCoordinator.swift */; }; + 12CE7D6F227B94B10024B6E8 /* TaggerKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12CE7D6E227B94B00024B6E8 /* TaggerKit.framework */; }; + 12CE7D71227B94B50024B6E8 /* TaggerKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12CE7D6E227B94B00024B6E8 /* TaggerKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 12D7D133221C321600B35452 /* ChannelListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12D7D132221C321600B35452 /* ChannelListController.swift */; }; 12D7D135221C42B700B35452 /* ChannelController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12D7D134221C42B700B35452 /* ChannelController.swift */; }; 12D7D137221D78E800B35452 /* Channel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12D7D136221D78E800B35452 /* Channel.swift */; }; @@ -36,39 +51,54 @@ 12E36DA1221424EA006FCDD7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 12E36D9F221424EA006FCDD7 /* Main.storyboard */; }; 12E36DA3221424ED006FCDD7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 12E36DA2221424ED006FCDD7 /* Assets.xcassets */; }; 12E36DA6221424ED006FCDD7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 12E36DA4221424ED006FCDD7 /* LaunchScreen.storyboard */; }; - 12E36DAF22143D40006FCDD7 /* Cartography.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DAE22143D40006FCDD7 /* Cartography.framework */; }; - 12E36DB322143D4E006FCDD7 /* Marklight.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DB222143D4E006FCDD7 /* Marklight.framework */; }; - 12E36DB522143D54006FCDD7 /* TinyConstraints.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DB422143D54006FCDD7 /* TinyConstraints.framework */; }; 12E36DB72214400A006FCDD7 /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DB62214400A006FCDD7 /* Post.swift */; }; - 12E36DB922144016006FCDD7 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DB822144016006FCDD7 /* User.swift */; }; - 12E36DBD221441D5006FCDD7 /* TinyConstraints.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DB422143D54006FCDD7 /* TinyConstraints.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 12E36DC0221441DB006FCDD7 /* Marklight.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DB222143D4E006FCDD7 /* Marklight.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 12E36DC2221441E1006FCDD7 /* MarkdownKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DB022143D47006FCDD7 /* MarkdownKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 12E36DC4221441EA006FCDD7 /* Cartography.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DAE22143D40006FCDD7 /* Cartography.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 12E36DCA22144635006FCDD7 /* NewPostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DC922144635006FCDD7 /* NewPostViewController.swift */; }; 12E36DCC22144725006FCDD7 /* PostViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DCB22144725006FCDD7 /* PostViewCell.swift */; }; 12E36DCE22144756006FCDD7 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DCD22144756006FCDD7 /* Constants.swift */; }; 12E36DD122148122006FCDD7 /* FullPostController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DD022148122006FCDD7 /* FullPostController.swift */; }; 12E36DD522156559006FCDD7 /* MyStackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E36DD422156559006FCDD7 /* MyStackView.swift */; }; - 12E36DD8221594A9006FCDD7 /* TagListView.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DD622159493006FCDD7 /* TagListView.framework */; }; - 12E36DD9221594A9006FCDD7 /* TagListView.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DD622159493006FCDD7 /* TagListView.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 12E36DDB221594B1006FCDD7 /* MarkdownKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12E36DB022143D47006FCDD7 /* MarkdownKit.framework */; }; + 12F3D6B0224106F700A69214 /* TabbarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F3D6AF224106F700A69214 /* TabbarController.swift */; }; + 12F3D6B22241097B00A69214 /* ProfileCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F3D6B12241097B00A69214 /* ProfileCoordinator.swift */; }; + 12FDAC7C226B5E2900995B4E /* Marklight.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC75226B5E2800995B4E /* Marklight.framework */; }; + 12FDAC7D226B5E2900995B4E /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC76226B5E2800995B4E /* Result.framework */; }; + 12FDAC7E226B5E2900995B4E /* Cartography.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC77226B5E2900995B4E /* Cartography.framework */; }; + 12FDAC81226B5E2900995B4E /* MarkdownKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC7A226B5E2900995B4E /* MarkdownKit.framework */; }; + 12FDAC84226B5E3700995B4E /* Alamofire.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC79226B5E2900995B4E /* Alamofire.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 12FDAC88226B5E4800995B4E /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC79226B5E2900995B4E /* Alamofire.framework */; }; + 12FDAC8A226B5E4B00995B4E /* Cartography.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC77226B5E2900995B4E /* Cartography.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 12FDAC8B226B5E4E00995B4E /* Hero.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC78226B5E2900995B4E /* Hero.framework */; }; + 12FDAC8C226B5E4E00995B4E /* Hero.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC78226B5E2900995B4E /* Hero.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 12FDAC8E226B5E5000995B4E /* MarkdownKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC7A226B5E2900995B4E /* MarkdownKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 12FDAC90226B5E5200995B4E /* Marklight.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC75226B5E2800995B4E /* Marklight.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 12FDAC91226B5E5400995B4E /* Pulley.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC7B226B5E2900995B4E /* Pulley.framework */; }; + 12FDAC92226B5E5400995B4E /* Pulley.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC7B226B5E2900995B4E /* Pulley.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 12FDAC94226B5E5600995B4E /* Result.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC76226B5E2800995B4E /* Result.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 12FDAC96226B61C400995B4E /* TinyConstraints.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC95226B61C400995B4E /* TinyConstraints.framework */; }; + 12FDAC98226B61CB00995B4E /* TinyConstraints.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC95226B61C400995B4E /* TinyConstraints.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 12FDAC9B226B652C00995B4E /* ReactiveSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC99226B652400995B4E /* ReactiveSwift.framework */; }; + 12FDAC9C226B652C00995B4E /* ReactiveSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC99226B652400995B4E /* ReactiveSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 12FDAC9F226B9E7C00995B4E /* ReactiveCocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC9D226B9E7600995B4E /* ReactiveCocoa.framework */; }; + 12FDACA0226B9E7C00995B4E /* ReactiveCocoa.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 12FDAC9D226B9E7600995B4E /* ReactiveCocoa.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ - 12E36DBE221441D5006FCDD7 /* Embed Frameworks */ = { + 12FDAC85226B5E3700995B4E /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( - 12E36DC0221441DB006FCDD7 /* Marklight.framework in Embed Frameworks */, - 124CC7072221C2C3009DF531 /* Alamofire.framework in Embed Frameworks */, - 12E36DC4221441EA006FCDD7 /* Cartography.framework in Embed Frameworks */, - 12E36DD9221594A9006FCDD7 /* TagListView.framework in Embed Frameworks */, - 12E36DBD221441D5006FCDD7 /* TinyConstraints.framework in Embed Frameworks */, - 123AD0E5223C1C4300326173 /* WSTagsField.framework in Embed Frameworks */, - 12E36DC2221441E1006FCDD7 /* MarkdownKit.framework in Embed Frameworks */, + 12FDAC8E226B5E5000995B4E /* MarkdownKit.framework in Embed Frameworks */, + 12FDAC98226B61CB00995B4E /* TinyConstraints.framework in Embed Frameworks */, + 12FDAC94226B5E5600995B4E /* Result.framework in Embed Frameworks */, + 12FDAC84226B5E3700995B4E /* Alamofire.framework in Embed Frameworks */, + 12FDAC8A226B5E4B00995B4E /* Cartography.framework in Embed Frameworks */, + 12FDAC9C226B652C00995B4E /* ReactiveSwift.framework in Embed Frameworks */, + 12FDAC92226B5E5400995B4E /* Pulley.framework in Embed Frameworks */, + 12FDAC8C226B5E4E00995B4E /* Hero.framework in Embed Frameworks */, + 12FDAC90226B5E5200995B4E /* Marklight.framework in Embed Frameworks */, + 12CE7D71227B94B50024B6E8 /* TaggerKit.framework in Embed Frameworks */, + 12FDACA0226B9E7C00995B4E /* ReactiveCocoa.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -77,19 +107,35 @@ /* Begin PBXFileReference section */ 1210A0BA223940310080686D /* SimplifiedChannelsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimplifiedChannelsList.swift; sourceTree = ""; }; - 123AD0E2223C1C3900326173 /* WSTagsField.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WSTagsField.framework; path = Carthage/Build/iOS/WSTagsField.framework; sourceTree = ""; }; + 121A8971226B1EAC005EE599 /* TagsSuggestionController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagsSuggestionController.swift; sourceTree = ""; }; + 1220FF93227C41270092C6BC /* TaggsSelectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaggsSelectionViewController.swift; sourceTree = ""; }; + 123589FC22899339002F8028 /* MessageViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageViewCell.swift; sourceTree = ""; }; 123E37A4221F1B3200F6E42A /* LogInViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogInViewController.swift; sourceTree = ""; }; 123E37A6221F1DD700F6E42A /* MainController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainController.swift; sourceTree = ""; }; 124CC7022221C00C009DF531 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = ""; }; - 124CC7042221C2BA009DF531 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = Carthage/Build/iOS/Alamofire.framework; sourceTree = ""; }; - 1253867C2223DC9F00CC5EA7 /* RepeatingTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepeatingTimer.swift; sourceTree = ""; }; + 125A9A892285C3DA00513E2A /* CodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeViewController.swift; sourceTree = ""; }; + 125A9A8D2286068A00513E2A /* RegisterTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterTableViewController.swift; sourceTree = ""; }; + 125A9A8F228609C500513E2A /* InititalsViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InititalsViewCell.swift; sourceTree = ""; }; + 125A9A912286100800513E2A /* FacultyTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FacultyTableViewController.swift; sourceTree = ""; }; 125BD57C22171D2A008A3575 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = ""; }; 125BD57E22171D73008A3575 /* Extentions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extentions.swift; sourceTree = ""; }; 125BD5802217314A008A3575 /* NewsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsVC.swift; sourceTree = ""; }; + 1261BB92227B364C003898CF /* ChannelViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelViewController.swift; sourceTree = ""; }; + 1261BB94227B3991003898CF /* AddToChannelVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToChannelVC.swift; sourceTree = ""; }; + 1261BB9D227B793D003898CF /* ChatInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatInfoViewController.swift; sourceTree = ""; }; + 12758256227F1401001F291F /* DialogViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DialogViewController.swift; sourceTree = ""; }; 1288B5CD221F1158002BE6B1 /* DataStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStorage.swift; sourceTree = ""; }; 1291BE2C2221312C009D3F23 /* ChannelsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelsCoordinator.swift; sourceTree = ""; }; 1291BE332221569B009D3F23 /* TabbarCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabbarCoordinator.swift; sourceTree = ""; }; - 1291BE3522218DBF009D3F23 /* LogInCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogInCoordinator.swift; sourceTree = ""; }; + 129320032279B4270035C7B3 /* MessagesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesViewController.swift; sourceTree = ""; }; + 129320062279B5690035C7B3 /* MessagesCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessagesCoordinator.swift; sourceTree = ""; }; + 129320082279C3B50035C7B3 /* PeopleToWriteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PeopleToWriteViewController.swift; path = GDproject/Controller/Messages/PeopleToWriteViewController.swift; sourceTree = SOURCE_ROOT; }; + 1293200C2279D7310035C7B3 /* CompletionTree.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompletionTree.swift; sourceTree = ""; }; + 1298810A227F13840032ACA3 /* DialogCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DialogCell.swift; sourceTree = ""; }; + 12BA4B9A224101A400DF93D7 /* ApplicationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationCoordinator.swift; sourceTree = ""; }; + 12BA4B9C224101E700DF93D7 /* BaseCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseCoordinator.swift; sourceTree = ""; }; + 12BA4B9E224102B700DF93D7 /* LogInCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogInCoordinator.swift; sourceTree = ""; }; + 12CE7D6E227B94B00024B6E8 /* TaggerKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TaggerKit.framework; path = Carthage/Build/iOS/TaggerKit.framework; sourceTree = ""; }; 12D7D132221C321600B35452 /* ChannelListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelListController.swift; sourceTree = ""; }; 12D7D134221C42B700B35452 /* ChannelController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelController.swift; sourceTree = ""; }; 12D7D136221D78E800B35452 /* Channel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Channel.swift; sourceTree = ""; }; @@ -105,18 +151,24 @@ 12E36DA2221424ED006FCDD7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 12E36DA5221424ED006FCDD7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 12E36DA7221424ED006FCDD7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 12E36DAE22143D40006FCDD7 /* Cartography.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cartography.framework; path = Carthage/Build/iOS/Cartography.framework; sourceTree = ""; }; - 12E36DB022143D47006FCDD7 /* MarkdownKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MarkdownKit.framework; path = Carthage/Build/iOS/MarkdownKit.framework; sourceTree = ""; }; - 12E36DB222143D4E006FCDD7 /* Marklight.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Marklight.framework; path = Carthage/Build/iOS/Marklight.framework; sourceTree = ""; }; - 12E36DB422143D54006FCDD7 /* TinyConstraints.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TinyConstraints.framework; path = Carthage/Build/iOS/TinyConstraints.framework; sourceTree = ""; }; 12E36DB62214400A006FCDD7 /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = ""; }; - 12E36DB822144016006FCDD7 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; 12E36DC922144635006FCDD7 /* NewPostViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewPostViewController.swift; sourceTree = ""; }; 12E36DCB22144725006FCDD7 /* PostViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostViewCell.swift; sourceTree = ""; }; 12E36DCD22144756006FCDD7 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 12E36DD022148122006FCDD7 /* FullPostController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullPostController.swift; sourceTree = ""; }; 12E36DD422156559006FCDD7 /* MyStackView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyStackView.swift; sourceTree = ""; }; - 12E36DD622159493006FCDD7 /* TagListView.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TagListView.framework; path = Carthage/Build/iOS/TagListView.framework; sourceTree = ""; }; + 12F3D6AF224106F700A69214 /* TabbarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabbarController.swift; sourceTree = ""; }; + 12F3D6B12241097B00A69214 /* ProfileCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileCoordinator.swift; sourceTree = ""; }; + 12FDAC75226B5E2800995B4E /* Marklight.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Marklight.framework; path = Carthage/Build/iOS/Marklight.framework; sourceTree = ""; }; + 12FDAC76226B5E2800995B4E /* Result.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Result.framework; path = Carthage/Build/iOS/Result.framework; sourceTree = ""; }; + 12FDAC77226B5E2900995B4E /* Cartography.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cartography.framework; path = Carthage/Build/iOS/Cartography.framework; sourceTree = ""; }; + 12FDAC78226B5E2900995B4E /* Hero.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Hero.framework; path = Carthage/Build/iOS/Hero.framework; sourceTree = ""; }; + 12FDAC79226B5E2900995B4E /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = Carthage/Build/iOS/Alamofire.framework; sourceTree = ""; }; + 12FDAC7A226B5E2900995B4E /* MarkdownKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MarkdownKit.framework; path = Carthage/Build/iOS/MarkdownKit.framework; sourceTree = ""; }; + 12FDAC7B226B5E2900995B4E /* Pulley.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Pulley.framework; path = Carthage/Build/iOS/Pulley.framework; sourceTree = ""; }; + 12FDAC95226B61C400995B4E /* TinyConstraints.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = TinyConstraints.framework; path = Carthage/Build/iOS/TinyConstraints.framework; sourceTree = ""; }; + 12FDAC99226B652400995B4E /* ReactiveSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveSwift.framework; path = Carthage/Build/iOS/ReactiveSwift.framework; sourceTree = ""; }; + 12FDAC9D226B9E7600995B4E /* ReactiveCocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReactiveCocoa.framework; path = Carthage/Build/iOS/ReactiveCocoa.framework; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -124,13 +176,17 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 12E36DB522143D54006FCDD7 /* TinyConstraints.framework in Frameworks */, - 124CC7052221C2BB009DF531 /* Alamofire.framework in Frameworks */, - 12E36DDB221594B1006FCDD7 /* MarkdownKit.framework in Frameworks */, - 12E36DD8221594A9006FCDD7 /* TagListView.framework in Frameworks */, - 12E36DAF22143D40006FCDD7 /* Cartography.framework in Frameworks */, - 123AD0E3223C1C3900326173 /* WSTagsField.framework in Frameworks */, - 12E36DB322143D4E006FCDD7 /* Marklight.framework in Frameworks */, + 12FDAC7C226B5E2900995B4E /* Marklight.framework in Frameworks */, + 12FDAC96226B61C400995B4E /* TinyConstraints.framework in Frameworks */, + 12FDAC91226B5E5400995B4E /* Pulley.framework in Frameworks */, + 12FDAC9F226B9E7C00995B4E /* ReactiveCocoa.framework in Frameworks */, + 12FDAC88226B5E4800995B4E /* Alamofire.framework in Frameworks */, + 12FDAC81226B5E2900995B4E /* MarkdownKit.framework in Frameworks */, + 12FDAC7D226B5E2900995B4E /* Result.framework in Frameworks */, + 12FDAC9B226B652C00995B4E /* ReactiveSwift.framework in Frameworks */, + 12FDAC7E226B5E2900995B4E /* Cartography.framework in Frameworks */, + 12CE7D6F227B94B10024B6E8 /* TaggerKit.framework in Frameworks */, + 12FDAC8B226B5E4E00995B4E /* Hero.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -150,33 +206,68 @@ path = Profile; sourceTree = ""; }; - 1291BE32222154C3009D3F23 /* Coordinator tab bar? */ = { + 1291BE32222154C3009D3F23 /* Coordinators */ = { isa = PBXGroup; children = ( + 12BA4B9A224101A400DF93D7 /* ApplicationCoordinator.swift */, + 129320062279B5690035C7B3 /* MessagesCoordinator.swift */, + 12F3D6AF224106F700A69214 /* TabbarController.swift */, + 12F3D6B12241097B00A69214 /* ProfileCoordinator.swift */, + 12BA4B9E224102B700DF93D7 /* LogInCoordinator.swift */, + 12BA4B9C224101E700DF93D7 /* BaseCoordinator.swift */, + 1291BE2C2221312C009D3F23 /* ChannelsCoordinator.swift */, 1291BE332221569B009D3F23 /* TabbarCoordinator.swift */, ); - path = "Coordinator tab bar?"; + path = Coordinators; sourceTree = ""; }; 1291BE3722218DC4009D3F23 /* Log In */ = { isa = PBXGroup; children = ( + 125A9A912286100800513E2A /* FacultyTableViewController.swift */, + 125A9A8F228609C500513E2A /* InititalsViewCell.swift */, + 125A9A8D2286068A00513E2A /* RegisterTableViewController.swift */, + 125A9A892285C3DA00513E2A /* CodeViewController.swift */, 123E37A4221F1B3200F6E42A /* LogInViewController.swift */, - 1291BE3522218DBF009D3F23 /* LogInCoordinator.swift */, ); path = "Log In"; sourceTree = ""; }; + 129320052279B4300035C7B3 /* Messages */ = { + isa = PBXGroup; + children = ( + 1261BB9D227B793D003898CF /* ChatInfoViewController.swift */, + 129320082279C3B50035C7B3 /* PeopleToWriteViewController.swift */, + 1298810C227F138C0032ACA3 /* Dialog */, + 129320032279B4270035C7B3 /* MessagesViewController.swift */, + 123589FC22899339002F8028 /* MessageViewCell.swift */, + ); + path = Messages; + sourceTree = ""; + }; + 1298810C227F138C0032ACA3 /* Dialog */ = { + isa = PBXGroup; + children = ( + 12758256227F1401001F291F /* DialogViewController.swift */, + 1298810A227F13840032ACA3 /* DialogCell.swift */, + ); + path = Dialog; + sourceTree = ""; + }; 12E36D8F221424EA006FCDD7 = { isa = PBXGroup; children = ( - 123AD0E2223C1C3900326173 /* WSTagsField.framework */, - 124CC7042221C2BA009DF531 /* Alamofire.framework */, - 12E36DD622159493006FCDD7 /* TagListView.framework */, - 12E36DB422143D54006FCDD7 /* TinyConstraints.framework */, - 12E36DB222143D4E006FCDD7 /* Marklight.framework */, - 12E36DB022143D47006FCDD7 /* MarkdownKit.framework */, - 12E36DAE22143D40006FCDD7 /* Cartography.framework */, + 12CE7D6E227B94B00024B6E8 /* TaggerKit.framework */, + 12FDAC9D226B9E7600995B4E /* ReactiveCocoa.framework */, + 12FDAC99226B652400995B4E /* ReactiveSwift.framework */, + 12FDAC95226B61C400995B4E /* TinyConstraints.framework */, + 12FDAC79226B5E2900995B4E /* Alamofire.framework */, + 12FDAC77226B5E2900995B4E /* Cartography.framework */, + 12FDAC78226B5E2900995B4E /* Hero.framework */, + 12FDAC7A226B5E2900995B4E /* MarkdownKit.framework */, + 12FDAC75226B5E2800995B4E /* Marklight.framework */, + 12FDAC7B226B5E2900995B4E /* Pulley.framework */, + 12FDAC76226B5E2800995B4E /* Result.framework */, 12E36D9A221424EA006FCDD7 /* GDproject */, 12E36D99221424EA006FCDD7 /* Products */, 12E36DDA221594B1006FCDD7 /* Frameworks */, @@ -212,9 +303,9 @@ isa = PBXGroup; children = ( 124CC7022221C00C009DF531 /* Model.swift */, + 1293200C2279D7310035C7B3 /* CompletionTree.swift */, 12E36DB62214400A006FCDD7 /* Post.swift */, 12D7D136221D78E800B35452 /* Channel.swift */, - 12E36DB822144016006FCDD7 /* User.swift */, ); path = "Simple model"; sourceTree = ""; @@ -232,8 +323,8 @@ 12E36DCF22145923006FCDD7 /* Controller */ = { isa = PBXGroup; children = ( - 1253867C2223DC9F00CC5EA7 /* RepeatingTimer.swift */, - 1291BE32222154C3009D3F23 /* Coordinator tab bar? */, + 1291BE32222154C3009D3F23 /* Coordinators */, + 129320052279B4300035C7B3 /* Messages */, 1291BE3722218DC4009D3F23 /* Log In */, 1288B5CC221F0EFE002BE6B1 /* Profile */, 12E36DD322156519006FCDD7 /* News and channels */, @@ -244,13 +335,16 @@ 12E36DD322156519006FCDD7 /* News and channels */ = { isa = PBXGroup; children = ( + 1261BB92227B364C003898CF /* ChannelViewController.swift */, + 1220FF93227C41270092C6BC /* TaggsSelectionViewController.swift */, + 1261BB94227B3991003898CF /* AddToChannelVC.swift */, 12D7D134221C42B700B35452 /* ChannelController.swift */, + 121A8971226B1EAC005EE599 /* TagsSuggestionController.swift */, 12D7D132221C321600B35452 /* ChannelListController.swift */, 1210A0BA223940310080686D /* SimplifiedChannelsList.swift */, 12E36DC922144635006FCDD7 /* NewPostViewController.swift */, 12E36DD022148122006FCDD7 /* FullPostController.swift */, 12E36D9D221424EA006FCDD7 /* NewsController.swift */, - 1291BE2C2221312C009D3F23 /* ChannelsCoordinator.swift */, 125BD5802217314A008A3575 /* NewsVC.swift */, 12E36DCB22144725006FCDD7 /* PostViewCell.swift */, ); @@ -274,7 +368,7 @@ 12E36D94221424EA006FCDD7 /* Sources */, 12E36D95221424EA006FCDD7 /* Frameworks */, 12E36D96221424EA006FCDD7 /* Resources */, - 12E36DBE221441D5006FCDD7 /* Embed Frameworks */, + 12FDAC85226B5E3700995B4E /* Embed Frameworks */, ); buildRules = ( ); @@ -297,6 +391,11 @@ TargetAttributes = { 12E36D97221424EA006FCDD7 = { CreatedOnToolsVersion = 10.1; + SystemCapabilities = { + com.apple.Keychain = { + enabled = 0; + }; + }; }; }; }; @@ -336,31 +435,49 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 129320072279B5690035C7B3 /* MessagesCoordinator.swift in Sources */, 124CC7032221C00C009DF531 /* Model.swift in Sources */, + 125A9A90228609C500513E2A /* InititalsViewCell.swift in Sources */, + 1220FF94227C41270092C6BC /* TaggsSelectionViewController.swift in Sources */, + 12F3D6B0224106F700A69214 /* TabbarController.swift in Sources */, 12E36DD522156559006FCDD7 /* MyStackView.swift in Sources */, 12D7D137221D78E800B35452 /* Channel.swift in Sources */, - 1291BE3622218DBF009D3F23 /* LogInCoordinator.swift in Sources */, - 12E36DB922144016006FCDD7 /* User.swift in Sources */, 1210A0BB223940310080686D /* SimplifiedChannelsList.swift in Sources */, 125BD5812217314A008A3575 /* NewsVC.swift in Sources */, 12E36DB72214400A006FCDD7 /* Post.swift in Sources */, 12DB7FDF221877160096878E /* InviteViewController.swift in Sources */, + 121A8972226B1EAC005EE599 /* TagsSuggestionController.swift in Sources */, + 1293200D2279D7310035C7B3 /* CompletionTree.swift in Sources */, 12E36DCA22144635006FCDD7 /* NewPostViewController.swift in Sources */, + 1261BB9E227B793D003898CF /* ChatInfoViewController.swift in Sources */, 12E36D9E221424EA006FCDD7 /* NewsController.swift in Sources */, + 1298810B227F13840032ACA3 /* DialogCell.swift in Sources */, + 125A9A922286100800513E2A /* FacultyTableViewController.swift in Sources */, + 125A9A8E2286068A00513E2A /* RegisterTableViewController.swift in Sources */, + 123589FD22899339002F8028 /* MessageViewCell.swift in Sources */, 12E36DCC22144725006FCDD7 /* PostViewCell.swift in Sources */, 123E37A5221F1B3200F6E42A /* LogInViewController.swift in Sources */, 125BD57F22171D73008A3575 /* Extentions.swift in Sources */, 12D7D133221C321600B35452 /* ChannelListController.swift in Sources */, + 125A9A8A2285C3DA00513E2A /* CodeViewController.swift in Sources */, 1291BE342221569B009D3F23 /* TabbarCoordinator.swift in Sources */, + 129320092279C3B50035C7B3 /* PeopleToWriteViewController.swift in Sources */, + 12BA4B9B224101A400DF93D7 /* ApplicationCoordinator.swift in Sources */, 1291BE2D2221312D009D3F23 /* ChannelsCoordinator.swift in Sources */, 12E36DD122148122006FCDD7 /* FullPostController.swift in Sources */, + 129320042279B4270035C7B3 /* MessagesViewController.swift in Sources */, + 12758257227F1401001F291F /* DialogViewController.swift in Sources */, 1288B5CE221F1158002BE6B1 /* DataStorage.swift in Sources */, + 12BA4B9F224102B700DF93D7 /* LogInCoordinator.swift in Sources */, + 12F3D6B22241097B00A69214 /* ProfileCoordinator.swift in Sources */, 125BD57D22171D2A008A3575 /* ProfileViewController.swift in Sources */, - 1253867D2223DC9F00CC5EA7 /* RepeatingTimer.swift in Sources */, + 1261BB95227B3991003898CF /* AddToChannelVC.swift in Sources */, 12DB7FDB2218590C0096878E /* InfoCell.swift in Sources */, 12DB7FD922181E660096878E /* BasicInfoCell.swift in Sources */, + 1261BB93227B364C003898CF /* ChannelViewController.swift in Sources */, 12D7D135221C42B700B35452 /* ChannelController.swift in Sources */, 12E36D9C221424EA006FCDD7 /* AppDelegate.swift in Sources */, + 12BA4B9D224101E700DF93D7 /* BaseCoordinator.swift in Sources */, 12DB7FD722181CD20096878E /* BasicInfoController.swift in Sources */, 12E36DCE22144756006FCDD7 /* Constants.swift in Sources */, 12DB7FDD221872F80096878E /* SettingsViewController.swift in Sources */, @@ -510,6 +627,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 9Z6LFGKA9G; FRAMEWORK_SEARCH_PATHS = ( @@ -521,9 +639,10 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = rednikina.com.drHSE.GDproject; + PRODUCT_BUNDLE_IDENTIFIER = rednikina.com.drHSE.GDproject.sos; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -532,6 +651,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = 9Z6LFGKA9G; FRAMEWORK_SEARCH_PATHS = ( @@ -543,9 +663,10 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = rednikina.com.drHSE.GDproject; + PRODUCT_BUNDLE_IDENTIFIER = rednikina.com.drHSE.GDproject.sos; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; diff --git a/GDproject/Base.lproj/Main.storyboard b/GDproject/Base.lproj/Main.storyboard index ca4d690..fd5587e 100644 --- a/GDproject/Base.lproj/Main.storyboard +++ b/GDproject/Base.lproj/Main.storyboard @@ -1,16 +1,30 @@ - + - + + + + + + + + + + + + + + + @@ -20,7 +34,7 @@ - + @@ -76,6 +90,9 @@ @@ -131,7 +148,7 @@ - + @@ -168,7 +185,7 @@ - + @@ -212,7 +229,251 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -354,7 +615,7 @@ - + @@ -365,33 +626,35 @@ - + - - - + - + + + - - - + - - + @@ -416,54 +679,352 @@ - - + + - - + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + - + - - + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -476,33 +1037,10 @@ - - - - - - - - - - - - - - - - - - - - - - - - + @@ -515,31 +1053,15 @@ - - - - + - - - - - - - - - - - - - - + - + @@ -548,6 +1070,7 @@ + @@ -555,9 +1078,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/GDproject/Constants.swift b/GDproject/Constants.swift index 1a937f7..e638202 100644 --- a/GDproject/Constants.swift +++ b/GDproject/Constants.swift @@ -6,6 +6,7 @@ // Copyright © 2019 drHSE. All rights reserved. // +import UIKit /// constant for cell in posts NewsController.swift let postCellId = "PostCell" @@ -42,7 +43,29 @@ let newsController = "NewsController" /// constant for switching to view controller for log in app let logInController = "LogInController" +/// constant for getting to messages +let messagesViewControllerId = "MessagesViewController" + +/// constant for displaying list of people to send message to +let peopleToWriteVC = "PeopleToWriteViewController" + +let dialogVC = "DialogViewController" let profileViewController = "ProfileViewController" let simplifiedChannelsList = "SimplifiedChannelsList" + +let channelViewControllerId = "ChannelViewController" + +let addToChannelVCId = "AddToChannelVC" + +let taggsSelectionViewController = "TaggsSelectionViewController" + +let dialogViewController = "DialogViewController" + +let tagsSuggestionController = "TagsSuggestionController" + +let chatInfoViewController = "ChatInfoViewController" + +let blueSystemColor = UIColor(red: 0, green: 137/255, blue: 249/255, alpha: 0.5) + diff --git a/GDproject/Controller/ News and channels/AddToChannelVC.swift b/GDproject/Controller/ News and channels/AddToChannelVC.swift new file mode 100644 index 0000000..7cdbc40 --- /dev/null +++ b/GDproject/Controller/ News and channels/AddToChannelVC.swift @@ -0,0 +1,132 @@ +// +// AddToChannelVC.swift +// GDproject +// +// Created by cstore on 02/05/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import UIKit + +enum DataSourse{ + case people, tags +} + +class AddToChannelVC: UITableViewController { + + var channel: Model.Channels? + weak var update: UpdatableChannel? + + // data sources for people and hashtags + var dataSourcePeople: [Model.Users] = Model.Channels.fullPeople + + var reloadtable: Bool = false { + didSet{ + tableView.reloadData() + } + } + + var filteredDataSource = [Model.Users]() + + func filterContentForSearchText(_ searchText: String, scope: String = "All") + { + filteredDataSource = dataSourcePeople.filter { $0.fullName().lowercased().contains(searchText.lowercased()) } + tableView.reloadData() + } + + let searchC = UISearchController(searchResultsController: nil) + + override func viewDidLoad() { + super.viewDidLoad() + + navigationItem.title = "People" + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") + self.navigationItem.searchController = searchC + navigationItem.largeTitleDisplayMode = .never + self.navigationItem.hidesSearchBarWhenScrolling = false + definesPresentationContext = true + searchC.searchResultsUpdater = self + searchC.obscuresBackgroundDuringPresentation = false + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + update?.updateChannel(with: channel!) + } + // MARK: - Table view data source + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + var isFiltering: Bool { + return searchC.isActive && !searchBarIsEmpty() + } + + func searchBarIsEmpty() -> Bool { + return searchC.searchBar.text?.isEmpty ?? true + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + if isFiltering { + return filteredDataSource.count + } else { + return dataSourcePeople.count + } + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell + { + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) + + if isFiltering{ + cell.textLabel?.text = filteredDataSource[indexPath.row].fullName() + if channel!.people.contains(filteredDataSource[indexPath.row].id) { + cell.backgroundColor = #colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1) + } else { + cell.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) + } + } else { + cell.textLabel?.text = dataSourcePeople[indexPath.row].fullName() + if channel!.people.contains(dataSourcePeople[indexPath.row].id) { + cell.backgroundColor = #colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1) + } else { + cell.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) + } + } + + cell.selectionStyle = .none + + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if let cell = tableView.cellForRow(at: indexPath) { + if isFiltering{ + if cell.backgroundColor == #colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1) { + let filtered = channel!.people.filter{ $0 != filteredDataSource[indexPath.row].id } + channel?.people = filtered + cell.backgroundColor = #colorLiteral(red: 0.9999960065, green: 1, blue: 1, alpha: 1) + } else { + channel?.people.append(filteredDataSource[indexPath.row].id) + cell.backgroundColor = #colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1) + } + } else { + if cell.backgroundColor == #colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1) { + let filtered = channel!.people.filter{ $0 != dataSourcePeople[indexPath.row].id } + channel?.people = filtered + cell.backgroundColor = #colorLiteral(red: 0.9999960065, green: 1, blue: 1, alpha: 1) + } else { + channel?.people.append(dataSourcePeople[indexPath.row].id) + cell.backgroundColor = #colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1) + } + } + } + } +} + +extension AddToChannelVC : UISearchResultsUpdating { + func updateSearchResults(for searchController: UISearchController) { + filterContentForSearchText(searchController.searchBar.text!) + } +} diff --git a/GDproject/Controller/ News and channels/ChannelController.swift b/GDproject/Controller/ News and channels/ChannelController.swift index d81153c..60e6f42 100644 --- a/GDproject/Controller/ News and channels/ChannelController.swift +++ b/GDproject/Controller/ News and channels/ChannelController.swift @@ -25,7 +25,7 @@ class ChannelController: UIViewController, UITableViewDelegate, UITableViewDataS var channel: Model.Channels? var myProtocol: DataDelegate? - var fullTags: [String] = Array(Model.Channels.fullTags) + var fullTags: [String] = CompletionTree.getCompletion(tree: Model.hashTagTree!, word: "") var dataSourcePeople: [Model.Users] = [] @@ -33,6 +33,9 @@ class ChannelController: UIViewController, UITableViewDelegate, UITableViewDataS var activeDataSource: ActiveTable = .people + // func to show preview of the current channel + var onShowingPreview: ((Model.Channels)->())? + @IBOutlet weak var viewww: UIView! @IBOutlet weak var textField: UITextField! @@ -61,16 +64,33 @@ class ChannelController: UIViewController, UITableViewDelegate, UITableViewDataS // TODO: update channel override func viewWillDisappear(_ animated: Bool) { - super.viewWillDisappear(animated) - if let _ = channel?.id { print("update") - Model.updateChannel(with: channel!) - } else { print("create") - Model.createChannel(with: channel!) + + defer { + super.viewWillDisappear(animated) + } + + guard let _ = self.navigationController?.viewControllers.lastIndex(of: self) else { + if let _ = channel?.id { print("update") + Model.updateChannel(with: channel!) + } else { print("create") + //Model.createChannel(with: channel!) + } + return + } + // nou + } + + @objc func showPreview(){ + if let channel = channel { + onShowingPreview?(channel) } } func setUpController(){ + // setting up the preview button for the editing or created channel + navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Preview", style: .done, target: self, action: #selector(showPreview)) + tableView.delegate = self tableView.dataSource = self tableView.register(UITableViewCell.self, forCellReuseIdentifier: itemCellId) @@ -232,9 +252,7 @@ class ChannelController: UIViewController, UITableViewDelegate, UITableViewDataS } tableView.reloadData() case 1: - dataSourceTags = fullTags.filter({ (s) -> Bool in - s.lowercased().contains(text.lowercased()) - }) + dataSourceTags = CompletionTree.getCompletion(tree: Model.hashTagTree!, word: text) tableView.reloadData() default: break diff --git a/GDproject/Controller/ News and channels/ChannelListController.swift b/GDproject/Controller/ News and channels/ChannelListController.swift index 0e0a65c..b62d257 100644 --- a/GDproject/Controller/ News and channels/ChannelListController.swift +++ b/GDproject/Controller/ News and channels/ChannelListController.swift @@ -13,20 +13,24 @@ struct ChannelData{ var subtitle = String() } +protocol ChannelListData: class { + func reloadData (with channels: [Model.Channels]) +} + // TODO: make search controller availiable -class ChannelListController: UITableViewController, DataDelegate { +class ChannelListController: UITableViewController { // MARK: - Output - var onChannelSelected: ((Model.Channels) -> Void)? + var onEditingModeBegins: ((Model.Channels, IndexPath)->Void)? // MARK: - filter search controller var filteredDataSource = [Model.Channels]() - var myProtocol: DataDelegate? var displayingChannel: Model.Channels? var toReload: Bool = false { - didSet{ + didSet { tableView.reloadData() } } @@ -80,7 +84,7 @@ class ChannelListController: UITableViewController, DataDelegate { askForUpdates() } - private func askForUpdates(){ + func askForUpdates(){ Model.channelsList { [weak self] (channels) in self?.dataSource = [ChannelListController.generalChannel] + channels self?.toReload = true @@ -89,17 +93,14 @@ class ChannelListController: UITableViewController, DataDelegate { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(true) - askForUpdates() + //askForUpdates() + tabBarController?.tabBar.isHidden = false } @objc func addChannel() { // editing mode is on automatically - let vc = storyboard?.instantiateViewController(withIdentifier: channelControllerId) as! ChannelController - vc.index = 1 - vc.myProtocol = self - vc.channel = Model.Channels(people: [], name: "Untitled", tags: []) - navigationController?.pushViewController(vc, animated: true) + onEditingModeBegins?(Model.Channels(people: [], name: "Untitled", id: 0, tags: []),IndexPath(row: 1, section: 0)) } static let generalChannel = Model.Channels(people: [], name: "General", id: -1, tags: []) @@ -143,27 +144,18 @@ class ChannelListController: UITableViewController, DataDelegate { override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { - - let editButton = UITableViewRowAction(style: .normal, title: "Edit") { [weak self] (action, indexPath) in - let vc = self?.storyboard?.instantiateViewController(withIdentifier: channelControllerId) as! ChannelController - vc.index = indexPath.row - vc.myProtocol = self! - vc.channel = self?.dataSource[indexPath.row] - self?.navigationController?.pushViewController(vc, animated: true) + let editButton = UITableViewRowAction(style: .normal, title: "Edit") { [unowned self] (rowAction, indexPath) in + self.onEditingModeBegins?(self.dataSource[indexPath.row], indexPath) } - editButton.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1) + editButton.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1) let deleteButton = UITableViewRowAction(style: .normal, title: "Delete") { [weak self] (action, indexPath) in Model.channelsDelete(by: self!.dataSource[indexPath.row].id!, completion: { print("хз что тут делать при успехе") }) - - if (self!.dataSource[indexPath.row].id == self!.displayingChannel?.id ?? -1) - { - self?.myProtocol?.passData(for: 0, channel: self!.dataSource[0]) - } + self?.tableView.beginUpdates() self?.dataSource.remove(at: indexPath.row) self?.tableView.deleteRows(at: [indexPath], with: .none) @@ -175,17 +167,12 @@ class ChannelListController: UITableViewController, DataDelegate { return [editButton, deleteButton] } - // TODO: remove popping override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if isFiltering { - //onChannelSelected?(filteredDataSource[indexPath.row]) - myProtocol?.passData(for: 0, channel: filteredDataSource[indexPath.row]) - navigationController?.popViewController(animated: true) + onChannelSelected?(filteredDataSource[indexPath.row]) } else { - myProtocol?.passData(for: 0, channel: dataSource[indexPath.row]) - navigationController?.popViewController(animated: true) - //onChannelSelected?(ChannelListController.dataSource[indexPath.row]) + onChannelSelected?(dataSource[indexPath.row]) } } } diff --git a/GDproject/Controller/ News and channels/ChannelViewController.swift b/GDproject/Controller/ News and channels/ChannelViewController.swift new file mode 100644 index 0000000..98be2f1 --- /dev/null +++ b/GDproject/Controller/ News and channels/ChannelViewController.swift @@ -0,0 +1,208 @@ +// +// ChannelViewController.swift +// MessageApp +// +// Created by cstore on 02/05/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import UIKit + +protocol UpdatableName: class{ + func updateName(with name: String) +} + +protocol UpdatableChannel: class{ + func updateChannel(with channel: Model.Channels) +} + +class ChannelViewController: UITableViewController, UpdatableName, UpdatableChannel, TagsReceiver +{ + var onChoosingHashTags: (([String]?)->())? + + var onChoosingPeople: ((Model.Channels?)->())? + + func receiveTags(tags: [String]) { + self.channel?.tags = tags + } + + func updateName(with name: String){ + channel?.name = name + } + + func updateChannel(with channel: Model.Channels) { + self.channel = channel + } + + var channel: Model.Channels? + + // func to show preview of the current channel + var onShowingPreview: ((Model.Channels)->())? + + override func viewDidLoad() + { + super.viewDidLoad() + tableView.keyboardDismissMode = .onDrag + navigationItem.rightBarButtonItems = [ UIBarButtonItem(barButtonSystemItem: .play, target: self, action: #selector(showPreview)), self.editButtonItem] + navigationItem.largeTitleDisplayMode = .never + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "searchCell") + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + tableView.reloadData() + } + + override func viewWillDisappear(_ animated: Bool) { + + defer { + super.viewWillDisappear(animated) + } + + guard let _ = self.navigationController?.viewControllers.lastIndex(of: self) else + { + if let id = channel?.id, id != 0 + { + Model.updateChannel(with: channel!) + } else { + Model.createChannel(with: channel!) { + [weak self] in + self?.showAlertOn(result: $0) + return + } + } + return + } + // nou + } + + @objc func showPreview() + { + if let channel = channel { + onShowingPreview?(channel) + } + } + + // MARK: - Table view data source + + override func numberOfSections(in tableView: UITableView) -> Int { + return 3 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + switch section { + case 1: + return (channel?.people.count ?? 0) + 1 + case 2: + return (channel?.tags.count ?? 0) + 1 + default: + return 1 + } + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + switch section { + case 1: + return "People" + case 2: + return "Tags" + default: + return "Title" + } + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + + switch indexPath.section{ + case 1 , 2: + return whichCell(tableView, cellForRowAt: indexPath) + default: + let cell = tableView.dequeueReusableCell(withIdentifier: "titleCell") as! TitleCell + + cell.fill(title: channel?.name) + cell.update = self + + return cell + } + } + + private func whichCell(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell + { + let row = indexPath.row + let section = indexPath.section + + if row == 0 + { + let cell = tableView.dequeueReusableCell(withIdentifier: "searchCell")! + cell.textLabel?.text = "Add more" + cell.accessoryType = .disclosureIndicator + return cell + } + + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) + + if section == 1 { + cell.textLabel?.text = "👤 \(Model.Channels.fullPeopleDict[channel?.people[row-1] ?? 0]!.fullName())" + } else { + cell.textLabel?.text = "# \(channel!.tags[row-1])" + } + + return cell + } + + override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) + { + if editingStyle == UITableViewCell.EditingStyle.delete + { + if indexPath.section == 1 { + channel?.people.remove(at: indexPath.row-1) + } else { + channel?.tags.remove(at: indexPath.row-1) + } + } + + tableView.reloadData() + } + + override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + if indexPath.row == 0 { + return false + } + + return true + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) + { + if indexPath.row == 0 && indexPath.section == 1 + { + onChoosingPeople?(channel) + } + + if indexPath.row == 0 && indexPath.section == 2 { + onChoosingHashTags?(channel?.tags) + } + } +} + + +class TitleCell: UITableViewCell, UITextFieldDelegate { + + @IBOutlet weak var titleLabel: UITextField! + + weak var update: UpdatableName? + + override func awakeFromNib() { + super.awakeFromNib() + } + + func fill(title: String?){ + titleLabel.text = title + titleLabel.addTarget(self, action: #selector(changedText(_:)), for: .editingChanged) + } + + @objc func changedText(_ textField: UITextField){ + update?.updateName(with: textField.text!) + } +} diff --git a/GDproject/Controller/ News and channels/ChannelsCoordinator.swift b/GDproject/Controller/ News and channels/ChannelsCoordinator.swift deleted file mode 100644 index 6d8ab72..0000000 --- a/GDproject/Controller/ News and channels/ChannelsCoordinator.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// ChannelsCoordinator.swift -// GDproject -// -// Created by cstore on 23/02/2019. -// Copyright © 2019 drHSE. All rights reserved. -// - -import Foundation -import UIKit - -final class ChannelsCoordinator{ - - // MARK: - Properties - private var channel: Model.Channels { didSet { updateInterfaces() } } - private weak var navigationController: UINavigationController? - - // MARK:- Init - init(currentChannel: Model.Channels, navigationController: UINavigationController) { - self.channel = currentChannel - self.navigationController = navigationController - } - - func start(){ - showCurrentChannel() - } - - // MARK: - Private implementation - private func showListOfChannels(){ - let controller = UIStoryboard.makeChannelsListController() - controller.onChannelSelected = { [weak self] channel - in - self?.channel = channel - _ = self?.navigationController?.popViewController(animated: true) - } - navigationController?.pushViewController(controller, animated: true) - } - - private func showCurrentChannel(){ - let controller = UIStoryboard.makeNewsController() - controller.channel = channel - controller.onSelectChannel = { [weak self] in - self?.showListOfChannels() - } - navigationController?.setViewControllers([controller], animated: false) - //navigationController?.pushViewController(controller, animated: false) - } - - // MARK:- update only viewControllers which are depandable on chosen channel - private func updateInterfaces() { - navigationController?.viewControllers.forEach { - ($0 as? UpdateableWithChannel)?.channel = channel - } - } -} diff --git a/GDproject/Controller/ News and channels/FullPostController.swift b/GDproject/Controller/ News and channels/FullPostController.swift index ef3435b..14151d9 100644 --- a/GDproject/Controller/ News and channels/FullPostController.swift +++ b/GDproject/Controller/ News and channels/FullPostController.swift @@ -25,27 +25,10 @@ class FullPostController: UITableViewController { func setUpNavigationBar(){ navigationItem.largeTitleDisplayMode = .never - navigationItem.title = "\(post?.authorId ?? 0)" - navigationItem.rightBarButtonItems = [UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(self.options))] + navigationItem.title = "Post" } - @objc func options(){ - // drafts - // saved - - let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - let editAction = UIAlertAction(title: "Send via message", style: .default) - let shareAction = UIAlertAction(title: "Send via project", style: .default) - let settingsAction = UIAlertAction(title: "Copy link", style: .default) - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) - - optionMenu.addAction(editAction) - optionMenu.addAction(shareAction) - optionMenu.addAction(settingsAction) - optionMenu.addAction(cancelAction) - - self.present(optionMenu, animated: true, completion: nil) - } + // MARK: - Table view data source override func numberOfSections(in tableView: UITableView) -> Int { @@ -102,6 +85,7 @@ class FullPostController: UITableViewController { cell.onUserDisplay = { [weak self] (id) in let vc = self?.storyboard!.instantiateViewController(withIdentifier: profileViewController) as! ProfileViewController vc.idProfile = id + self?.navigationController?.pushViewController(vc, animated: true) } default: diff --git a/GDproject/Controller/ News and channels/NewPostViewController.swift b/GDproject/Controller/ News and channels/NewPostViewController.swift index 53a7a46..60ff35d 100644 --- a/GDproject/Controller/ News and channels/NewPostViewController.swift +++ b/GDproject/Controller/ News and channels/NewPostViewController.swift @@ -10,23 +10,38 @@ import UIKit import Cartography import Marklight import TinyConstraints -import WSTagsField +import TaggerKit +// import WSTagsField -class NewPostViewController: UIViewController, UITextViewDelegate { +class NewPostViewController: UIViewController, UITextViewDelegate, TagsReceiver +{ + func receiveTags(tags: [String]) { + currentTags = tags + } @IBOutlet weak var view1: UIView! - @IBOutlet weak var viewForTags: UIView! + var currentTags: [String] = [] - fileprivate let tagsField = WSTagsField() + // We want the whole experience, let's create two TKCollectionViews + let productTags = TKCollectionView() + + @IBAction func chooseTags(_ sender: UIButton) { + let vc = storyboard?.instantiateViewController(withIdentifier: taggsSelectionViewController) as! TaggsSelectionViewController + + NewPostViewController.draft = textView.text + vc.receiver = self + vc.currentTags = currentTags + + navigationController?.pushViewController(vc, animated: true) + } + + // fileprivate let tagsField = WSTagsField() // Keep strong instance of the `NSTextStorage` subclass let textStorage = MarklightTextStorage() - weak var parentVC: NewsController? - var myProtocol: NewPostDelegate? - static var draft: String = "" - static var hashTagsDraft: [String] = [] + static var hashTagsDraft: [String] = [ ] var textView: UITextView! @@ -57,11 +72,11 @@ class NewPostViewController: UIViewController, UITextViewDelegate { setUpMD() setUpTextView() - setUpAccessoryView() - setUpTagsView() + // setUpAccessoryView() + //setUpTagsView() } - func setUpTagsView(){ + /* func setUpTagsView(){ tagsField.frame = viewForTags.bounds viewForTags.addSubview(tagsField) @@ -89,15 +104,16 @@ class NewPostViewController: UIViewController, UITextViewDelegate { tagsField.acceptTagOption = .space textFieldEvents() - } + }*/ override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - navigationController?.navigationBar.prefersLargeTitles = false + //navigationController?.navigationBar.prefersLargeTitles = false + navigationItem.largeTitleDisplayMode = .never navigationItem.title = "New post" textView.text = NewPostViewController.draft - tagsField.addTags(NewPostViewController.hashTagsDraft) + // tagsField.addTags(NewPostViewController.hashTagsDraft) } @@ -125,7 +141,7 @@ class NewPostViewController: UIViewController, UITextViewDelegate { func setUpTextView(){ view.addConstraint(bottomTextViewConstraint) view1.addSubview(textView) - textView.edgesToSuperview(insets: .top(8) + .left(8) + .bottom(40+8) + .right(8)) + textView.edgesToSuperview(insets: .top(8) + .left(8) + .bottom(8) + .right(8)) if #available(iOS 11.0, *) { textView.smartDashesType = .no @@ -194,7 +210,7 @@ class NewPostViewController: UIViewController, UITextViewDelegate { } func setUpMD(){ - textStorage.marklightTextProcessor.codeColor = UIColor.orange + textStorage.marklightTextProcessor.codeColor = UIColor.gray textStorage.marklightTextProcessor.quoteColor = UIColor.darkGray textStorage.marklightTextProcessor.syntaxColor = UIColor.blue textStorage.marklightTextProcessor.codeFontName = "Courier" @@ -216,10 +232,27 @@ class NewPostViewController: UIViewController, UITextViewDelegate { var indexOfPost = 0 // MARK:- new post @objc func newPost(){ - Model.createAndPublish(body: [Model.Attachments(markdown: textView!.text)], tags: tagsField.tags.map { $0.text }) + self.navigationItem.title = "Sending..." + Model.createAndPublish(body: [Model.Attachments(markdown: textView!.text)], tags: currentTags) { + [weak self] in + + switch $0 { + case .success1, .success: + NewPostViewController.draft = "" + NewPostViewController.hashTagsDraft = [] + self?.moveBackToParentVC?() + default: + self?.showAlertOn(result: $0) + self?.navigationItem.title = "New post" + } + + } + + // NewPostViewController.draft = "" + // NewPostViewController.hashTagsDraft = [] // adding row to uiTableView after adding new post // myProtocol?.addPost(post: p) - moveBackToParentVC() + // moveBackToParentVC?() // somewhere here i will be sending server notifications about new post arrival } @@ -231,8 +264,8 @@ class NewPostViewController: UIViewController, UITextViewDelegate { [weak self] _ in NewPostViewController.draft = self?.textView.text ?? "" - NewPostViewController.hashTagsDraft = self?.tagsField.tags.map { $0.text } ?? [] - self?.moveBackToParentVC() + NewPostViewController.hashTagsDraft = self?.currentTags ?? [] + self?.moveBackToParentVC?() } let deleteAction = UIAlertAction(title: "Delete draft", style: .destructive) @@ -241,7 +274,7 @@ class NewPostViewController: UIViewController, UITextViewDelegate { in NewPostViewController.draft = "" NewPostViewController.hashTagsDraft = [] - self?.moveBackToParentVC() + self?.moveBackToParentVC?() } let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) @@ -258,16 +291,7 @@ class NewPostViewController: UIViewController, UITextViewDelegate { actionSaveDraft() } - func moveBackToParentVC(){ - let transition = CATransition() - transition.duration = 0.5 - transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) - transition.type = CATransitionType.reveal - transition.subtype = CATransitionSubtype.fromBottom - navigationController?.view.layer.add(transition, forKey: nil) - navigationController?.popViewController(animated: false) - textView!.resignFirstResponder() - } + var moveBackToParentVC: (()->())? func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool { @@ -281,7 +305,7 @@ class NewPostViewController: UIViewController, UITextViewDelegate { textView.scrollRectToVisible(rect, animated: animated) } - fileprivate func textFieldEvents() { + /*fileprivate func textFieldEvents() { tagsField.onDidAddTag = { _, _ in print("onDidAddTag") } @@ -305,16 +329,16 @@ class NewPostViewController: UIViewController, UITextViewDelegate { tagsField.onDidUnselectTagView = { _, tagView in print("Unselect \(tagView)") } - } + }*/ } extension NewPostViewController: UITextFieldDelegate { func textFieldShouldReturn(_ textField: UITextField) -> Bool { - if textField == tagsField { + /*if textField == tagsField { textView.becomeFirstResponder() - } + }*/ return true } diff --git a/GDproject/Controller/ News and channels/NewsController.swift b/GDproject/Controller/ News and channels/NewsController.swift index f829a78..0505be3 100644 --- a/GDproject/Controller/ News and channels/NewsController.swift +++ b/GDproject/Controller/ News and channels/NewsController.swift @@ -12,72 +12,49 @@ import Cartography protocol UpdateableWithChannel: class { var channel: Model.Channels? { get set } + func updateChannel(on channel: Model.Channels) } -protocol NewPostDelegate { - func addPost(post: Post) -} + protocol UpdateableWithUser: class { var user: Model.Users? { get set } } // MARK:- Controller with posts and channels availiable. // Search is availiable within every table (posts and channels). Has button-functionality for boths post and chnnels -class NewsController: UITableViewController, UISearchControllerDelegate, NewPostDelegate, UpdateableWithChannel, DataDelegate +class NewsController: UIViewController, UISearchControllerDelegate, UpdateableWithChannel, UISearchResultsUpdating { - func passData(for row: Int, channel: Model.Channels) { - if channel.id == -1{ - self.channel = nil - } else { - self.channel = channel - } + func updateChannel(on channel: Model.Channels) { + self.channel = channel + news.currChannel = channel + decideWhatChannelDisplay() } - var dictionary: [Int: Model.Users]? { - didSet { - - var newPosts: [Model.Posts] = [] - - posts!.forEach({ (post) in - - newPosts.append(Model.Posts(body: post.body, authorId: post.authorId, id: post.id, user: dictionary![post.authorId]!, date: post.updated, tags: post.tags)) - - post.tags.forEach { Model.Channels.fullTags.insert($0) } - - }) - - - news.dataSourse = newPosts - tableView.reloadData() - } - } + var changedChannelName: ((String)->())? - var posts: [Model.Posts]? { - didSet{ - - } - } + @IBOutlet weak var tableView: UITableView! - var channel: Model.Channels? { - didSet { - navigationItem.title = channel?.name ?? "" - } - } + var channel: Model.Channels? + var anonymousChannel: (users: [Int: Model.Users], posts: [Model.Posts])? // MARK: - Output - var onSelectChannel: (() -> Void)? - - func addPost(post: Post) { - //news.dataSourse.insert(post, at: 0) - } - var searchController = UISearchController(searchResultsController: nil) + var searchController: UISearchController? var news = NewsVC() + var type: HeaderType? = .NEWS var refreshContr = UIRefreshControl() override func viewDidLoad() { super.viewDidLoad() + + let updater = TagsSuggestionController() + updater.delegate = self + searchController = UISearchController(searchResultsController: updater) + navigationItem.largeTitleDisplayMode = .always + + navigationController?.navigationBar.prefersLargeTitles = true navigationItem.title = "Loading ..." tableView.refreshControl = refreshContr // Configure Refresh Control @@ -86,25 +63,26 @@ class NewsController: UITableViewController, UISearchControllerDelegate, NewPost tableView.register(PostViewCell.self, forCellReuseIdentifier: postCellId) tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") - // setUpSearchContr() + setUpSearchContr() + // navigationItem. news.viewController = self - news.type = .NEWS + news.type = type == .NEWS ? .NEWS : type! + news.currChannel = channel setUpNavigationItemsforPosts() - - //setUpBanner() } func setUpSearchContr(){ - searchController.delegate = self - searchController.obscuresBackgroundDuringPresentation = false + searchController?.delegate = self + searchController?.obscuresBackgroundDuringPresentation = false navigationItem.searchController = searchController definesPresentationContext = true - searchController.searchBar.placeholder = "Search anything" + searchController?.searchResultsUpdater = self + searchController?.searchBar.placeholder = "Search tags" } - @objc func refreshPostsData( _ ff: UIRefreshControl){ + @objc func refreshPostsData( _ ff: UIRefreshControl) { decideWhatChannelDisplay() } @@ -112,19 +90,9 @@ class NewsController: UITableViewController, UISearchControllerDelegate, NewPost print("news clear") } - let label : UILabel = { - let label = UILabel() - label.text = "No posts to display yet!" - label.font = UIFont.systemFont(ofSize: 18) - label.textColor = .black - label.isHidden = true - label.numberOfLines = 0 - return label - }() - override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - searchController.isActive = false + searchController?.isActive = false // TODO:- display something if no posts are availiable decideWhatChannelDisplay() @@ -133,128 +101,59 @@ class NewsController: UITableViewController, UISearchControllerDelegate, NewPost } func decideWhatChannelDisplay(){ - if let channel = channel, let id = channel.id { - - Model.getChannel(with: id) { [weak self] in - self?.posts = $0.posts - self?.dictionary = $0.users - self?.refreshContr.endRefreshing() + + switch type! { + case .NEWS, .NONE: + if let channel = channel, let id = channel.id, id != -1 { + + Model.getAnonymousChannel(by: channel) { [weak self] in + self?.news.dataSourse = $0.posts + self?.news.dictionary = $0.users + self?.refreshContr.endRefreshing() + self?.changedChannelName?(channel.name) + } + + } else { + Model.getLast { [weak self] in + self?.news.dataSourse = $0.posts + self?.news.dictionary = $0.users + self?.refreshContr.endRefreshing() + self?.changedChannelName?("General") + } } - - } else { - - Model.getLast { [weak self] in - self?.posts = $0.posts - self?.dictionary = $0.users - self?.refreshContr.endRefreshing() - self?.navigationItem.title = "General" - } - + default: + refreshContr.endRefreshing() + break } + } func setUpNavigationItemsforPosts(){ - navigationItem.rightBarButtonItems = [UIBarButtonItem(barButtonSystemItem: .compose, target: self, action: #selector(self.writePost(_:))),UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(self.showChannels(_:))) - ] tableView.delegate = news tableView.dataSource = news tableView.reloadData() } - // MARK:- attempt with coordinator - @objc func showChannels(_ barItem: UIBarButtonItem){ - let vc = UIStoryboard.makeChannelsListController() - vc.myProtocol = self - vc.displayingChannel = channel - self.navigationController?.pushViewController(vc, animated: true) - } - - @objc func writePost(_ barItem: UIBarButtonItem) + func updateSearchResults(for searchController: UISearchController) { - let vc = storyboard?.instantiateViewController(withIdentifier: "NewPostViewController") as! NewPostViewController - - vc.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Post", style: .plain, target: vc, action: #selector(vc.newPost)) - vc.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Close", style: .plain, target: vc, action: #selector(vc.closeView)) - - vc.myProtocol = self - - let transition = CATransition() - transition.duration = 0.5 - transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) - transition.type = CATransitionType.moveIn - transition.subtype = CATransitionSubtype.fromTop - navigationController?.view.layer.add(transition, forKey: nil) - navigationController?.pushViewController(vc, animated: false) - } - - // for animating the banner - var topConstraint: NSLayoutConstraint? - - let bannerView: UIView = { - let view = UIView() - view.backgroundColor = .blue - view.layer.cornerRadius = 25.0 - view.clipsToBounds = true - return view - }() - - let statusLabel: UILabel = { - let label = UILabel() - label.text = "-" - label.textColor = .white - label.textAlignment = .center - return label - }() - - // MARK:- banner - private func setUpBanner() - { - view.addSubview(bannerView) - bannerView.addSubview(statusLabel) - statusLabel.edgesToSuperview() - - let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:))) - - bannerView.addGestureRecognizer(tap) - topConstraint = NSLayoutConstraint(item: bannerView, attribute: .top, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .top, multiplier: 1, constant: -300) - view.addConstraint(topConstraint!) - - bannerView.edgesToSuperview(excluding: [.bottom, .top, .left], insets: .right(20), usingSafeArea: true) - bannerView.height(50) - bannerView.width(50) - } - - // when table is scrolling no deletion is availiable - @objc func handleTap(sender: UITapGestureRecognizer? = nil) { - let indexPath = IndexPath(row: 0, section: 0) - self.tableView.scrollToRow(at: indexPath, at: .top, animated: true) -// isBannerVisible = false -// changeConstraint(isVisible: false) - } - - // animation for banner - func changeConstraint(isVisible: Bool){ - topConstraint?.constant = isVisible ? 50 : -300 - - UIView.animate(withDuration: 0.5, delay: 0, options: .curveEaseOut, animations: { - self.view.layoutIfNeeded() - }) { (completed) in - + let filteredData = CompletionTree.getCompletion(tree: Model.hashTagTree!, word: searchController.searchBar.text?.lowercased() ?? "") + + // Apply the filtered results to the search results table. + if let resultsController = searchController.searchResultsController as? TagsSuggestionController { + resultsController.suggestions = filteredData + resultsController.tableView.reloadData() } } - var isBannerVisible: Bool = false + func willPresentSearchController(_ searchController: UISearchController) { + searchController.searchResultsController?.view.isHidden = false; + } - override func scrollViewDidScroll(_ scrollView: UIScrollView) { - print(scrollView.contentOffset.y) - if scrollView.contentOffset.y >= 50 && !isBannerVisible{ - isBannerVisible = true - changeConstraint(isVisible: true) - } - - if isBannerVisible && scrollView.contentOffset.y == 0{ - isBannerVisible = false - changeConstraint(isVisible: false) - } + func didPresentSearchController(_ searchController: UISearchController) { + searchController.searchResultsController?.view.isHidden = false; + } + + func didDismissSearchController(_ searchController: UISearchController) { + searchController.searchBar.text = "" } } diff --git a/GDproject/Controller/ News and channels/NewsVC.swift b/GDproject/Controller/ News and channels/NewsVC.swift index 5997c0a..71e7c94 100644 --- a/GDproject/Controller/ News and channels/NewsVC.swift +++ b/GDproject/Controller/ News and channels/NewsVC.swift @@ -19,20 +19,40 @@ struct PostCellData{ let markdownParser = MarkdownParser(font: UIFont.systemFont(ofSize: 16)) markdownParser.enabledElements = .disabledAutomaticLink - markdownParser.code.textBackgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1) + markdownParser.code.font = UIFont(name: "Menlo", size: 16) + markdownParser.code.textBackgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1) + markdownParser.code.color = #colorLiteral(red: 0.2549019754, green: 0.2745098174, blue: 0.3019607961, alpha: 1) return markdownParser.parse(markdown) } } -class NewsVC: UIViewController, UITableViewDelegate, UITableViewDataSource{ +class NewsVC: UIViewController, UITableViewDelegate, UITableViewDataSource { + + + var onChannelDidChange: ((([Int: Model.Users],[Model.Posts]))->())? + + var onFullPost: ((HeaderType, Model.Posts)->())? + + var dictionary: [Int: Model.Users] = [:] { + didSet { + + dataSourse = dataSourse.map { + var copy = $0 + copy.user = dictionary[$0.authorId] + return copy + } + + (viewController as? NewsController)?.tableView.reloadData() + (viewController as? ProfileViewController)?.tableView.reloadData() + } + } + + var currChannel : Model.Channels? var dataSourse: [Model.Posts] = [] { didSet { - cellDataSourse = [] - dataSourse.forEach { (item) in - cellDataSourse.append(PostCellData(attributedData: PostCellData.create(with: item.body))) - } + cellDataSourse = dataSourse.map { PostCellData(attributedData: PostCellData.create(with: $0.body)) } } } @@ -48,11 +68,7 @@ class NewsVC: UIViewController, UITableViewDelegate, UITableViewDataSource{ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - let vc = viewController!.storyboard!.instantiateViewController(withIdentifier: fullPostControllerId) as! FullPostController - vc.type = type - vc.post = dataSourse[indexPath.row] - viewController!.navigationController!.pushViewController(vc, animated: true) + onFullPost?(type, dataSourse[indexPath.row]) } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { @@ -72,9 +88,14 @@ class NewsVC: UIViewController, UITableViewDelegate, UITableViewDataSource{ vc.idProfile = id self?.viewController!.navigationController!.pushViewController(vc, animated: true) } - case .NONE: + + cell.onAnonymousChannelDisplay = { + [weak self] in + (self?.viewController as? UpdateableWithChannel)?.updateChannel(on: Model.Channels(people: [], name: $0, id: 0, tags: [$0])) + } + default: cell.onUserDisplay = { (id) in - print("tapped when profile is open already \(id)") + print("tapped when profile is opened already \(id)") } } @@ -89,10 +110,37 @@ class NewsVC: UIViewController, UITableViewDelegate, UITableViewDataSource{ func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { return 100.0 } -} + + var prevLast = -1 + + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) + { + // pagination + if indexPath.row == cellDataSourse.count - 1 && prevLast != indexPath.row + { + if let ch = currChannel, let id = ch.id, id != -1 { + // check this! + Model.getAnonymousChannel(by: ch, exclusiveFrom: dataSourse.last?.id) { [weak self] in + self?.dataSourse.append(contentsOf: $0.posts) + $0.users.forEach { self?.dictionary[$0.key] = $0.value } + } + + } else { + // check this! + Model.getLast(on: 10, from: dataSourse.last?.id ) + { [weak self] in + self?.dataSourse.append(contentsOf: $0.posts) + $0.users.forEach { self?.dictionary[$0.key] = $0.value } + } + } + prevLast = indexPath.row + } + } +} enum HeaderType { case NONE case NEWS + case ANONYMOUS } diff --git a/GDproject/Controller/ News and channels/PostViewCell.swift b/GDproject/Controller/ News and channels/PostViewCell.swift index 672cade..8565439 100644 --- a/GDproject/Controller/ News and channels/PostViewCell.swift +++ b/GDproject/Controller/ News and channels/PostViewCell.swift @@ -8,13 +8,15 @@ import UIKit import Cartography -import MarkdownKit import TinyConstraints class PostViewCell: UITableViewCell { + var onUserDisplay: ((Int)->())? + var onAnonymousChannelDisplay: ((String)->())? + let nameLabel: UIButton = { let button = UIButton() button.setTitleColor(.black, for: .normal) @@ -40,6 +42,7 @@ class PostViewCell: UITableViewCell let shareButton: UIButton = { let button = UIButton(type: UIButton.ButtonType.detailDisclosure) + button.isHidden = true return button }() @@ -84,7 +87,7 @@ class PostViewCell: UITableViewCell nameLabel.setTitle("\(post.user?.firstName ?? "") \(post.user?.lastName ?? "")", for: .normal) nameLabel.addTarget(self, action: #selector(displayProfile), for: .touchUpInside) if let user = post.user{ - fullNameLabel.text = "\(user.firstName) \(user.middleName) \(user.lastName)" + fullNameLabel.text = "\(user.faculty.name)" } else { fullNameLabel.text = "\(post.authorId)" @@ -120,8 +123,8 @@ class PostViewCell: UITableViewCell for hash in hashtags { let button = UIButton() button.setTitle("#" + hash, for: .normal) - //button.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1) - //button.layer.cornerRadius = 10 + button.addTarget(self, action: #selector(setAnonymousChannel(on:)), for: .touchUpInside) + // button.addGestureRecognizer(longPressRecognizer) button.titleLabel?.font = UIFont.monospacedDigitSystemFont(ofSize: 15, weight: .semibold) button.setTitleColor(#colorLiteral(red: 0, green: 0.4784313725, blue: 1, alpha: 1), for: .normal) buttons.append(button) @@ -179,7 +182,13 @@ class PostViewCell: UITableViewCell } @objc func displayProfile(){ - print("buttonTapped") - onUserDisplay?(post!.authorId) + if let id = post?.authorId { + onUserDisplay?(id) + } + } + + @objc func setAnonymousChannel(on button: UIButton){ + print("\(button.titleLabel?.text ?? "nothing")") + onAnonymousChannelDisplay?(String(button.titleLabel!.text!.dropFirst())) } } diff --git a/GDproject/Controller/ News and channels/TaggsSelectionViewController.swift b/GDproject/Controller/ News and channels/TaggsSelectionViewController.swift new file mode 100644 index 0000000..af3aff7 --- /dev/null +++ b/GDproject/Controller/ News and channels/TaggsSelectionViewController.swift @@ -0,0 +1,106 @@ +// +// TaggsSelectionViewController.swift +// GDproject +// +// Created by cstore on 03/05/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import UIKit +import TaggerKit + +protocol TagsReceiver: class { + func receiveTags(tags: [String]) +} + +class TaggsSelectionViewController: UIViewController { + + @IBOutlet weak var addTagsTextField: TKTextField! + + @IBOutlet weak var searchContainerView: UIView! + + @IBOutlet weak var testContainer: UIView! + + // We want the whole experience, let's create two TKCollectionViews + let productTags = TKCollectionView() + let allTags = TKCollectionView() + + var currentTags: [String]? + weak var receiver: TagsReceiver? + + // MARK: - Lifecycle Methods + override func viewDidLoad() { + super.viewDidLoad() + + navigationItem.title = "Hashtags" + addTagsTextField.delegate = self + // Customisation example + // testCollection.customFont = UIFont.boldSystemFont(ofSize: 14) // Custom font + // testCollection.customCornerRadius = 14.0 // Corner radius of tags + // testCollection.customSpacing = 20.0 // Spacing between cells + // testCollection.customBackgroundColor = UIColor.red // Background of cells + + + // These are the tags already added by the user, give an aray of strings to the collection + if let tags = currentTags { + productTags.tags = tags + } + // These are intended to be all the tags the user has added in the app, which are going to be filtered + allTags.tags = CompletionTree.getCompletion(tree: Model.hashTagTree!, word: "") + + /* + We set this collection's action to .removeTag, + becasue these are supposed to be the tags the user has already added + */ + productTags.action = .removeTag + + + // Set the current controller as the delegate of both collections + productTags.delegate = self + allTags.delegate = self + + // "testCollection" takes the tags sent by "searchCollection" + allTags.receiver = productTags + + // The tags in "searchCollection" are going to be added, so we set the action to addTag + allTags.action = .addTag + + + // Set the sender and receiver of the TextField + addTagsTextField.sender = allTags + addTagsTextField.receiver = productTags + + add(productTags, toView: testContainer) + add(allTags, toView: searchContainerView) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + if let receiver = receiver{ + receiver.receiveTags(tags: productTags.tags) + } + } + + /* + These methods come from UIViewController now conforming to TKCollectionViewDelegate, + You use these to do whatever you want when a tag is added or removed (e.g. save to file, etc) + */ + override func tagIsBeingAdded(name: String?) { + // Example: save testCollection.tags to UserDefault + print("added \(name!)") + } + + override func tagIsBeingRemoved(name: String?) { + print("removed \(name!)") + } +} + +// textField deletage to close keyboard on return +extension TaggsSelectionViewController: UITextFieldDelegate +{ + func textFieldShouldReturn(_ textField: UITextField) -> Bool + { + self.view.endEditing(true) + return false + } +} diff --git a/GDproject/Controller/ News and channels/TagsSuggestionController.swift b/GDproject/Controller/ News and channels/TagsSuggestionController.swift new file mode 100644 index 0000000..1145b6b --- /dev/null +++ b/GDproject/Controller/ News and channels/TagsSuggestionController.swift @@ -0,0 +1,52 @@ +// +// AnonimousChannelController.swift +// GDproject +// +// Created by cstore on 20/04/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import UIKit + +class TagsSuggestionController: UITableViewController +{ + weak var delegate: UpdateableWithChannel? + + var suggestions = [String]() + var channel: Model.Channels? + + override func viewDidLoad() + { + super.viewDidLoad() + if let tree = Model.hashTagTree { + suggestions = CompletionTree.getCompletion(tree: tree, word: "") + print(suggestions) + } + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") + tableView.reloadData() + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return suggestions.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell + { + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) + cell.textLabel?.text = suggestions[indexPath.row] + + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) + { + channel = Model.Channels(people: [], name: "\(self.suggestions[indexPath.row])", id: 0, tags: [self.suggestions[indexPath.row]]) + + delegate?.updateChannel(on: channel!) + self.dismiss(animated: true) + } +} diff --git a/GDproject/Controller/Coordinator tab bar?/TabbarCoordinator.swift b/GDproject/Controller/Coordinator tab bar?/TabbarCoordinator.swift deleted file mode 100644 index c4b7dba..0000000 --- a/GDproject/Controller/Coordinator tab bar?/TabbarCoordinator.swift +++ /dev/null @@ -1,14 +0,0 @@ -import UIKit - -class TabbarCoordinator{ - var window: UIWindow! - - func start(){ - let tabbar = UIStoryboard.tabBarController() - window.rootViewController = tabbar - } - - init(window: UIWindow) { - self.window = window - } -} diff --git a/GDproject/Controller/Coordinators/ApplicationCoordinator.swift b/GDproject/Controller/Coordinators/ApplicationCoordinator.swift new file mode 100644 index 0000000..fa3c122 --- /dev/null +++ b/GDproject/Controller/Coordinators/ApplicationCoordinator.swift @@ -0,0 +1,69 @@ +// +// ApplicationCoordinator.swift +// RxSwift +// +// Created by cstore on 04/03/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import Foundation +import UIKit + + + +fileprivate enum LaunchInstructor { + case main, auth + + static func configure( + isAutorized: Bool = DataStorage.standard.isLoggedIn) -> LaunchInstructor { + + if isAutorized{ + return .main + } else { + return .auth + } + } +} + +final class ApplicationCoordinator: BaseCoordinator{ + private var window: UIWindow! + + init(window: UIWindow) { + self.window = window + } + + private var instructor: LaunchInstructor { + return LaunchInstructor.configure() + } + + override func start() { + switch instructor { + case .auth: runAuthFlow() + case .main: runMainFlow() + } + } + + private func runAuthFlow() { + let coordinator = LogInCoordinator(window: window) + coordinator.didEndFlow = { [weak self, weak coordinator] in + self?.start() + self?.removeDependency(coordinator) + } + addDependency(coordinator) + coordinator.start() + } + + private func runMainFlow() { + let storyboard = UIStoryboard(name: "Main", bundle: nil) + let tabBar = storyboard.instantiateViewController(withIdentifier: "TabbarController") as! TabbarController + let coordinator = TabBarCoordinator(tabbarView: tabBar, window: window!) + + coordinator.didEndFlow = { [weak self, weak coordinator] in + self?.start() + self?.removeDependency(coordinator) + } + + addDependency(coordinator) + coordinator.start() + } +} diff --git a/GDproject/Controller/Coordinators/BaseCoordinator.swift b/GDproject/Controller/Coordinators/BaseCoordinator.swift new file mode 100644 index 0000000..4994c58 --- /dev/null +++ b/GDproject/Controller/Coordinators/BaseCoordinator.swift @@ -0,0 +1,47 @@ +// +// BaseCoordinator.swift +// RxSwift +// +// Created by cstore on 02/03/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import Foundation +import UIKit + +protocol Coordinator: class { + func start() +} + +class BaseCoordinator: Coordinator { + + var childCoordinators: [Coordinator] = [] + + func start() { + + } + + // add only unique object + func addDependency(_ coordinator: Coordinator) { + guard !childCoordinators.contains(where: { $0 === coordinator }) else { return } + childCoordinators.append(coordinator) + } + + func removeDependency(_ coordinator: Coordinator?) { + guard + childCoordinators.isEmpty == false, + let coordinator = coordinator + else { return } + + // Clear child-coordinators recursively + if let coordinator = coordinator as? BaseCoordinator, !coordinator.childCoordinators.isEmpty { + coordinator.childCoordinators + .filter({ $0 !== coordinator }) + .forEach({ coordinator.removeDependency($0) }) + } + for (index, element) in childCoordinators.enumerated() where element === coordinator { + childCoordinators.remove(at: index) + break + } + } +} diff --git a/GDproject/Controller/Coordinators/ChannelsCoordinator.swift b/GDproject/Controller/Coordinators/ChannelsCoordinator.swift new file mode 100644 index 0000000..50b741f --- /dev/null +++ b/GDproject/Controller/Coordinators/ChannelsCoordinator.swift @@ -0,0 +1,139 @@ +// +// ChannelsCoordinator.swift +// RxSwift +// +// Created by cstore on 03/03/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import Foundation +import UIKit +import Pulley + +class ChannelsCoordinator: BaseCoordinator{ + let storyboard = UIStoryboard(name: "Main", bundle: nil) + private weak var navigationController: UINavigationController? + + init(nc: UINavigationController) { + self.navigationController = nc + navigationController?.navigationItem.largeTitleDisplayMode = .always + } + + override func start() { + show() + } + + private func show() { + + let channels = storyboard.instantiateViewController(withIdentifier: channelListControllerId) as! ChannelListController + + channels.onChannelSelected = { [unowned self] + (channel) in + self.navigationController?.pushViewController(self.presentNewsController(with: channel), animated: true) + } + + channels.askForUpdates() + + channels.onEditingModeBegins = { [unowned self] (channel, indexPath) in + let vc = self.presentChannelController(with: channel) + self.navigationController?.pushViewController(vc, animated: true) + vc.tabBarController?.tabBar.isHidden = true + } + + let nc = presentNewsController() + navigationController?.setViewControllers([channels, nc], animated: false) + } + + /// Function that presents channel controller + /// + /// - Parameter channel: channel that needs to be displayed + func presentChannelController(with channel: Model.Channels? = nil) -> ChannelViewController { + let mainContentVC = storyboard.instantiateViewController(withIdentifier: channelViewControllerId) as! ChannelViewController + + // to show preview we need to instantiate newsController but with different functionality + mainContentVC.onShowingPreview = { [weak mainContentVC, unowned self] ch in + let vc = self.presentNewsController(with: ch, previewMode: true) + mainContentVC?.navigationController?.pushViewController(vc, animated: true) + } + + // to go for choosing hashtags + mainContentVC.onChoosingHashTags = { [weak mainContentVC, unowned self] ch in + let vc = self.storyboard.instantiateViewController(withIdentifier: taggsSelectionViewController) as! TaggsSelectionViewController + + vc.currentTags = ch + vc.receiver = mainContentVC + + self.navigationController?.pushViewController(vc, animated: true) + } + + mainContentVC.onChoosingPeople = { [weak mainContentVC, unowned self] channel in + let vc = self.storyboard.instantiateViewController(withIdentifier: addToChannelVCId) as! AddToChannelVC + + vc.channel = channel + vc.update = mainContentVC + + self.navigationController?.pushViewController(vc, animated: true) + } + + mainContentVC.channel = channel + return mainContentVC + } + + func presentNewsController(with channel: Model.Channels? = nil, previewMode: Bool = false) -> NewsController + { + let mainContentVC = storyboard.instantiateViewController(withIdentifier: newsController) as! NewsController + mainContentVC.channel = channel + + if !previewMode { + mainContentVC.news.onFullPost = { + [weak self] (type,post) in + + let vc = self?.storyboard.instantiateViewController(withIdentifier: fullPostControllerId) as! FullPostController + vc.type = type + vc.post = post + self?.navigationController!.pushViewController(vc, animated: true) + } + + mainContentVC.news.onChannelDidChange = { + print("anon with \($0.0.count) users") + } + + mainContentVC.navigationItem.rightBarButtonItems = [UIBarButtonItem(barButtonSystemItem: .compose, target: self, action: #selector(writePost(_:))) + ] + } + + mainContentVC.changedChannelName = { + [weak mainContentVC] (title) in mainContentVC?.navigationItem.title = title + } + + return mainContentVC + } + + @objc private func writePost(_ barItem: UIBarButtonItem) + { + let vc = storyboard.instantiateViewController(withIdentifier: "NewPostViewController") as! NewPostViewController + + vc.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Post", style: .plain, target: vc, action: #selector(vc.newPost)) + vc.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Close", style: .plain, target: vc, action: #selector(vc.closeView)) + + let transition = CATransition() + transition.duration = 0.5 + transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) + transition.type = CATransitionType.moveIn + transition.subtype = CATransitionSubtype.fromTop + navigationController?.view.layer.add(transition, forKey: nil) + navigationController?.pushViewController(vc, animated: false) + + vc.moveBackToParentVC = { + [weak self] in + + let transition = CATransition() + transition.duration = 0.5 + transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) + transition.type = CATransitionType.reveal + transition.subtype = CATransitionSubtype.fromBottom + self?.navigationController?.view.layer.add(transition, forKey: nil) + self?.navigationController?.popViewController(animated: false) + } + } +} diff --git a/GDproject/Controller/Coordinators/LogInCoordinator.swift b/GDproject/Controller/Coordinators/LogInCoordinator.swift new file mode 100644 index 0000000..e58e6c0 --- /dev/null +++ b/GDproject/Controller/Coordinators/LogInCoordinator.swift @@ -0,0 +1,70 @@ +// +// LogInCoordinator.swift +// RxSwift +// +// Created by cstore on 01/03/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import UIKit + +class LogInCoordinator: BaseCoordinator { + + let storyboard = UIStoryboard(name: "Main", bundle: nil) + + var didEndFlow: (()->())? + var window: UIWindow! + var navigationController: UINavigationController? + + init(window: UIWindow) { + self.window = window + self.window?.rootViewController = UINavigationController() + self.navigationController = window.rootViewController as? UINavigationController + } + + override func start(){ + let logInVC = LogInViewController() + logInVC.authenticate = { [weak self, weak logInVC] (email) in + + DataStorage.standard.setEmail(email: email) + Model.authenticate(with: email) { [weak self] + (authStatus) in + + print(authStatus.userStatus) + + if (authStatus.userStatus == "invalid") { + logInVC?.presentAlertInvalidCode() + } + else if (authStatus.userStatus == "canRegister") { // register form + self?.presentRegisterVC() + } else { // validation code + self?.presentViewControllerForCodeInput() + } + } + } + + navigationController?.pushViewController(logInVC, animated: false) + } + + func presentViewControllerForCodeInput() + { + let vc = storyboard.instantiateViewController(withIdentifier: "CodeViewController") as! CodeViewController + self.navigationController?.pushViewController(vc, animated: true) + vc.onSuccessLogIn = { + [weak self] in self?.didEndFlow?() + } + } + + func presentRegisterVC(){ + + let vc1 = storyboard.instantiateViewController(withIdentifier: "RegisterTableViewController") as! RegisterTableViewController + + vc1.onRegistration = { [weak self] in + self?.presentViewControllerForCodeInput() + } + + self.navigationController?.pushViewController(vc1, animated: true) + } + + +} diff --git a/GDproject/Controller/Coordinators/MessagesCoordinator.swift b/GDproject/Controller/Coordinators/MessagesCoordinator.swift new file mode 100644 index 0000000..7e81436 --- /dev/null +++ b/GDproject/Controller/Coordinators/MessagesCoordinator.swift @@ -0,0 +1,87 @@ +// +// MessagesCoordinator.swift +// GDproject +// +// Created by cstore on 01/05/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + + +import Foundation +import UIKit + +class MessagesCoordinator: BaseCoordinator { + + var didEndSession: (()->())? + + let storyboard = UIStoryboard(name: "Main", bundle: nil) + private weak var navigationController: UINavigationController? + + init(nc: UINavigationController) { + self.navigationController = nc + } + + override func start() { + show() + } + + private func show(){ + let vc = storyboard.instantiateViewController(withIdentifier: messagesViewControllerId) as! MessagesViewController + + // choose person to write message to + vc.onUserDisplayList = { [weak vc, unowned self] in + + let newVC = self.storyboard.instantiateViewController(withIdentifier: peopleToWriteVC) as! PeopleToWriteViewController + + newVC.whatToDoWithSelection = { [weak newVC, weak self] people in + newVC?.navigationController?.popViewController(animated: true) + + // detect is it a user or group dialog + let count = people.count + if count == 1 { + + let user = people.first!.key + let createdDialog = Model.Dialog.userChat(Model.UserChat(user: user)) + + let vc = DialogViewController() + vc.users = Model.Channels.fullPeopleDict + vc.dialog = createdDialog + self?.navigationController?.pushViewController(vc, animated: true) + + } else { + var group = Model.Group(users: people, name: "Untitled", id: 0) + group.users[DataStorage.standard.getUserId()] = Model.UserPermission(isAdmin: true) + + Model.createGroupChat(from: group, completion: { [weak self] id in + + let newVC1 = DialogViewController() + group.id = id + newVC1.users = Model.Channels.fullPeopleDict + newVC1.dialog = Model.Dialog.groupChat(Model.GroupChat(group: group)) + self?.navigationController?.pushViewController(newVC1, animated: true) + }) + } + } + + vc?.navigationController?.pushViewController(newVC, animated: true) + } + + vc.onDialogDisplay = { [weak vc] in + + let newVC = DialogViewController() + newVC.dialog = $0.dialog + newVC.users = $0.users + newVC.onUserDisplay = { [weak self] id in + + let vc = self?.storyboard.instantiateViewController(withIdentifier: profileViewController) as! ProfileViewController + vc.idProfile = id + self?.navigationController!.pushViewController(vc, animated: true) + + } + + vc?.navigationController?.pushViewController(newVC, animated: true) + } + + navigationController?.viewControllers = [vc] + } +} diff --git a/GDproject/Controller/Coordinators/ProfileCoordinator.swift b/GDproject/Controller/Coordinators/ProfileCoordinator.swift new file mode 100644 index 0000000..1a18aca --- /dev/null +++ b/GDproject/Controller/Coordinators/ProfileCoordinator.swift @@ -0,0 +1,60 @@ +// +// ProfileCoordinator.swift +// RxSwift +// +// Created by cstore on 02/03/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import Foundation +import UIKit + +class ProfileCoordinator: BaseCoordinator { + + var didEndSession: (()->())? + + private weak var navigationController: UINavigationController? + + init(nc: UINavigationController) { + self.navigationController = nc + } + + override func start() { + show() + } + + private func show() { + let storyboard = UIStoryboard(name: "Main", bundle: nil) + let profile = storyboard.instantiateViewController(withIdentifier: "ProfileViewController") as! ProfileViewController + + profile.logOut = { + Model.logout() { + [weak self] in + + DataStorage.standard.setIsLoggedIn(value: false, with: 0) + self?.didEndSession?() + } + } + + profile.deleteAllSessions = { + Model.deactivateAll() { + [weak self] in + + DataStorage.standard.setIsLoggedIn(value: false, with: 0) + self?.didEndSession?() + } + } + + profile.onSettings = { [weak self, weak storyboard, weak profile] in + + let vc = storyboard?.instantiateViewController(withIdentifier: "RegisterTableViewController") as! RegisterTableViewController + vc.delegate = profile + vc.userActive = profile?.user + + self?.navigationController?.pushViewController(vc, animated: true) + } + + + navigationController?.setViewControllers([profile], animated: false) + } +} diff --git a/GDproject/Controller/Coordinators/TabbarController.swift b/GDproject/Controller/Coordinators/TabbarController.swift new file mode 100644 index 0000000..b02b09c --- /dev/null +++ b/GDproject/Controller/Coordinators/TabbarController.swift @@ -0,0 +1,50 @@ +// +// TabbarController.swift +// RxSwift +// +// Created by cstore on 03/03/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import Foundation +import UIKit + +protocol TabbarView: class { + var onChannelsFlowSelect: ((UINavigationController) -> ())? { get set } + var onProfileFlowSelect: ((UINavigationController) -> ())? { get set } + var onMessagesFlowSelect: ((UINavigationController) -> ())? { get set } + var onViewDidLoad: ((UINavigationController) -> ())? { get set } +} + +final class TabbarController: UITabBarController, UITabBarControllerDelegate, TabbarView { + + + var onChannelsFlowSelect: ((UINavigationController) -> ())? + var onProfileFlowSelect: ((UINavigationController) -> ())? + var onMessagesFlowSelect: ((UINavigationController) -> ())? + + var onViewDidLoad: ((UINavigationController) -> ())? + + override func viewDidLoad() { + super.viewDidLoad() + + delegate = self + if let controller = customizableViewControllers?.first as? UINavigationController { + onViewDidLoad?(controller) + } + } + + func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) + { + + guard let controller = viewControllers?[selectedIndex] as? UINavigationController else { return } + + if selectedIndex == 0 { + onChannelsFlowSelect?(controller) + } else if selectedIndex == 2 { + onProfileFlowSelect?(controller) + } else { + onMessagesFlowSelect?(controller) + } + } +} diff --git a/GDproject/Controller/Coordinators/TabbarCoordinator.swift b/GDproject/Controller/Coordinators/TabbarCoordinator.swift new file mode 100644 index 0000000..8ce6c93 --- /dev/null +++ b/GDproject/Controller/Coordinators/TabbarCoordinator.swift @@ -0,0 +1,68 @@ +// +// TabBarCoordinator.swift +// RxSwift +// +// Created by cstore on 02/03/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import Foundation +import UIKit + + +class TabBarCoordinator: BaseCoordinator { + + var didEndFlow: (()->())? + + private let tabbarView: TabbarView + private weak var window: UIWindow? + + init(tabbarView: TabbarView, window: UIWindow) { + self.tabbarView = tabbarView + self.window = window + } + + override func start() { + tabbarView.onViewDidLoad = runChannelsFlow() + tabbarView.onChannelsFlowSelect = runChannelsFlow() + tabbarView.onProfileFlowSelect = runProfileFlow() + tabbarView.onMessagesFlowSelect = runMessagesFlow() + window?.rootViewController = tabbarView as! TabbarController + } + + private func runChannelsFlow() -> ((UINavigationController) -> ()) + { + return { [unowned self] navController in + if navController.viewControllers.isEmpty == true { + let channelCoordinator = ChannelsCoordinator(nc: navController) + self.addDependency(channelCoordinator) + channelCoordinator.start() + } + } + } + + private func runMessagesFlow() -> ((UINavigationController) -> ()){ + return { [unowned self] navController in + if navController.viewControllers.isEmpty == true { + let messagesCoordinator = MessagesCoordinator(nc: navController) + self.addDependency(messagesCoordinator) + messagesCoordinator.start() + } + } + } + + private func runProfileFlow() -> ((UINavigationController) -> ()) + { + return { [unowned self] navController in + if navController.viewControllers.isEmpty == true { + let profileCoordinator = ProfileCoordinator(nc: navController) + profileCoordinator.didEndSession = { [weak self, weak profileCoordinator] in + self?.removeDependency(profileCoordinator) + self?.didEndFlow?() + } + self.addDependency(profileCoordinator) + profileCoordinator.start() + } + } + } +} diff --git a/GDproject/Controller/Log In/CodeViewController.swift b/GDproject/Controller/Log In/CodeViewController.swift new file mode 100644 index 0000000..ac2b60c --- /dev/null +++ b/GDproject/Controller/Log In/CodeViewController.swift @@ -0,0 +1,144 @@ +// +// CodeViewController.swift +// GDproject +// +// Created by cstore on 10/05/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import UIKit +import TinyConstraints + +class CodeViewController: UIViewController { + + @IBOutlet weak var f1: UITextField! + @IBOutlet weak var f2: UITextField! + @IBOutlet weak var f3: UITextField! + @IBOutlet weak var f4: UITextField! + @IBOutlet weak var f5: UITextField! + @IBOutlet weak var f6: UITextField! + + var loading = UIActivityIndicatorView(style: .gray) + + var onSuccessLogIn: (()->())? + + override func viewDidLoad() { + super.viewDidLoad() + setUpConstraint() + + navigationItem.title = DataStorage.standard.getEmail() + + f1.addTarget(self, action: #selector(textFiledDidChange(textField:)), for: .editingChanged) + f2.addTarget(self, action: #selector(textFiledDidChange(textField:)), for: .editingChanged) + f3.addTarget(self, action: #selector(textFiledDidChange(textField:)), for: .editingChanged) + f4.addTarget(self, action: #selector(textFiledDidChange(textField:)), for: .editingChanged) + f5.addTarget(self, action: #selector(textFiledDidChange(textField:)), for: .editingChanged) + f6.addTarget(self, action: #selector(textFiledDidChange(textField:)), for: .editingChanged) + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + loading.stopAnimating() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + f1.becomeFirstResponder() + } + + @objc func textFiledDidChange(textField: UITextField) { + let text = textField.text + + // only 1 char + if text?.utf16.count == 1 { + switch textField { + case f1: + f2.becomeFirstResponder() + case f2: + f3.becomeFirstResponder() + case f3: + f4.becomeFirstResponder() + case f4: + f5.becomeFirstResponder() + case f5: + f6.becomeFirstResponder() + case f6: + f6.resignFirstResponder() + default: + break + } + } + } + + + func presentAlertInvalidData(with text: String) + { + let alert = UIAlertController(title: "Invalid code", message: text, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + self.present(alert, animated: true, completion: nil) + + loading.stopAnimating() + } + + @IBOutlet weak var bottomConstraint: NSLayoutConstraint! + + @IBAction func whereToGoNext(_ sender: UIButton) + { + 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 { + presentAlertInvalidData(with: "Some fields are missing"); + return + } + + if f1t.isEmpty || f2t.isEmpty || f3t.isEmpty || f4t.isEmpty || f5t.isEmpty || f6t.isEmpty { + presentAlertInvalidData(with: "Some fields are missing"); + return + } + + let code = "\(f1.text!)\(f2.text!)\(f3.text!)\(f4.text!)\(f5.text!)\(f6.text!)" + if let codeToInt = Int(code) + { + loading.startAnimating() + Model.verify(with: codeToInt) { [weak self] in + // if everything is okay we can authemticicate + if !$0 { + self?.presentAlertInvalidData(with: "Wrong code. Try again!") + self?.loading.stopAnimating() + return + } else { + // Model.authemticiateMe + Model.authenticateMe { [weak self] (res) in + if res { + self?.onSuccessLogIn?() + } + } + return + } + } + } + } + + func setUpConstraint() + { + self.view.addSubview(loading) + loading.centerInSuperview() + // for keyboard notifications + NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotifications), name: UIResponder.keyboardWillShowNotification, object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotifications), name: UIResponder.keyboardWillHideNotification, object: nil) + } + + @objc func handleKeyboardNotifications(notification: NSNotification){ + if let userInfo = notification.userInfo { + // UIKeyboardFrameEndUserInfoKey + let keyBoardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue + bottomConstraint.constant = notification.name == UIResponder.keyboardWillShowNotification ? keyBoardFrame.height - self.view.safeAreaInsets.bottom : 0 + + UIView.animate(withDuration: 0, delay: 0, options: .curveEaseOut, animations: { + self.view.layoutIfNeeded() + }) + } + } + +} diff --git a/GDproject/Controller/Log In/FacultyTableViewController.swift b/GDproject/Controller/Log In/FacultyTableViewController.swift new file mode 100644 index 0000000..50e6905 --- /dev/null +++ b/GDproject/Controller/Log In/FacultyTableViewController.swift @@ -0,0 +1,88 @@ +// +// FacultyTableViewController.swift +// GDproject +// +// Created by cstore on 10/05/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import UIKit + +protocol ChosenFactulty: class { + func onChooseFaculty(faculty: Model.Faculty) +} + +class FacultyTableViewController: UITableViewController { + + weak var delegate: ChosenFactulty? + + var currentFaculties: [Model.Faculty] = []{ + didSet { + tableView.reloadData() + } + } + + var isFiltering: Bool { + return searchController.isActive && !searchBarIsEmpty() + } + + func searchBarIsEmpty() -> Bool { + return searchController.searchBar.text?.isEmpty ?? true + } + + var searchController = UISearchController(searchResultsController: nil) + + override func viewDidLoad() { + super.viewDidLoad() + + self.navigationItem.title = "Faculties" + self.navigationItem.searchController = searchController + self.navigationItem.hidesSearchBarWhenScrolling = false + searchController.obscuresBackgroundDuringPresentation = false + + setUpTimer() + } + + func setUpTimer(){ + Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { (timer) in + DispatchQueue.main.async { [weak self] in + + if self!.isFiltering { + Model.searchFaculty(string: (self?.searchController.searchBar.text)!) { [weak self] in + self?.currentFaculties = $0 + } + } + } + } + } + + + // MARK: - Table view data source + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return currentFaculties.count + } + + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell + { + let cell = tableView.dequeueReusableCell(withIdentifier: "F", for: indexPath) + + cell.textLabel?.text = currentFaculties[indexPath.row].name + cell.detailTextLabel?.text = currentFaculties[indexPath.row].campusName + cell.selectionStyle = .none + + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + delegate?.onChooseFaculty(faculty: currentFaculties[indexPath.row]) + searchController.dismiss(animated: true, completion: nil) + self.navigationController?.popViewController(animated: true) + } + +} diff --git a/GDproject/Controller/Log In/InititalsViewCell.swift b/GDproject/Controller/Log In/InititalsViewCell.swift new file mode 100644 index 0000000..8cdbf80 --- /dev/null +++ b/GDproject/Controller/Log In/InititalsViewCell.swift @@ -0,0 +1,23 @@ +// +// InititalsViewCell.swift +// GDproject +// +// Created by cstore on 10/05/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import UIKit + +class InititalsViewCell: UITableViewCell { + + @IBOutlet weak var initialsTextField: UITextField! + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + func fill(with name: String){ + initialsTextField.text = name + } +} diff --git a/GDproject/Controller/Log In/LogInCoordinator.swift b/GDproject/Controller/Log In/LogInCoordinator.swift deleted file mode 100644 index 90c7266..0000000 --- a/GDproject/Controller/Log In/LogInCoordinator.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// LogInCoordinator.swift -// GDproject -// -// Created by cstore on 23/02/2019. -// Copyright © 2019 drHSE. All rights reserved. -// - -import UIKit - -class LogInCoordinator{ - - private weak var navigationController: UINavigationController? - private var window: UIWindow! - // MARK:- Init - init(navigationController: UINavigationController, window: UIWindow) { - self.navigationController = navigationController - self.window = window - } - - func start(){ - showLogInPage() - } - - // MARK: - Private implementation -// private func showStatusPage(){ -// let controller = TabbarCoordinator(window: window) -// controller.start() -// } - - private func showLogInPage(){ - let controller = UIStoryboard.makeLogIn() - - controller.onLogIn = { - (id) in Model.authenticate(with: id) { - (res) in - - if (res) { DataStorage.standard.setUserKey(with: id) } - else { DataStorage.standard.setUserKey(with: 0) } - - controller.authenticateSucceeded = res - } - } - - navigationController?.pushViewController(controller, animated: false) - } - -} diff --git a/GDproject/Controller/Log In/LogInViewController.swift b/GDproject/Controller/Log In/LogInViewController.swift index a7130c4..08d5f00 100644 --- a/GDproject/Controller/Log In/LogInViewController.swift +++ b/GDproject/Controller/Log In/LogInViewController.swift @@ -1,128 +1,135 @@ // // LogInViewController.swift -// NewsFeed +// RxSwift // -// Created by cstore on 20/01/2019. +// Created by cstore on 01/03/2019. // Copyright © 2019 drHSE. All rights reserved. // import UIKit +import TinyConstraints +import ReactiveSwift +import ReactiveCocoa +import Result class LogInViewController: UIViewController { - @IBOutlet weak var mailTextField: UITextField! + var loading = UIActivityIndicatorView(style: .gray) - @IBOutlet weak var indicatorView: UIActivityIndicatorView! + var authenticate: ((String)->())? - var onLogIn: ((Int)->())? - - var authenticateSucceeded: Bool? { - didSet { - if !authenticateSucceeded! { - indicatorView.stopAnimating() - indicatorView.isHidden = true - } - } - } - - static let titleColor = UIColor(red: 0, green: 137/255, blue: 249/255, alpha: 0.5) - - var bottomConstraint: NSLayoutConstraint? - - let logInButton: UIButton = { - let button = UIButton(type: .system) - button.setTitle("Log In", for: .normal) - button.setTitleColor(titleColor, for: .normal) - button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16) - button.isEnabled = false - button.addTarget(self, action: #selector(activateLogInProcess), for: .touchUpInside) - return button - }() - - let keyboardBar: UIView = { - let view = UIView() - view.backgroundColor = .white - return view - }() - - override func viewDidLoad() { - super.viewDidLoad() - setUpView() - configureTapgesture() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) + func presentAlertInvalidCode() + { + let alert = UIAlertController(title: "Invalid email", message: "Try again. Use @hse.ru mail.", preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + self.present(alert, animated: true, completion: nil) + mailTextField.text = "" + loading.stopAnimating() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) + + loading.stopAnimating() } - private func configureTapgesture(){ - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap)) - view.addGestureRecognizer(tapGesture) - } + let logInLabel: UILabel = { + let label = UILabel() + label.text = "Log In" + label.textColor = .black + label.font = UIFont.boldSystemFont(ofSize: 34) + return label + }() + + let mailTextField: UITextField = { + let textField = UITextField() + textField.backgroundColor = #colorLiteral(red: 0.937254902, green: 0.937254902, blue: 0.9568627451, alpha: 1) + textField.placeholder = "Mail" + textField.borderStyle = .roundedRect + textField.textColor = .black + textField.autocorrectionType = UITextAutocorrectionType.no + textField.autocapitalizationType = UITextAutocapitalizationType.none + textField.clearButtonMode = .always + return textField + }() + + let logInButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle("Log In", for: .normal) + button.setTitleColor(blueSystemColor, for: .normal) + button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16) + button.addTarget(self, action: #selector(handleTap), for: .touchUpInside) + button.isEnabled = false + return button + }() @objc func handleTap(){ - view.endEditing(true) + loading.startAnimating() + authenticate?(mailTextField.text!) } - @objc func activateLogInProcess(){ - if logInButton.isEnabled { - // MARK:- when log in is succeeded do I need to go there? - if let id = Int(mailTextField.text!){ - indicatorView.isHidden = false - indicatorView.startAnimating() - onLogIn?(id) - view.endEditing(true) - } else { - logInButton.isEnabled = false - logInButton.setTitleColor(LogInViewController.titleColor.withAlphaComponent(0.5), for: .normal) - } - } - } + private lazy var keyboardBar = UIView() + private lazy var contentView = UIView() + private var bottomConstraint: NSLayoutConstraint? func setUpView(){ - indicatorView.isHidden = true - mailTextField.delegate = self - view.addSubview(keyboardBar) + // logIn stack with textField and label + view.addSubview(contentView) + view.addSubview(loading) - view.addConstraintsWithFormat(format: "H:|[v0]|", views: keyboardBar) - view.addConstraintsWithFormat(format: "V:[v0(50)]", views: keyboardBar) + loading.centerInSuperview() - setUpBarComponents() - - configureKeyboardBehavior() + contentView.edgesToSuperview(excluding: .bottom, insets: .left(16) + .right(16) + .top(80), usingSafeArea: true) + let views = [logInLabel, mailTextField] + contentView.stack(views, axis: .vertical, spacing: 10) } - func configureKeyboardBehavior(){ + func configureKeyboard(){ + + mailTextField.keyboardType = .emailAddress + // configure keyboardBar setUp + view.addSubview(keyboardBar) + keyboardBar.height(50) + keyboardBar.edgesToSuperview(excluding: [.top,.bottom]) bottomConstraint = NSLayoutConstraint(item: keyboardBar, attribute: .bottom, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .bottom, multiplier: 1, constant: 0) view.addConstraint(bottomConstraint!) + // configure keyboardBar components + keyboardBar.addSubview(logInButton) + logInButton.height(50) + logInButton.rightToSuperview(view.rightAnchor, offset: -16, relation: .equal, isActive: true) + + } + + func logicOfLogInInputValidation() -> ((String?)->()) { + + let logic: ((String?)->()) = { [weak self] (someText) in + if let text = someText, !text.isEmpty, text.contains("@") { + self?.logInButton.isEnabled = true + } else { + self?.logInButton.isEnabled = false + } + } + + return logic + } + + override func viewDidLoad() { + super.viewDidLoad() + self.view.backgroundColor = .white + setUpView() + configureKeyboard() + + let mailFieldValuesSignal: Signal = mailTextField.reactive.continuousTextValues + + mailFieldValuesSignal.observeValues(logicOfLogInInputValidation()) + // for keyboard notifications NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotifications), name: UIResponder.keyboardWillShowNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotifications), name: UIResponder.keyboardWillHideNotification, object: nil) - - // for log in button notifications - NotificationCenter.default.addObserver(self, selector: #selector(inputDidChanged), name: UITextField.textDidChangeNotification, object: mailTextField) - - NotificationCenter.default.addObserver(self, selector: #selector(inputDidChanged), name: UITextField.textDidBeginEditingNotification, object: mailTextField) } - @objc func inputDidChanged(notification: NSNotification){ - if mailTextField.text?.isEmpty ?? true - { - logInButton.isEnabled = false - logInButton.setTitleColor(LogInViewController.titleColor.withAlphaComponent(0.5), for: .normal) - } - else - { - logInButton.isEnabled = true - logInButton.setTitleColor(LogInViewController.titleColor.withAlphaComponent(1), for: .normal) - } - } @objc func handleKeyboardNotifications(notification: NSNotification){ if let userInfo = notification.userInfo{ @@ -136,18 +143,13 @@ class LogInViewController: UIViewController { } } - func setUpBarComponents(){ - keyboardBar.addSubview(logInButton) - keyboardBar.addConstraintsWithFormat(format: "H:[v0(60)]-16-|", views: logInButton) - keyboardBar.addConstraintsWithFormat(format: "V:|[v0]|", views: logInButton) - } } - -extension LogInViewController: UITextFieldDelegate{ - func textFieldShouldReturn(_ textField: UITextField) -> Bool - { - textField.resignFirstResponder() - return true +extension UIButton { + override open var isEnabled: Bool { + didSet { + let color = isEnabled ? self.titleLabel?.textColor.withAlphaComponent(1) : self.titleLabel?.textColor.withAlphaComponent(0.5) + self.setTitleColor(color, for: .normal) + } } } diff --git a/GDproject/Controller/Log In/RegisterTableViewController.swift b/GDproject/Controller/Log In/RegisterTableViewController.swift new file mode 100644 index 0000000..716fec5 --- /dev/null +++ b/GDproject/Controller/Log In/RegisterTableViewController.swift @@ -0,0 +1,198 @@ +// +// RegisterTableViewController.swift +// GDproject +// +// Created by cstore on 10/05/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import UIKit + +class RegisterTableViewController: UITableViewController, ChosenFactulty +{ + + func presentAlertInvalidData(message: String) + { + let alert = UIAlertController(title: "Invalid data", message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + self.present(alert, animated: true, completion: nil) + } + + func onChooseFaculty(faculty: Model.Faculty) { + self.faculty = faculty + tableView.reloadData() + } + + weak var delegate: UpdateUser? + + var onRegistration: (()->())? + + var faculty: Model.Faculty? + var user: Model.NewRegistration = Model.NewRegistration(email: DataStorage.standard.getEmail()!, firstName: nil, middleName: nil, lastName: nil, faculty: nil) + + var userActive: Model.Users? { + didSet { + if let userA = userActive { + user.firstName = userA.firstName + user.middleName = userA.middleName + user.lastName = userA.lastName + + faculty = userA.faculty + self.updateUserIfCan = true + tableView.reloadData() + } + } + } + + var updateUserIfCan: Bool = false + + override func viewDidLoad() + { + super.viewDidLoad() + + navigationItem.largeTitleDisplayMode = .never + tableView.keyboardDismissMode = .interactive + navigationItem.title = user.email + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(endRegistration)) + } + + + @objc func endRegistration() + { + let fieldsMissing = "Some fields are missing. Add more information" + updateUser() + guard let faculty = faculty else { presentAlertInvalidData(message: fieldsMissing); return } + + user.faculty = faculty.url + + guard let name = user.firstName else { presentAlertInvalidData(message: fieldsMissing); return } + guard let middle = user.middleName else { presentAlertInvalidData(message: fieldsMissing); return } + guard let last = user.lastName else { presentAlertInvalidData(message: fieldsMissing); return } + + if updateUserIfCan { + guard let delegate = delegate else { return } + guard var userA = userActive else { return } + + // updating current + userA.firstName = name + userA.faculty = faculty + userA.middleName = middle + userA.lastName = last + + Model.userUpdate(with: user) { [weak self] in + + if $0 { + delegate.updateUserObj(with: userA) + self?.navigationController?.popViewController(animated: true) + } else { + self?.presentAlertInvalidData(message: "Something went wrong! Try again") + } + + } + } else { + + Model.register(object: user) + { [weak self] in + self?.onRegistration?() + } + } + } + // MARK: - Table view data source + + override func numberOfSections(in tableView: UITableView) -> Int { + return 2 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + + switch section { + case 0: + return 3 + default: + if let _ = faculty { + return 2 + } else { + return 1 + } + } + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? + { + switch section { + case 0: + return "Initials" + default: + return "Faculty" + } + } + + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell + { + switch indexPath.section { + case 0: + let cell = tableView.dequeueReusableCell(withIdentifier: "nameCell", for: indexPath) as! InititalsViewCell + switch indexPath.row{ + case 0: + if let name = user.firstName { + cell.fill(with: name) + } else { + cell.initialsTextField.placeholder = "First name" + } + case 1: + if let name = user.middleName { + cell.fill(with: name) + } else { + cell.initialsTextField.placeholder = "Middle name" + } + default: + if let name = user.lastName { + cell.fill(with: name) + } else { + cell.initialsTextField.placeholder = "Last name" + } + } + cell.selectionStyle = .none + return cell + default: + switch indexPath.row { + case 0: + let cell = tableView.dequeueReusableCell(withIdentifier: "cell111", for: indexPath) + cell.selectionStyle = .none + return cell + default: + let cell = tableView.dequeueReusableCell(withIdentifier: "facultyCell", for: indexPath) + cell.textLabel?.text = faculty!.name + cell.detailTextLabel?.text = faculty!.campusName + cell.selectionStyle = .none + return cell + } + } + } + + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + if indexPath.section == 1 && indexPath.row == 0 { + + updateUser() + + let vc = storyboard?.instantiateViewController(withIdentifier: "FacultyTableViewController") as! FacultyTableViewController + + vc.delegate = self + + self.navigationController?.pushViewController(vc, animated: true) + } + } + + func updateUser(){ + guard let cell = tableView.cellForRow(at: IndexPath(row: 0, section: 0)) as? InititalsViewCell else {return} + guard let cell1 = tableView.cellForRow(at: IndexPath(row: 1, section: 0)) as? InititalsViewCell else {return} + guard let cell2 = tableView.cellForRow(at: IndexPath(row: 2, section: 0)) as? InititalsViewCell else {return} + + user.firstName = cell.initialsTextField.text! + user.middleName = cell1.initialsTextField.text! + user.lastName = cell2.initialsTextField.text! + } + +} diff --git a/GDproject/Controller/Messages/ChatInfoViewController.swift b/GDproject/Controller/Messages/ChatInfoViewController.swift new file mode 100644 index 0000000..7ff813a --- /dev/null +++ b/GDproject/Controller/Messages/ChatInfoViewController.swift @@ -0,0 +1,308 @@ +// +// ChatInfoViewController.swift +// GDproject +// +// Created by cstore on 02/05/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import UIKit + +/// Class for displaying chat info +/// +class ChatInfoViewController: UITableViewController { + + enum PersonStatus{ + case admin + case ordinary + } + + weak var delegate: UpdatableGroup? + + var groupChat: Model.Group? { + didSet{ + if let groupChat = groupChat { + usersArray = groupChat.users.map { $0.key } + } + } + } + + var usersArray: [Int] = [] { + didSet { + tableView.reloadData() + } + } + + var users: [Int: Model.Users] = [:] + + func canIEditThisChat() -> PersonStatus + { + let myId = UserDefaults.standard.integer(forKey: UserDefaultsKeys.id.rawValue) + + if let groupChatUserPermission = groupChat?.users[myId]?.isAdmin { + return groupChatUserPermission ? .admin : .ordinary + } + + return .ordinary + } + + var myPermissions: PersonStatus = .ordinary + + + + override func viewDidLoad() { + super.viewDidLoad() + + tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") + myPermissions = canIEditThisChat() + tableView.reloadData() + + switch myPermissions { + case .admin: + navigationItem.rightBarButtonItem = self.editButtonItem + default: + return + } + } + + // MARK: - Table view data source + + override func numberOfSections(in tableView: UITableView) -> Int { + return 3 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + switch section { + case 0: + return 1 + case 1: + return 1 + default: + return usersArray.count + (myPermissions == .ordinary ? 0 : 1) + } + } + + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell + { + let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) + + if indexPath.row == 0 { + switch indexPath.section { + case 0: + cell.textLabel?.text = groupChat!.name + cell.selectionStyle = .none + return cell + case 1: + cell.textLabel?.text = "Leave chat" + cell.textLabel?.textColor = #colorLiteral(red: 1, green: 0.1491314173, blue: 0, alpha: 1) + cell.selectionStyle = .none + return cell + default: + switch myPermissions{ + case .admin: + cell.textLabel?.text = "Add participants" + cell.accessoryType = .disclosureIndicator + case .ordinary: + cell.textLabel?.text = name(for: users[usersArray[indexPath.row]]) + } + cell.selectionStyle = .none + return cell + } + } + + switch myPermissions{ + case .admin: + cell.textLabel?.text = name(for: users[usersArray[indexPath.row-1]]) + case .ordinary: + cell.textLabel?.text = name(for: users[usersArray[indexPath.row]]) + } + cell.selectionStyle = .none + return cell + } + + private func name(for user: Model.Users?) -> String { + if let user = user, let perm = groupChat?.users[user.id]?.isAdmin { + if perm { + return "🤴🏻 \(user.fullName())" + } + return "👤 \(user.fullName())" + } + + return "left" + } + + override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? + { + switch section { + case 0: + return "Title" + case 1: + return "Opportunities" + default: + return "Participants" + } + } + + + func editName(for cell: UITableViewCell){ + let alert = UIAlertController(title: "Are you sure?", message: "Title:", preferredStyle: .alert) + + let action1 = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) + + alert.addTextField { [weak self] (tf) in + tf.textColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) + tf.text = self?.groupChat?.name + } + + let action2 = UIAlertAction(title: "OK", style: .default) { [unowned self] _ in + let name = alert.textFields?.first?.text + cell.textLabel?.text = name + self.groupChat?.name = name! + Model.updateGroupChat(with: self.groupChat!) + self.delegate?.updateGroup(with: self.groupChat!) + } + + alert.addAction(action1) + alert.addAction(action2) + + present(alert, animated: true, completion: nil) + } + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) + { + guard let groupChat = groupChat else { + return + } + + guard let cell = tableView.cellForRow(at: indexPath) else { + return + } + + if indexPath.row == 0 { + switch indexPath.section{ + case 0: + switch myPermissions{ + case .admin: + editName(for: cell) + default: + break + } + case 1: + Model.leaveGroupChat(id: groupChat.id) { + [weak self] in + self?.navigationController?.popViewController(animated: true) + } + default: + switch myPermissions{ + case .admin: + showUserChoiceVC() + default: + showParticipantPage(with: usersArray[indexPath.row]) + } + } + } + + + if indexPath.section == 2 && indexPath.row != 0 { + switch myPermissions{ + case .admin: + showParticipantPage(with: usersArray[indexPath.row-1]) + default: + showParticipantPage(with: usersArray[indexPath.row]) + } + } + } + + var onUserDiplay: ((Int)->())? + + func showParticipantPage(with user: Int?) { + if let user = user { + onUserDiplay?(user) + } + } + + func showUserChoiceVC() + { + let vc = storyboard?.instantiateViewController(withIdentifier: peopleToWriteVC) as! PeopleToWriteViewController + + vc.whatToDoWithSelection = { [weak self, weak vc] mapa in + + mapa.forEach { self?.users[$0.key] = Model.Channels.fullPeopleDict[$0.key] } + mapa.forEach { self?.groupChat?.users[$0.key] = $0.value } + vc?.navigationController?.popViewController(animated: true) + } + + navigationController?.pushViewController(vc, animated: true) + } + + override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) + { + if editingStyle == UITableViewCell.EditingStyle.delete { + groupChat?.users[usersArray[indexPath.row-1]] = nil + } + + tableView.reloadData() + } + + override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { + if indexPath.row == 0 { + return false + } + + return true + } + + override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? + { + let editButton = UITableViewRowAction(style: .normal, title: "Promote") { [unowned self] (rowAction, indexPath) in + + self.tableView.beginUpdates() + self.groupChat?.users[self.usersArray[indexPath.row-1]]?.isAdmin = true + self.tableView.reloadRows(at: [indexPath], with: .none) + self.tableView.endUpdates() + } + + editButton.backgroundColor = #colorLiteral(red: 0.476841867, green: 0.5048075914, blue: 1, alpha: 1) + + let restrictButton = UITableViewRowAction(style: .normal, title: "Restrict") { [unowned self] (rowAction, indexPath) in + + self.tableView.beginUpdates() + self.groupChat?.users[self.usersArray[indexPath.row-1]]?.isAdmin = false + self.tableView.reloadRows(at: [indexPath], with: .none) + self.tableView.endUpdates() + } + + restrictButton.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1) + + let deleteButton = UITableViewRowAction(style: .normal, title: "Delete") { [unowned self] (action, indexPath) in + + self.tableView.beginUpdates() + self.groupChat?.users[self.usersArray[indexPath.row-1]] = nil + self.tableView.deleteRows(at: [indexPath], with: .none) + self.tableView.endUpdates() + } + + deleteButton.backgroundColor = #colorLiteral(red: 1, green: 0.1491314173, blue: 0, alpha: 1) + + return [editButton, restrictButton, deleteButton] + } + + /// update chatInfo + override func viewWillDisappear(_ animated: Bool) { + + defer { + super.viewWillDisappear(animated) + } + + guard let _ = self.navigationController?.viewControllers.lastIndex(of: self) else { + switch myPermissions { + case .ordinary: + return + case .admin: + Model.updateGroupChat(with: groupChat!) + delegate?.updateGroup(with: groupChat!) + } + return + } + } +} diff --git a/GDproject/Controller/Messages/Dialog/DialogCell.swift b/GDproject/Controller/Messages/Dialog/DialogCell.swift new file mode 100644 index 0000000..7524af4 --- /dev/null +++ b/GDproject/Controller/Messages/Dialog/DialogCell.swift @@ -0,0 +1,132 @@ +// +// DialogCell.swift +// GDproject +// +// Created by cstore on 05/05/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import UIKit +import TinyConstraints +import MarkdownKit + +class DialogCell: UITableViewCell { + + let nameLabel: UIButton = { + let button = UIButton() + button.setTitleColor(.black, for: .normal) + button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 16) + button.contentHorizontalAlignment = UIControl.ContentHorizontalAlignment.left + return button + }() + + @objc func displayProfile() + { + if let id = self.user?.id { + print("diplay") + onUserDisplay?(id) + } + } + + var onUserDisplay: ((Int)->())? + + + let timeLabel: UILabel = { + let label = UILabel() + label.font = UIFont.systemFont(ofSize: 9) + label.textAlignment = NSTextAlignment.right + label.textColor = #colorLiteral(red: 0.4352941176, green: 0.4431372549, blue: 0.4745098039, alpha: 1) + return label + }() + + func createTextView(with text: NSAttributedString, _ isSelectable: Bool) -> UITextView + { + let textView = UITextView() + textView.isScrollEnabled = false + textView.isEditable = false + textView.sizeToFit() + + if isSelectable { + textView.isSelectable = true + } else { + textView.isUserInteractionEnabled = false + } + + textView.attributedText = text + return textView + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) + { + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + required init?(coder aDecoder: NSCoder) + { + fatalError("init(coder:) has not been implemented") + } + + var user: Model.Users? { + didSet{ + nameLabel.setTitle(user!.fullName(), for: .normal) + } + } + + var textView: UITextView! + + func fill(with markdownText: NSAttributedString, byUser: Model.Users, when: String) + { + let myId = DataStorage.standard.getUserId() + // important + contentView.subviews.forEach({ $0.removeFromSuperview() }) + + nameLabel.addTarget(self, action: #selector(displayProfile), for: .touchUpInside) + + self.user = byUser + textView = createTextView(with: markdownText, true) + textView.layer.cornerRadius = 10 + textView.clipsToBounds = true + + self.contentView.addSubview(textView) + self.contentView.addSubview(timeLabel) + + timeLabel.text = when.getDate() + textView.width(min: 50, max: self.contentView.bounds.width-70, priority: .required, isActive: true) + + if myId == byUser.id + { + layoutMyMessage() + } else { + layOutOtherMessage() + } + } + + func layOutOtherMessage(){ + self.contentView.addSubview(nameLabel) + + nameLabel.setTitle(user!.fullName(), for: .normal) + + nameLabel.edgesToSuperview(excluding: .bottom, insets: .top(4) + .left(8)) + textView.edgesToSuperview(excluding: [.top, .right] , insets: .left(8) + .bottom(20)) + textView.topToBottom(of: nameLabel) + + textView.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1) + textView.textColor = .white + timeLabel.edgesToSuperview(excluding: .top, insets: .right(70) + .left(8)) + timeLabel.topToBottom(of: textView, offset: 4) + timeLabel.textAlignment = .left + } + + func layoutMyMessage() + { + textView.edgesToSuperview(excluding: .left, insets: .right(8) + .bottom(20) + .top(4)) + textView.backgroundColor = UIColor(red:0.08, green:0.49, blue:0.98, alpha:1.0) + textView.textColor = .white + timeLabel.edgesToSuperview(excluding: .top, insets: .left(70) + .right(8)) + timeLabel.topToBottom(of: textView, offset: 4) + timeLabel.textAlignment = .right + } +} + + + diff --git a/GDproject/Controller/Messages/Dialog/DialogViewController.swift b/GDproject/Controller/Messages/Dialog/DialogViewController.swift new file mode 100644 index 0000000..a4d70ad --- /dev/null +++ b/GDproject/Controller/Messages/Dialog/DialogViewController.swift @@ -0,0 +1,340 @@ +// +// DialogViewController.swift +// GDproject +// +// Created by cstore on 01/05/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import UIKit +import TinyConstraints +import Marklight + +protocol UpdatableGroup:class { + func updateGroup(with group: Model.Group) +} + +class DialogViewController: UIViewController, UpdatableGroup, UITableViewDelegate, UITableViewDataSource +{ + var onUserDisplay: ((Int)->())? + + var tableView: UITableView = UITableView() + + func updateGroup(with group: Model.Group){ + self.groupChat?.group = group + setTitleForGroup(groupChat: groupChat!) + } + + let cellId = "cell3" + var onInfoShow: (()->())? + + var dialog: Model.Dialog? { + didSet{ + switch dialog! { + case .groupChat(let chat): + self.groupChat = chat + case .userChat(let chat): + self.userChat = chat + } + } + } + + var groupChat: Model.GroupChat? + var userChat: Model.UserChat? + + var users: [Int: Model.Users]? + + var groupId: Int? + + var cellData: [PostCellData] = [] { + didSet { + tableView.reloadData() + canBePaginated = true + } + } + + override func viewDidLoad() { + super.viewDidLoad() + + setConstraints() + magicForKeyboardChanges() + + self.tableView.delegate = self + self.tableView.dataSource = self + + tableView.setContentOffset(CGPoint(x: 0, y: 50), animated: false) + + navigationItem.largeTitleDisplayMode = .never + + tableView.keyboardDismissMode = .onDrag + tableView.separatorStyle = .none + tableView.register(DialogCell.self, forCellReuseIdentifier: cellId) + tableView.transform = CGAffineTransform(scaleX: 1, y: -1) + tableView.contentInsetAdjustmentBehavior = .never + tableView.contentOffset = CGPoint(x: 0, y: 30) + + if let groupChat = groupChat { + setTitleForGroup(groupChat: groupChat) + } else if let userChat = userChat{ + setTitleForChat(userChat: userChat) + } + } + + var messageSendView: UIView = { + let view = UIView() + view.backgroundColor = #colorLiteral(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0) + return view + }() + + var messageTextView: UITextView = + { + let textStorage = MarklightTextStorage() + textStorage.marklightTextProcessor.codeColor = UIColor.orange + textStorage.marklightTextProcessor.quoteColor = UIColor.darkGray + textStorage.marklightTextProcessor.syntaxColor = UIColor.blue + textStorage.marklightTextProcessor.codeFontName = "Courier" + textStorage.marklightTextProcessor.fontTextStyle = UIFont.TextStyle.subheadline.rawValue + textStorage.marklightTextProcessor.hideSyntax = false + + let layoutManager = NSLayoutManager() + + // Assign the `UITextView`'s `NSLayoutManager` to the `NSTextStorage` subclass + //textStorage.addLayoutManager(textView.layoutManager) + textStorage.addLayoutManager(layoutManager) + + let textContainer = NSTextContainer() + layoutManager.addTextContainer(textContainer) + + let textView = UITextView(frame: CGRect.zero, textContainer: textContainer) + textView.isEditable = true + textView.isScrollEnabled = true + textView.backgroundColor = .white + return textView + }() + + var sendButton: UIButton = { + let button = UIButton() + button.setTitle("Send", for: .normal) + button.setTitleColor(.blue, for: .normal) + button.titleLabel?.font = UIFont.systemFont(ofSize: 17, weight: .semibold) + button.addTarget(self, action: #selector(sendMessage), for: .touchUpInside) + return button + }() + + @objc func sendMessage() + { + var destination: Model.MessageDestination? + + if let group = groupChat { + destination = Model.MessageDestination.groupChatDestination(group.group.id) + } else if let user = userChat { + destination = Model.MessageDestination.userChatDestination(user.user) + } + + if let destination = destination + { + Model.sendMessage(message: Model.SendMessage(body: Model.Attachments(markdown: messageTextView.text), destination: destination)) { [unowned self] in + + switch $0 { + case .success, .success1: + self.getMessagesNew(for: self.dialog!) + self.messageTextView.text = "" + default: + self.showAlertOn(result: $0) + } + } + + prevLast = -1 + } + } + + var lineView: UIView = { + let view = UIView() + view.backgroundColor = #colorLiteral(red: 0.6000000238, green: 0.6000000238, blue: 0.6000000238, alpha: 1) + return view + }() + + var bottomConstraint: NSLayoutConstraint! + + func setConstraints(){ + self.view.addSubview(tableView) + self.view.addSubview(messageSendView) + + messageSendView.addSubview(messageTextView) + messageSendView.addSubview(sendButton) + messageSendView.addSubview(lineView) + + messageSendView.edgesToSuperview(excluding: [.top,.bottom]) + + bottomConstraint = NSLayoutConstraint(item: messageSendView, attribute: .bottom, relatedBy: .equal, toItem: self.view.safeAreaLayoutGuide, attribute: .bottom, multiplier: 1, constant: 0) + view.addConstraint(bottomConstraint) + + messageSendView.height(60) + + sendButton.edgesToSuperview(excluding: .left) + sendButton.width(60) + + lineView.edgesToSuperview(excluding: .bottom) + lineView.height(0.5) + + messageTextView.edgesToSuperview(excluding: .right) + messageTextView.rightToLeft(of: sendButton) + + tableView.edgesToSuperview(excluding: .bottom) + tableView.bottomToTop(of: messageSendView) + + self.view.layoutSubviews() + } + + func magicForKeyboardChanges() + { + NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotifications), name: UIResponder.keyboardWillShowNotification, object: nil) + + NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotifications), name: UIResponder.keyboardWillHideNotification, object: nil) + } + + @objc func handleKeyboardNotifications(notification: NSNotification){ + if let userInfo = notification.userInfo{ + // UIKeyboardFrameEndUserInfoKey + + let keyBoardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue + + bottomConstraint.constant = notification.name == UIResponder.keyboardWillShowNotification ? -(keyBoardFrame.height) + self.view.safeAreaInsets.bottom : 0 + + UIView.animate(withDuration: 0, delay: 0, options: .curveEaseOut, animations: { + self.view.layoutIfNeeded() + }) { (completed) in + + } + } + } + + func setTitleForChat(userChat: Model.UserChat){ + navigationItem.title = "\(users![userChat.user]!.fullName())" + navigationItem.rightBarButtonItem = UIBarButtonItem(title: "👤", style: .plain, target: self, action: #selector(showPersonPage)) + } + + @objc func showPersonPage(){ + if let id = userChat?.user{ + onUserDisplay?(id) + } + } + + func setTitleForGroup(groupChat: Model.GroupChat){ + navigationItem.title = "\(groupChat.group.name)" + navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Info", style: .plain, target: self, action: #selector(moveToInfoVC)) + } + + // onInfoShow + @objc func moveToInfoVC(){ + let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: chatInfoViewController) as! ChatInfoViewController + + vc.delegate = self + vc.users = users! + vc.onUserDiplay = onUserDisplay + + if let groupChat = groupChat { + vc.groupChat = groupChat.group + } + + navigationController?.pushViewController(vc, animated: true) + } + + var currentMessagesInChat: [Model.LastMessage] = [] { + didSet + { + cellData = currentMessagesInChat.map { PostCellData(attributedData: PostCellData.create(with: [$0.body])) } + } + } + + // MARK: - Table view data source + + func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return cellData.count + } + + override func viewWillAppear(_ animated: Bool) + { + super.viewWillAppear(animated) + tabBarController?.tabBar.isHidden = true + + if let dialog = dialog { + getMessagesNew(for: dialog) + } + } + + func getMessages(for dialog: Model.Dialog, starting from: Int? = nil) + { + switch dialog { + case .groupChat(let groupChat): + Model.getMessagesFor(typeOfChat: Model.Dialog.groupChat(groupChat), chat: groupChat.group.id, exclusiveFrom: from) + { [weak self] in + self?.currentMessagesInChat.append(contentsOf: $0) + } + case .userChat(let userChat): + Model.getMessagesFor(typeOfChat: Model.Dialog.userChat(userChat), chat: userChat.user, exclusiveFrom: from) + { [weak self] in + self?.currentMessagesInChat.append(contentsOf: $0) + } + } + } + + + func getMessagesNew(for dialog: Model.Dialog, starting from: Int? = nil) + { + switch dialog { + case .groupChat(let groupChat): + Model.getMessagesFor(typeOfChat: Model.Dialog.groupChat(groupChat), chat: groupChat.group.id, exclusiveFrom: from) + { [weak self] in + self?.currentMessagesInChat = $0 + } + case .userChat(let userChat): + Model.getMessagesFor(typeOfChat: Model.Dialog.userChat(userChat), chat: userChat.user, exclusiveFrom: from) + { [weak self] in + self?.currentMessagesInChat = $0 + } + } + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell + { + let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! DialogCell + + //In cellForRowAtIndexPath + cell.transform = CGAffineTransform(scaleX: 1, y: -1) + cell.selectionStyle = .none + + + if let user = users?[currentMessagesInChat[indexPath.row].author] + { + cell.fill(with: cellData[indexPath.row].attributedData, byUser: user, when: currentMessagesInChat[indexPath.row].time) + + cell.onUserDisplay = onUserDisplay + } + + return cell + } + + var prevLast = -1 + var canBePaginated = false + func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) + { + if indexPath.row == cellData.count - 1 && prevLast != indexPath.row && canBePaginated && cellData.count >= 24 + { + print("exclusiveFrom \(currentMessagesInChat.last?.id ?? 0)") + if let dialog = dialog + { + getMessages(for: dialog, starting: currentMessagesInChat.last?.id) + } + + prevLast = indexPath.row + } + } +} + + + diff --git a/GDproject/Controller/Messages/MessageViewCell.swift b/GDproject/Controller/Messages/MessageViewCell.swift new file mode 100644 index 0000000..5d180b3 --- /dev/null +++ b/GDproject/Controller/Messages/MessageViewCell.swift @@ -0,0 +1,38 @@ +// +// MessageViewCell.swift +// GDproject +// +// Created by cstore on 13/05/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import UIKit + +class MessageViewCell: UITableViewCell { + + @IBOutlet weak var dialogName: UILabel! + + @IBOutlet weak var lastMessagePreview: UILabel! + + @IBOutlet weak var dateLabel: UILabel! + + override func awakeFromNib() { + super.awakeFromNib() + // Initialization code + } + + func fill(with dialog: Model.Dialog, user: Model.Users? = nil) + { + switch dialog { + + case .groupChat(let group): + dialogName.text = group.group.name + lastMessagePreview.text = group.lastMessage?.body.markdown + dateLabel.text = group.lastMessage?.time.getDate() + case .userChat(let userChat): + dialogName.text = "👤 \(user!.fullName())" + lastMessagePreview.text = userChat.lastMessage?.body.markdown + dateLabel.text = userChat.lastMessage?.time.getDate() + } + } +} diff --git a/GDproject/Controller/Messages/MessagesViewController.swift b/GDproject/Controller/Messages/MessagesViewController.swift new file mode 100644 index 0000000..21d7bd6 --- /dev/null +++ b/GDproject/Controller/Messages/MessagesViewController.swift @@ -0,0 +1,181 @@ +// +// MessagesViewController.swift +// GDproject +// +// Created by cstore on 01/05/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import UIKit + +protocol SelectableDialog: class { + func openDialog(with dialog: Model.Dialog) +} + +class MessagesViewController: UITableViewController, UISearchControllerDelegate, UISearchResultsUpdating, SelectableDialog { + + func openDialog(with dialog: Model.Dialog) { + onDialogDisplay?((dialog, users)) + } + + func updateSearchResults(for searchController: UISearchController) { + + let filteredData = currentActiveDialogs + let inputText = searchController.searchBar.text ?? "" + + let filteredResults = filteredData.filter { + + switch $0 { + case .groupChat(let gropuChat): + return gropuChat.group.name.contains(inputText) + + case .userChat(let userChat): + let user = users[userChat.user] + guard let u = user else { return false } + return u.fullName().contains(inputText) + + } + + } + + // Apply the filtered results to the search results table. + if let resultsController = searchController.searchResultsController as? SuggestedDialogsTableViewController { + resultsController.users = users + resultsController.currentDialogs = filteredResults + resultsController.tableView.reloadData() + } + } + + // curreent Active which can be displayed + var currentActiveDialogs: [Model.Dialog] = [] { + didSet { + tableView.reloadData() + } + } + + func setUpSearchContr(){ + + searchController?.delegate = self + searchController?.obscuresBackgroundDuringPresentation = true + navigationItem.searchController = searchController + definesPresentationContext = true + searchController?.searchResultsUpdater = self + searchController?.searchBar.placeholder = "Search dialogs" + + } + + // curreent users + var users: [Int: Model.Users] = [:] + + var onUserDisplayList: (()->())? + + var onDialogDisplay: (((dialog: Model.Dialog, users: [Int:Model.Users]))->())? + + var searchController: UISearchController? + + override func viewDidLoad() { + super.viewDidLoad() + + let updater = SuggestedDialogsTableViewController() + + updater.delegate = self + searchController = UISearchController(searchResultsController: updater) + + // Uncomment the following line to display an Edit button in the navigation bar for this view controller. + self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Write", style: .plain, target: self, action: #selector(choosePerson)) + + self.navigationItem.title = "Messages" + // self.navigationItem.largeTitleDisplayMode = .always + setUpSearchContr() + navigationController?.navigationBar.prefersLargeTitles = true + self.navigationItem.hidesSearchBarWhenScrolling = false + } + + @objc func choosePerson(){ + onUserDisplayList?() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + tabBarController?.tabBar.isHidden = false + Model.getChatAll { [weak self] in + self?.currentActiveDialogs = $0.0 + self?.users = $0.1 + } + } + + // MARK: - Table view data source + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return currentActiveDialogs.count + } + + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell + { + let cell = tableView.dequeueReusableCell(withIdentifier: "MessageViewCell", for: indexPath) as! MessageViewCell + + switch currentActiveDialogs[indexPath.row].self { + case .groupChat: + cell.fill(with: currentActiveDialogs[indexPath.row]) + case .userChat(let userChat): + cell.fill(with: currentActiveDialogs[indexPath.row], user: users[userChat.user]) + } + + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + let tuple = (currentActiveDialogs[indexPath.row],users) + onDialogDisplay?(tuple) + } +} + + +class SuggestedDialogsTableViewController: UITableViewController { + + var idCell = "cell" + + var users: [Int: Model.Users] = [:] + + weak var delegate: SelectableDialog? + + var currentDialogs: [Model.Dialog] = [] + + override func viewDidLoad() { + super.viewDidLoad() + tableView.register(UITableViewCell.self, forCellReuseIdentifier: idCell) + tableView.reloadData() + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return currentDialogs.count + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell + { + let cell = tableView.dequeueReusableCell(withIdentifier: idCell, for: indexPath) + + switch currentDialogs[indexPath.row] { + case .groupChat(let groupChat): + cell.textLabel?.text = groupChat.group.name + case .userChat(let userChat): + cell.textLabel?.text = users[userChat.user]?.fullName() + } + + cell.accessoryType = .disclosureIndicator + return cell + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + delegate?.openDialog(with: currentDialogs[indexPath.row]) + // self.dismiss(animated: true) + } +} diff --git a/GDproject/Controller/Messages/PeopleToWriteViewController.swift b/GDproject/Controller/Messages/PeopleToWriteViewController.swift new file mode 100644 index 0000000..cc2ec6a --- /dev/null +++ b/GDproject/Controller/Messages/PeopleToWriteViewController.swift @@ -0,0 +1,67 @@ +// +// PeopleToWriteViewController.swift +// GDproject +// +// Created by cstore on 01/05/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import UIKit + +class PeopleToWriteViewController: UITableViewController { + + // TODO: - edit button when it's used for selection + var whatToDoWithSelection: (([Int: Model.UserPermission])->())? + + let searchC = UISearchController(searchResultsController: nil) + + let users = Model.Channels.fullPeople + + var chosenUsers: [Int: Model.UserPermission] = [:] + + override func viewDidLoad() { + super.viewDidLoad() + + tableView.isEditing = true + self.navigationItem.title = "People" + + self.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(newMessage)) + + self.navigationItem.largeTitleDisplayMode = .never + self.navigationItem.searchController = searchC + self.navigationItem.hidesSearchBarWhenScrolling = false + } + + override func setEditing(_ editing: Bool, animated: Bool) { + super.setEditing(true, animated: animated) + } + + override func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return users.count + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + chosenUsers[users[indexPath.row].id] = Model.UserPermission(isAdmin: false) + } + + override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell + { + let cell = tableView.dequeueReusableCell(withIdentifier: "peopleToWriteCell", for: indexPath) + + cell.textLabel?.text = users[indexPath.row].fullName() + + return cell + } + + @objc func newMessage() + { + if chosenUsers.count != 0 + { + whatToDoWithSelection?(chosenUsers) + } + } +} diff --git a/GDproject/Controller/Profile/BasicInfoController.swift b/GDproject/Controller/Profile/BasicInfoController.swift index 6735aa1..05ccba7 100644 --- a/GDproject/Controller/Profile/BasicInfoController.swift +++ b/GDproject/Controller/Profile/BasicInfoController.swift @@ -16,17 +16,20 @@ struct CellData{ class BasicInfoController: UIViewController, UITableViewDelegate, UITableViewDataSource { - var dataSourse: [CellData] = [ - CellData(opened: false, title: "Phone", sectionData: ["+7 901 733 01 79"]), - CellData(opened: false, title: "Mail", sectionData: ["vbogomazova@edu.hse.ru"]), - CellData(opened: false, title: "Research ID", sectionData: ["4567834336789456737"]), - CellData(opened: false, title: "Published", sectionData: ["Это будет очень длинное название книги 1, чтобы сделать проверку", "Книга 2", "Книга 3"]), - CellData(opened: false, title: "Courses", sectionData: ["Курс 1", "Курс 2"]), - CellData(opened: false, title: "Link", sectionData: ["https://wwww.hse.ru"]) - ] + var userInfo: Model.Users? { + didSet { + if let userInfo = userInfo { + dataSourse = [CellData(opened: false, title: "Contacts", sectionData: [userInfo.email, userInfo.faculty.address]), + CellData(opened: false, title: "Faculty", sectionData: [userInfo.faculty.campusName, userInfo.faculty.name, userInfo.faculty.address, userInfo.faculty.path]), + CellData(opened: false, title: "Interests", sectionData: userInfo.faculty.tags)] + } + } + } + + var dataSourse: [CellData] = [ ] func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - if (dataSourse[section].opened){ + if (dataSourse[section].opened) { return dataSourse[section].sectionData.count + 1 } else { return 1 @@ -45,8 +48,10 @@ class BasicInfoController: UIViewController, UITableViewDelegate, UITableViewDat cell.selectionStyle = .none return cell } else { - let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) - cell.textLabel?.text = dataSourse[indexPath.section].sectionData[indexPath.row - 1] + let cell = tableView.dequeueReusableCell(withIdentifier: infoCellId, for: indexPath) as! InfoCell + cell.fill(title: dataSourse[indexPath.section].sectionData[indexPath.row - 1]) + cell.accessoryType = .none + cell.selectionStyle = .none return cell } } diff --git a/GDproject/Controller/Profile/InfoCell.swift b/GDproject/Controller/Profile/InfoCell.swift index 70823d6..8918914 100644 --- a/GDproject/Controller/Profile/InfoCell.swift +++ b/GDproject/Controller/Profile/InfoCell.swift @@ -16,8 +16,9 @@ class InfoCell: UITableViewCell{ label.font = UIFont.systemFont(ofSize: 16) label.sizeToFit() label.isScrollEnabled = false - label.isUserInteractionEnabled = false + // label.isUserInteractionEnabled = false label.isEditable = false + label.dataDetectorTypes = .all label.textColor = .black return label }() diff --git a/GDproject/Controller/Profile/ProfileViewController.swift b/GDproject/Controller/Profile/ProfileViewController.swift index 57a0e12..80b0fff 100644 --- a/GDproject/Controller/Profile/ProfileViewController.swift +++ b/GDproject/Controller/Profile/ProfileViewController.swift @@ -9,8 +9,17 @@ import UIKit import TinyConstraints -class ProfileViewController: UIViewController +protocol UpdateUser: class { + func updateUserObj(with user: Model.Users) +} + +class ProfileViewController: UIViewController, UpdateUser { + func updateUserObj(with user: Model.Users) + { + self.user = user + Model.Channels.fullPeopleDict[user.id] = user + } @IBOutlet weak var segmentedControl: UISegmentedControl! @@ -30,14 +39,33 @@ class ProfileViewController: UIViewController @IBOutlet weak var newMessageButton: UIButton! - var protoDictionary: [Int: UIImage] = [9: #imageLiteral(resourceName: "9"), 5051: #imageLiteral(resourceName: "5051"), 69: #imageLiteral(resourceName: "69"), 42: #imageLiteral(resourceName: "42")] + var logOut: (()->())? - func fill(with user: Model.Users){ - self.facultyLabel.text = "Студент: Факультет Компьютерных Наук" + var deleteAllSessions: (()->())? + + var onSettings: (()->())? + + @IBAction func sendMessage(_ sender: UIButton) + { + if let userId = idProfile + { + let createdDialog = Model.Dialog.userChat(Model.UserChat(user: userId)) + let vc = DialogViewController() + vc.users = Model.Channels.fullPeopleDict + vc.dialog = createdDialog + self.navigationController?.pushViewController(vc, animated: true) + } + } + + var protoDictionary: [String: UIImage] = ["135213": #imageLiteral(resourceName: "9"), "135288": #imageLiteral(resourceName: "5051"), "22723" : #imageLiteral(resourceName: "69"), "135083": #imageLiteral(resourceName: "42")] + + func fill(with user: Model.Users) + { + self.facultyLabel.text = user.faculty.name self.nameLabel.text = "\(user.firstName) \(user.middleName)" self.surnameLabel.text = "\(user.lastName)" - self.profileImageView.image = protoDictionary[user.id]?.roundedImage - self.placeLabel.text = "📍Москва, Кочновский пр.3" + self.profileImageView.image = protoDictionary[user.faculty.campusCode]?.roundedImage + self.placeLabel.text = "📍\(user.faculty.address)" if user.id == DataStorage.standard.getUserId() { newMessageButton.isHidden = true } else { @@ -47,46 +75,61 @@ class ProfileViewController: UIViewController var user: Model.Users? { didSet { - self.fill(with: user!) - Model.getPostsForUser(with: user!.id) { [weak self] (posts) in - self?.dataSourse = posts + if let user = user { + self.fill(with: user) + navigationItem.title = "\(user.firstName) \(user.lastName)" + } else if let id = idProfile { + Model.getUsers(for: [id]) { [unowned self ] in + self.user = $0[id] + } } - navigationItem.title = "\(user!.firstName) \(user!.lastName)" + } + } + + var channel: Model.Channels? { + didSet { + self.update() + } + } + + func update() + { + Model.getAnonymousChannel(by: channel!) { [unowned self] in + self.posts.dataSourse = $0.posts + self.posts.dictionary = $0.users + self.user = $0.users[self.idProfile!] } } var basicInfo = BasicInfoController() var posts = NewsVC() - var dataSourse: [Model.Posts]?{ - didSet{ - - var newPosts: [Model.Posts] = [] - - dataSourse?.forEach({ (post) in - newPosts.append(Model.Posts(body: post.body, authorId: post.authorId, id: post.id, user: user!, date: post.updated, tags: post.tags)) - }) - - self.posts.dataSourse = newPosts - tableView.reloadData() - } - } - override func viewDidLoad() { super.viewDidLoad() - - - + if idProfile == nil { + idProfile = DataStorage.standard.getUserId() + } + posts.viewController = self posts.type = .NONE + posts.currChannel = channel + + posts.onFullPost = { + [weak self] (type,post) in + + let vc = self?.storyboard?.instantiateViewController(withIdentifier: fullPostControllerId) as! FullPostController + vc.type = type + vc.post = post + self?.navigationController!.pushViewController(vc, animated: true) + } tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell") tableView.register(PostViewCell.self, forCellReuseIdentifier: postCellId) tableView.register(BasicInfoCell.self, forCellReuseIdentifier: basicInfoCellId) - + tableView.register(InfoCell.self, forCellReuseIdentifier: infoCellId) posts.viewController = self @@ -96,83 +139,105 @@ class ProfileViewController: UIViewController tableView.reloadData() } - var idProfile: Int? + var idProfile: Int? { + didSet { + channel = Model.Channels(people: [idProfile!], name: "", id: 0, tags: []) + } + } override func viewWillAppear(_ animated: Bool) { + tabBarController?.tabBar.isHidden = false + if idProfile == nil { idProfile = DataStorage.standard.getUserId() } + user = Model.Channels.fullPeopleDict[idProfile!] - if let id = idProfile { - if let user = Model.idUser[id] { - self.user = user - } else { - Model.getUsers(for: [id]) { [weak self] (dic) in - self?.user = dic[id] - } - } - - // requst for postsforuser was here. moved because of concrr - } + update() setUpNavigarionBar() } func setUpNavigarionBar(){ - navigationController?.navigationBar.prefersLargeTitles = true - //navigationItem.title = "\(user?.id ?? 0)" + // navigationController?.navigationBar.prefersLargeTitles = true let uibarbutton = UIBarButtonItem(title: "More", style: .plain, target: self, action: #selector(showInformation)) navigationItem.rightBarButtonItems = [uibarbutton] navigationItem.largeTitleDisplayMode = .always } - - let copyLink: UIAlertAction = { - let b = UIAlertAction(title: "Copy link", style: .default) - return b - }() - @objc func showInformation(){ - // drafts - // saved - let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet) - let channelAction = UIAlertAction(title: "Add to a channel", style: .default){ - [weak self] (_) in - - let vc = self?.storyboard?.instantiateViewController(withIdentifier: simplifiedChannelsList) as! SimplifiedChannelsList - - vc.user = self?.user! - - let transition = CATransition() - transition.duration = 0.5 - transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) - transition.type = CATransitionType.moveIn - transition.subtype = CATransitionSubtype.fromTop - self?.navigationController?.view.layer.add(transition, forKey: nil) - self?.navigationController?.pushViewController(vc, animated: false) - } - let settingsAction = UIAlertAction(title: "Setting", style: .default) - { [weak self] (_) in - - let vc = self?.storyboard?.instantiateViewController(withIdentifier: "SettingsViewController") as! SettingsViewController - self?.navigationController?.pushViewController(vc, animated: true) - } + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) - let logoutAction = UIAlertAction(title: "Log out", style: .destructive) - { - (_) in - DataStorage.standard.setIsLoggedIn(value: false, with: 0) + + + if idProfile == DataStorage.standard.getUserId() { + let logoutAction = UIAlertAction(title: "Log out", style: .destructive) + { [weak self] (_) in + + if let logOut = self?.logOut { + logOut() + } else { + Model.logout() { + DataStorage.standard.setIsLoggedIn(value: false, with: 0) + (UIApplication.shared.delegate as! AppDelegate).relaunch() + } + } + } + + let deleetAllSessionsAction = UIAlertAction(title: "Delete all sessions", style: .destructive) + { [weak self] _ in + + if let delete = self?.deleteAllSessions { + delete() + } else { + Model.deactivateAll() { + DataStorage.standard.setIsLoggedIn(value: false, with: 0) + (UIApplication.shared.delegate as! AppDelegate).relaunch() + } + } + } + + let settingsAction = UIAlertAction(title: "Edit profile", style: .default) + { [weak self] (_) in + + if let settings = self?.onSettings{ + settings() + } else { + let vc = self?.storyboard?.instantiateViewController(withIdentifier: "RegisterTableViewController") as! RegisterTableViewController + vc.delegate = self + vc.userActive = self?.user + + self?.navigationController?.pushViewController(vc, animated: true) + } + } + + optionMenu.addAction(settingsAction) + optionMenu.addAction(logoutAction) + optionMenu.addAction(deleetAllSessionsAction) + } else { + let channelAction = UIAlertAction(title: "Add to a channel", style: .default) + { + [weak self] (_) in + + let vc = self?.storyboard?.instantiateViewController(withIdentifier: simplifiedChannelsList) as! SimplifiedChannelsList + vc.user = self?.user + + let transition = CATransition() + transition.duration = 0.5 + transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut) + transition.type = CATransitionType.moveIn + transition.subtype = CATransitionSubtype.fromTop + self?.navigationController?.view.layer.add(transition, forKey: nil) + self?.navigationController?.pushViewController(vc, animated: false) + } + + optionMenu.addAction(channelAction) } - optionMenu.addAction(channelAction) - optionMenu.addAction(settingsAction) - optionMenu.addAction(copyLink) - optionMenu.addAction(logoutAction) optionMenu.addAction(cancelAction) - self.present(optionMenu, animated: true, completion: nil) } @@ -202,6 +267,7 @@ class ProfileViewController: UIViewController func changeToBasicInfo(){ tableView.delegate = basicInfo tableView.dataSource = basicInfo + basicInfo.userInfo = self.user tableView.reloadData() } } diff --git a/GDproject/Controller/RepeatingTimer.swift b/GDproject/Controller/RepeatingTimer.swift deleted file mode 100644 index 19e02c6..0000000 --- a/GDproject/Controller/RepeatingTimer.swift +++ /dev/null @@ -1,59 +0,0 @@ -/// RepeatingTimer mimics the API of DispatchSourceTimer but in a way that prevents -/// crashes that occur from calling resume multiple times on a timer that is -/// already resumed (noted by https://github.com/SiftScience/sift-ios/issues/52 - -import Foundation - -class RepeatingTimer { - - let timeInterval: TimeInterval - - init(timeInterval: TimeInterval) { - self.timeInterval = timeInterval - } - - private lazy var timer: DispatchSourceTimer = { - let t = DispatchSource.makeTimerSource() - t.schedule(deadline: .now() + self.timeInterval, repeating: self.timeInterval) - t.setEventHandler(handler: { [weak self] in - self?.eventHandler?() - }) - return t - }() - - var eventHandler: (() -> Void)? - - private enum State { - case suspended - case resumed - } - - private var state: State = .suspended - - deinit { - timer.setEventHandler {} - timer.cancel() - /* - If the timer is suspended, calling cancel without resuming - triggers a crash. This is documented here https://forums.developer.apple.com/thread/15902 - */ - resume() - eventHandler = nil - } - - func resume() { - if state == .resumed { - return - } - state = .resumed - timer.resume() - } - - func suspend() { - if state == .suspended { - return - } - state = .suspended - timer.suspend() - } -} diff --git a/GDproject/DataStorage.swift b/GDproject/DataStorage.swift index 26a4e26..b65b2d1 100644 --- a/GDproject/DataStorage.swift +++ b/GDproject/DataStorage.swift @@ -37,18 +37,17 @@ class DataStorage{ return UserDefaults.standard.integer(forKey: UserDefaultsKeys.id.rawValue) } + func setEmail(email: String) { + UserDefaults.standard.set(email, forKey: UserDefaultsKeys.email.rawValue) + } + + func getEmail() -> String? { + return UserDefaults.standard.string(forKey: UserDefaultsKeys.email.rawValue) + } /** Function to determine is user logged in already or not */ - var isLoggedIn: Bool = UserDefaults.standard.bool(forKey: UserDefaultsKeys.loggedIn.rawValue) { - didSet{ - if isLoggedIn && getUserId() != 0 { - (UIApplication.shared.delegate as? AppDelegate)?.tabCoordinator.start() - } else { - (UIApplication.shared.delegate as? AppDelegate)?.logInAgain() - } - } - } + var isLoggedIn: Bool = UserDefaults.standard.bool(forKey: UserDefaultsKeys.loggedIn.rawValue) } /** @@ -57,4 +56,5 @@ class DataStorage{ enum UserDefaultsKeys: String{ case loggedIn case id + case email } diff --git a/GDproject/Extentions.swift b/GDproject/Extentions.swift index 571607e..473a177 100644 --- a/GDproject/Extentions.swift +++ b/GDproject/Extentions.swift @@ -60,3 +60,50 @@ extension UIStoryboard { return UIStoryboard.userEdit.instantiateViewController(withIdentifier: logInController) as! LogInViewController } } + + +extension String { + func getDate() -> String + { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"//this your string date format + dateFormatter.timeZone = TimeZone(abbreviation: "UTC") + let date = dateFormatter.date(from: self) + + dateFormatter.dateFormat = "MMM d, yyyy HH:mm" ///this is what you want to convert format + dateFormatter.timeZone = NSTimeZone.local + let timeStamp = dateFormatter.string(from: date!) + + return timeStamp + } +} + +extension UIViewController { + func showAlertOn(result: ResultR) { + + let message: String + + switch result { + case .impossibleContent: + message = "Impossible content" + case .exceededContent: + message = "The content exceeded" + case .longContent: + message = "The content is too long" + case .badAccess: + message = "Try reloading the page again" + case .alreadyRegistered: + message = "User is already registered" + case .tooMuchToAdd: + message = "Limit of channels exceeded" + case .success, .success1: + return + default: + message = "Something went wrong" + } + + let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + self.present(alert, animated: true, completion: nil) + } +} diff --git a/GDproject/Simple model/CompletionTree.swift b/GDproject/Simple model/CompletionTree.swift new file mode 100644 index 0000000..2d03cca --- /dev/null +++ b/GDproject/Simple model/CompletionTree.swift @@ -0,0 +1,44 @@ +// +// CompletionTree.swift +// GDproject +// +// Created by cstore on 01/05/2019. +// Copyright © 2019 drHSE. All rights reserved. +// + +import Foundation + + +struct CompletionTree: Codable { + var value: String? + var subtree: [String : CompletionTree] + + static func getCompletion(tree: CompletionTree, word: String) -> [String] { + if word == "" { + return getValues(tree: tree) + } + + let character = String(word.first!) + + if tree.subtree[character] == nil { + return [] + } else { + return getCompletion(tree: tree.subtree[character]!, word: String(word.dropFirst())) + } + } + + static func getValues(tree: CompletionTree) -> [String] + { + var out = [String]() + + if let treeVal = tree.value { + out.append(treeVal) + } + + for (_, subtree) in tree.subtree { + out += getValues(tree: subtree) + } + + return out + } +} diff --git a/GDproject/Simple model/Model.swift b/GDproject/Simple model/Model.swift index 6362d6c..83b6bd1 100644 --- a/GDproject/Simple model/Model.swift +++ b/GDproject/Simple model/Model.swift @@ -7,40 +7,85 @@ // import Foundation +import UIKit import Alamofire +enum ResultR: Int { + case internalServerError = 500 + case exceededContent = 470 + case longContent = 471 + case incorrectContent = 472 + case impossibleContent = 473 + case invalidTocken = 498 + case alreadyRegistered = 409 + case invalidCode = 403 + case badAccess = 400 + case tooMuchToAdd = 406 + case notFound = 404 + case success1 = 204 + case success = 200 +} + class Model{ static let invalidTocken = 498 + static var hashTagTree: CompletionTree? + private static var isValidTocken: ((Int)->())? = { responce in + print(responce) if responce == invalidTocken { + DataStorage.standard.setIsLoggedIn(value: false, with: 0) + (UIApplication.shared.delegate as! AppDelegate).relaunch() + + Model.logout { + print("Logout Is Successful") + } } } private static let baseUrl = "https://valera-denis.herokuapp.com" static let decoder = JSONDecoder() - + + static let authMeURL = URL(string: "\(baseUrl)/authentication/me")! + static let deactivateURL = URL(string: "\(baseUrl)/deactivateAll")! + static let registerMeURL = URL(string: "\(baseUrl)/authentication/register")! + static let authVerifyURL = URL(string: "\(baseUrl)/authentication/verify")! static let authenticationURL = URL(string:"\(baseUrl)/authentication/login")! + static let logOutURL = URL(string:"\(baseUrl)/authentication/logout")! static let postsLastURL = URL(string:"\(baseUrl)/posts/last")! static let postsForUserURL = URL(string:"\(baseUrl)/posts/forUser")! static let postsPublishURL = URL(string:"\(baseUrl)/posts/publish")! static let usersURL = URL(string:"\(baseUrl)/users")! static let usersAllURL = URL(string:"\(baseUrl)/users/all")! + static let usersUpdateURL = URL(string:"\(baseUrl)/users/update")! static let channelsGetURL = URL(string: "\(baseUrl)/channels/get")! static let channelsUpdateURL = URL(string: "\(baseUrl)/channels/update")! static let channelsListURL = URL(string: "\(baseUrl)/channels")! static let channelsCreateURL = URL(string: "\(baseUrl)/channels/create")! static let channelsDeleteURL = URL(string: "\(baseUrl)/channels/delete")! + static let channelsGetAnonURL = URL(string: "\(baseUrl)/channels/getAnonymous")! + static let complexURL = URL(string: "\(baseUrl)/complex")! + static let hashTagTreeURL = URL(string: "\(baseUrl)/tags/completions")! + static let createGroupChatURL = URL(string: "\(baseUrl)/chats/createGroupChat")! + static let chatsGetAllURL = URL(string: "\(baseUrl)/chats/getAll")! + static let getGroupChatURL = URL(string: "\(baseUrl)/chats/getGroupChat")! + static let leaveGroupChatURL = URL(string: "\(baseUrl)/chats/leaveGroupChat")! + static let updateGroupChatURL = URL(string: "\(baseUrl)/chats/updateGroupChat")! + static let messagesGetGroupChatURL = URL(string: "\(baseUrl)/messages/get/groupChat")! //r + static let messagesSendURL = URL(string: "\(baseUrl)/messages/send")! + static let messagesGetUserChatURL = URL(string: "\(baseUrl)/messages/get/userChat")! + static let facultySearchURL = URL(string: "\(baseUrl)/faculty/search")! - struct QueryPosts: Codable{ + struct QueryPosts: Codable { var users: [Int: Users] - var posts: [Posts] + var response: [T] } struct Posts: Codable { + var body: [Attachments] var authorId: Int var id: Int @@ -103,7 +148,12 @@ class Model{ var lastName: String var firstName: String var id: Int + var faculty: Faculty + var email: String + func fullName() -> String { + return "\(firstName) \(lastName)" + } } struct Attachments: Codable { @@ -125,7 +175,7 @@ class Model{ struct Channels: Codable { - static var fullTags = Set() + // static var fullTags = Set() static var fullPeople = [Users]() static var fullPeopleDict = [Int:Users]() @@ -163,24 +213,108 @@ class Model{ } } - static func authenticate(with id: Int, completion: @escaping ((Bool)->())) { + struct AuthStatus: Codable { + var userStatus: String - let json: [String:Any] = ["authenticationId" : id] - let jsonData = try? JSONSerialization.data(withJSONObject: json) - - var request = URLRequest(url: authenticationURL) + init(){ + userStatus = "invalid" + } + } + + static func logout(completion: @escaping (()->())){ + var request = URLRequest(url: logOutURL) request.httpMethod = "POST" - // insert json data to the request - request.httpBody = jsonData + AF.request(request).response { (responce) in + isValidTocken?(responce.response?.statusCode ?? 498) + + if let code = responce.response?.statusCode, code == 498 { + return + } + + completion() + } + } + + /* + "email": "vchernyshev@hse.ru", + "middleName": "Algebrovich", + "lastName": "Leonidov", + "faculty": "cs.hse.ru/dse", + "firstName": "Seva" + */ + + struct NewRegistration: Codable{ + var email: String + var firstName: String? + var middleName: String? + var lastName: String? + var faculty: String? + } + + static func register(object: NewRegistration, completion: @escaping (()->())) { + var request = URLRequest(url: registerMeURL) + request.httpBody = try? JSONEncoder().encode(object) + + request.httpMethod = "POST" request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") AF.request(request).response { (responce) in - guard let realResponse = responce.response, realResponse.statusCode == 200 else - { - print("Not a 200 response") + guard let code = responce.response?.statusCode else { return } + isValidTocken?(code) + + if let code = responce.response?.statusCode, code == 498 { + return + } + + if code == 204 { + + let fields = responce.response?.allHeaderFields as? [String :String] + let cookies = HTTPCookie.cookies(withResponseHeaderFields: fields!, for: responce.response!.url!) + HTTPCookieStorage.shared.setCookie(cookies[0]) + + completion() + } + } + } + + // true only if everything is good + static func verify(with code: Int, completion: @escaping ((Bool)->())){ + var request = URLRequest(url: authVerifyURL) + request.httpMethod = "POST" + request.httpBody = "\(code)".data(using: .utf8) + request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") + + AF.request(request).response { (responce) in + + guard let statusCode = responce.response?.statusCode else { completion(false); return } + isValidTocken?(statusCode) + + if let code = responce.response?.statusCode, code == 498 { + return + } + + if statusCode == 204 { + // at this point cookies are set + completion(true) + } else { completion(false) + } + } + } + + // yes or no + static func authenticateMe(completion: @escaping ((Bool)->())){ + var request = URLRequest(url: authMeURL) + request.httpMethod = "POST" + + AF.request(request).response { (responce) in + + guard let statusCode = responce.response?.statusCode else { completion(false); return } + isValidTocken?(statusCode) + + if let code = responce.response?.statusCode, code == 498 { return } @@ -190,26 +324,87 @@ class Model{ return } - let fields = realResponse.allHeaderFields as? [String :String] - - let cookies = HTTPCookie.cookies(withResponseHeaderFields: fields!, for: realResponse.url!) - - HTTPCookieStorage.shared.setCookie(cookies[0]) - DataStorage.standard.setIsLoggedIn(value: true, with: personId) completion(true) } } - - static func getLast(completion: @escaping (((users:[Int: Users], posts:[Posts]))->())){ - let jsonString = "30" - var request = URLRequest(url: postsLastURL) + // register, ok or invalid + static func authenticate(with email: String, completion: @escaping ((AuthStatus)->())) { + let json: [String:Any] = ["authenticationEmail" : email] + let jsonData = try? JSONSerialization.data(withJSONObject: json) + + var request = URLRequest(url: authenticationURL) + request.httpMethod = "POST" + + // insert json data to the request + request.httpBody = jsonData + request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") + + AF.request(request).response { (response) in + + guard let realResponse = response.response, realResponse.statusCode == 200 else + { + print("Not a 200 response") + completion(AuthStatus()) + return + } + + guard let json = response.data else { return } + guard let res = try? decoder.decode(AuthStatus.self, from: json) else { return } + + if realResponse.statusCode == 200 && res.userStatus == "registered" + { + // at this point cookies are set + let fields = realResponse.allHeaderFields as? [String :String] + let cookies = HTTPCookie.cookies(withResponseHeaderFields: fields!, for: realResponse.url!) + HTTPCookieStorage.shared.setCookie(cookies[0]) + } + + completion(res) + } + } + + struct GeneralRequest: Codable + { + var direction: String + var limit: Int + var exclusiveFrom: Int? + var request: T + + init(direction: String = "backward", limit: Int, exclusiveFrom: Int?, request: T) { + self.direction = direction + self.limit = limit + self.exclusiveFrom = exclusiveFrom + self.request = request + } + + enum CodingKeys: String, CodingKey { + case limit + case exclusiveFrom + case request + case direction + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(limit, forKey: .limit) + try container.encode(exclusiveFrom, forKey: .exclusiveFrom) + try container.encode(request, forKey: .request) + try container.encode(direction, forKey: .direction) + } + } + + static func getLast(on limit: Int = 10, from pointInTime: Int? = nil, completion: @escaping (((users:[Int: Users], posts:[Posts]))->())) + { + let postRequest = GeneralRequest<[Int]>(limit: limit, exclusiveFrom: pointInTime, request: []) + + var request = URLRequest(url: postsLastURL) + request.httpBody = try? JSONEncoder().encode(postRequest) request.httpMethod = "POST" // insert json data to the request - request.httpBody = jsonString.data(using: .utf8) request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") AF.request(request).responseJSON { @@ -217,18 +412,22 @@ class Model{ isValidTocken?(response.response?.statusCode ?? 498) + if let code = response.response?.statusCode, code == 498 { + return + } + guard let json = response.data else { return } - guard let newQueery = try? decoder.decode(QueryPosts.self, from: json) else { print("no") + guard let newQueery = try? decoder.decode(QueryPosts.self, from: json) else { print("no") return } idUser = newQueery.users - completion((newQueery.users, newQueery.posts)) + completion((newQueery.users, newQueery.response)) } } - static func createAndPublish(body: [Attachments], tags: [String]){ + static func createAndPublish(body: [Attachments], tags: [String], completion: @escaping ((ResultR)->())){ let jsonUpd = CreatedPost(body: body, tags: tags) var request = URLRequest(url: postsPublishURL) @@ -242,17 +441,30 @@ class Model{ (response) in isValidTocken?(response.response?.statusCode ?? 498) + + if let code = response.response?.statusCode, code == 498 { + return + } + + if let code = response.response?.statusCode, + let result = ResultR(rawValue: code) + { + completion(result) + } else { + completion(.internalServerError) + } } } +// static func requestForPosts(limit: Int = 10, ){ +// +// } - static func getPostsForUser(with id: Int, completion: @escaping (([Posts])->())){ - let json = [ - "request" : id, - "limit": 10 - ] + static func getPostsForUser(for limit: Int = 10, from pointInTime: Int? = nil, with id: Int, completion: @escaping (([Posts])->())) + { + let postsRequest = GeneralRequest(limit: limit, exclusiveFrom: pointInTime, request: id) - let jsonData = try? JSONSerialization.data(withJSONObject: json) + let jsonData = try? JSONEncoder().encode(postsRequest) var request = URLRequest(url: postsForUserURL) request.httpMethod = "POST" @@ -266,11 +478,15 @@ class Model{ isValidTocken?(response.response?.statusCode ?? 498) + if let code = response.response?.statusCode, code == 498 { + return + } + guard let json = response.data else { return } - guard let newPost = try? decoder.decode(QueryPosts.self, from: json) else { return } + guard let newPost = try? decoder.decode(QueryPosts.self, from: json) else { return } - completion(newPost.posts) + completion(newPost.response) } } @@ -278,7 +494,7 @@ class Model{ static func getUsers(for ids: [Int], completion: @escaping (([Int:Users])->())){ let json = "\(Set(ids))" - print(json) + var request = URLRequest(url: usersURL) request.httpMethod = "POST" @@ -292,11 +508,16 @@ class Model{ isValidTocken?(response.response?.statusCode ?? 498) + if let code = response.response?.statusCode, code == 498 { + return + } + guard let json = response.data else { return } guard let users = try? decoder.decode([Users].self, from: json) else { return } var dict: [Int:Users] = [:] + users.forEach({ (user) in dict[user.id] = user }) @@ -306,13 +527,10 @@ class Model{ } // get channel (with id): in responce -- PostQuery - static func getChannel(with channelId: Int, completion: @escaping (((users:[Int: Users], posts:[Posts]))->())){ - let json = [ - "request" : channelId, - "limit": 10 - ] - - let jsonData = try? JSONSerialization.data(withJSONObject: json) + static func getChannel(with channelId: Int, on limit: Int = 10, from pointInTime: Int? = nil, completion: @escaping (((users:[Int: Users], posts:[Posts]))->())) + { + let postRequest = GeneralRequest(limit: limit, exclusiveFrom: pointInTime, request: channelId) + let jsonData = try? JSONEncoder().encode(postRequest) var request = URLRequest(url: channelsGetURL) request.httpMethod = "POST" @@ -326,18 +544,21 @@ class Model{ isValidTocken?(response.response?.statusCode ?? 498) + if let code = response.response?.statusCode, code == 498 { + return + } guard let json = response.data else { return } - guard let newQueery = try? decoder.decode(QueryPosts.self, from: json) else { return } + guard let newQueery = try? decoder.decode(QueryPosts.self, from: json) else { return } idUser = newQueery.users - completion((newQueery.users, newQueery.posts)) + completion((newQueery.users, newQueery.response)) } } - static func createChannel(with channel: Channels) { + static func createChannel(with channel: Channels, completion: @escaping ((ResultR)->())) { var request = URLRequest(url: channelsCreateURL) request.httpMethod = "POST" @@ -348,6 +569,13 @@ class Model{ (response) in isValidTocken?(response.response?.statusCode ?? 498) + + if let code = response.response?.statusCode, let result = ResultR(rawValue: code) { + completion(result) + return + } + + completion(ResultR.internalServerError) } } @@ -360,6 +588,9 @@ class Model{ AF.request(request).responseJSON { (response) in isValidTocken?(response.response?.statusCode ?? 498) + if let code = response.response?.statusCode, code == 498 { + return + } guard let json = response.data else { return } @@ -378,6 +609,9 @@ class Model{ AF.request(request).response { (response) in isValidTocken?(response.response?.statusCode ?? 498) + if let code = response.response?.statusCode, code == 498 { + return + } } completion() } @@ -393,6 +627,9 @@ class Model{ (response) in isValidTocken?(response.response?.statusCode ?? 498) + if let code = response.response?.statusCode, code == 498 { + return + } } } @@ -415,6 +652,499 @@ class Model{ } isValidTocken?(response.response?.statusCode ?? 498) + if let code = response.response?.statusCode, code == 498 { + return + } + } + } + + struct AnonymousChannel: Codable { + + var limit = 10 + var request: RequestPeopleTags + + enum CodingKeys: String, CodingKey { + case limit + case request + } + + init(people: [Int], tags: [String]) { + self.request = RequestPeopleTags(people: people, tags: tags) + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(limit, forKey: .limit) + try container.encode(request, forKey: .request) + } + + struct RequestPeopleTags: Codable { + + var people: [Int] + var tags: [String] + + init(people: [Int], tags: [String]) { + self.people = people + self.tags = tags + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(people, forKey: .people) + try container.encode(tags, forKey: .tags) + } + + enum CodingKeys: String, CodingKey { + case people + case tags + } + + } + } + /* + { + "limit": 11, + "request": { + "people": [ + 2, + 7, + 8 + ], + "tags": [ + "thisIsHashTag", + "thisIsAlsoHashTag" + ] + } + } + */ + static func getAnonymousChannel(by anonymousChannel: Model.Channels, exclusiveFrom: Int? = nil, completion: @escaping (((users:[Int: Users], posts:[Posts]))->())){ + + let req = GeneralRequest(limit: 10, exclusiveFrom: exclusiveFrom, request: anonymousChannel) + + var request = URLRequest(url: channelsGetAnonURL) + request.httpMethod = "POST" + request.httpBody = try? JSONEncoder().encode(req) + + request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") + AF.request(request).response { + (response) in + + isValidTocken?(response.response?.statusCode ?? 498) + if let code = response.response?.statusCode, code == 498 { + return + } + + guard let json = response.data else { return } + + guard let newQueery = try? decoder.decode(QueryPosts.self, from: json) else { return } + completion((newQueery.users, newQueery.response)) + } + } + + static func getCompl(completion: @escaping ((CompletionTree)->())) { + + AF.request(URLRequest(url: hashTagTreeURL)).responseJSON { + (response) in + isValidTocken?(response.response?.statusCode ?? 498) + if let code = response.response?.statusCode, code == 498 { + return + } + guard let json = response.data else { return } + guard let tree = try? decoder.decode(CompletionTree.self, from: json) else { return } + completion(tree) + } + } + + static func getChatAll(limit: Int = 10, exclusiveFrom: Int? = nil, request: [Int] = [], completion: @escaping ((([Dialog],[Int:Model.Users]))->())) + { + let req = GeneralRequest<[Int]>(limit: limit, exclusiveFrom: exclusiveFrom, request: request) + var request = URLRequest(url: chatsGetAllURL) + request.httpMethod = "POST" + request.httpBody = try? JSONEncoder().encode(req) + request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") + + AF.request(request).responseJSON { response in + isValidTocken?(response.response?.statusCode ?? 498) + + if let code = response.response?.statusCode, code == 498 { + return + } + + guard let json = response.data else { return } + let dialogs = try! decoder.decode(QueryPosts.self, from: json) + + completion((dialogs.response,dialogs.users)) + } + } + + enum Dialog: Codable + { + case groupChat(GroupChat) + case userChat(UserChat) + + private enum DialogKeys: CodingKey{ + case groupChat + case userChat + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DialogKeys.self) + if container.contains(.groupChat){ + self = .groupChat(try GroupChat(from: container.superDecoder(forKey: .groupChat))) + } else if container.contains(.userChat){ + self = .userChat(try UserChat(from: container.superDecoder(forKey: .userChat))) + } else { + throw DecodingError.keyNotFound(DialogKeys.groupChat, DecodingError.Context(codingPath: container.codingPath, debugDescription: "hz")) + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: DialogKeys.self) + switch self{ + case .groupChat(let chat): + try chat.encode(to: container.superEncoder(forKey: .groupChat)) + case .userChat(let chat): + try chat.encode(to: container.superEncoder(forKey: .userChat)) + } + } + } + + struct GroupChat: Codable { + var group: Group + var lastMessage: LastMessage? + + + init(group: Group, lastMessage: LastMessage? = nil) { + self.group = group + self.lastMessage = lastMessage + } + } + + struct UserChat: Codable { + var user: Int + var lastMessage: LastMessage? + + init(user: Int, lastMessage: LastMessage? = nil) { + self.user = user + self.lastMessage = lastMessage + } + } + + struct Group: Codable { + var users: [Int: UserPermission] + var name: String + var id: Int + + init(users: [Int: UserPermission] = [:], name: String = "", id: Int) { + self.id = id + self.users = users + self.name = name + } + } + + struct UserPermission: Codable { + var isAdmin: Bool + } + + struct LastMessage: Codable { + var body: Attachments + var destination: MessageDestination + var time: String + var author: Int + var id: Int + + enum MessageCodingKeys: CodingKey{ + case user + case group + case body + case time + case author + case id + } + + init(from decoder: Decoder) throws + { + let container = try decoder.container(keyedBy: MessageCodingKeys.self) + time = try container.decode(String.self, forKey: .time) + body = try container.decode(Attachments.self, forKey: .body) + author = try container.decode(Int.self, forKey: .author) + id = try container.decode(Int.self, forKey: .id) + + if container.contains(.user){ + destination = MessageDestination.userChatDestination(try Int(from: container.superDecoder(forKey: .user))) + } else if container.contains(.group){ + destination = MessageDestination.groupChatDestination(try Int(from: container.superDecoder(forKey: .group))) + } else { + throw DecodingError.keyNotFound(MessageCodingKeys.group, DecodingError.Context(codingPath: container.codingPath, debugDescription: "hz gr")) + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: MessageCodingKeys.self) + + try container.encode(time, forKey: .time) + try container.encode(id, forKey: .id) + try container.encode(author, forKey: .author) + try container.encode(body, forKey: .body) + + switch destination { + case .userChatDestination(let uId): + try uId.encode(to: container.superEncoder(forKey: .user)) + case .groupChatDestination(let gId): + try gId.encode(to: container.superEncoder(forKey: .group)) + } + } + } + + enum MessageDestination: Codable + { + func encode(to encoder: Encoder) throws + { + var container = encoder.container(keyedBy: MessCodingKeys.self) + switch self { + case .userChatDestination(let uId): + try uId.encode(to: container.superEncoder(forKey: .user)) + case .groupChatDestination(let gId): + try gId.encode(to: container.superEncoder(forKey: .group)) + } + } + + init(from decoder: Decoder) throws + { + let container = try decoder.container(keyedBy: MessCodingKeys.self) + if container.contains(.user){ + self = .userChatDestination(try Int(from: container.superDecoder(forKey: .user))) + } else if container.contains(.group){ + self = .groupChatDestination(try Int(from: container.superDecoder(forKey: .group))) + } else { + throw DecodingError.keyNotFound(MessCodingKeys.group, DecodingError.Context(codingPath: container.codingPath, debugDescription: "hz gr")) + } + } + + enum MessCodingKeys: CodingKey{ + case user + case group + } + + case userChatDestination(Int) + case groupChatDestination(Int) + + } + + static func createGroupChat(from group: Group, completion: @escaping ((Int)->())) { + let req = group + var request = URLRequest(url: createGroupChatURL) + request.httpMethod = "POST" + request.httpBody = try? JSONEncoder().encode(req) + request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") + + AF.request(request).response { (response) in + + isValidTocken?(response.response?.statusCode ?? 498) + if let code = response.response?.statusCode, code == 498 { + return + } + + guard let json = response.data else { return } + let stringInt = String.init(data: json, encoding: String.Encoding.utf8) + let intId = Int.init(stringInt!) + + completion(intId!) + } + } + + static func getMessagesFor(typeOfChat: Model.Dialog, chat id: Int, exclusiveFrom: Int? = nil, limit l: Int = 25, direction: String = "backward", completion: @escaping (([LastMessage])->())) + { + let req = GeneralRequest(direction: direction, limit: l, exclusiveFrom: exclusiveFrom, request: id) + var request: URLRequest? + + switch typeOfChat { + case .groupChat: + request = URLRequest(url: messagesGetGroupChatURL) + case .userChat: + request = URLRequest(url: messagesGetUserChatURL) + } + + request!.httpMethod = "POST" + request!.httpBody = try? JSONEncoder().encode(req) + request!.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") + + AF.request(request!).response { (response) in + + isValidTocken?(response.response?.statusCode ?? 498) + if let code = response.response?.statusCode, code == 498 { + return + } + + guard let json = response.data else { return } + guard let messages = try? decoder.decode([LastMessage].self, from: json) else { return } + + completion(messages) + } + } + + static func leaveGroupChat(id: Int, completion: @escaping (()->())) + { + var request = URLRequest(url: leaveGroupChatURL) + request.httpMethod = "POST" + request.httpBody = "\(id)".data(using: .utf8) + request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") + + AF.request(request).response { (response) in + isValidTocken?(response.response?.statusCode ?? 498) + + if let code = response.response?.statusCode, code == 498 { + return + } + } + + completion() + } + + static func updateGroupChat(with group: Model.Group) + { + var request = URLRequest(url: updateGroupChatURL) + request.httpMethod = "POST" + request.httpBody = try? JSONEncoder().encode(group) + request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") + + AF.request(request).response { (response) in + isValidTocken?(response.response?.statusCode ?? 498) + + if let code = response.response?.statusCode, code == 498 { + return + } + } + + // completion() + } + + struct SendMessage: Codable { + var body: Attachments + var destination: MessageDestination + + init(from decoder: Decoder) throws + { + let container = try decoder.container(keyedBy: LastMessage.MessageCodingKeys.self) + body = try container.decode(Attachments.self, forKey: .body) + + if container.contains(.user){ + destination = MessageDestination.userChatDestination(try Int(from: container.superDecoder(forKey: .user))) + } else if container.contains(.group){ + destination = MessageDestination.groupChatDestination(try Int(from: container.superDecoder(forKey: .group))) + } else { + throw DecodingError.keyNotFound(LastMessage.MessageCodingKeys.group, DecodingError.Context(codingPath: container.codingPath, debugDescription: "hz gr1")) + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: LastMessage.MessageCodingKeys.self) + try container.encode(body, forKey: .body) + + switch destination { + case .userChatDestination(let uId): + try uId.encode(to: container.superEncoder(forKey: .user)) + case .groupChatDestination(let gId): + try gId.encode(to: container.superEncoder(forKey: .group)) + } + } + + init(body: Attachments, destination: MessageDestination) + { + self.body = body + self.destination = destination + } + } + + static func sendMessage(message: SendMessage, completion: @escaping ((ResultR)->())){ + + var request = URLRequest(url: messagesSendURL) + request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") + request.httpMethod = "POST" + request.httpBody = try? JSONEncoder().encode(message) + + AF.request(request).response { (response) in + isValidTocken?(response.response?.statusCode ?? 498) + + if let code = response.response?.statusCode, code == 498 { + return + } + + if let result = ResultR(rawValue: response.response!.statusCode){ + completion(result) + } else { + completion(.internalServerError) + } + } + } + + struct Faculty: Codable + { + + var path: String + var url: String + var campusName: String + var campusCode: String + var name: String + var tags: [String] + var address: String + + } + + static func searchFaculty(string: String, completion: @escaping (([Model.Faculty])->())) + { + var request = URLRequest(url: facultySearchURL) + request.httpMethod = "POST" + request.httpBody = string.data(using: .utf8) + request.setValue("text/plain;charset=utf-8", forHTTPHeaderField: "Content-Type") + + AF.request(request).response { (response) in + isValidTocken?(response.response?.statusCode ?? 498) + + if let code = response.response?.statusCode, code == 498 { + return + } + + guard let json = response.data else { return } + guard let faculties = try? decoder.decode([Faculty].self, from: json) else { return } + + completion(faculties) + } + } + + static func userUpdate(with newUser: NewRegistration, completion: @escaping ((Bool)->())) { + var request = URLRequest(url: usersUpdateURL) + request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type") + request.httpMethod = "POST" + request.httpBody = try? JSONEncoder().encode(newUser) + + AF.request(request).response { (response) in + isValidTocken?(response.response?.statusCode ?? 498) + + if let code = response.response?.statusCode, code == 498 { + return + } + + if let code = response.response?.statusCode, code != 204{ + completion (false) + } else { + completion(true) + } + } + } + + static func deactivateAll(completion: @escaping (()->())){ + var request = URLRequest(url: deactivateURL) + request.httpMethod = "DELETE" + + AF.request(request).response { (responce) in + isValidTocken?(responce.response?.statusCode ?? 498) + + if let code = responce.response?.statusCode, code == 498 { + return + } + + completion() } } } diff --git a/GDproject/Simple model/Post.swift b/GDproject/Simple model/Post.swift index a15e7ca..101f94e 100644 --- a/GDproject/Simple model/Post.swift +++ b/GDproject/Simple model/Post.swift @@ -9,31 +9,6 @@ import Foundation import UIKit -struct Post { - var dataArray: [Media] - var fromUser: User - var atDate: String - var comments: [Comment] = [] - - var hashtags: [String] = ["ФКН", "Подбельский", "НИУВШЭ", "Шершаков", "ПАД", "Интересное","Мемы","Забавное","Учеба","Наука"] - - init(dataArray: [Media], from: User, date: String, comments: [Comment] = []) - { - self.dataArray = dataArray - fromUser = from - atDate = date - self.comments = comments - } - - init(dataArray: [Media]) { - self.dataArray = dataArray - fromUser = User(name: "vbogomazova", id: 2, fullName: "Богомазова Вероника Львовна") - atDate = "23.03.19 в 23:33" - } - -} - - enum Media{ case text(String) case image([UIImage]) @@ -41,7 +16,6 @@ enum Media{ } struct Comment { - var from: User var atTime: String var withData: String } diff --git a/GDproject/Simple model/User.swift b/GDproject/Simple model/User.swift deleted file mode 100644 index f4e2011..0000000 --- a/GDproject/Simple model/User.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// User.swift -// NewsFeed -// -// Created by cstore on 12/01/2019. -// Copyright © 2019 drHSE. All rights reserved. -// - -import Foundation -import UIKit - -struct User { - //var photo: UIImage? = #imageLiteral(resourceName: "image") - var login: String - var id: Int - var fullName: String - var initials: (surname: String, name: String, optional: String?) = ("Богомазова","Вероника","Львовна") - var placeOfWork: String? - var faculty: String? - let posts: [Post] = [] - - - init(name: String, id: Int, fullName: String) { - self.login = name - self.id = id - self.fullName = fullName - } - - init(surname: String, name: String, optional: String?, emailName: String, id: Int, place: String?, faculty: String?) - { - self.id = id - self.login = emailName - self.initials = (surname, name, optional) - - self.fullName = "\(surname) \(name) \(optional ?? "")" - self.placeOfWork = place - if let f = faculty { - self.faculty = f - } - } -} diff --git a/GDproject/Supporting files/AppDelegate.swift b/GDproject/Supporting files/AppDelegate.swift index 18b89c3..9f0dc28 100644 --- a/GDproject/Supporting files/AppDelegate.swift +++ b/GDproject/Supporting files/AppDelegate.swift @@ -12,57 +12,28 @@ import UIKit class AppDelegate: UIResponder, UIApplicationDelegate { - var coordinator: LogInCoordinator! - var tabCoordinator: TabbarCoordinator! - var window: UIWindow? - var rootController: UINavigationController? { - return self.window!.rootViewController as? UINavigationController - } - - func logInAgain(){ - let root = UIStoryboard.navRoot() - window?.rootViewController = root - window?.makeKeyAndVisible() - coordinator = LogInCoordinator(navigationController: root, window: UIApplication.shared.keyWindow!) - coordinator!.start() - } + var appCoordinator: ApplicationCoordinator! func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - coordinator = LogInCoordinator(navigationController: rootController!, window: window!) - tabCoordinator = TabbarCoordinator(window: window!) - // TODO:- determine what to do here: log in or tabbar - if DataStorage.standard.isLoggedIn { - tabCoordinator.start() - } else { - coordinator.start() + window = UIWindow(frame: UIScreen.main.bounds) + window?.makeKeyAndVisible() + + // window?.rootViewController = UINavigationController() + Model.getCompl { (complTree) in + Model.hashTagTree = complTree } + appCoordinator = ApplicationCoordinator(window: window!) + appCoordinator.start() + return true } - - func applicationWillResignActive(_ application: UIApplication) { - // 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. - // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. - } - - func applicationDidEnterBackground(_ application: UIApplication) { - // 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. - // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. - } - - func applicationWillEnterForeground(_ application: UIApplication) { - // 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. - } - - func applicationDidBecomeActive(_ application: UIApplication) { - // 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. - } - - func applicationWillTerminate(_ application: UIApplication) { - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. + + func relaunch(){ + appCoordinator.start() } } diff --git a/GDproject/Supporting files/Assets.xcassets/chat.imageset/Contents.json b/GDproject/Supporting files/Assets.xcassets/chat.imageset/Contents.json new file mode 100644 index 0000000..98484a4 --- /dev/null +++ b/GDproject/Supporting files/Assets.xcassets/chat.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "chat.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/GDproject/Supporting files/Assets.xcassets/chat.imageset/chat.pdf b/GDproject/Supporting files/Assets.xcassets/chat.imageset/chat.pdf new file mode 100644 index 0000000..285bf16 Binary files /dev/null and b/GDproject/Supporting files/Assets.xcassets/chat.imageset/chat.pdf differ