on master help sign in
23 files changed+1204−427
since splitsnapshots
unifiedsplit
Cartfile+4−1 Cartfile.resolved+1−1 Melk.xcodeproj/project.pbxproj+92−40 Melk.xcodeproj/xcuserdata/ilya.xcuserdatad/xcschemes/Melk.xcscheme+3−3 Melk/AppDelegate.swift+1−3 Melk/CardContainer.swift+100 Melk/CardView.swift+126−10 Melk/CollectionView.swift+4−2 Melk/LocalVKDataTypes.swift+137−91 Melk/MainCoordinator.swift+30−14 Melk/MainViewController.swift+26−4 Melk/SecondaryCoordinator.swift+24−1 Melk/SecondaryViewController.swift+125−9 Melk/StackView.swift+3−1 Melk/Streaming.swift+195 Melk/StreamingParser.swift+80 Melk/StreamingService.swift+61 Melk/TableViewCells.swift+12−3 Melk/VKData.swift+34−34 Melk/VKPublicCrap.swift+6−3 Melk/VKServer.swift+123 Melk/VkServices.swift−207 README.md+17
Cartfile+4−1
@@ -1,5 +1,8 @@
11github "Alamofire/Alamofire"
2−github "WE-St0r/SwiftyVK"
2+# github "WE-St0r/SwiftyVK"
33github "kean/Nuke" ~> 4.0
44github "realm/realm-cocoa.git"
55github "tidwall/SwiftWebSocket"
6+# github "SwiftyJSON/SwiftyJSON"
7+# github "utahiosmac/Marshal"
8+github "Anviking/Decodable"
Cartfile.resolved+1−1
@@ -1,5 +1,5 @@
11github "Alamofire/Alamofire" "4.5.0"
2−github "WE-St0r/SwiftyVK" "2.0.8"
2+github "Anviking/Decodable" "v0.5.1"
33github "kean/Nuke" "4.1.2"
44github "realm/realm-cocoa" "v2.8.3"
55github "tidwall/SwiftWebSocket" "2.6.5"
Melk.xcodeproj/project.pbxproj+92−40
@@ -10,8 +10,10 @@
1010 0919AEA71F0D3D9D00894FFA /* SecondaryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919AEA61F0D3D9D00894FFA /* SecondaryViewController.swift */; };
1111 0919AEA91F0D459600894FFA /* CardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919AEA81F0D459600894FFA /* CardView.swift */; };
1212 0919AEAD1F0D706700894FFA /* StackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919AEAC1F0D706700894FFA /* StackView.swift */; };
13+ 09231E351F2394ED007C836D /* CardContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09231E341F2394ED007C836D /* CardContainer.swift */; };
1314 09939A561F097DA90014719D /* TableViewCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09939A551F097DA90014719D /* TableViewCells.swift */; };
1415 09A456361F0ED9A800BAC32D /* CollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A456351F0ED9A800BAC32D /* CollectionView.swift */; };
16+ 09AFAB041F159C9200EBD200 /* StreamingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AFAB031F159C9200EBD200 /* StreamingService.swift */; };
1517 09B781591F11630D0007D2BF /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = 09B781581F11630D0007D2BF /* .gitignore */; };
1618 09BF41EF1F07F71F00AFAD57 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF41EE1F07F71F00AFAD57 /* AppDelegate.swift */; };
1719 09BF41F61F07F71F00AFAD57 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 09BF41F51F07F71F00AFAD57 /* Assets.xcassets */; };
@@ -23,37 +25,41 @@
2325 09BF42141F08341B00AFAD57 /* PublicCrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF42121F08341B00AFAD57 /* PublicCrap.swift */; };
2426 09BF42161F0834D400AFAD57 /* VKPublicCrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF42151F0834D400AFAD57 /* VKPublicCrap.swift */; };
2527 09BF42181F08352E00AFAD57 /* VKData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF42171F08352E00AFAD57 /* VKData.swift */; };
26− 09BF421A1F08357E00AFAD57 /* VkServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF42191F08357E00AFAD57 /* VkServices.swift */; };
27− 09BF42261F0835B200AFAD57 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42021F080A5700AFAD57 /* Alamofire.framework */; };
28− 09BF42271F0835B200AFAD57 /* Alamofire.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42021F080A5700AFAD57 /* Alamofire.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
29− 09BF42281F0835B200AFAD57 /* Nuke.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42031F080A5700AFAD57 /* Nuke.framework */; };
30− 09BF42291F0835B200AFAD57 /* Nuke.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42031F080A5700AFAD57 /* Nuke.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
31− 09BF422A1F0835B200AFAD57 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42041F080A5700AFAD57 /* Realm.framework */; };
32− 09BF422B1F0835B200AFAD57 /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42041F080A5700AFAD57 /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
33− 09BF422C1F0835B200AFAD57 /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42051F080A5700AFAD57 /* RealmSwift.framework */; };
34− 09BF422D1F0835B200AFAD57 /* RealmSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42051F080A5700AFAD57 /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
35− 09BF422E1F0835B200AFAD57 /* SwiftyVK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42061F080A5700AFAD57 /* SwiftyVK.framework */; };
36− 09BF422F1F0835B200AFAD57 /* SwiftyVK.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42061F080A5700AFAD57 /* SwiftyVK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
3728 09BF42321F08388100AFAD57 /* SecondaryCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF42311F08388100AFAD57 /* SecondaryCoordinator.swift */; };
38− 09BF42341F083B5800AFAD57 /* SwiftWebSocket.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42331F083B5800AFAD57 /* SwiftWebSocket.framework */; };
39− 09BF42351F083B6000AFAD57 /* SwiftWebSocket.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42331F083B5800AFAD57 /* SwiftWebSocket.framework */; };
40− 09BF42361F083B6000AFAD57 /* SwiftWebSocket.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42331F083B5800AFAD57 /* SwiftWebSocket.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
4129 09BF42561F08413000AFAD57 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF42551F08413000AFAD57 /* MainViewController.swift */; };
30+ 09F03F231F164E7E000CE916 /* StreamingParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F03F221F164E7E000CE916 /* StreamingParser.swift */; };
31+ 09F03F261F165884000CE916 /* Decodable.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09F03F241F165876000CE916 /* Decodable.framework */; };
32+ 09F03F271F165884000CE916 /* Decodable.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 09F03F241F165876000CE916 /* Decodable.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
33+ 09F03F281F165884000CE916 /* SwiftyJSON.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09AFAAFF1F159AE300EBD200 /* SwiftyJSON.framework */; };
34+ 09F03F291F165884000CE916 /* SwiftyJSON.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 09AFAAFF1F159AE300EBD200 /* SwiftyJSON.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
35+ 09F03F2A1F165884000CE916 /* SwiftWebSocket.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42331F083B5800AFAD57 /* SwiftWebSocket.framework */; };
36+ 09F03F2B1F165884000CE916 /* SwiftWebSocket.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42331F083B5800AFAD57 /* SwiftWebSocket.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
37+ 09F03F2C1F165884000CE916 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42021F080A5700AFAD57 /* Alamofire.framework */; };
38+ 09F03F2D1F165884000CE916 /* Alamofire.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42021F080A5700AFAD57 /* Alamofire.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
39+ 09F03F2E1F165884000CE916 /* Nuke.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42031F080A5700AFAD57 /* Nuke.framework */; };
40+ 09F03F2F1F165884000CE916 /* Nuke.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42031F080A5700AFAD57 /* Nuke.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
41+ 09F03F301F165884000CE916 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42041F080A5700AFAD57 /* Realm.framework */; };
42+ 09F03F311F165884000CE916 /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42041F080A5700AFAD57 /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
43+ 09F03F321F165884000CE916 /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42051F080A5700AFAD57 /* RealmSwift.framework */; };
44+ 09F03F331F165884000CE916 /* RealmSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42051F080A5700AFAD57 /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
45+ 09F03F361F1674ED000CE916 /* VKServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F03F351F1674ED000CE916 /* VKServer.swift */; };
46+ 09F0D79E1F129DCB00A13751 /* Streaming.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09F0D79D1F129DCB00A13751 /* Streaming.swift */; };
4247/* End PBXBuildFile section */
4348
4449/* Begin PBXCopyFilesBuildPhase section */
45− 09BF42301F0835B200AFAD57 /* Embed Frameworks */ = {
50+ 09F03F341F165884000CE916 /* Embed Frameworks */ = {
4651 isa = PBXCopyFilesBuildPhase;
4752 buildActionMask = 2147483647;
4853 dstPath = "";
4954 dstSubfolderSpec = 10;
5055 files = (
51− 09BF422B1F0835B200AFAD57 /* Realm.framework in Embed Frameworks */,
52− 09BF42271F0835B200AFAD57 /* Alamofire.framework in Embed Frameworks */,
53− 09BF42361F083B6000AFAD57 /* SwiftWebSocket.framework in Embed Frameworks */,
54− 09BF422F1F0835B200AFAD57 /* SwiftyVK.framework in Embed Frameworks */,
55− 09BF422D1F0835B200AFAD57 /* RealmSwift.framework in Embed Frameworks */,
56− 09BF42291F0835B200AFAD57 /* Nuke.framework in Embed Frameworks */,
56+ 09F03F311F165884000CE916 /* Realm.framework in Embed Frameworks */,
57+ 09F03F291F165884000CE916 /* SwiftyJSON.framework in Embed Frameworks */,
58+ 09F03F331F165884000CE916 /* RealmSwift.framework in Embed Frameworks */,
59+ 09F03F271F165884000CE916 /* Decodable.framework in Embed Frameworks */,
60+ 09F03F2F1F165884000CE916 /* Nuke.framework in Embed Frameworks */,
61+ 09F03F2B1F165884000CE916 /* SwiftWebSocket.framework in Embed Frameworks */,
62+ 09F03F2D1F165884000CE916 /* Alamofire.framework in Embed Frameworks */,
5763 );
5864 name = "Embed Frameworks";
5965 runOnlyForDeploymentPostprocessing = 0;
@@ -64,8 +70,11 @@
6470 0919AEA61F0D3D9D00894FFA /* SecondaryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondaryViewController.swift; sourceTree = "<group>"; };
6571 0919AEA81F0D459600894FFA /* CardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardView.swift; sourceTree = "<group>"; };
6672 0919AEAC1F0D706700894FFA /* StackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackView.swift; sourceTree = "<group>"; };
73+ 09231E341F2394ED007C836D /* CardContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardContainer.swift; sourceTree = "<group>"; };
6774 09939A551F097DA90014719D /* TableViewCells.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCells.swift; sourceTree = "<group>"; };
6875 09A456351F0ED9A800BAC32D /* CollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionView.swift; sourceTree = "<group>"; };
76+ 09AFAAFF1F159AE300EBD200 /* SwiftyJSON.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyJSON.framework; path = Carthage/Build/iOS/SwiftyJSON.framework; sourceTree = SOURCE_ROOT; };
77+ 09AFAB031F159C9200EBD200 /* StreamingService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StreamingService.swift; sourceTree = "<group>"; };
6978 09B781581F11630D0007D2BF /* .gitignore */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = .gitignore; path = ../.gitignore; sourceTree = "<group>"; };
7079 09BF41EB1F07F71F00AFAD57 /* Melk.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Melk.app; sourceTree = BUILT_PRODUCTS_DIR; };
7180 09BF41EE1F07F71F00AFAD57 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@@ -77,17 +86,19 @@
7786 09BF42031F080A5700AFAD57 /* Nuke.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nuke.framework; path = Carthage/Build/iOS/Nuke.framework; sourceTree = SOURCE_ROOT; };
7887 09BF42041F080A5700AFAD57 /* Realm.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Realm.framework; path = Carthage/Build/iOS/Realm.framework; sourceTree = SOURCE_ROOT; };
7988 09BF42051F080A5700AFAD57 /* RealmSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RealmSwift.framework; path = Carthage/Build/iOS/RealmSwift.framework; sourceTree = SOURCE_ROOT; };
80− 09BF42061F080A5700AFAD57 /* SwiftyVK.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyVK.framework; path = Carthage/Build/iOS/SwiftyVK.framework; sourceTree = SOURCE_ROOT; };
8189 09BF420D1F0833A700AFAD57 /* LocalVKDataTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalVKDataTypes.swift; sourceTree = "<group>"; };
8290 09BF420F1F0833C100AFAD57 /* MainCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainCoordinator.swift; sourceTree = "<group>"; };
8391 09BF42111F08341B00AFAD57 /* MyWeirdness.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyWeirdness.swift; sourceTree = "<group>"; };
8492 09BF42121F08341B00AFAD57 /* PublicCrap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PublicCrap.swift; sourceTree = "<group>"; };
8593 09BF42151F0834D400AFAD57 /* VKPublicCrap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VKPublicCrap.swift; sourceTree = "<group>"; };
8694 09BF42171F08352E00AFAD57 /* VKData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VKData.swift; sourceTree = "<group>"; };
87− 09BF42191F08357E00AFAD57 /* VkServices.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VkServices.swift; sourceTree = "<group>"; };
8895 09BF42311F08388100AFAD57 /* SecondaryCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondaryCoordinator.swift; sourceTree = "<group>"; };
8996 09BF42331F083B5800AFAD57 /* SwiftWebSocket.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftWebSocket.framework; path = Carthage/Build/iOS/SwiftWebSocket.framework; sourceTree = SOURCE_ROOT; };
9097 09BF42551F08413000AFAD57 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
98+ 09F03F221F164E7E000CE916 /* StreamingParser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StreamingParser.swift; sourceTree = "<group>"; };
99+ 09F03F241F165876000CE916 /* Decodable.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Decodable.framework; path = Carthage/Build/iOS/Decodable.framework; sourceTree = SOURCE_ROOT; };
100+ 09F03F351F1674ED000CE916 /* VKServer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VKServer.swift; sourceTree = "<group>"; };
101+ 09F0D79D1F129DCB00A13751 /* Streaming.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Streaming.swift; sourceTree = "<group>"; };
91102/* End PBXFileReference section */
92103
93104/* Begin PBXFrameworksBuildPhase section */
@@ -95,19 +106,57 @@
95106 isa = PBXFrameworksBuildPhase;
96107 buildActionMask = 2147483647;
97108 files = (
98− 09BF422A1F0835B200AFAD57 /* Realm.framework in Frameworks */,
99− 09BF42261F0835B200AFAD57 /* Alamofire.framework in Frameworks */,
100− 09BF42341F083B5800AFAD57 /* SwiftWebSocket.framework in Frameworks */,
101− 09BF422E1F0835B200AFAD57 /* SwiftyVK.framework in Frameworks */,
102− 09BF422C1F0835B200AFAD57 /* RealmSwift.framework in Frameworks */,
103− 09BF42281F0835B200AFAD57 /* Nuke.framework in Frameworks */,
104− 09BF42351F083B6000AFAD57 /* SwiftWebSocket.framework in Frameworks */,
109+ 09F03F301F165884000CE916 /* Realm.framework in Frameworks */,
110+ 09F03F281F165884000CE916 /* SwiftyJSON.framework in Frameworks */,
111+ 09F03F321F165884000CE916 /* RealmSwift.framework in Frameworks */,
112+ 09F03F261F165884000CE916 /* Decodable.framework in Frameworks */,
113+ 09F03F2E1F165884000CE916 /* Nuke.framework in Frameworks */,
114+ 09F03F2A1F165884000CE916 /* SwiftWebSocket.framework in Frameworks */,
115+ 09F03F2C1F165884000CE916 /* Alamofire.framework in Frameworks */,
105116 );
106117 runOnlyForDeploymentPostprocessing = 0;
107118 };
108119/* End PBXFrameworksBuildPhase section */
109120
110121/* Begin PBXGroup section */
122+ 09231E331F2394C5007C836D /* Card */ = {
123+ isa = PBXGroup;
124+ children = (
125+ 0919AEA81F0D459600894FFA /* CardView.swift */,
126+ 09231E341F2394ED007C836D /* CardContainer.swift */,
127+ );
128+ name = Card;
129+ sourceTree = "<group>";
130+ };
131+ 098397F71F1E887F008C88DD /* Main */ = {
132+ isa = PBXGroup;
133+ children = (
134+ 09BF420F1F0833C100AFAD57 /* MainCoordinator.swift */,
135+ 09BF42551F08413000AFAD57 /* MainViewController.swift */,
136+ 09939A551F097DA90014719D /* TableViewCells.swift */,
137+ );
138+ name = Main;
139+ sourceTree = "<group>";
140+ };
141+ 098397F81F1E888F008C88DD /* Secondary */ = {
142+ isa = PBXGroup;
143+ children = (
144+ 09BF42311F08388100AFAD57 /* SecondaryCoordinator.swift */,
145+ 0919AEA61F0D3D9D00894FFA /* SecondaryViewController.swift */,
146+ );
147+ name = Secondary;
148+ sourceTree = "<group>";
149+ };
150+ 098397F91F1E88A3008C88DD /* Streaming */ = {
151+ isa = PBXGroup;
152+ children = (
153+ 09AFAB031F159C9200EBD200 /* StreamingService.swift */,
154+ 09F03F221F164E7E000CE916 /* StreamingParser.swift */,
155+ 09F0D79D1F129DCB00A13751 /* Streaming.swift */,
156+ );
157+ name = Streaming;
158+ sourceTree = "<group>";
159+ };
111160 09A456341F0ED98800BAC32D /* Public */ = {
112161 isa = PBXGroup;
113162 children = (
@@ -136,18 +185,16 @@
136185 09BF41ED1F07F71F00AFAD57 /* Melk */ = {
137186 isa = PBXGroup;
138187 children = (
188+ 098397F71F1E887F008C88DD /* Main */,
189+ 098397F81F1E888F008C88DD /* Secondary */,
190+ 098397F91F1E88A3008C88DD /* Streaming */,
191+ 09231E331F2394C5007C836D /* Card */,
139192 09BF41EE1F07F71F00AFAD57 /* AppDelegate.swift */,
140193 09BF42111F08341B00AFAD57 /* MyWeirdness.swift */,
141194 09BF42121F08341B00AFAD57 /* PublicCrap.swift */,
142− 09BF420F1F0833C100AFAD57 /* MainCoordinator.swift */,
143− 09BF42311F08388100AFAD57 /* SecondaryCoordinator.swift */,
144− 0919AEA81F0D459600894FFA /* CardView.swift */,
145− 0919AEA61F0D3D9D00894FFA /* SecondaryViewController.swift */,
146− 09BF42551F08413000AFAD57 /* MainViewController.swift */,
147− 09939A551F097DA90014719D /* TableViewCells.swift */,
195+ 09F03F351F1674ED000CE916 /* VKServer.swift */,
148196 09BF420D1F0833A700AFAD57 /* LocalVKDataTypes.swift */,
149197 09BF42151F0834D400AFAD57 /* VKPublicCrap.swift */,
150− 09BF42191F08357E00AFAD57 /* VkServices.swift */,
151198 09BF42171F08352E00AFAD57 /* VKData.swift */,
152199 09BF41F51F07F71F00AFAD57 /* Assets.xcassets */,
153200 09BF41F71F07F71F00AFAD57 /* LaunchScreen.storyboard */,
@@ -163,12 +210,13 @@
163210 09BF420C1F080A5F00AFAD57 /* Dependencies */ = {
164211 isa = PBXGroup;
165212 children = (
213+ 09F03F241F165876000CE916 /* Decodable.framework */,
214+ 09AFAAFF1F159AE300EBD200 /* SwiftyJSON.framework */,
166215 09BF42331F083B5800AFAD57 /* SwiftWebSocket.framework */,
167216 09BF42021F080A5700AFAD57 /* Alamofire.framework */,
168217 09BF42031F080A5700AFAD57 /* Nuke.framework */,
169218 09BF42041F080A5700AFAD57 /* Realm.framework */,
170219 09BF42051F080A5700AFAD57 /* RealmSwift.framework */,
171− 09BF42061F080A5700AFAD57 /* SwiftyVK.framework */,
172220 );
173221 name = Dependencies;
174222 sourceTree = "<group>";
@@ -183,7 +231,7 @@
183231 09BF41E71F07F71F00AFAD57 /* Sources */,
184232 09BF41E81F07F71F00AFAD57 /* Frameworks */,
185233 09BF41E91F07F71F00AFAD57 /* Resources */,
186− 09BF42301F0835B200AFAD57 /* Embed Frameworks */,
234+ 09F03F341F165884000CE916 /* Embed Frameworks */,
187235 );
188236 buildRules = (
189237 );
@@ -249,17 +297,21 @@
249297 buildActionMask = 2147483647;
250298 files = (
251299 09939A561F097DA90014719D /* TableViewCells.swift in Sources */,
300+ 09F03F361F1674ED000CE916 /* VKServer.swift in Sources */,
252301 0919AEAD1F0D706700894FFA /* StackView.swift in Sources */,
253302 09BF42141F08341B00AFAD57 /* PublicCrap.swift in Sources */,
254303 09BF42161F0834D400AFAD57 /* VKPublicCrap.swift in Sources */,
304+ 09F0D79E1F129DCB00A13751 /* Streaming.swift in Sources */,
255305 09BF42181F08352E00AFAD57 /* VKData.swift in Sources */,
256306 09BF42101F0833C100AFAD57 /* MainCoordinator.swift in Sources */,
257307 09A456361F0ED9A800BAC32D /* CollectionView.swift in Sources */,
258− 09BF421A1F08357E00AFAD57 /* VkServices.swift in Sources */,
308+ 09231E351F2394ED007C836D /* CardContainer.swift in Sources */,
259309 09BF42131F08341B00AFAD57 /* MyWeirdness.swift in Sources */,
260310 0919AEA91F0D459600894FFA /* CardView.swift in Sources */,
261311 09BF42321F08388100AFAD57 /* SecondaryCoordinator.swift in Sources */,
262312 09BF41EF1F07F71F00AFAD57 /* AppDelegate.swift in Sources */,
313+ 09AFAB041F159C9200EBD200 /* StreamingService.swift in Sources */,
314+ 09F03F231F164E7E000CE916 /* StreamingParser.swift in Sources */,
263315 09BF420E1F0833A700AFAD57 /* LocalVKDataTypes.swift in Sources */,
264316 09BF42561F08413000AFAD57 /* MainViewController.swift in Sources */,
265317 0919AEA71F0D3D9D00894FFA /* SecondaryViewController.swift in Sources */,
Melk.xcodeproj/xcuserdata/ilya.xcuserdatad/xcschemes/Melk.xcscheme+3−3
@@ -42,9 +42,9 @@
4242 </AdditionalOptions>
4343 </TestAction>
4444 <LaunchAction
45− buildConfiguration = "Debug"
46− selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
47− selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
45+ buildConfiguration = "Release"
46+ selectedDebuggerIdentifier = ""
47+ selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
4848 launchStyle = "0"
4949 useCustomWorkingDirectory = "NO"
5050 ignoresPersistentStateOnLaunch = "NO"
Melk/AppDelegate.swift+1−3
@@ -7,7 +7,7 @@
77//
88
99import UIKit
10−import SwiftyVK
10+//import SwiftyVK
1111
1212@UIApplicationMain
1313class AppDelegate: UIResponder, UIApplicationDelegate {
@@ -38,8 +38,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
3838 }
3939
4040 func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
41− let app = options[.sourceApplication] as? String
42− VK.process(url: url, sourceApplication: app)
4341 return true
4442 }
4543
Melk/CardContainer.swiftnew file+100
@@ -0,0 +1,100 @@
1+//
2+// CardContainer.swift
3+// Melk
4+//
5+// Created by Ilya Kos on 7/22/17.
6+// Copyright © 2017 Ilya Kos. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class CardContainer: UIView {
12+
13+ func animate() {
14+ blurView.effect = blur
15+ card.transform = smallTransform
16+ card.layer.opacity = 0
17+ shadowView.layer.opacity = 0
18+ shadowView.transform = smallTransform
19+ shadowView.layer.shadowRadius = 0
20+ UIView.animate(withDuration: 0.5, delay: 0, options: [.curveEaseInOut], animations: {
21+ self.card.transform = self.normalTransform
22+ self.shadowView.transform = self.normalTransform
23+ self.shadowView.layer.shadowRadius = self.shadowRadius
24+ self.shadowView.layer.opacity = 1
25+ self.blurView.effect = nil
26+ self.card.layer.opacity = 1
27+ }, completion: nil)
28+ }
29+
30+ // MARK: Passthrough to CardView
31+ var post: WallPost? {
32+ get {
33+ return card.post
34+ }
35+ set {
36+ card.post = newValue
37+ }
38+ }
39+
40+ var minWidth: CGFloat {
41+ get {
42+ return card.minWidth
43+ }
44+ set {
45+ card.minWidth = newValue
46+ }
47+ }
48+
49+
50+ var height: CGFloat {
51+ get {
52+ return card.height
53+ }
54+ set {
55+ card.height = newValue
56+ }
57+ }
58+
59+ private let card: CardView
60+ private let blurView = UIVisualEffectView()
61+ private let shadowView = UIView()
62+ private let blur = UIBlurEffect(style: UIBlurEffectStyle.light)
63+ private let scale: CGFloat = 0.5
64+ private let smallTransform: CGAffineTransform
65+ private let normalTransform = CGAffineTransform(scaleX: 1, y: 1)
66+ private let shadowRadius: CGFloat = 12
67+ private let shadowOpacity: Float = 0.15
68+ private let shadowOffset: CGFloat = 16
69+
70+ override init(frame: CGRect) {
71+ card = CardView(frame: frame)
72+ smallTransform = CGAffineTransform(scaleX: scale, y: scale)
73+ super.init(frame: frame)
74+ shadowView.layer.shadowColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1).cgColor
75+ shadowView.layer.shadowRadius = shadowRadius
76+ shadowView.layer.shadowOpacity = shadowOpacity
77+ shadowView.backgroundColor = UIColor.clear
78+ shadowView.clipsToBounds = false
79+ shadowView.layer.shadowOffset = CGSize.zero
80+ addSubview(shadowView)
81+ addSubview(card)
82+ addSubview(blurView)
83+ }
84+
85+ required init?(coder aDecoder: NSCoder) {
86+ fatalError("init(coder:) has not been implemented")
87+ }
88+
89+ override func layoutSubviews() {
90+ super.layoutSubviews()
91+ card.layoutIfNeeded()
92+ self.frame = card.frame
93+ shadowView.layer.shadowPath = CGPath(roundedRect: card.frame, cornerWidth: card.layer.cornerRadius, cornerHeight: card.layer.cornerRadius,
94+ transform: nil)
95+ shadowView.layer.shadowOffset.height = shadowOffset
96+ shadowView.frame = card.frame
97+ blurView.frame = CGRect(x: -shadowRadius, y: 0, width: card.frame.width + shadowRadius*2, height: card.frame.height + shadowRadius + shadowOffset)
98+ }
99+
100+}
Melk/CardView.swift+126−10
@@ -12,20 +12,51 @@ class CardView: UIView {
1212
1313 var post: WallPost? {
1414 didSet {
15+// print(post?.owner)
1516 update()
1617 }
1718 }
19+ var minWidth: CGFloat = 300 {
20+ didSet {
21+// update()
22+ }
23+ }
24+ var height: CGFloat = 400 {
25+ didSet {
26+// update()
27+ }
28+ }
1829
1930 override init(frame: CGRect) {
2031 super.init(frame: frame)
2132 layer.masksToBounds = true
2233 layer.cornerRadius = 20
23− backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
34+ gradient.colors = [#colorLiteral(red: 0.9841352105, green: 0.9841352105, blue: 0.9841352105, alpha: 1).cgColor, #colorLiteral(red: 0.9456828237, green: 0.9456828237, blue: 0.9456828237, alpha: 1).cgColor]
35+ gradient.startPoint = CGPoint(x: 0.5, y: 0)
36+ gradient.endPoint = CGPoint(x: 0.5, y: 1)
37+ layer.addSublayer(gradient)
38+// backgroundColor = #colorLiteral(red: 0.9638966278, green: 0.9734401588, blue: 0.9734401588, alpha: 1)
2439 }
2540
2641 required init?(coder aDecoder: NSCoder) {
2742 fatalError("init(coder:) has not been implemented")
2843 }
44+
45+ private let gradient = CAGradientLayer()
46+
47+
48+ private let padding = CGPoint(x: 32, y: 32)
49+ private let vPadding1: CGFloat = 16
50+ private let vPadding2: CGFloat = 16
51+ private var minTextHeight: CGFloat {
52+ return CGFloat(height * 0.2)
53+ }
54+ private let gradientRatio: CGFloat = 0.2
55+ private let minGradientAmount: CGFloat = 70
56+ private let hiddenByGradient: CGFloat = 10
57+ private var maxAttachmentDim: CGFloat {
58+ return minWidth / 2.3
59+ }
2960
3061// private let avatar = UIImage()
3162// private let name = UIImage()
@@ -34,14 +65,13 @@ class CardView: UIView {
3465 subviews.map {$0.removeFromSuperview()}
3566
3667 let largeStack = StackView(axis: .vertical, alignment: .left)
37− largeStack.padding = CGPoint(x: 32, y: 32)
68+ largeStack.padding = padding
3869 addSubview(largeStack)
3970
4071 let userView = StackView(axis: .horizontal, alignment: .center)
41−// userView.backgroundColor = #colorLiteral(red: 0.5568627715, green: 0.3529411852, blue: 0.9686274529, alpha: 1)
4272
4373
44− let avatarDim: CGFloat = 50
74+ let avatarDim: CGFloat = 64
4575 let avatar = UIImageView()
4676 post?.owner?.avatar?.load(into: avatar)
4777 avatar.layer.cornerRadius = avatarDim/2
@@ -51,30 +81,116 @@ class CardView: UIView {
5181 avatar.frame.size = CGSize(width: avatarDim, height: avatarDim)
5282 userView.addArrangedSubview(.view(avatar))
5383
54− userView.addArrangedSubview(.spacer(8))
84+ userView.addArrangedSubview(.spacer(16))
5585
5686 let userName = UILabel()
5787 let firstName = post?.owner?.firstName ?? ""
5888 let lastName = post?.owner?.lastName ?? ""
5989 userName.text = "\(firstName) \(lastName)"
6090 userName.font = UIFont.systemFont(ofSize: 28, weight: UIFontWeightHeavy)
91+ userName.textColor = UIColor.darkText
6192 userName.sizeToFit()
6293 userView.addArrangedSubview(.view(userName))
63− userView.frame.origin = CGPoint(x: 16, y: 16)
94+
95+ userView.layoutIfNeeded()
6496
6597 largeStack.addArrangedSubview(.view(userView))
6698
67− largeStack.addArrangedSubview(.spacer(16))
99+ largeStack.addArrangedSubview(.spacer(vPadding1))
100+
101+
102+ let width = max(minWidth - 2 * padding.x, userView.frame.width)
103+
104+
105+ let photoView = CollectionView()
106+ photoView.alignment = .top
107+ photoView.horizontalPadding = 8
108+ photoView.verticalPadding = 8
109+ photoView.padding = CGPoint.zero
110+ photoView.maxWidth = width
111+ var maxPhotoDim: CGFloat = maxAttachmentDim
112+ if let attachments = post?.attachments {
113+ for attachment in attachments {
114+ if let v = attachment.value {
115+ switch v {
116+ case .photo(let photo):
117+ if var pWidth = photo.width.value >>> {CGFloat($0)},
118+ var pHeight = photo.height.value >>> {CGFloat($0)} {
119+ let photoV = UIImageView()
120+ photoV.layer.minificationFilter = kCAFilterTrilinear
121+ photoV.contentMode = .scaleAspectFill
122+ photo.image?.load(into: photoV)
123+ var k: CGFloat!
124+ if pWidth > pHeight {
125+ k = CGFloat(pWidth) / maxPhotoDim
126+ } else {
127+ k = CGFloat(pHeight) / maxPhotoDim
128+ }
129+ pWidth /= k
130+ pHeight /= k
131+ photoV.frame.size = CGSize(width: pWidth, height: pHeight)
132+ photoView.addArrangedSubview(photoV)
133+ }
134+
135+ default:
136+ break // TODO: Handle other data types
137+ }
138+ }
139+ }
140+ }
141+ photoView.layoutIfNeeded()
142+
143+ var textHeight = height
144+ textHeight -= padding.y * 2 + vPadding1 + userView.frame.height
145+ if let count = post?.attachments.count{
146+ if count > 0 {
147+ textHeight -= vPadding2 + photoView.frame.height
148+ }
149+ }
150+ print(textHeight)
151+ textHeight = max(textHeight, minTextHeight)
152+ print(textHeight)
68153
69154 let body = UILabel()
70155 body.numberOfLines = 0
71− let bodyWidth: Double = 300
156+ let bodyWidth: Double = Double(width)
72157 body.font = UIFont.systemFont(ofSize: 22, weight: UIFontWeightSemibold)
73− body.text = post?.body ?? "Spicy jalapeno flank landjaeger sausage tongue tail fatback frankfurter. Meatball tongue tail short ribs pastrami. Short ribs chicken pig tenderloin sausage pork. Corned beef t-bone pancetta jerky chuck picanha venison, turducken tenderloin shoulder boudin drumstick hamburger. Pork meatball meatloaf prosciutto pig andouille. Sirloin bacon hamburger short ribs pork cupim shank biltong turducken bresaola kevin prosciutto shoulder meatball. Strip steak frankfurter pork chop biltong jowl beef rump venison pancetta turkey tongue."
158+ body.text = post?.body
159+ body.textColor = UIColor.darkText
74160 let size = body.textRect(forBounds: CGRect(x: 0, y: 0, width: bodyWidth, height: Double.infinity), limitedToNumberOfLines: 0).size
75− body.frame.size = size
161+ body.frame.size.width = size.width
162+ if size.height > textHeight {
163+ body.frame.size.height = textHeight
164+ let maskView = UIView()
165+ let maskLayer = CAGradientLayer()
166+ maskLayer.colors = [UIColor.black.cgColor, UIColor.clear.cgColor]
167+ maskLayer.endPoint = CGPoint(x: 0.5, y: 1 - (hiddenByGradient/textHeight))
168+ let minPoint = minGradientAmount / textHeight
169+ maskLayer.startPoint = CGPoint(x: 0.5, y: 1 - max(minPoint, gradientRatio))
170+ print(maskLayer.startPoint)
171+ maskLayer.frame = body.bounds
172+ maskView.frame = body.bounds
173+ maskView.layer.addSublayer(maskLayer)
174+ body.mask = maskView
175+ } else {
176+ body.frame.size.height = size.height
177+ }
76178
77179 largeStack.addArrangedSubview(.view(body))
78180
181+ if let count = post?.attachments.count{
182+ if count > 0 {
183+ largeStack.addArrangedSubview(.spacer(vPadding2))
184+ largeStack.addArrangedSubview(.view(photoView))
185+ }
186+ }
187+
188+ largeStack.layoutIfNeeded()
189+
190+ frame.size = CGSize(width: width + padding.x * 2, height: min(height, largeStack.frame.height))
191+
192+ gradient.frame = self.bounds
193+ gradient.endPoint = CGPoint(x: 0.5, y: bounds.height / height)
194+
79195 }
80196}
Melk/CollectionView.swift+4−2
@@ -68,7 +68,7 @@ class CollectionView: UIView {
6868 var firstStack = true
6969
7070 for view in arrangedSubviews {
71− print("new view")
71+// print("new view")
7272 if firstView {
7373 lastStack.addArrangedSubview(.view(view))
7474 firstView = false
@@ -94,7 +94,9 @@ class CollectionView: UIView {
9494 }
9595 }
9696 lastStack.layoutIfNeeded()
97− largeStack.addArrangedSubview(.spacer(verticalPadding))
97+ if !firstStack {
98+ largeStack.addArrangedSubview(.spacer(verticalPadding))
99+ }
98100 largeStack.addArrangedSubview(.view(lastStack))
99101 largeStack.frame.origin = CGPoint.zero
100102 largeStack.layoutIfNeeded()
Melk/LocalVKDataTypes.swift+137−91
@@ -11,7 +11,8 @@ import RealmSwift
1111import Alamofire
1212import CoreLocation
1313import Nuke
14−import SwiftyVK
14+import SwiftyJSON
15+//import SwiftyVK
1516
1617
1718typealias Token = String
@@ -292,17 +293,25 @@ class User: Object {
292293 }
293294 }
294295
296+// func get(completion: ((User?) -> ())? = nil) {
297+// VK.API.Users.get([VK.Arg.userIDs: "\(id)"]).send(onSuccess: { (response) in
298+// if let json = response.array?.first {
299+// let us = VKData.processUser(json: json)
300+// completion?(us)
301+// } else {
302+// completion?(nil)
303+// }
304+// }, onError: { (error) in
305+// completion?(nil)
306+// })
307+// }
295308 func get(completion: ((User?) -> ())? = nil) {
296− VK.API.Users.get([VK.Arg.userIDs: "\(id)"]).send(onSuccess: { (response) in
297− if let json = response.array?.first {
298− let us = VKData.processUser(json: json)
299− completion?(us)
300− } else {
301− completion?(nil)
309+ VKServer.get(user: self) { (data) in
310+ if let json = JSON.init(data: data)["response"].array?.first {
311+ let user = VKData.processUser(json: json)
312+ completion?(user)
302313 }
303− }, onError: { (error) in
304− completion?(nil)
305− })
314+ }
306315 }
307316
308317
@@ -493,19 +502,19 @@ class Message: GenericMessage, Comparable, SortedArrayItem {
493502 }
494503 }
495504
496− func get(completion: ((Message?) -> ())? = nil) {
497− VK.API.Messages.getById([VK.Arg.messageIds: String(id)]).send(onSuccess: { (response) in
498− if let json = response.array?.first {
499− let mes = VKData.processMessage(response)
500− completion?(mes)
501− } else {
502− completion?(nil)
503− }
504− }, onError: { (error) in
505− completion?(nil)
506− })
507− // TODO: Implement propperly?
508− }
505+// func get(completion: ((Message?) -> ())? = nil) {
506+// VK.API.Messages.getById([VK.Arg.messageIds: String(id)]).send(onSuccess: { (response) in
507+// if let json = response.array?.first {
508+// let mes = VKData.processMessage(response)
509+// completion?(mes)
510+// } else {
511+// completion?(nil)
512+// }
513+// }, onError: { (error) in
514+// completion?(nil)
515+// })
516+// // TODO: Implement propperly?
517+// }
509518
510519 class func by(id: MessageID) -> Message {
511520 let realm = try! Realm()
@@ -1239,24 +1248,32 @@ class Photo: Object {
12391248 }
12401249 }
12411250
1251+// func get(completion: ((Photo?) -> ())? = nil) {
1252+// if let ownerID = owner?.id {
1253+// var ph = "\(ownerID)_\(id)"
1254+// if let key = accessKey {
1255+// ph.append("_\(key)")
1256+// }
1257+// VK.API.Photos.getById([VK.Arg.photos: ph]).send(onSuccess: { (response) in
1258+// if let json = response.array?.first {
1259+// let photo = VKData.processPhoto(json)
1260+// completion?(photo)
1261+// } else {
1262+// completion?(nil)
1263+// }
1264+// }, onError: { (error) in
1265+// completion?(nil)
1266+// })
1267+// } else {
1268+// completion?(nil)
1269+// }
1270+// }
12421271 func get(completion: ((Photo?) -> ())? = nil) {
1243− if let ownerID = owner?.id {
1244− var ph = "\(ownerID)_\(id)"
1245− if let key = accessKey {
1246− ph.append("_\(key)")
1272+ VKServer.get(photo: self) { data in
1273+ if let json = JSON.init(data: data)["response"].array?.first {
1274+ let photo = VKData.processPhoto(json)
1275+ completion?(photo)
12471276 }
1248− VK.API.Photos.getById([VK.Arg.photos: ph]).send(onSuccess: { (response) in
1249− if let json = response.array?.first {
1250− let photo = VKData.processPhoto(json)
1251− completion?(photo)
1252− } else {
1253− completion?(nil)
1254− }
1255− }, onError: { (error) in
1256− completion?(nil)
1257− })
1258− } else {
1259− completion?(nil)
12601277 }
12611278 }
12621279
@@ -1366,33 +1383,42 @@ class WallPost: Object {
13661383 // self.copyOwnerID = copyOwnerID
13671384 // }
13681385
1386+// func get(completion: ((WallPost?) -> ())? = nil) {
1387+// if let ownerID = owner?.id {
1388+// var ph = "\(ownerID)_\(id)"
1389+// // if let key = accessKey {
1390+// // ph.append("_\(key)")
1391+// // }
1392+// VK.API.Wall.getById([VK.Arg.posts: ph]).send(onSuccess: { (response) in
1393+// if let json = response.array?.first {
1394+// let photo = VKData.processWallPost(json)
1395+// completion?(photo)
1396+// } else {
1397+// completion?(nil)
1398+// }
1399+// }, onError: { (error) in
1400+// completion?(nil)
1401+// })
1402+// } else {
1403+// completion?(nil)
1404+// }
1405+// }
13691406 func get(completion: ((WallPost?) -> ())? = nil) {
1370− if let ownerID = owner?.id {
1371− var ph = "\(ownerID)_\(id)"
1372− // if let key = accessKey {
1373− // ph.append("_\(key)")
1374− // }
1375− VK.API.Wall.getById([VK.Arg.posts: ph]).send(onSuccess: { (response) in
1376− if let json = response.array?.first {
1377− let photo = VKData.processWallPost(json)
1378− completion?(photo)
1379− } else {
1380− completion?(nil)
1381− }
1382− }, onError: { (error) in
1383− completion?(nil)
1384− })
1385− } else {
1386− completion?(nil)
1407+ VKServer.get(wallPost: self) { (data) in
1408+ if let json = JSON.init(data: data)["response"].array?.first {
1409+ let post = VKData.processWallPost(json)
1410+ completion?(post)
1411+ }
13871412 }
13881413 }
1414+
13891415
13901416
13911417 override class func primaryKey() -> String? {
13921418 return "id"
13931419 }
13941420
1395− class func by(id: UserID) -> WallPost {
1421+ class func by(id: WallPostID) -> WallPost {
13961422 let realm = try! Realm()
13971423 if let us = realm.object(ofType: WallPost.self, forPrimaryKey: id) {
13981424 return us
@@ -1432,6 +1458,10 @@ class WallPostReply: Object {
14321458 }
14331459 }
14341460
1461+ func get(completion: (() -> ())) {
1462+ // TODO: Get wallPostReply
1463+ }
1464+
14351465 override class func primaryKey() -> String? {
14361466 return "id"
14371467 }
@@ -1712,24 +1742,32 @@ class Video: Object {
17121742 }
17131743 }
17141744
1745+// func get(completion: ((Video?) -> ())? = nil) {
1746+// if let peerID = owner?.id {
1747+// var vi = "\(peerID)_\(id)"
1748+// if let key = accessKey {
1749+// vi.append("_\(key)")
1750+// }
1751+// VK.API.Video.get([VK.Arg.videos: vi]).send(onSuccess: { (response) in
1752+// if let json = response["items"].array?.first {
1753+// let video = VKData.processVideo(json)
1754+// completion?(video)
1755+// } else {
1756+// completion?(nil)
1757+// }
1758+// }, onError: { (error) in
1759+// completion?(nil)
1760+// })
1761+// } else {
1762+// completion?(nil)
1763+// }
1764+// }
17151765 func get(completion: ((Video?) -> ())? = nil) {
1716− if let peerID = owner?.id {
1717− var vi = "\(peerID)_\(id)"
1718− if let key = accessKey {
1719− vi.append("_\(key)")
1766+ VKServer.get(video: self) { (data) in
1767+ if let json = JSON.init(data: data)["response"].array?.first {
1768+ let video = VKData.processVideo(json)
1769+ completion?(video)
17201770 }
1721− VK.API.Video.get([VK.Arg.videos: vi]).send(onSuccess: { (response) in
1722− if let json = response["items"].array?.first {
1723− let video = VKData.processVideo(json)
1724− completion?(video)
1725− } else {
1726− completion?(nil)
1727− }
1728− }, onError: { (error) in
1729− completion?(nil)
1730− })
1731− } else {
1732− completion?(nil)
17331771 }
17341772 }
17351773
@@ -1836,24 +1874,32 @@ class Document: Object {
18361874 }
18371875 }
18381876
1877+// func get(completion: ((Document?) -> ())? = nil) {
1878+// if let ownerID = owner?.id {
1879+// var doc = "\(ownerID)_\(id)"
1880+// // if let key = accessKey {
1881+// // doc.append("_\(key)")
1882+// // }
1883+// VK.API.Docs.getById([VK.Arg.docs: doc]).send(onSuccess: { response in
1884+// if let json = response.array?.first {
1885+// let document = VKData.processDocument(json)
1886+// completion?(document)
1887+// } else {
1888+// completion?(nil)
1889+// }
1890+// }, onError: { (error) in
1891+// completion?(nil)
1892+// })
1893+// } else {
1894+// completion?(nil)
1895+// }
1896+// }
18391897 func get(completion: ((Document?) -> ())? = nil) {
1840− if let ownerID = owner?.id {
1841− var doc = "\(ownerID)_\(id)"
1842− // if let key = accessKey {
1843− // doc.append("_\(key)")
1844− // }
1845− VK.API.Docs.getById([VK.Arg.docs: doc]).send(onSuccess: { response in
1846− if let json = response.array?.first {
1847− let document = VKData.processDocument(json)
1848− completion?(document)
1849− } else {
1850− completion?(nil)
1851− }
1852− }, onError: { (error) in
1853− completion?(nil)
1854− })
1855− } else {
1856− completion?(nil)
1898+ VKServer.get(doc: self) { (data) in
1899+ if let json = JSON.init(data: data)["response"].array?.first {
1900+ let doc = VKData.processDocument(json)
1901+ completion?(doc)
1902+ }
18571903 }
18581904 }
18591905
@@ -2053,7 +2099,7 @@ class Multichat: Object {
20532099 if let us = realm.object(ofType: Multichat.self, forPrimaryKey: id) {
20542100 return us
20552101 } else {
2056− VkServices.getMultichat(for: id)
2102+// VkServices.getMultichat(for: id)
20572103 let us = Multichat()
20582104 realmWrite { realm in
20592105 us.id = id
Melk/MainCoordinator.swift+30−14
@@ -7,7 +7,8 @@
77//
88
99import UIKit
10−import SwiftyVK
10+import RealmSwift
11+//import SwiftyVK
1112
1213class MainCoordinator {
1314 let window: UIWindow
@@ -15,35 +16,49 @@ class MainCoordinator {
1516 let loadingVC = UIViewController() // TODO: An actual loading VC
1617 let navC: UINavigationController!
1718 let secondaryCoord = SecondaryCoordinator()
19+ let stream = StreamingService()
1820 init(window: UIWindow) {
1921 self.window = window
2022 window.rootViewController = loadingVC
2123
24+ Realm.Configuration.defaultConfiguration = Realm.Configuration
25+ .init(inMemoryIdentifier: "com.ilyakooo0.Melk.realm",
26+ readOnly: false,
27+ deleteRealmIfMigrationNeeded: true)
2228
2329 navC = UINavigationController(rootViewController: mainVC)
24− updateScreens()
2530
31+ mainVC.present = { tags in
32+ if tags.count > 0 {
33+ self.secondaryCoord.present(tag: tags.first!)
34+ self.stream.rules = tags
35+ self.stream.connect()
36+ }
37+ }
38+ updateScreens()
39+ stream.handleResult = { result in
40+ self.secondaryCoord.add(result: result)
41+// switch result {
42+// case .post(let post):
43+//
44+// }
45+ }
2646 }
2747
2848 func start() {
29− VK.configure(withAppId: appID, delegate: self)
30− if VK.state == .authorized {
31− authorized()
32− } else {
33− VK.logIn()
34− }
49+// VK.configure(withAppId: appID, delegate: self)
50+// if VK.state != .authorized {
51+// VK.logIn()
52+// }
53+ print("didAuthorize")
54+ window.rootViewController = navC
3555 }
3656
3757 func updateScreens() {
3858 secondaryCoord.updateScreens()
3959 }
40−
41− fileprivate func authorized() {
42− print("didAuthorize")
43− window.rootViewController = navC
44− }
4560}
46−
61+/*
4762extension MainCoordinator: VKDelegate {
4863 /** ---DEPRECATED. TOKEN NOW STORED IN KEYCHAIN--- Called when SwiftyVK need know where a token is located
4964 - returns: Path to save/read token or nil if should save token to UserDefaults*/
@@ -93,3 +108,4 @@ extension MainCoordinator: VKDelegate {
93108 }
94109
95110}
111+ */
Melk/MainViewController.swift+26−4
@@ -10,7 +10,9 @@ import UIKit
1010
1111class MainViewController: UIViewController {
1212
13− let mainView = MainView()
13+ var present: (([String]) -> ())?
14+
15+ private let mainView = MainView()
1416
1517 init() {
1618 super.init(nibName: nil, bundle: nil)
@@ -26,8 +28,10 @@ class MainViewController: UIViewController {
2628 fatalError("init(coder:) has not been implemented")
2729 }
2830
29− func presentTags() {
30−
31+ @objc private func presentTags() {
32+ present?(
33+ mainView.tags.flatMap({$0}).filter({$0 != ""})
34+ )
3135 }
3236}
3337
@@ -43,6 +47,8 @@ class MainView: UIView {
4347 tableView.rowHeight = UITableViewAutomaticDimension
4448 tableView.register(TitleTableViewCell.self, forCellReuseIdentifier: TitleTableViewCell.identifier)
4549 tableView.register(TagTableViewCell.self, forCellReuseIdentifier: TagTableViewCell.identifier)
50+ tableView.separatorStyle = .none
51+ tableView.allowsSelection = false
4652 setNeedsLayout()
4753 }
4854
@@ -50,6 +56,17 @@ class MainView: UIView {
5056 tableView.frame = bounds
5157 }
5258
59+ fileprivate var tags: [String?] = [nil] {
60+ didSet {
61+ if tags.last! != nil && tags.last! != "" {
62+ if tags.count < 10 {
63+ tags.append(nil)
64+ tableView.insertRows(at: [IndexPath.init(row: tags.count - 1, section: 0)], with: .automatic)
65+ }
66+ }
67+ }
68+ }
69+
5370 required init?(coder aDecoder: NSCoder) {
5471 fatalError("init(coder:) has not been implemented")
5572 }
@@ -60,13 +77,18 @@ extension MainView: UITableViewDataSource {
6077 if let cell = tableView.dequeueReusableCell(withIdentifier: TagTableViewCell.identifier) as? TagTableViewCell {
6178// cell.titleText = "Testing testing 1 2 3"
6279 cell.style = indexPath.item == 0 ? .large : .normal
80+ let index = indexPath.row
81+ cell.tagDidChange = { tag in
82+ self.tags[index] = tag
83+ }
84+ cell.tagString = tags[index]
6385 return cell
6486 }
6587 return UITableViewCell()
6688 }
6789
6890 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
69− return 3
91+ return tags.count
7092 }
7193
7294 func numberOfSections(in tableView: UITableView) -> Int {
Melk/SecondaryCoordinator.swift+24−1
@@ -12,19 +12,42 @@ class SecondaryCoordinator {
1212
1313 var windows: [UIWindow] = []
1414
15+ private var didStartPresenting = false
16+ private var tag: String = ""
17+
18+ func add(result: StreamingServiceResult) {
19+ for vc in VCs {
20+ vc.add(result: result)
21+ }
22+ }
23+
24+ func present(tag newTag: String) {
25+ didStartPresenting = true
26+ self.tag = newTag
27+ VCs.map({ $0.present(tag: newTag) })
28+ }
29+
1530 init() {
1631
1732 }
1833
34+ private var VCs: [SecondaryViewController] = []
35+
1936 func updateScreens() {
2037 windows.removeAll()
38+ VCs.removeAll()
2139 for screen in UIScreen.screens.dropFirst() {
2240 let window = UIWindow(frame: screen.bounds)
2341 window.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
2442 window.screen = screen
2543 window.isHidden = false
26− window.rootViewController = SecondaryViewController()
44+ let VC = SecondaryViewController()
45+ VCs.append(VC)
46+ window.rootViewController = VC
2747 windows.append(window)
48+ if didStartPresenting {
49+ VC.present(tag: tag)
50+ }
2851 }
2952 }
3053}
Melk/SecondaryViewController.swift+125−9
@@ -9,29 +9,145 @@
99import UIKit
1010
1111class SecondaryViewController: UIViewController {
12+
1213 init() {
1314 super.init(nibName: nil, bundle: nil)
14− self.view = SecondaryView(frame: view.bounds)
15+ self.view = sView
16+ }
17+
18+ func add(result: StreamingServiceResult) {
19+ sView.add(result: result)
20+ }
21+
22+ func present(tag: String) {
23+ sView.present(tag: tag)
1524 }
1625
1726 required init?(coder aDecoder: NSCoder) {
1827 fatalError("init(coder:) has not been implemented")
1928 }
29+
30+ private let sView = SecondaryView()
2031}
2132
2233class SecondaryView: UIView {
34+
35+ var mainTag: String? {
36+ get {
37+ return tagLabel.text
38+ }
39+ set {
40+ tagLabel.text = newValue
41+
42+ }
43+ }
44+
45+
46+ private let spacer: CGFloat = 32
47+ private let bottomSpacer: CGFloat = 64
48+ private let tagSpacer: CGFloat = 32
49+ private let tagLabel = UILabel()
50+ private let melkLabel = UILabel()
51+
52+ func present(tag: String) {
53+ UIView.perform(.delete, on: [melkLabel], options: [], animations: nil) { (_) in
54+ self.melkLabel.removeFromSuperview()
55+ self.mainTag = tag
56+ self.tagLabel.sizeToFit()
57+ self.layoutIfNeeded(animated: true)
58+ }
59+ }
60+
61+ func add(result: StreamingServiceResult) {
62+ switch result {
63+ case .post(let post):
64+ let card = CardContainer()
65+ card.minWidth = min(bounds.width / 2.3, 550)
66+ card.height = cardHeight
67+ card.post = post
68+ cards.addArrangedSubview(.spacer(spacer))
69+ cards.addArrangedSubview(.view(card))
70+ card.animate()
71+ }
72+ self.layoutIfNeeded(animated: true)
73+// self.setNeedsLayout(animated: true) // Doesn't work for some reason
74+ }
75+
76+// private let card = CardView()
77+ private let cards = StackView(axis: .horizontal, alignment: .top)
78+
2379 override init(frame: CGRect) {
2480 super.init(frame: frame)
81+ backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
82+ addSubview(cards)
83+
84+ melkLabel.text = "Melk"
85+ melkLabel.font = UIFont.systemFont(ofSize: 200, weight: UIFontWeightHeavy)
86+ melkLabel.textColor = #colorLiteral(red: 0.9601849914, green: 0.9601849914, blue: 0.9601849914, alpha: 1)
87+ addSubview(melkLabel)
88+
89+ tagLabel.font = UIFont.systemFont(ofSize: 69, weight: UIFontWeightBold)
90+ tagLabel.textColor = #colorLiteral(red: 0.3176470588, green: 0.3179988265, blue: 0.3179988265, alpha: 1)
91+ addSubview(tagLabel)
92+ }
93+
94+ func setNeedsLayout(animated: Bool) {
95+ self.animate = animated
96+ self.setNeedsLayout()
97+ }
98+ func layoutIfNeeded(animated: Bool) {
99+ self.animate = animated
100+ self.layoutIfNeeded()
101+ }
102+ private var animate: Bool = false
103+
104+ private func cleanCards() {
105+ var offset = 0
106+ outLoop: for (i, element) in cards.arrangedSubviews.enumerated() {
107+ switch element {
108+ case .view(let card):
109+ let cardFrame = convert(card.frame, from: cards)
110+ if cardFrame.intersects(bounds) {
111+ break outLoop
112+ } else {
113+ cards.arrangedSubviews.remove(at: i-1-offset)
114+ cards.arrangedSubviews.remove(at: i-1-offset)
115+ offset += 2
116+ }
117+ case .spacer(_):
118+ break
119+ }
120+ }
121+ }
122+
123+ private let padding = CGPoint(x: 32, y: 16)
124+ private var cardHeight: CGFloat = 400
125+
126+ override func layoutSubviews() {
127+ super.layoutSubviews()
25128
26− let card = CardView(frame: CGRect(x: 20, y: 20, width: 600, height: 300))
27− let post = WallPost()
28− post.owner = User()
29− post.owner?.firstName = "David"
30− post.owner?.avatar = RealmPhoto()
31− post.owner?.avatar?.imageSURL = "https://static.independent.co.uk/s3fs-public/styles/article_small/public/thumbnails/image/2017/04/01/17/twitter-egg.jpg"
32− card.post = post
33− addSubview(card)
129+ melkLabel.sizeToFit()
130+ melkLabel.center = center
34131
132+ let updates = {
133+ self.tagLabel.sizeToFit()
134+ self.tagLabel.center.x = self.center.x
135+ self.tagLabel.frame.origin.y = self.tagSpacer
136+ self.cards.frame.origin.y = self.tagLabel.frame.maxY + self.padding.y
137+ self.cards.layoutIfNeeded()
138+ self.cards.frame.origin.x = self.frame.width - self.padding.x - self.cards.frame.width
139+ }
140+ if animate {
141+ UIView.animate(withDuration: 0.7, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.6, options: [], animations: updates, completion: { _ in
142+ self.cleanCards()
143+ })
144+// UIView.animate(withDuration: 0.4, delay: 0.0, options: [.curveEaseOut], animations: updates, completion: nil)
145+ } else {
146+ updates()
147+ self.cleanCards()
148+ }
149+ cardHeight = bounds.height - tagLabel.frame.height - padding.y - tagSpacer - bottomSpacer
150+ animate = false
35151 }
36152
37153 required init?(coder aDecoder: NSCoder) {
Melk/StackView.swift+3−1
@@ -44,6 +44,8 @@ class StackView: UIView {
4444 super.layoutSubviews()
4545 print("laying out subviews")
4646
47+ subviews.map({$0.removeFromSuperview()}) // TODO: There must be a better way!
48+
4749 var height: CGFloat = 0
4850 var width: CGFloat = 0
4951 switch axis {
@@ -62,7 +64,7 @@ class StackView: UIView {
6264 height += a
6365 }
6466 case .view(let view):
65−
67+ view.layoutIfNeeded()
6668 switch axis {
6769 case .horizontal:
6870 view.frame.origin.x = width
Melk/Streaming.swiftnew file+195
@@ -0,0 +1,195 @@
1+//
2+// Streaming.swift
3+// Melk
4+//
5+// Created by Ilya Kos on 7/9/17.
6+// Copyright © 2017 Ilya Kos. All rights reserved.
7+//
8+
9+import Foundation
10+import SwiftWebSocket
11+import Alamofire
12+import SwiftyJSON
13+
14+class Streaming {
15+
16+ let streamingServerSurl = "https://api.vk.com/method/streaming.getServerUrl"
17+ let params: [String: String] = [
18+ "access_token": accessKey,
19+ "v": vkAPIVersion
20+ ]
21+
22+ private var key: String?
23+ private var server: String?
24+ let ws = WebSocket()
25+
26+ var processItem: ((Any) -> ())?
27+
28+ func getServer(completion: (() -> ())? = nil) {
29+ Alamofire.request(streamingServerSurl, parameters: params).validate().responseData { (response) in
30+ switch response.result {
31+ case .success(let data):
32+ let json = JSON.init(data: data)["response"]
33+ self.key = json["key"].string
34+ self.server = json["endpoint"].string
35+ case .failure(let error):
36+ print(error)
37+ }
38+ DispatchQueue.main.async {
39+ completion?()
40+ }
41+ }
42+ }
43+
44+ func add(rule: Rule, completion: (() -> ())? = nil) {
45+ if let key = key,
46+ let server = server {
47+ let url = "https://\(server)/rules?key=\(key)"
48+ let request: [String: Any] = [
49+ "rule": [
50+ "value": rule.rule,
51+ "tag": rule.tag
52+ ]
53+ ]
54+ print(try? JSONSerialization.data(withJSONObject: request, options: .prettyPrinted) =>> {String.init(data: $0, encoding: .utf8)})
55+ Alamofire.request(url, method: .post, parameters: request, encoding: JSONEncoding.default)
56+ .validate().responseData(completionHandler: { (response) in
57+ switch response.result {
58+ case .success(let data):
59+ let json = JSON.init(data: data)
60+ print(json) // TODO: Handle error
61+ case .failure(let error):
62+ print(error)
63+ }
64+ DispatchQueue.main.async {
65+ completion?()
66+ }
67+ })
68+ } else {
69+ getServer() {
70+ self.add(rule: rule, completion: completion)
71+ }
72+ }
73+ }
74+ func add(rules: [Rule], completion: (() -> ())? = nil) {
75+ if let server = server,
76+ let key = key {
77+ var numOfCompletions = rules.count + 1
78+ let complete: (() -> ()) = {
79+ numOfCompletions -= 1
80+ if numOfCompletions == 0 {
81+ completion?()
82+ }
83+ }
84+
85+ complete()
86+
87+ for rule in rules {
88+ add(rule: rule, completion: complete)
89+ }
90+ } else {
91+ getServer() {
92+ self.add(rules: rules, completion: completion)
93+ }
94+ }
95+ }
96+ func getRules(completion: @escaping (([Rule]?) -> ())) {
97+ if let server = server,
98+ let key = key {
99+ let url = "https://\(server)/rules?key=\(key)"
100+ Alamofire.request(url, method: .get).validate().responseData { (response) in
101+ switch response.result {
102+ case .success(let data):
103+ let json = JSON.init(data: data)
104+ print(json)
105+ if json["code"].int == 200 {
106+ var resultRules: [Rule] = []
107+ if let rules = json["rules"].array {
108+ for rule in rules {
109+ if let tag = rule["tag"].string,
110+ let value = rule["value"].string {
111+ resultRules.append((rule: value, tag: tag))
112+ }
113+ }
114+ }
115+ completion(resultRules)
116+ } // TODO" Handle errors
117+ case .failure(let error):
118+ print(error) // TODO: HAndl;e error
119+ }
120+ }
121+ } else {
122+ getServer() {
123+ self.getRules(completion: completion)
124+ }
125+ }
126+ }
127+ func remove(rule: Rule, completion: (() -> ())? = nil) {
128+ if let server = server,
129+ let key = key {
130+ let url = "https://\(server)/rules?key=\(key)"
131+ let request: [String: Any] = [
132+ "tag": rule.tag
133+ ]
134+ Alamofire.request(url, method: .delete, parameters: request, encoding: JSONEncoding.default).validate().responseData { response in
135+ switch response.result {
136+ case .success(let data):
137+ let json = JSON.init(data: data)
138+ print(json) // TODO: HAndle error
139+ completion?()
140+ case .failure(let error):
141+ print(error) // TODO: HAndl;e error
142+ }
143+ }
144+ } else {
145+ getServer() {
146+ self.remove(rule: rule, completion: completion)
147+ }
148+ }
149+ }
150+ func removeAllRules(completion: (() -> ())? = nil) {
151+ getRules { (rules) in
152+ if let rules = rules {
153+
154+ var numOfCompleted = rules.count + 1
155+ let complete : (() -> ()) = {
156+ numOfCompleted -= 1
157+ if numOfCompleted == 0 {
158+ completion?()
159+ }
160+ }
161+
162+ complete()
163+
164+ for rule in rules {
165+ self.remove(rule: rule, completion: complete)
166+ }
167+ }
168+ }
169+ }
170+ func connect(completion: (() -> ())? = nil) {
171+ if let key = key,
172+ let server = server {
173+ let surl = "wss://\(server)/stream?key=\(key)"
174+ let url = URL(string: surl)!
175+ ws.event.message = { message in
176+ self.processItem?(message)
177+ }
178+ let request = NSMutableURLRequest(url: url)
179+ request.httpMethod = "GET"
180+ request.addValue("upgrade", forHTTPHeaderField: "Connection")
181+ request.addValue("websocket", forHTTPHeaderField: "Upgrade")
182+ request.addValue("13", forHTTPHeaderField: "Sec-Websocket-Version")
183+ ws.open(request: request as URLRequest)
184+ DispatchQueue.main.async {
185+ completion?()
186+ }
187+ } else {
188+ getServer() {
189+ self.connect(completion: completion)
190+ }
191+ }
192+ }
193+}
194+
195+typealias Rule = (rule: String, tag: String)
Melk/StreamingParser.swiftnew file+80
@@ -0,0 +1,80 @@
1+//
2+// StreamingParser.swift
3+// Melk
4+//
5+// Created by Ilya Kos on 7/12/17.
6+// Copyright © 2017 Ilya Kos. All rights reserved.
7+//
8+
9+import Foundation
10+import Decodable
11+
12+class StreamingParser {
13+ static func parse(json: DJSON, completion: @escaping ((StreamingResponse) -> ())) {
14+ do {
15+ let code: Int = try json => "code"
16+ switch code {
17+ case 100:
18+ let event = try json => "event"
19+ let eventType: String = try event => "event_type"
20+ let eventID = try event => "event_id"
21+ let postID: WallPostID = try eventID => "post_id"
22+ let ownerID: UserID = try eventID => "post_owner_id"
23+ // let
24+ var post: WallPost!
25+ var comp = 2
26+ realmWrite { _ in
27+ post = WallPost.by(id: postID)
28+ post.owner = User.by(id: ownerID)
29+ switch eventType {
30+ case "post":
31+ break
32+ case "comment":
33+ /*
34+ if let id: Int = try? eventID => "comment_id" {
35+ let comment = WallPostReply.by(id: id)
36+ comment.wallPost = post
37+ comp += 1
38+ comment // TODO: get comment
39+ }
40+ */
41+ print("comment not implemented")
42+
43+ case "share":
44+ break
45+ default:
46+ print("error:\n\(json)")
47+ }
48+ }
49+// print(post.owner)
50+ let complete = {
51+ comp -= 1
52+ if comp == 0 {
53+// print(post.owner)
54+ completion(.post(post))
55+ }
56+ }
57+ post.owner?.get(completion: { (_) in
58+ complete()
59+ })
60+ post.get(completion: { (_) in
61+ complete()
62+ })
63+ break // handle event
64+ case 300:
65+ break // handle service message
66+ default:
67+ print("Not implemented:\n\(json)\n")
68+ }
69+ } catch let error {
70+ print(error)
71+ }
72+ }
73+}
74+
75+enum StreamingResponse {
76+ case service
77+ case post(WallPost)
78+}
79+
80+typealias DJSON = Any
Melk/StreamingService.swiftnew file+61
@@ -0,0 +1,61 @@
1+//
2+// StreamingService.swift
3+// Melk
4+//
5+// Created by Ilya Kos on 7/12/17.
6+// Copyright © 2017 Ilya Kos. All rights reserved.
7+//
8+
9+import Foundation
10+
11+class StreamingService {
12+ init() {
13+ streaming.processItem = { item in
14+ if let string = item as? String,
15+ let data = string.data(using: .utf8),
16+ let json = try? JSONSerialization.jsonObject(with: data, options: []) {
17+ StreamingParser.parse(json: json, completion: { (result) in
18+ switch result {
19+ case .post(let post):
20+ self.handleResult?(.post(post))
21+ case .service:
22+ break // TODO: HAndle servoce message
23+ }
24+ })
25+ }
26+ }
27+ }
28+ var rules: [String] = [] {
29+ didSet {
30+ updateRules()
31+ }
32+ }
33+
34+ func connect() {
35+ streaming.connect()
36+ }
37+
38+ var handleResult: ((StreamingServiceResult) -> ())?
39+
40+
41+
42+ private let streaming = Streaming()
43+
44+ private func updateRules() {
45+ streaming.removeAllRules {
46+ var outRules: [Rule] = []
47+ var i = 1
48+ for rule in self.rules {
49+ if i <= 10 {
50+ outRules.append((rule: rule, tag: String(i)))
51+ i += 1
52+ }
53+ }
54+ self.streaming.add(rules: outRules)
55+ }
56+ }
57+}
58+
59+enum StreamingServiceResult {
60+ case post(WallPost)
61+}
Melk/TableViewCells.swift+12−3
@@ -57,6 +57,12 @@ class TagTableViewCell: UITableViewCell {
5757 }
5858 }
5959
60+ var tagDidChange: ((String?) -> ())?
61+
62+ @objc private func textDidChange() {
63+ tagDidChange?(tagString)
64+ }
65+
6066 var style: TagStyle = .normal {
6167 didSet {
6268 updateStyle()
@@ -70,6 +76,9 @@ class TagTableViewCell: UITableViewCell {
7076
7177 textField.borderStyle = .roundedRect
7278 textField.adjustsFontSizeToFitWidth = false
79+ textField.autocapitalizationType = .none
80+ textField.autocorrectionType = .no
81+ textField.addTarget(self, action: #selector(self.textDidChange), for: .editingChanged)
7382
7483 contentView.addSubview(textField)
7584// textField.delegate = self
@@ -92,7 +101,7 @@ class TagTableViewCell: UITableViewCell {
92101 CGRect.infinite, limitedToNumberOfLines: 1)
93102 .height + 2*2
94103
95− let frame = CGRect(x: 16, y: 8, width: bounds.width - 16*2, height: height)
104+ let frame = CGRect(x: 16, y: style == .large ? 32 : 4, width: bounds.width - 16*2, height: height)
96105 textField.frame = frame
97106 print(textField.frame)
98107 }
@@ -103,8 +112,8 @@ class TagTableViewCell: UITableViewCell {
103112
104113
105114 static let identifier = "io.Melk.tagTableViewCell"
106− static let largeHeight: CGFloat = 37.5 + 8*2
107− static let normalHeight: CGFloat = 28.0 + 8*2
115+ static let largeHeight: CGFloat = 37.5 + 16 + 32
116+ static let normalHeight: CGFloat = 28.0 + 4*2
108117}
109118
110119//extension TagTableViewCell: UITextFieldDelegate {
Melk/VKData.swift+34−34
@@ -7,10 +7,10 @@
77//
88
99import Foundation
10−//import SwiftyJSON
10+import SwiftyJSON
1111import CoreLocation
1212import RealmSwift
13−import SwiftyVK
13+//import SwiftyVK
1414
1515class VKData {
1616 // var users: [UserID: User] = [:]
@@ -31,7 +31,7 @@ class VKData {
3131 // var polls: [PollID: Poll] = [:]
3232 // var photoAlbums: [PhotoAlbumID: PhotoAlbum] = [:]
3333
34− class func processUser(json: SwiftyVK.JSON) -> User? {
34+ class func processUser(json: JSON) -> User? {
3535 print("in")
3636 if let id = json["id"].int, let firstName = json["first_name"].string, let lastName = json["last_name"].string {
3737 var user: User!
@@ -49,7 +49,7 @@ class VKData {
4949 }
5050 user.birthday = formatter.date(from: birthday)
5151 }
52− user.avatar = RealmPhoto.by(user.avatarID, surl: json["photo_max"].string)
52+ user.avatar = RealmPhoto.by(user.avatarID, surl: json["photo_100"].string)// was "photo_max"
5353 user.photo = RealmPhoto.by(user.photoID, surl: json["photo_max_orig"].string)
5454 if let friendStat = json["friend_status"].int {
5555 user.friendStatus = FriendStatus(rawValue: friendStat)
@@ -90,7 +90,7 @@ class VKData {
9090 return nil
9191 }
9292
93− class func processGeolocation(_ json: SwiftyVK.JSON) -> Geolocation? {
93+ class func processGeolocation(_ json: JSON) -> Geolocation? {
9494 if let coords = json["coordinates"].string{
9595 let nums = coords.characters.split(separator: " ").map({ (n) -> Double? in
9696 NumberFormatter().number(from: String(n))?.doubleValue
@@ -112,7 +112,7 @@ class VKData {
112112 }
113113 }
114114
115− class func processMedia(_ json: SwiftyVK.JSON) -> Media? {
115+ class func processMedia(_ json: JSON) -> Media? {
116116 if let type = json["type"].string {
117117 switch type {
118118 case "photo":
@@ -174,7 +174,7 @@ class VKData {
174174 return nil
175175 }
176176
177− class func processPhotoAlbum(_ json: SwiftyVK.JSON) -> PhotoAlbum? {
177+ class func processPhotoAlbum(_ json: JSON) -> PhotoAlbum? {
178178 if let id = json["id"].int{
179179 var album: PhotoAlbum!
180180 realmWrite { _ in
@@ -209,7 +209,7 @@ class VKData {
209209 return nil
210210 }
211211
212− class func processGift(_ json: SwiftyVK.JSON) -> Gift? {
212+ class func processGift(_ json: JSON) -> Gift? {
213213 if let id = json["id"].int {
214214 var gift: Gift!
215215 realmWrite({ _ in
@@ -221,7 +221,7 @@ class VKData {
221221 return nil
222222 }
223223
224− class func processWallPostReply(_ json: SwiftyVK.JSON) -> WallPostReply? {
224+ class func processWallPostReply(_ json: JSON) -> WallPostReply? {
225225 let likes = json["likes"]
226226 if let id = json["id"].int {
227227 var reply: WallPostReply!
@@ -248,7 +248,7 @@ class VKData {
248248 return nil
249249 }
250250
251− class func processPollAnswer(_ json: SwiftyVK.JSON) -> PollAnswer? {
251+ class func processPollAnswer(_ json: JSON) -> PollAnswer? {
252252 if let id = json["id"].int,
253253 let body = json["text"].string,
254254 let votes = json["votes"].int,
@@ -258,7 +258,7 @@ class VKData {
258258 return nil
259259 }
260260
261− class func processPoll(_ json: SwiftyVK.JSON) -> Poll? {
261+ class func processPoll(_ json: JSON) -> Poll? {
262262 if let id = json["id"].int {
263263 var poll: Poll!
264264 realmWrite { _ in
@@ -279,7 +279,7 @@ class VKData {
279279 return nil
280280 }
281281
282− class func processPhoto(_ json: SwiftyVK.JSON) -> Photo? {
282+ class func processPhoto(_ json: JSON) -> Photo? {
283283 if let id = json["id"].int {
284284 var photo: Photo!
285285 realmWrite { _ in
@@ -289,13 +289,13 @@ class VKData {
289289 photo.width.value = json["width"].int
290290 photo.height.value = json["height"].int
291291 var surl: String?
292− if let imageSurl = json["photo_2560"].string {
293− surl = imageSurl
294− } else if let imageSurl = json["photo_1280"].string {
295− surl = imageSurl
296− } else if let imageSurl = json["photo_807"].string {
297− surl = imageSurl
298− } else if let imageSurl = json["photo_604"].string {
292+// if let imageSurl = json["photo_2560"].string {
293+// surl = imageSurl
294+// } else if let imageSurl = json["photo_1280"].string {
295+// surl = imageSurl
296+// } else if let imageSurl = json["photo_807"].string {
297+// surl = imageSurl
298+ /*} else*/ if let imageSurl = json["photo_604"].string {
299299 surl = imageSurl
300300 } else if let imageSurl = json["photo_130"].string {
301301 surl = imageSurl
@@ -312,7 +312,7 @@ class VKData {
312312 }
313313 }
314314
315− class func processVideo(_ json: SwiftyVK.JSON) -> Video? {
315+ class func processVideo(_ json: JSON) -> Video? {
316316 if let id = json["id"].int {
317317 var video: Video!
318318 realmWrite { _ in
@@ -337,7 +337,7 @@ class VKData {
337337 }
338338 }
339339
340− class func processAudio(_ json: SwiftyVK.JSON) -> Audio? {
340+ class func processAudio(_ json: JSON) -> Audio? {
341341 if let id = json["id"].int {
342342 var audio: Audio!
343343 realmWrite { _ in
@@ -354,7 +354,7 @@ class VKData {
354354 }
355355 }
356356
357− class func processDocument(_ json: SwiftyVK.JSON) -> Document? {
357+ class func processDocument(_ json: JSON) -> Document? {
358358 if let id = json["id"].int {
359359 var doc: Document!
360360 realmWrite { _ in
@@ -371,7 +371,7 @@ class VKData {
371371 return nil
372372 }
373373
374− class func processSticker(_ json: SwiftyVK.JSON) -> Sticker? {
374+ class func processSticker(_ json: JSON) -> Sticker? {
375375 if let id = json["id"].int,
376376 let pack = json["product_id"].int {
377377 var sticker: Sticker!
@@ -387,7 +387,7 @@ class VKData {
387387 return nil
388388 }
389389
390− class func processRating(_ json: SwiftyVK.JSON) -> Rating? {
390+ class func processRating(_ json: JSON) -> Rating? {
391391 if let stars = json["stars"].int,
392392 let reviewCount = json["reviews_count"].int {
393393 return Rating(stars: stars, reviewCount: reviewCount)
@@ -395,7 +395,7 @@ class VKData {
395395 return nil
396396 }
397397
398− class func processButton(_ json: SwiftyVK.JSON) -> LinkButton? {
398+ class func processButton(_ json: JSON) -> LinkButton? {
399399 if let title = json["title"].string,
400400 let surl = json["url"].string,
401401 let url = URL(string: surl) {
@@ -404,7 +404,7 @@ class VKData {
404404 return nil
405405 }
406406
407− class func processLink(_ json: SwiftyVK.JSON) -> Link? {
407+ class func processLink(_ json: JSON) -> Link? {
408408 if let surl = json["url"].string {
409409 var link: Link!
410410 realmWrite({ (_) in
@@ -422,7 +422,7 @@ class VKData {
422422 return nil
423423 }
424424
425− class func processWallPost(_ json: SwiftyVK.JSON) -> WallPost? {
425+ class func processWallPost(_ json: JSON) -> WallPost? {
426426 let comments = json["comments"]
427427 let likes = json["likes"]
428428 let reposts = json["reposts"]
@@ -430,7 +430,7 @@ class VKData {
430430 var post: WallPost!
431431 realmWrite { _ in
432432 post = WallPost.by(id: id)
433− post.owner = json["to_id"].int >>> {User.by(id: $0)}
433+ post.owner = json["owner_id"].int >>> {User.by(id: $0)}
434434 post.creator = json["from_id"].int >>> {User.by(id: $0)}
435435 post.date = json["date"].int >>> {Date.init(timeIntervalSince1970: TimeInterval($0))}
436436 post.body = json["text"].string
@@ -469,7 +469,7 @@ class VKData {
469469 return nil
470470 }
471471
472− class func prpocessServiceMessageAction(_ json: SwiftyVK.JSON) -> ServiceMessageAction? {
472+ class func prpocessServiceMessageAction(_ json: JSON) -> ServiceMessageAction? {
473473 if let sAction = json["action"].string {
474474 switch sAction {
475475 case "chat_photo_update":
@@ -519,7 +519,7 @@ class VKData {
519519 return nil
520520 }
521521
522− class func processMessage(_ json: SwiftyVK.JSON) -> Message? {
522+ class func processMessage(_ json: JSON) -> Message? {
523523 if let user = json["user_id"].int >>> {User.by(id: $0)},
524524 let date = json["date"].int >>> {Date(timeIntervalSince1970: TimeInterval($0))},
525525 let id = json["id"].int {
@@ -556,7 +556,7 @@ class VKData {
556556 }
557557 }
558558
559− class func processGenericMessage(_ json: SwiftyVK.JSON) -> GenericMessage? {
559+ class func processGenericMessage(_ json: JSON) -> GenericMessage? {
560560 if let user = json["user_id"].int >>> {User.by(id: $0)},
561561 let date = json["date"].int >>> {Date(timeIntervalSince1970: TimeInterval($0))} {
562562 let message = GenericMessage()
@@ -587,7 +587,7 @@ class VKData {
587587
588588
589589
590− class func processNotificationSettings(_ json: SwiftyVK.JSON) -> ChatNotificationSettings? {
590+ class func processNotificationSettings(_ json: JSON) -> ChatNotificationSettings? {
591591 if let sound = json["sound"].int,
592592 let date = json["disabled_until"].int {
593593 if date == -1 {
@@ -600,7 +600,7 @@ class VKData {
600600 }
601601 }
602602
603− class func processMultichat(_ json: SwiftyVK.JSON) -> Multichat? { // from .getChat
603+ class func processMultichat(_ json: JSON) -> Multichat? { // from .getChat
604604 if let id = json["id"].int {
605605 var chat: Multichat!
606606 realmWrite { _ in
@@ -634,7 +634,7 @@ class VKData {
634634 return nil
635635 }
636636
637− class func processDialog(_ json: SwiftyVK.JSON) -> Dialog? {
637+ class func processDialog(_ json: JSON) -> Dialog? {
638638 let message = json["message"]
639639 if let id = message["chat_id"].int { // Multichat
640640 if let sMessage = processMessage(message) {
Melk/VKPublicCrap.swift+6−3
@@ -6,8 +6,8 @@
66// Copyright © 2016 Ilya Kos. All rights reserved.
77//
88
9−import SwiftyVK
10−
9+//import SwiftyVK
10+/*
1111let permissions: Set<VK.Scope> = [
1212// .notify,
1313// .friends,
@@ -30,7 +30,8 @@ let permissions: Set<VK.Scope> = [
3030// .offline,
3131// .market,
3232]
33−let vkAPIVersion = "5.60"
33+ */
34+let vkAPIVersion = "5.67"
3435let appID = "6097091"
3536
3637let multichatchatId = 2000000000
@@ -38,3 +39,5 @@ let multichatchatId = 2000000000
3839enum Methods {
3940 static let getDialogs = "getDialogs"
4041}
42+
43+let accessKey = "363b0ecf363b0ecf363b0ecf9a3666060c3363b363b0ecf6f6cb5bcd453ddb4d0fadf8a"
Melk/VKServer.swiftnew file+123
@@ -0,0 +1,123 @@
1+//
2+// VKServer.swift
3+// Melk
4+//
5+// Created by Ilya Kos on 7/12/17.
6+// Copyright © 2017 Ilya Kos. All rights reserved.
7+//
8+
9+import Foundation
10+import Alamofire
11+
12+class VKServer {
13+ static func get(photo: Photo, completion: @escaping ((Data) -> ())) {
14+ func string(`for` photo: Photo) -> String? {
15+ if let ownerID = photo.owner?.id {
16+ let id = photo.id
17+ var out = "\(ownerID)_\(id)"
18+ if let accessKey = photo.accessKey {
19+ out.append("_\(accessKey)")
20+ }
21+ return out
22+ }
23+ return nil
24+ }
25+ let params: Parameters = [
26+ "photos": string(for: photo)!
27+ ]
28+ Alamofire.request(url(for: "photos.getById"), parameters: params)
29+ .validate().responseData { (response) in
30+ switch response.result {
31+ case .success(let data):
32+ completion(data)
33+ case .failure(let error):
34+ print(error)
35+ }
36+ }
37+ }
38+ static func get(video: Video, completion: @escaping ((Data) -> ())) {
39+ func string(`for` video: Video) -> String? {
40+ if let ownerID = video.owner?.id {
41+ let id = video.id
42+ var out = "\(ownerID)_\(id)"
43+ if let accessKey = video.accessKey {
44+ out.append("_\(accessKey)")
45+ }
46+ return out
47+ }
48+ return nil
49+ }
50+ let params: Parameters = [
51+ "videos": string(for: video)!
52+ ]
53+ Alamofire.request(url(for: "videos.get"), parameters: params)
54+ .validate().responseData { (response) in
55+ switch response.result {
56+ case .success(let data):
57+ completion(data)
58+ case .failure(let error):
59+ print(error)
60+ }
61+ }
62+ }
63+ static func get(doc: Document, completion: @escaping ((Data) -> ())) {
64+ if let userID = doc.owner?.id {
65+ let id = doc.id
66+ let params: Parameters = [
67+ "docs": "\(userID)_\(id)"
68+ ]
69+ Alamofire.request(url(for: "docs.getById"), parameters: params)
70+ .validate().responseData(completionHandler: { (response) in
71+ switch response.result {
72+ case .success(let data):
73+ completion(data)
74+ case .failure(let error):
75+ print(error)
76+ }
77+ })
78+ }
79+ }
80+ static func get(user: User, completion: @escaping ((Data) -> ())) {
81+ let id = user.id
82+ let params: Parameters = [
83+ "user_ids": id,
84+ "fields": "photo_100"
85+ ]
86+ Alamofire.request(url(for: "users.get"), parameters: params)
87+ .validate().responseData { (response) in
88+ switch response.result {
89+ case .success(let data):
90+ completion(data)
91+ case .failure(let error):
92+ print(error)
93+ }
94+ }
95+ }
96+ static func get(wallPost: WallPost, completion: @escaping ((Data) -> ())) {
97+ if let userID = wallPost.owner?.id {
98+ let id = wallPost.id
99+ let params: Parameters = [
100+ "posts": "\(userID)_\(id)"
101+ ]
102+ Alamofire.request(url(for: "wall.getById"), parameters: params)
103+ .validate().responseData(completionHandler: { (response) in
104+ switch response.result {
105+ case .success(let data):
106+ completion(data)
107+ case .failure(let error):
108+ print(error)
109+ }
110+ })
111+ }
112+ }
113+
114+ static func get(wallPostReply: WallPostReply, completion: @escaping ((Data) -> ())) {
115+ // TODO: Get wallPostReply
116+ }
117+
118+}
119+
120+fileprivate func url(`for` method: String) -> URL {
121+ let surl = "https://api.vk.com/method/\(method)?access_token=\(accessKey)&v=\(vkAPIVersion)&lang=0"
122+ return URL.init(string: surl)!
123+}
Melk/VkServices.swiftdeleted−207
@@ -1,207 +0,0 @@
1−//
2−// VkServices.swift
3−// golos
4−//
5−// Created by Ilya Kos on 7/24/16.
6−// Copyright © 2016 Ilya Kos. All rights reserved.
7−//
8−
9−import UIKit
10−import Alamofire
11−import SwiftyVK
12−
13−// let realm = try! Realm()
14−
15−class VkServices {
16− class func getDialogs(offset: Int = 0, count: Int = 200, completion: (() -> ())? = nil) { // Messages
17− VK.API.remote(method: Methods.getDialogs, parameters:
18− [VK.Arg.offset: String(offset),
19− VK.Arg.count: String(count)])
20− .send(onSuccess: { (response) in
21− if let usersJSON = response[1].array,
22− let chatUsersJSON = response[2].array,
23− let dialogs = response[0]["items"].array,
24− let chatUserInvitersJSON = response[3].array,
25− let multichatsJSON = response[4].array { // TODO: Catch error from vk
26− userQueue.async {
27− let _ = usersJSON.map(VKData.processUser)
28− let _ = chatUsersJSON.map(VKData.processUser)
29− let _ = chatUserInvitersJSON.flatMap(VKData.processUser)
30− let _ = multichatsJSON.map(VKData.processMultichat)
31− let _ = dialogs.map(VKData.processDialog)
32− print("DONEZO")
33− DispatchQueue.main.async {
34− completion?()
35− }
36− }
37− }
38− }, onError: { error in
39− print(error)
40− }) // TODO: Handle error
41− }
42−
43−
44− /// - Parameters:
45− /// - dialog: in the `200000000` and `-` fomat
46− /// - count: count
47− /// - offset: offset
48− /// - completion: completion handler
49− class func getMessages(forDialog dialog: DialogID, count: Int = 20, offset: Int = 0, completion: (() -> ())? = nil) {
50− var params: [VK.Arg: String] = [
51− VK.Arg.peerId: String(dialog),
52− VK.Arg.offset: String(offset),
53− VK.Arg.count: String(count)]
54− var request = VK.API.Messages.getHistory()
55− request.add(parameters: params)
56− request.send(onSuccess: { (response) in
57− userQueue.async {
58− realmWrite { _ in
59− let rDialog = RealmDialog.by(peerID: dialog)
60− var f = rDialog.value
61− f <- response["items"].array?.flatMap(VKData.processMessage)
62− f?.lastReadMessageID = max(response["out_read"].intValue, response["in_read"].intValue) // TODO: Probrably should use seperate things for readin and readout
63− response["count"].int >>> { rDialog.value?.count.value = $0 }
64− }
65− }
66− }, onError: nil) // TODO: Handle error
67−
68− completion?()
69− }
70−
71− class func send(_ message: Message, to dialog: RealmDialog, completion: (() -> ())? = nil) {
72− let date = Date()
73− let time = Int(date.timeIntervalSince1970) * 100
74− var params: [VK.Arg: String] = [:]
75− if let d = dialog.value {
76− switch d {
77− case let chat as Chat:
78− params[VK.Arg.peerId] = String(chat.userID)
79− case let mchat as Multichat:
80− params[VK.Arg.chatId] = String(mchat.id)
81− default:
82− break
83− }
84− }
85− if dialog.dateLastSent == date {
86− realmWrite({ (_) in
87− dialog.dateSentNumber += 1
88− })
89− } else {
90− realmWrite({ (_) in
91− dialog.dateSentNumber = 1
92− dialog.dateLastSent = date
93− })
94− }
95−
96− var i = 0
97− func photoUploadRequest() {
98− if i < message.outPhotos.count {
99− if let data = UIImagePNGRepresentation(message.outPhotos[i]) {
100− let request = VK.API.Upload.Photo.toMessage(SwiftyVK.Media.init(imageData: data, type: .PNG))
101− request.send(onSuccess: { (response) in
102− if let photo = VKData.processPhoto(response) { // TODO: this probrably shouldnt happen on the main queue
103− message.attachments.append(RealmMedia(value: Media.photo(photo)))
104− }
105− i += 1
106− photoUploadRequest()
107− }, onError: nil) // TODO: Handle error
108− } else {
109− i += 1
110− photoUploadRequest()
111− }
112− }
113− }
114−
115−
116−
117− params[VK.Arg.randomId] = String(time + dialog.dateSentNumber)
118− params[VK.Arg.message] = String(message.body)
119− message.geolocation?.geoCoordinates?.coordinate.latitude >>> {params[VK.Arg.lat] = String($0)}
120− message.geolocation?.geoCoordinates?.coordinate.longitude >>> {params[VK.Arg.long] = String($0)}
121− var att = ""
122− var stickerID: Int?
123− for m in message.attachments {
124− m.string >>> { att.append("\($0),") }
125− if stickerID == nil {
126− if let v = m.value {
127− switch v {
128− case .sticker(let s):
129− stickerID = s.id
130− default:
131− break
132− }
133− }
134− }
135− }
136− params[VK.Arg.attachments] = att
137− var fm = ""
138− for m in message.forwardedMessages {
139− m.id >>> {fm.append("\($0),")}
140− }
141− params[VK.Arg.forwardMessages] = fm
142− stickerID >>> {params[VK.Arg.stickerId] = String($0)}
143− VK.API.Messages.send(params).send(onSuccess: { (response) in
144− if let id = response.int {
145− realmWrite({ (_) in
146− message.id = id
147− dialog.value?.messages <~ message // TODO: Uncomment this
148− })
149− }
150− completion?()
151− }, onError: nil) // TODO: Handle error
152− }
153− class func getMultichat(for id: MultichatID) {
154− let params: [VK.Arg: String] = [
155− VK.Arg.chatId: String(id),
156− VK.Arg.fields: "nickname, screen_name, sex, bdate, city, country, timezone, photo_50, photo_100, photo_200_orig, has_mobile, contacts, education, online, counters, relation, last_seen, status, can_write_private_message, can_see_all_posts, can_post, universities",
157−
158− ]
159− VK.API.Messages.getChat(params).send(onSuccess: { (response) in
160− VKData.processMultichat(response)
161− }, onError: nil) // TODO: Handle error
162− }
163−
164−}
165−
166−fileprivate extension RealmMedia {
167− var string: String? {
168− if let v = value {
169− switch v {
170− case .photo(let p):
171− if let o = p.owner {
172− var out = "photo\(o.id)_\(p.id)"
173− p.accessKey >>> {out.append("_\($0)")}
174− return out
175− }
176− case .video(let p):
177− if let o = p.owner {
178− var out = "video\(o.id)_\(p.id)"
179− p.accessKey >>> {out.append("_\($0)")}
180− return out
181− }
182− case .audio(let p):
183− if let o = p.owner {
184− return "audio\(o.id)_\(p.id)"
185− }
186− case .doc(let p), .graffiti(let p):
187− if let o = p.owner {
188− return "doc\(o.id)_\(p.id)"
189− }
190− case .wall(let p):
191− if let o = p.owner {
192− return "photo\(o.id)_\(p.id)"
193− }
194− case .market(let p):
195− if let o = p.owner {
196− return "photo\(o.id)_\(p.id)"
197− }
198− default: break
199− }
200− }
201− return nil
202− }
203−}
204−
205−private func URLFor(method: String) -> String {
206− return "https://api.vk.com/method/" + method
207−}
README.mdnew file+17
@@ -0,0 +1,17 @@
1+# Melk
2+
3+_Might not work properly with new streaming API. This project was developed while the API was in beta._
4+
5+[![Melk demo](http://img.youtube.com/vi/9SNc0bLDCL0/0.jpg)](http://www.youtube.com/watch?v=9SNc0bLDCL0)
6+
7+При проведении различных мероприятий организаторы довольно часто призывают людей, посещающих эти мероприятия, выложить у себя в соц сети что-нибудь об этом мероприятии, чаще всего под мероприятие даже выделяется отдельный хештег.
8+
9+Для привлечения внимания к хештегу и поощрения его использования можно выводить некоторые из выложенных с хештегом записей туда, где все смогут это увидеть.
10+
11+При проведении больших мероприятий желательно использовать большое количество независимых систем, чтобы если летит что-то одно, все остальное продолжало работать.
12+
13+Полноценный десктопный компьютер для данной цели не нужен, поэтому приложение работает просто на телефоне или планшете.
14+
15+Самый первый хештег или ключевое слово будет крупно показано вместе со всеми записями.
16+
17+После того, как будет нажата кнопка “Present”, записи с основным хештегом будут выводиться на любые подключенные к устройству экраны.