on master help sign in
29 files changed+5228−73
since splitsnapshots
unifiedsplit
.gitignore+1 Cartfile+8 Cartfile.resolved+5 Melk.xcodeproj/project.pbxproj+195−16 Melk.xcodeproj/xcuserdata/ilya.xcuserdatad/xcschemes/Melk.xcscheme+3−3 Melk/AppDelegate.swift+28−1 Melk/Assets.xcassets/AppIcon.appiconset/Contents.json+10 Melk/Base.lproj/Main.storyboard−26 Melk/CardContainer.swift+100 Melk/CardView.swift+196 Melk/CollectionView.swift+112 Melk/Info.plist+18−2 Melk/LocalVKDataTypes.swift+2270 Melk/MainCoordinator.swift+111 Melk/MainViewController.swift+107 Melk/MyWeirdness.swift+104 Melk/PublicCrap.swift+259 Melk/SecondaryCoordinator.swift+53 Melk/SecondaryViewController.swift+156 Melk/StackView.swift+156 Melk/Streaming.swift+195 Melk/StreamingParser.swift+80 Melk/StreamingService.swift+61 Melk/TableViewCells.swift+145 Melk/VKData.swift+672 Melk/VKPublicCrap.swift+43 Melk/VKServer.swift+123 Melk/ViewController.swift−25 README.md+17
.gitignorenew file+1
@@ -0,0 +1 @@
1+Carthage/
Cartfilenew file+8
@@ -0,0 +1,8 @@
1+github "Alamofire/Alamofire"
2+# github "WE-St0r/SwiftyVK"
3+github "kean/Nuke" ~> 4.0
4+github "realm/realm-cocoa.git"
5+github "tidwall/SwiftWebSocket"
6+# github "SwiftyJSON/SwiftyJSON"
7+# github "utahiosmac/Marshal"
8+github "Anviking/Decodable"
Cartfile.resolvednew file+5
@@ -0,0 +1,5 @@
1+github "Alamofire/Alamofire" "4.5.0"
2+github "Anviking/Decodable" "v0.5.1"
3+github "kean/Nuke" "4.1.2"
4+github "realm/realm-cocoa" "v2.8.3"
5+github "tidwall/SwiftWebSocket" "2.6.5"
Melk.xcodeproj/project.pbxproj+195−16
@@ -7,21 +7,98 @@
77 objects = {
88
99/* Begin PBXBuildFile section */
10+ 0919AEA71F0D3D9D00894FFA /* SecondaryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919AEA61F0D3D9D00894FFA /* SecondaryViewController.swift */; };
11+ 0919AEA91F0D459600894FFA /* CardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919AEA81F0D459600894FFA /* CardView.swift */; };
12+ 0919AEAD1F0D706700894FFA /* StackView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0919AEAC1F0D706700894FFA /* StackView.swift */; };
13+ 09231E351F2394ED007C836D /* CardContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09231E341F2394ED007C836D /* CardContainer.swift */; };
14+ 09939A561F097DA90014719D /* TableViewCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09939A551F097DA90014719D /* TableViewCells.swift */; };
15+ 09A456361F0ED9A800BAC32D /* CollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A456351F0ED9A800BAC32D /* CollectionView.swift */; };
16+ 09AFAB041F159C9200EBD200 /* StreamingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09AFAB031F159C9200EBD200 /* StreamingService.swift */; };
17+ 09B781591F11630D0007D2BF /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = 09B781581F11630D0007D2BF /* .gitignore */; };
1018 09BF41EF1F07F71F00AFAD57 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF41EE1F07F71F00AFAD57 /* AppDelegate.swift */; };
11− 09BF41F11F07F71F00AFAD57 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF41F01F07F71F00AFAD57 /* ViewController.swift */; };
12− 09BF41F41F07F71F00AFAD57 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 09BF41F21F07F71F00AFAD57 /* Main.storyboard */; };
1319 09BF41F61F07F71F00AFAD57 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 09BF41F51F07F71F00AFAD57 /* Assets.xcassets */; };
1420 09BF41F91F07F71F00AFAD57 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 09BF41F71F07F71F00AFAD57 /* LaunchScreen.storyboard */; };
21+ 09BF42011F07F75800AFAD57 /* Cartfile in Resources */ = {isa = PBXBuildFile; fileRef = 09BF42001F07F75800AFAD57 /* Cartfile */; };
22+ 09BF420E1F0833A700AFAD57 /* LocalVKDataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF420D1F0833A700AFAD57 /* LocalVKDataTypes.swift */; };
23+ 09BF42101F0833C100AFAD57 /* MainCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF420F1F0833C100AFAD57 /* MainCoordinator.swift */; };
24+ 09BF42131F08341B00AFAD57 /* MyWeirdness.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF42111F08341B00AFAD57 /* MyWeirdness.swift */; };
25+ 09BF42141F08341B00AFAD57 /* PublicCrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF42121F08341B00AFAD57 /* PublicCrap.swift */; };
26+ 09BF42161F0834D400AFAD57 /* VKPublicCrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF42151F0834D400AFAD57 /* VKPublicCrap.swift */; };
27+ 09BF42181F08352E00AFAD57 /* VKData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF42171F08352E00AFAD57 /* VKData.swift */; };
28+ 09BF42321F08388100AFAD57 /* SecondaryCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF42311F08388100AFAD57 /* SecondaryCoordinator.swift */; };
29+ 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 */; };
1547/* End PBXBuildFile section */
1648
49+/* Begin PBXCopyFilesBuildPhase section */
50+ 09F03F341F165884000CE916 /* Embed Frameworks */ = {
51+ isa = PBXCopyFilesBuildPhase;
52+ buildActionMask = 2147483647;
53+ dstPath = "";
54+ dstSubfolderSpec = 10;
55+ files = (
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 */,
63+ );
64+ name = "Embed Frameworks";
65+ runOnlyForDeploymentPostprocessing = 0;
66+ };
67+/* End PBXCopyFilesBuildPhase section */
68+
1769/* Begin PBXFileReference section */
70+ 0919AEA61F0D3D9D00894FFA /* SecondaryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondaryViewController.swift; sourceTree = "<group>"; };
71+ 0919AEA81F0D459600894FFA /* CardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardView.swift; sourceTree = "<group>"; };
72+ 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>"; };
74+ 09939A551F097DA90014719D /* TableViewCells.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCells.swift; sourceTree = "<group>"; };
75+ 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>"; };
78+ 09B781581F11630D0007D2BF /* .gitignore */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = .gitignore; path = ../.gitignore; sourceTree = "<group>"; };
1879 09BF41EB1F07F71F00AFAD57 /* Melk.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Melk.app; sourceTree = BUILT_PRODUCTS_DIR; };
1980 09BF41EE1F07F71F00AFAD57 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
20− 09BF41F01F07F71F00AFAD57 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
21− 09BF41F31F07F71F00AFAD57 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
2281 09BF41F51F07F71F00AFAD57 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
2382 09BF41F81F07F71F00AFAD57 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
2483 09BF41FA1F07F71F00AFAD57 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
84+ 09BF42001F07F75800AFAD57 /* Cartfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Cartfile; sourceTree = SOURCE_ROOT; };
85+ 09BF42021F080A5700AFAD57 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = Carthage/Build/iOS/Alamofire.framework; sourceTree = SOURCE_ROOT; };
86+ 09BF42031F080A5700AFAD57 /* Nuke.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nuke.framework; path = Carthage/Build/iOS/Nuke.framework; sourceTree = SOURCE_ROOT; };
87+ 09BF42041F080A5700AFAD57 /* Realm.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Realm.framework; path = Carthage/Build/iOS/Realm.framework; sourceTree = SOURCE_ROOT; };
88+ 09BF42051F080A5700AFAD57 /* RealmSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RealmSwift.framework; path = Carthage/Build/iOS/RealmSwift.framework; sourceTree = SOURCE_ROOT; };
89+ 09BF420D1F0833A700AFAD57 /* LocalVKDataTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalVKDataTypes.swift; sourceTree = "<group>"; };
90+ 09BF420F1F0833C100AFAD57 /* MainCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainCoordinator.swift; sourceTree = "<group>"; };
91+ 09BF42111F08341B00AFAD57 /* MyWeirdness.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyWeirdness.swift; sourceTree = "<group>"; };
92+ 09BF42121F08341B00AFAD57 /* PublicCrap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PublicCrap.swift; sourceTree = "<group>"; };
93+ 09BF42151F0834D400AFAD57 /* VKPublicCrap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VKPublicCrap.swift; sourceTree = "<group>"; };
94+ 09BF42171F08352E00AFAD57 /* VKData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VKData.swift; sourceTree = "<group>"; };
95+ 09BF42311F08388100AFAD57 /* SecondaryCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondaryCoordinator.swift; sourceTree = "<group>"; };
96+ 09BF42331F083B5800AFAD57 /* SwiftWebSocket.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftWebSocket.framework; path = Carthage/Build/iOS/SwiftWebSocket.framework; sourceTree = SOURCE_ROOT; };
97+ 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>"; };
25102/* End PBXFileReference section */
26103
27104/* Begin PBXFrameworksBuildPhase section */
@@ -29,12 +106,66 @@
29106 isa = PBXFrameworksBuildPhase;
30107 buildActionMask = 2147483647;
31108 files = (
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 */,
32116 );
33117 runOnlyForDeploymentPostprocessing = 0;
34118 };
35119/* End PBXFrameworksBuildPhase section */
36120
37121/* 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+ };
160+ 09A456341F0ED98800BAC32D /* Public */ = {
161+ isa = PBXGroup;
162+ children = (
163+ 0919AEAC1F0D706700894FFA /* StackView.swift */,
164+ 09A456351F0ED9A800BAC32D /* CollectionView.swift */,
165+ );
166+ name = Public;
167+ sourceTree = "<group>";
168+ };
38169 09BF41E21F07F71F00AFAD57 = {
39170 isa = PBXGroup;
40171 children = (
@@ -54,16 +185,42 @@
54185 09BF41ED1F07F71F00AFAD57 /* Melk */ = {
55186 isa = PBXGroup;
56187 children = (
188+ 098397F71F1E887F008C88DD /* Main */,
189+ 098397F81F1E888F008C88DD /* Secondary */,
190+ 098397F91F1E88A3008C88DD /* Streaming */,
191+ 09231E331F2394C5007C836D /* Card */,
57192 09BF41EE1F07F71F00AFAD57 /* AppDelegate.swift */,
58− 09BF41F01F07F71F00AFAD57 /* ViewController.swift */,
59− 09BF41F21F07F71F00AFAD57 /* Main.storyboard */,
193+ 09BF42111F08341B00AFAD57 /* MyWeirdness.swift */,
194+ 09BF42121F08341B00AFAD57 /* PublicCrap.swift */,
195+ 09F03F351F1674ED000CE916 /* VKServer.swift */,
196+ 09BF420D1F0833A700AFAD57 /* LocalVKDataTypes.swift */,
197+ 09BF42151F0834D400AFAD57 /* VKPublicCrap.swift */,
198+ 09BF42171F08352E00AFAD57 /* VKData.swift */,
60199 09BF41F51F07F71F00AFAD57 /* Assets.xcassets */,
61200 09BF41F71F07F71F00AFAD57 /* LaunchScreen.storyboard */,
62201 09BF41FA1F07F71F00AFAD57 /* Info.plist */,
202+ 09BF42001F07F75800AFAD57 /* Cartfile */,
203+ 09A456341F0ED98800BAC32D /* Public */,
204+ 09B781581F11630D0007D2BF /* .gitignore */,
205+ 09BF420C1F080A5F00AFAD57 /* Dependencies */,
63206 );
64207 path = Melk;
65208 sourceTree = "<group>";
66209 };
210+ 09BF420C1F080A5F00AFAD57 /* Dependencies */ = {
211+ isa = PBXGroup;
212+ children = (
213+ 09F03F241F165876000CE916 /* Decodable.framework */,
214+ 09AFAAFF1F159AE300EBD200 /* SwiftyJSON.framework */,
215+ 09BF42331F083B5800AFAD57 /* SwiftWebSocket.framework */,
216+ 09BF42021F080A5700AFAD57 /* Alamofire.framework */,
217+ 09BF42031F080A5700AFAD57 /* Nuke.framework */,
218+ 09BF42041F080A5700AFAD57 /* Realm.framework */,
219+ 09BF42051F080A5700AFAD57 /* RealmSwift.framework */,
220+ );
221+ name = Dependencies;
222+ sourceTree = "<group>";
223+ };
67224/* End PBXGroup section */
68225
69226/* Begin PBXNativeTarget section */
@@ -74,6 +231,7 @@
74231 09BF41E71F07F71F00AFAD57 /* Sources */,
75232 09BF41E81F07F71F00AFAD57 /* Frameworks */,
76233 09BF41E91F07F71F00AFAD57 /* Resources */,
234+ 09F03F341F165884000CE916 /* Embed Frameworks */,
77235 );
78236 buildRules = (
79237 );
@@ -124,9 +282,10 @@
124282 isa = PBXResourcesBuildPhase;
125283 buildActionMask = 2147483647;
126284 files = (
285+ 09B781591F11630D0007D2BF /* .gitignore in Resources */,
127286 09BF41F91F07F71F00AFAD57 /* LaunchScreen.storyboard in Resources */,
128287 09BF41F61F07F71F00AFAD57 /* Assets.xcassets in Resources */,
129− 09BF41F41F07F71F00AFAD57 /* Main.storyboard in Resources */,
288+ 09BF42011F07F75800AFAD57 /* Cartfile in Resources */,
130289 );
131290 runOnlyForDeploymentPostprocessing = 0;
132291 };
@@ -137,22 +296,31 @@
137296 isa = PBXSourcesBuildPhase;
138297 buildActionMask = 2147483647;
139298 files = (
140− 09BF41F11F07F71F00AFAD57 /* ViewController.swift in Sources */,
299+ 09939A561F097DA90014719D /* TableViewCells.swift in Sources */,
300+ 09F03F361F1674ED000CE916 /* VKServer.swift in Sources */,
301+ 0919AEAD1F0D706700894FFA /* StackView.swift in Sources */,
302+ 09BF42141F08341B00AFAD57 /* PublicCrap.swift in Sources */,
303+ 09BF42161F0834D400AFAD57 /* VKPublicCrap.swift in Sources */,
304+ 09F0D79E1F129DCB00A13751 /* Streaming.swift in Sources */,
305+ 09BF42181F08352E00AFAD57 /* VKData.swift in Sources */,
306+ 09BF42101F0833C100AFAD57 /* MainCoordinator.swift in Sources */,
307+ 09A456361F0ED9A800BAC32D /* CollectionView.swift in Sources */,
308+ 09231E351F2394ED007C836D /* CardContainer.swift in Sources */,
309+ 09BF42131F08341B00AFAD57 /* MyWeirdness.swift in Sources */,
310+ 0919AEA91F0D459600894FFA /* CardView.swift in Sources */,
311+ 09BF42321F08388100AFAD57 /* SecondaryCoordinator.swift in Sources */,
141312 09BF41EF1F07F71F00AFAD57 /* AppDelegate.swift in Sources */,
313+ 09AFAB041F159C9200EBD200 /* StreamingService.swift in Sources */,
314+ 09F03F231F164E7E000CE916 /* StreamingParser.swift in Sources */,
315+ 09BF420E1F0833A700AFAD57 /* LocalVKDataTypes.swift in Sources */,
316+ 09BF42561F08413000AFAD57 /* MainViewController.swift in Sources */,
317+ 0919AEA71F0D3D9D00894FFA /* SecondaryViewController.swift in Sources */,
142318 );
143319 runOnlyForDeploymentPostprocessing = 0;
144320 };
145321/* End PBXSourcesBuildPhase section */
146322
147323/* Begin PBXVariantGroup section */
148− 09BF41F21F07F71F00AFAD57 /* Main.storyboard */ = {
149− isa = PBXVariantGroup;
150− children = (
151− 09BF41F31F07F71F00AFAD57 /* Base */,
152− );
153− name = Main.storyboard;
154− sourceTree = "<group>";
155− };
156324 09BF41F71F07F71F00AFAD57 /* LaunchScreen.storyboard */ = {
157325 isa = PBXVariantGroup;
158326 children = (
@@ -262,7 +430,12 @@
262430 buildSettings = {
263431 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
264432 DEVELOPMENT_TEAM = 8J63JZYYHZ;
433+ FRAMEWORK_SEARCH_PATHS = (
434+ "$(inherited)",
435+ "$(PROJECT_DIR)/Carthage/Build/iOS",
436+ );
265437 INFOPLIST_FILE = Melk/Info.plist;
438+ IPHONEOS_DEPLOYMENT_TARGET = 10.0;
266439 LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
267440 PRODUCT_BUNDLE_IDENTIFIER = com.ilyakooo0.Melk;
268441 PRODUCT_NAME = "$(TARGET_NAME)";
@@ -275,7 +448,12 @@
275448 buildSettings = {
276449 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
277450 DEVELOPMENT_TEAM = 8J63JZYYHZ;
451+ FRAMEWORK_SEARCH_PATHS = (
452+ "$(inherited)",
453+ "$(PROJECT_DIR)/Carthage/Build/iOS",
454+ );
278455 INFOPLIST_FILE = Melk/Info.plist;
456+ IPHONEOS_DEPLOYMENT_TARGET = 10.0;
279457 LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
280458 PRODUCT_BUNDLE_IDENTIFIER = com.ilyakooo0.Melk;
281459 PRODUCT_NAME = "$(TARGET_NAME)";
@@ -302,6 +480,7 @@
302480 09BF41FF1F07F71F00AFAD57 /* Release */,
303481 );
304482 defaultConfigurationIsVisible = 0;
483+ defaultConfigurationName = Release;
305484 };
306485/* End XCConfigurationList section */
307486 };
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+28−1
@@ -7,17 +7,40 @@
77//
88
99import UIKit
10+//import SwiftyVK
1011
1112@UIApplicationMain
1213class AppDelegate: UIResponder, UIApplicationDelegate {
13−
14+
1415 var window: UIWindow?
16+
17+ var coordinator: MainCoordinator!
1518
1619
1720 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
21+ let mainWindow = UIWindow(frame: UIScreen.main.bounds)
22+ mainWindow.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
23+ mainWindow.screen = UIScreen.main
24+ mainWindow.makeKeyAndVisible()
25+ coordinator = MainCoordinator(window: mainWindow)
26+ coordinator.start()
27+
28+ NotificationCenter.default.addObserver(forName: NSNotification.Name.UIScreenDidConnect, object: nil, queue: OperationQueue.main) { (_) in
29+ self.updateScreens()
30+ }
31+
32+ NotificationCenter.default.addObserver(forName: NSNotification.Name.UIScreenDidDisconnect, object: nil, queue: OperationQueue.main) { (_) in
33+ self.updateScreens()
34+ }
35+
1836 // Override point for customization after application launch.
1937 return true
2038 }
39+
40+ func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
41+ return true
42+ }
43+
2144
2245 func applicationWillResignActive(_ application: UIApplication) {
2346 // 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.
@@ -40,6 +63,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
4063 func applicationWillTerminate(_ application: UIApplication) {
4164 // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
4265 }
66+
67+ private func updateScreens() {
68+ coordinator.updateScreens()
69+ }
4370
4471
4572}
Melk/Assets.xcassets/AppIcon.appiconset/Contents.json+10
@@ -1,5 +1,15 @@
11{
22 "images" : [
3+ {
4+ "idiom" : "iphone",
5+ "size" : "20x20",
6+ "scale" : "2x"
7+ },
8+ {
9+ "idiom" : "iphone",
10+ "size" : "20x20",
11+ "scale" : "3x"
12+ },
313 {
414 "idiom" : "iphone",
515 "size" : "29x29",
Melk/Base.lproj/Main.storyboarddeleted−26
@@ -1,26 +0,0 @@
1−<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2−<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11134" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
3− <dependencies>
4− <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11106"/>
5− <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
6− </dependencies>
7− <scenes>
8− <!--View Controller-->
9− <scene sceneID="tne-QT-ifu">
10− <objects>
11− <viewController id="BYZ-38-t0r" customClass="ViewController" customModuleProvider="target" sceneMemberID="viewController">
12− <layoutGuides>
13− <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
14− <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
15− </layoutGuides>
16− <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
17− <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
18− <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
19− <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
20− </view>
21− </viewController>
22− <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
23− </objects>
24− </scene>
25− </scenes>
26−</document>
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.swiftnew file+196
@@ -0,0 +1,196 @@
1+//
2+// CardView.swift
3+// Melk
4+//
5+// Created by Ilya Kos on 7/5/17.
6+// Copyright © 2017 Ilya Kos. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class CardView: UIView {
12+
13+ var post: WallPost? {
14+ didSet {
15+// print(post?.owner)
16+ update()
17+ }
18+ }
19+ var minWidth: CGFloat = 300 {
20+ didSet {
21+// update()
22+ }
23+ }
24+ var height: CGFloat = 400 {
25+ didSet {
26+// update()
27+ }
28+ }
29+
30+ override init(frame: CGRect) {
31+ super.init(frame: frame)
32+ layer.masksToBounds = true
33+ layer.cornerRadius = 20
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)
39+ }
40+
41+ required init?(coder aDecoder: NSCoder) {
42+ fatalError("init(coder:) has not been implemented")
43+ }
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+ }
60+
61+// private let avatar = UIImage()
62+// private let name = UIImage()
63+
64+ private func update() {
65+ subviews.map {$0.removeFromSuperview()}
66+
67+ let largeStack = StackView(axis: .vertical, alignment: .left)
68+ largeStack.padding = padding
69+ addSubview(largeStack)
70+
71+ let userView = StackView(axis: .horizontal, alignment: .center)
72+
73+
74+ let avatarDim: CGFloat = 64
75+ let avatar = UIImageView()
76+ post?.owner?.avatar?.load(into: avatar)
77+ avatar.layer.cornerRadius = avatarDim/2
78+ avatar.clipsToBounds = true
79+ avatar.contentMode = .scaleAspectFill
80+ avatar.layer.minificationFilter = kCAFilterTrilinear
81+ avatar.frame.size = CGSize(width: avatarDim, height: avatarDim)
82+ userView.addArrangedSubview(.view(avatar))
83+
84+ userView.addArrangedSubview(.spacer(16))
85+
86+ let userName = UILabel()
87+ let firstName = post?.owner?.firstName ?? ""
88+ let lastName = post?.owner?.lastName ?? ""
89+ userName.text = "\(firstName) \(lastName)"
90+ userName.font = UIFont.systemFont(ofSize: 28, weight: UIFontWeightHeavy)
91+ userName.textColor = UIColor.darkText
92+ userName.sizeToFit()
93+ userView.addArrangedSubview(.view(userName))
94+
95+ userView.layoutIfNeeded()
96+
97+ largeStack.addArrangedSubview(.view(userView))
98+
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)
153+
154+ let body = UILabel()
155+ body.numberOfLines = 0
156+ let bodyWidth: Double = Double(width)
157+ body.font = UIFont.systemFont(ofSize: 22, weight: UIFontWeightSemibold)
158+ body.text = post?.body
159+ body.textColor = UIColor.darkText
160+ let size = body.textRect(forBounds: CGRect(x: 0, y: 0, width: bodyWidth, height: Double.infinity), limitedToNumberOfLines: 0).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+ }
178+
179+ largeStack.addArrangedSubview(.view(body))
180+
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+
195+ }
196+}
Melk/CollectionView.swiftnew file+112
@@ -0,0 +1,112 @@
1+//
2+// CollectionView.swift
3+// Melk
4+//
5+// Created by Ilya Kos on 7/6/17.
6+// Copyright © 2017 Ilya Kos. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class CollectionView: UIView {
12+ var arrangedSubviews: [UIView] = []{
13+ didSet {
14+ setNeedsLayout()
15+ }
16+ }
17+ /// Padding between content
18+ var horizontalPadding: CGFloat = 0 {
19+ didSet {
20+ setNeedsLayout()
21+ }
22+ }
23+ /// Padding between content
24+ var verticalPadding: CGFloat = 0 {
25+ didSet {
26+ setNeedsLayout()
27+ }
28+ }
29+ /// Padding around content
30+ var padding: CGPoint = CGPoint.zero {
31+ didSet {
32+ setNeedsLayout()
33+ }
34+ }
35+ var maxWidth: CGFloat = 200 {
36+ didSet {
37+ setNeedsLayout()
38+ }
39+ }
40+ /// Alignment of elements inside a row
41+ ///
42+ /// Valid values: .top, .center, .bottom
43+ ///
44+ /// Default value: .top
45+ var alignment: Alignment = .top
46+
47+ /// Alignment of rows
48+ ///
49+ /// Valid values: .left, .center, .right
50+ ///
51+ /// Default value: .left
52+ var rowAlignment: Alignment = .left
53+
54+ func addArrangedSubview(_ view: UIView) {
55+ arrangedSubviews.append(view)
56+ }
57+
58+ override func layoutSubviews() {
59+ subviews.map {$0.removeFromSuperview()}
60+
61+ let largeStack = StackView(axis: .vertical, alignment: rowAlignment)
62+ largeStack.padding = padding
63+
64+ var lastStack = StackView(axis: .horizontal, alignment: alignment)
65+ let contentWidth = maxWidth - padding.x*2
66+ var width = contentWidth
67+ var firstView = true
68+ var firstStack = true
69+
70+ for view in arrangedSubviews {
71+// print("new view")
72+ if firstView {
73+ lastStack.addArrangedSubview(.view(view))
74+ firstView = false
75+ width -= view.width
76+ } else {
77+ if width >= horizontalPadding + view.width {
78+ lastStack.addArrangedSubview(.spacer(horizontalPadding))
79+ lastStack.addArrangedSubview(.view(view))
80+ width -= horizontalPadding + view.width
81+ } else {
82+ if !firstStack {
83+ largeStack.addArrangedSubview(.spacer(verticalPadding))
84+ } else {
85+ firstStack = false
86+ }
87+ lastStack.layoutIfNeeded()
88+ largeStack.addArrangedSubview(.view(lastStack))
89+
90+ lastStack = StackView(axis: .horizontal, alignment: alignment)
91+ lastStack.addArrangedSubview(.view(view))
92+ width = contentWidth - view.width
93+ }
94+ }
95+ }
96+ lastStack.layoutIfNeeded()
97+ if !firstStack {
98+ largeStack.addArrangedSubview(.spacer(verticalPadding))
99+ }
100+ largeStack.addArrangedSubview(.view(lastStack))
101+ largeStack.frame.origin = CGPoint.zero
102+ largeStack.layoutIfNeeded()
103+ addSubview(largeStack)
104+ frame.size = largeStack.frame.size
105+ }
106+}
107+
108+fileprivate extension UIView {
109+ var width: CGFloat {
110+ return self.frame.width
111+ }
112+}
Melk/Info.plist+18−2
@@ -16,14 +16,30 @@
1616 <string>APPL</string>
1717 <key>CFBundleShortVersionString</key>
1818 <string>1.0</string>
19+ <key>CFBundleURLTypes</key>
20+ <array>
21+ <dict>
22+ <key>CFBundleTypeRole</key>
23+ <string>Editor</string>
24+ <key>CFBundleURLName</key>
25+ <string>com.ilyakooo0.Melk</string>
26+ <key>CFBundleURLSchemes</key>
27+ <array>
28+ <string>vk6097091</string>
29+ </array>
30+ </dict>
31+ </array>
1932 <key>CFBundleVersion</key>
2033 <string>1</string>
34+ <key>LSApplicationQueriesSchemes</key>
35+ <array>
36+ <string>vkauthorize</string>
37+ <string>vk6097091</string>
38+ </array>
2139 <key>LSRequiresIPhoneOS</key>
2240 <true/>
2341 <key>UILaunchStoryboardName</key>
2442 <string>LaunchScreen</string>
25− <key>UIMainStoryboardFile</key>
26− <string>Main</string>
2743 <key>UIRequiredDeviceCapabilities</key>
2844 <array>
2945 <string>armv7</string>
Melk/LocalVKDataTypes.swiftnew file+2270
@@ -0,0 +1,2270 @@
1+//
2+// File.swift
3+// golos
4+//
5+// Created by Ilya Kos on 12/12/16.
6+// Copyright © 2016 Ilya Kos. All rights reserved.
7+//
8+
9+import UIKit
10+import RealmSwift
11+import Alamofire
12+import CoreLocation
13+import Nuke
14+import SwiftyJSON
15+//import SwiftyVK
16+
17+
18+typealias Token = String
19+typealias UserID = Int
20+typealias MessageID = Int
21+typealias MultichatID = Int
22+
23+class RealmPhoto: Object {
24+
25+ convenience init(surl: String, id: String) {
26+ self.init()
27+ self.imageSURL = surl
28+ self.id = id
29+ }
30+
31+ @objc dynamic var id: String = ""
32+ var imageURL: URL? {
33+ get {
34+ return imageSURL =>> {URL(string: $0)}
35+ }
36+ set {
37+ imageSURL = newValue?.absoluteString
38+ }
39+ }
40+ @objc dynamic var imageSURL: String? {
41+ didSet {
42+ if oldValue != imageSURL {
43+ localImageSURL = nil
44+ }
45+ }
46+ }
47+ @objc private dynamic var localImageSURL: String? {
48+ didSet {
49+ if oldValue != localImageSURL {
50+ imageCache = nil
51+ }
52+ }
53+ }
54+ private var imageCache: UIImage?
55+
56+ convenience init(id: String, surl: String?) {
57+ self.init()
58+ self.id = id
59+ self.imageSURL = surl
60+ }
61+
62+ class func by(_ id: String, surl: String?) -> RealmPhoto {
63+ do {
64+ let realm = try Realm()
65+ if let ob = realm.object(ofType: RealmPhoto.self, forPrimaryKey: id) {
66+ if realm.isInWriteTransaction {
67+ surl =>> {ob.imageSURL = $0}
68+ } else {
69+ try? realm.write {
70+ surl =>> {ob.imageSURL = $0}
71+ }
72+ }
73+ return ob
74+ } else {
75+ let ob = RealmPhoto(id: id, surl: surl)
76+ if realm.isInWriteTransaction {
77+ realm.add(ob)
78+ } else {
79+ try? realm.write {
80+ realm.add(ob)
81+ }
82+ }
83+ return ob
84+ }
85+ } catch {
86+ let ob = RealmPhoto(id: id, surl: surl)
87+ return ob
88+ }
89+ }
90+
91+ override static func primaryKey() -> String? {
92+ return "id"
93+ }
94+
95+ override static func ignoredProperties() -> [String] {
96+ return ["imageURL", "imageCache", "completions", "request"]
97+ }
98+
99+ var completions: [((() -> ()), RealmPhotoToken)] = []
100+ var views: [(UIImageView?, RealmPhotoToken)] = []
101+ var request: DataRequest?
102+
103+ @discardableResult func load(into view: UIImageView, completion: (() -> ())? = nil, error errorClosure: (() -> ())? = nil) -> RealmPhotoToken {
104+
105+ let token = RealmPhotoToken()
106+
107+ func getFromServer() {
108+ if request == nil {
109+ let surl = "\(id).png"
110+ if let url = imageURL {
111+ print("getting from network")
112+ request = Alamofire.request(url, method: .get).validate().responseData(queue: userQueue) { (responce) in
113+ self.request = nil
114+ switch responce.result {
115+ case .success(let data):
116+ UIImage(data: data) >>> {image in
117+ write(cache: UIImagePNGRepresentation(image), withName: surl) {
118+ realmWrite({ (realm) in
119+ realm.object(ofType: RealmPhoto.self, forPrimaryKey: self.id)?.localImageSURL = surl
120+ })
121+ }
122+ self.imageCache = image
123+ DispatchQueue.main.async {
124+ if token.isValid {
125+ self.views.map { if $0.1.isValid {$0.0?.image = image} }
126+ self.completions.map { if $0.1.isValid { $0.0() } }
127+ self.completions = []
128+ }
129+ }
130+ }
131+ case .failure(let error):
132+ errorClosure?()
133+ // TODO: Error handling
134+ break
135+ }
136+ }
137+ }
138+ }
139+ }
140+
141+ if let surl = localImageSURL, // TODO: Should probrably get thing from server ???
142+ let url = url(forName: surl) {
143+ Nuke.loadImage(with: url, into: view) { [weak view] (response, fromMemory) in
144+ switch response {
145+ case .fulfilled(let image):
146+ if token.isValid {
147+ view?.image = image
148+ completion?()
149+ }
150+ case .rejected(let error): // TODO: Handle error?
151+ completion >>> {self.completions.append(($0, token))}
152+ self.views.append((view, token))
153+ getFromServer()
154+ }
155+ }
156+ } else {
157+ completion >>> {self.completions.append(($0, token))}
158+ self.views.append((view, token))
159+ getFromServer()
160+ }
161+
162+ return token
163+ }
164+
165+ // func image(_ completion: @escaping ((UIImage?) -> ())) {
166+ // print(id)
167+ // let surl = "\(id).png"
168+ // if imageCache != nil {
169+ // print("we have caching!")
170+ // }
171+ // imageCache = imageCache ?? (localImageSURL =>> getLocalImage) // TODO: imageCache always seems to be nil... ie always loading from disk for some reason
172+ // if let sImage = imageCache {
173+ // DispatchQueue.main.async { completion(sImage) }
174+ // } else if let url = imageURL {
175+ // print("getting from network")
176+ // Alamofire.request(url, method: .get).validate().responseData(queue: userQueue) { (responce) in
177+ // switch responce.result {
178+ // case .success(let data):
179+ // UIImage(data: data) >>> {image in
180+ // write(cache: UIImagePNGRepresentation(image), withName: surl) {
181+ // DispatchQueue.main.async {
182+ // do {
183+ // let realm = try Realm()
184+ // if !realm.isInWriteTransaction {
185+ // try realm.write {
186+ // self.localImageSURL = surl
187+ // }
188+ // } else {
189+ // self.localImageSURL = surl
190+ // }
191+ // } catch { }
192+ // }
193+ // }
194+ // self.imageCache = image
195+ // DispatchQueue.main.async {completion(image)}
196+ // }
197+ // case .failure(let error):
198+ // // TODO: Error handling
199+ // break
200+ // }
201+ // }.resume()
202+ // }
203+ // }
204+}
205+
206+public class RealmPhotoToken {
207+ public func cancel() {
208+ isValid = false
209+ }
210+ fileprivate var isValid = true
211+}
212+
213+
214+class User: Object {
215+ @objc dynamic var id: UserID = 0
216+ @objc dynamic var firstName: String?
217+ @objc dynamic var lastName: String?
218+ var deactivated: Deactivated? {
219+ get {
220+ return deactivatedStr =>> {Deactivated.init(rawValue: $0)}
221+ }
222+ set {
223+ deactivatedStr = newValue >>> {$0.rawValue}
224+ }
225+ }
226+ @objc private dynamic var deactivatedStr: String?
227+
228+ @objc dynamic var about: String?
229+ @objc dynamic var birthday: Date?
230+ let blacklisted = RealmOptional<Bool>()
231+ let canSendFriendRequest = RealmOptional<Bool>()
232+ let canSendPrivateMessage = RealmOptional<Bool>()
233+ let mutualFriendCount = RealmOptional<Int>()
234+ // TODO: var connections:
235+ @objc dynamic var mobilePhone: String?
236+ @objc dynamic var homePhone: String?
237+ // TODO: var exports
238+ var friendStatus: FriendStatus? {
239+ get {
240+ return friendStatusInt.value =>> {FriendStatus(rawValue: $0)}
241+ }
242+ set {
243+ friendStatusInt.value = newValue >>> {$0.rawValue}
244+ }
245+ }
246+ private let friendStatusInt = RealmOptional<Int>()
247+ let online = RealmOptional<Bool>()
248+ @objc dynamic var avatar: RealmPhoto?
249+ var avatarID: String {
250+ return "user\(id)avatar"
251+ }
252+ @objc dynamic var photo: RealmPhoto?
253+ var photoID: String {
254+ return "user\(id)photo"
255+ }
256+ var site: URL? {
257+ get {
258+ return siteSURL =>> {URL.init(string: $0)}
259+ }
260+ set {
261+ siteSURL = newValue >>> {$0.absoluteString}
262+ }
263+ }
264+ @objc dynamic var siteSURL: String?
265+ @objc dynamic var status: String?
266+ @objc dynamic var lastSeen: Date?
267+ // dynamic var lastUpdated: Date = Date()
268+ let friendCount = RealmOptional<Int>()
269+
270+ convenience init(id: UserID, firstName: String, lastName: String) {
271+ self.init()
272+ self.id = id
273+ self.firstName = firstName
274+ self.lastName = lastName
275+ // self.lastUpdated = Date()
276+ }
277+ class func by(id: UserID) -> User {
278+ let realm = try! Realm()
279+ if let us = realm.object(ofType: User.self, forPrimaryKey: id) {
280+ return us
281+ } else {
282+ let us = User()
283+ us.id = id
284+ if realm.isInWriteTransaction {
285+ realm.add(us)
286+ } else {
287+ try? realm.write {
288+ realm.add(us)
289+
290+ }
291+ }
292+ return us
293+ }
294+ }
295+
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+// }
308+ func get(completion: ((User?) -> ())? = 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)
313+ }
314+ }
315+ }
316+
317+
318+ override static func primaryKey() -> String? {
319+ return "id"
320+ }
321+
322+ override static func ignoredProperties() -> [String] {
323+ return ["site", "photoID", "avatarID"]
324+ }
325+ // func update() { lastUpdated = Date() }
326+
327+ // MARK: Hashable override
328+
329+ override var hashValue: Int {
330+ return id
331+ }
332+}
333+
334+class Geolocation: Object {
335+ var geoCoordinates: CLLocation? {
336+ get {
337+ if let long = long.value, let latt = latt.value {
338+ return CLLocation(latitude: latt, longitude: long)
339+ }
340+ return nil
341+ }
342+ set {
343+ if let latt = newValue?.coordinate.latitude,
344+ let long = newValue?.coordinate.longitude {
345+ self.latt.value = latt
346+ self.long.value = long
347+ } else {
348+ self.long.value = nil
349+ self.latt.value = nil
350+ }
351+ }
352+ }
353+ private let latt = RealmOptional<Double>()
354+ private let long = RealmOptional<Double>()
355+ let id = RealmOptional<Int>()
356+ @objc dynamic var title: String?
357+ @objc dynamic var image: RealmPhoto?
358+ var imageID: String {
359+ return "geo_lat\(latt.value ?? 0)_long\(long.value ?? 0)image"
360+ }
361+ var city: String?
362+ var country: String?
363+
364+ override static func ignoredProperties() -> [String] {
365+ return ["geoCoordinates", "imageID"]
366+ }
367+ convenience init(long: Double, latt: Double) {
368+ self.init()
369+ self.long.value = long
370+ self.latt.value = latt
371+ }
372+
373+}
374+
375+class GenericMessage: Object {
376+ @objc dynamic var date = Date() //
377+ @objc dynamic var body = "" //
378+ @objc dynamic var user: User?
379+
380+ var outPhotos: [UIImage] = [] // TODO: ~
381+
382+ @objc dynamic var geolocation: Geolocation?
383+
384+ let attachments = List<RealmMedia>()
385+ let forwardedMessages = List<RealmForwardedMessage>()
386+ //let sentMessageId: MessageID? I DONT KNOW WHY I PUT THIS HERE
387+
388+ // convenience init(body: String) {
389+ // self.init()
390+ // self.body = body
391+ // self.out.value = true
392+ // }
393+ // convenience init(image: UIImage) {
394+ // self.init()
395+ // self.outPhotos.append(image)
396+ // }
397+ override static func ignoredProperties() -> [String] {
398+ return ["outPhotos"]
399+ }
400+}
401+
402+class Message: GenericMessage, Comparable, SortedArrayItem {
403+ @objc dynamic var id = 0
404+ let randomID = RealmOptional<MessageID>()
405+ @objc dynamic var read = false
406+ @objc dynamic var replied = false
407+ @objc dynamic var important = false
408+ @objc dynamic var deleted = false
409+
410+ func equal(to message: Message) -> Bool {
411+ // print("\(lhs.id.value) \(rhs.id.value) \(lhs.id.value == rhs.id.value)")
412+ if let a = self.randomID.value,
413+ let b = message.randomID.value {
414+ if a == b {
415+ return true
416+ }
417+ }
418+ return self.id == message.id
419+ }
420+ internal func greater(than message: Message) -> Bool {
421+ return self.id < message.id
422+ }
423+
424+ static func ==(lhs: Message, rhs: Message) -> Bool {
425+ print("\(lhs.id) \(rhs.id) \(lhs.id == rhs.id)")
426+ return lhs.id == rhs.id
427+ }
428+
429+ @objc dynamic var out = false
430+
431+ // Multichat stuff
432+
433+ @objc dynamic var photoSURL: String?
434+ let chatID = RealmOptional<MultichatID>()
435+ @objc private dynamic var serviceMessageString: String?
436+ private var serviceMessageUser: User?
437+ private let serviceMessage = RealmOptional<Int>()
438+
439+ var action: ServiceMessageAction? {
440+ get {
441+ func getUser() -> ServiceMessageUser? {
442+ if let user = self.serviceMessageUser {
443+ return .user(user)
444+ } else if let email = self.serviceMessageString {
445+ return .email(email)
446+ } else {
447+ return nil
448+ }
449+ }
450+
451+ return serviceMessage.value =>> { service in
452+ switch service {
453+ case 0:
454+ return .photoUpdate
455+ case 1:
456+ return .photoRemove
457+ case 2:
458+ return self.serviceMessageString >>> { .create($0) }
459+ case 3:
460+ return self.serviceMessageString >>> { .titleUpdate($0) }
461+ case 4:
462+ return getUser() >>> { .inviteUser($0) }
463+ case 5:
464+ return getUser() >>> { .kickUser($0) }
465+ default:
466+ return nil
467+ }
468+ }
469+ }
470+ set {
471+ func set(user: ServiceMessageUser) {
472+ switch user {
473+ case .user(let use):
474+ self.serviceMessageUser = use
475+ case .email(let str):
476+ self.serviceMessageString = str
477+ }
478+ }
479+
480+ serviceMessageString = nil
481+ serviceMessageUser = nil
482+ serviceMessage.value = newValue >>> { new in
483+ switch new {
484+ case .photoUpdate:
485+ return 0
486+ case .photoRemove:
487+ return 1
488+ case .create(let str):
489+ self.serviceMessageString = str
490+ return 2
491+ case .titleUpdate(let str):
492+ self.serviceMessageString = str
493+ return 3
494+ case .inviteUser(let user):
495+ set(user: user)
496+ return 4
497+ case .kickUser(let user):
498+ set(user: user)
499+ return 5
500+ }
501+ }
502+ }
503+ }
504+
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+// }
518+
519+ class func by(id: MessageID) -> Message {
520+ let realm = try! Realm()
521+ if let us = realm.object(ofType: Message.self, forPrimaryKey: id) {
522+ return us
523+ } else {
524+ let us = Message()
525+ us.id = id
526+ realmWrite({ (_) in
527+ realm.add(us)
528+ })
529+ return us
530+ }
531+ }
532+
533+ override static func primaryKey() -> String? {
534+ return "id"
535+ }
536+}
537+//func ==(lhs: Message, rhs: Message) -> Bool {
538+// print("\(lhs.id.value) \(rhs.id.value) \(lhs.id.value == rhs.id.value)")
539+// return lhs.id.value == rhs.id.value
540+//}
541+func <(lhs: Message, rhs: Message) -> Bool {
542+ return lhs.id < rhs.id
543+}
544+
545+enum forwardedMessage {
546+ case genericMessage(GenericMessage)
547+ case message(Message)
548+}
549+
550+class RealmForwardedMessage: Object {
551+ var value: forwardedMessage? {
552+ get {
553+ if let gm = genericMessage {
554+ return .genericMessage(gm)
555+ } else if let m = message {
556+ return .message(m)
557+ } else {
558+ return nil
559+ }
560+ }
561+ set {
562+ realmWrite { _ in
563+ if let fm = newValue {
564+ switch fm {
565+ case .genericMessage(let gm):
566+ genericMessage = gm
567+ message = nil
568+ case .message(let m):
569+ genericMessage = nil
570+ message = m
571+ }
572+ } else {
573+ message = nil
574+ genericMessage = nil
575+ }
576+ }
577+ }
578+ }
579+ @objc private dynamic var genericMessage: GenericMessage?
580+ @objc private dynamic var message: Message?
581+
582+ var id: MessageID? {
583+ if let m = message {
584+ return m.id
585+ } else {
586+ return nil
587+ }
588+ }
589+
590+ convenience init(value: forwardedMessage) {
591+ self.init()
592+ self.value = value
593+ }
594+
595+ override static func ignoredProperties() -> [String] {
596+ return ["value"]
597+ }
598+}
599+
600+//struct Geolocation {
601+// var coordinates: CLLocation
602+// var title: String?
603+// var imageURL: URL? {
604+// get {
605+// return imageSURL =>> {URL.init(string: $0)}
606+// }
607+// set {
608+// imageSURL = newValue?.absoluteString
609+// }
610+// }
611+// var imageSURL: String?
612+// // func image(completion: ((UIImage) -> ())) {
613+// // imageFunc =>> {$0(completion)}
614+// // }
615+// var image: (@escaping ((UIImage) -> ()) -> ())?
616+// var city: String?
617+// var country: String?
618+// init(coordinates: CLLocation, title: String?, imageSURL: String?, image: (@escaping ((UIImage) -> ()) -> ()), city: String?, country: String?) {
619+// self.coordinates = coordinates
620+// self.title = title
621+// self.imageSURL = imageSURL
622+// self.image = image
623+// self.city = city
624+// self.country = country
625+// }
626+//}
627+typealias GeolocationID = Int
628+
629+enum Deactivated: String {
630+ case banned = "banned"
631+ case deleted = "deleted"
632+ case normal = "normal"
633+}
634+
635+enum FriendStatus: Int {
636+ case notFriends = 0
637+ case requestSent
638+ case requestRecieved
639+ case fiend
640+}
641+
642+enum MessageReadState: Int {
643+ case read = 0
644+ case notRead
645+}
646+enum MessageSendState: Int {
647+ case received = 0
648+ case sent
649+}
650+
651+enum MultichatNotificationSetting {
652+ case sound
653+ case disabled_until
654+}
655+
656+enum ServiceMessageAction {
657+ case photoUpdate // 0
658+ case photoRemove // 1
659+ case create(String) // 2
660+ case titleUpdate(String) // 3
661+ case inviteUser(ServiceMessageUser) // 4
662+ case kickUser(ServiceMessageUser) // 5
663+}
664+
665+enum ServiceMessageUser {
666+ case user(User)
667+ case email(String)
668+}
669+
670+typealias MessageChunkID = String
671+class MessageChunk: Object { // message ids go down
672+ subscript(index: Int) -> Message {
673+ return messages[index]
674+ }
675+
676+ @objc dynamic var id: MessageChunkID = ""
677+
678+ fileprivate let messages = List<Message>()
679+
680+ var firstMessage: Message? {
681+ return messages.first
682+ }
683+
684+ var lastMessage: Message? {
685+ return messages.last
686+ }
687+
688+ var firstID: MessageID? {
689+ return firstMessage?.id
690+ }
691+
692+ var lastID: MessageID? {
693+ return lastMessage?.id
694+ }
695+
696+ var count: Int {
697+ return messages.count
698+ }
699+
700+ fileprivate func append(message: Message?) {
701+ message >>> {messages.append($0)}
702+ }
703+
704+ override static func primaryKey() -> String? {
705+ return "id"
706+ }
707+
708+ override static func ignoredProperties() -> [String] {
709+ return ["firstMessage", "lastMessage", "firstID", "lastID", "count"]
710+ }
711+}
712+
713+func +=(lhs: MessageChunk, rhs: MessageChunk) {
714+ lhs.messages.append(objectsIn: rhs.messages)
715+}
716+
717+/// Returns: true if added. false if was already in list
718+func <~(lhs: MessageChunk?, rhs: Message?) -> Bool {
719+ var out: Bool = false
720+ realmWrite { (_) in
721+ out = lhs?.messages <~ rhs
722+ }
723+ return out
724+}
725+/// Returns: Indexes of added items
726+func <-(lhs: MessageChunk?, rhs: [Message?]?) -> [Int] {
727+ var out: [Int] = []
728+ realmWrite { (_) in
729+ out = lhs?.messages <- rhs
730+ }
731+ return out
732+}
733+/// Returns: Indexes of added items
734+func <-(lhs: MessageChunk?, rhs: MessageChunk?) -> [Int] {
735+ var out: [Int] = []
736+ realmWrite { (_) in
737+ out = lhs?.messages <- rhs?.messages
738+ }
739+ return out
740+}
741+
742+enum MessageEntry {
743+ case message(Message)
744+ case spacer
745+}
746+
747+typealias MessagesID = Int
748+class Messages: Object {
749+ subscript(ind: Int) -> MessageEntry? {
750+ assert(ind >= 0, "index out of bounds")
751+ if ind >= count || ind < 0 {
752+ return nil
753+ }
754+ var index = ind
755+ var i = 0
756+ while messageChunks[i].count < index {
757+ index -= messageChunks[i].count + 1
758+ i += 1
759+ }
760+ if index == messageChunks[i].count {
761+ return .spacer
762+ } else {
763+ return .message(messageChunks[i][index])
764+ }
765+ }
766+
767+ @objc dynamic var id: MessagesID = 0
768+
769+ @objc fileprivate dynamic var lastChunkID = 0
770+
771+ var endIndex: Int {
772+ return messageChunks.endIndex
773+ }
774+
775+ var first: Message? {
776+ return messageChunks.first?.firstMessage
777+ }
778+
779+ func count(upToChunk index: Int) -> Int {
780+ var n = 0
781+ for i in 0..<index {
782+ n += messageChunks[i].count + 1
783+ }
784+ return n
785+ }
786+
787+ public var count: Int {
788+ var n = messageChunks.count - 1
789+ for chunk in messageChunks {
790+ n += chunk.count
791+ }
792+ return n
793+ }
794+
795+ fileprivate let messageChunks = List<MessageChunk>()
796+
797+ public var chunks: List<MessageChunk> {
798+ return messageChunks
799+ }
800+
801+ fileprivate enum ChunkIndex {
802+ case inside(Int)
803+ case after(Int)
804+ case beforeFirst
805+ }
806+
807+ fileprivate func createChunk(at index: Int = 0) -> MessageChunk {
808+ let chunk = MessageChunk()
809+ realmWrite { (realm) in
810+ chunk.id = "\(id)_\(lastChunkID)"
811+ lastChunkID += 1
812+ realm.add(chunk)
813+ messageChunks.insert(chunk, at: index)
814+ }
815+ return chunk
816+ }
817+
818+ /// Return nil if there are no chunks
819+ fileprivate func chunkIndex(of message: Message) -> ChunkIndex? {
820+ if messageChunks.count == 0 {
821+ return nil
822+ }
823+ if let first = messageChunks.first?.firstMessage {
824+ if first.greater(than: message) {
825+ return .beforeFirst
826+ }
827+ }
828+ for (i, chunk) in messageChunks.enumerated() {
829+ if let lastMessage = chunk.lastMessage { // It should never be nil since a chunk is never empty
830+ if !message.greater(than: lastMessage) { // [ ... | ()] [...] ...
831+ if i == 0 {
832+ return .inside(i)
833+ } else {
834+ if let firstMessage = chunk.firstMessage { // It should never be nil since a chunk is never empty
835+ if message.greater(than: firstMessage) || message.equal(to: firstMessage) {
836+ return .inside(i)
837+ } else {
838+ return .after(i-1)
839+ }
840+ }
841+ }
842+ }
843+ }
844+ }
845+ return .after(endIndex-1)
846+ }
847+
848+
849+ override static func primaryKey() -> String? {
850+ return "id"
851+ }
852+
853+ override static func ignoredProperties() -> [String] {
854+ return ["endIndex"]
855+ }
856+
857+ class func by(id: MessagesID) -> Messages {
858+ let realm = try! Realm()
859+ if let us = realm.object(ofType: Messages.self, forPrimaryKey: id) {
860+ return us
861+ } else {
862+ let us = Messages()
863+ us.id = id
864+ realm.add(us)
865+ return us
866+ }
867+ }
868+
869+ // MARK: Messages obesrver
870+
871+ static let notificationName: Notification.Name = Notification.Name(rawValue: "MessagesWereModifiedNotificationName")
872+
873+ enum Changes {
874+ case added
875+ case modified
876+ case deleted
877+ }
878+}
879+
880+
881+
882+/// Returns: true if added. false if was already in list
883+func <~(lhs: Messages?, rhs: Message?) -> Bool {
884+ var out: Bool = false
885+ if let lhs = lhs,
886+ let rhs = rhs {
887+ realmWrite { (_) in
888+ if let index = lhs.chunkIndex(of: rhs) {
889+ switch index {
890+ case .after(let i):
891+ lhs.messageChunks[i].append(message: rhs)
892+ out = true
893+ case .inside(let i):
894+ out = lhs.messageChunks[i] <~ rhs
895+ case .beforeFirst:
896+ out = lhs.createChunk(at: 0) <~ rhs
897+ }
898+ } else {
899+ out = lhs.createChunk() <~ rhs
900+ }
901+ }
902+ }
903+ return out
904+}
905+/// Only accepts a list of SEQUENTIAL messages. Will silently break if not passed sequentially.
906+/// Returns: Indexes of added items
907+func <-(lhs: Messages?, rhs: [Message?]?) /*-> [Int]*/ {
908+ print(rhs)
909+ print(lhs)
910+ if let lhs = lhs,
911+ let rhs = rhs?.flatMap({$0}),
912+ let first = rhs.first,
913+ let last = rhs.last {
914+
915+ var deleted: [Int] = []
916+ var modified: [Int] = []
917+ var added: [Int] = []
918+ if let startI = lhs.chunkIndex(of: first),
919+ let endI = lhs.chunkIndex(of: last) {
920+ var start: Int!
921+ var end: Int!
922+ print("\t", startI, endI)
923+ switch startI {
924+ case .inside(let i):
925+ start = i
926+ case .after(let i):
927+ start = i + 1
928+ case .beforeFirst:
929+ start = -1
930+ }
931+ switch endI {
932+ case .after(let i):
933+ end = i
934+ case .inside(let i):
935+ end = i
936+ case .beforeFirst:
937+ end = -1
938+ }
939+
940+ if start < end {
941+ print(start, end)
942+ start = max(start, 0)
943+ var spacerIndexes = (start+1...end).map({lhs.count(upToChunk: $0) - 1})
944+ // deleted += spacerIndexes
945+ let count = lhs.count(upToChunk: start)
946+ var dIndex = 0
947+ for i in 0..<spacerIndexes.count {
948+ spacerIndexes[i] -= dIndex
949+ deleted.append(spacerIndexes[i])
950+ spacerIndexes[i] -= count
951+ dIndex += 1
952+ }
953+ var addedMs: [Int]!
954+ realmWriteWithCompletion { realm, completion in
955+ for x in start+1...end {
956+ print(lhs.chunks.count, x)
957+ lhs.chunks[start] += lhs.chunks[start+1]
958+ realm.delete(lhs.chunks[start+1])
959+ // lhs.chunks.remove(objectAtIndex: start+1)
960+ }
961+ let chunk = lhs.chunks[start]
962+ addedMs = chunk <- rhs
963+
964+ addedMs.map {$0 + count}
965+ added += addedMs
966+
967+ completion {
968+ print("after notification", lhs.chunks)
969+ NotificationCenter.default.post(name: Messages.notificationName,
970+ object: lhs.id,
971+ userInfo: [Messages.Changes.added: added,
972+ Messages.Changes.modified: modified,
973+ Messages.Changes.deleted: deleted])
974+ }
975+ }
976+ } else { // within one
977+ assert(start == end, "start index is larger than end index!")
978+ realmWriteWithCompletion { _, completion in
979+ if start == -1 {
980+ added += lhs.createChunk(at: 0) <- rhs
981+ added.append(0) // IDK o[]o[]o []o[]o
982+ } else {
983+ added += (lhs.messageChunks[start] <- rhs).map {$0 + lhs.count(upToChunk: start)}
984+ }
985+ completion {
986+ NotificationCenter.default.post(name: Messages.notificationName,
987+ object: lhs.id,
988+ userInfo: [Messages.Changes.added: added,
989+ Messages.Changes.modified: modified,
990+ Messages.Changes.deleted: deleted])
991+ }
992+ }
993+ }
994+ } else { // empty
995+ realmWriteWithCompletion { _, completion in
996+ added += lhs.createChunk() <- rhs
997+ completion {
998+ NotificationCenter.default.post(name: Messages.notificationName,
999+ object: lhs.id,
1000+ userInfo: [Messages.Changes.added: added,
1001+ Messages.Changes.modified: modified,
1002+ Messages.Changes.deleted: deleted])
1003+ }
1004+ }
1005+ }
1006+ print(lhs)
1007+ print(added)
1008+ print(deleted)
1009+ }
1010+}
1011+
1012+enum Media {
1013+ case photo(Photo)
1014+ case video(Video)
1015+ case audio(Audio)
1016+ case doc(Document)
1017+ case wall(WallPost)
1018+ case wallReply(WallPostReply)
1019+ case sticker(Sticker)
1020+ case link(Link)
1021+ case gift(Gift)
1022+ case market(Market)
1023+ case marketAlbum(MarketAlbum)
1024+ case graffiti(Document)
1025+ case note(Note)
1026+ case poll(Poll)
1027+ case album(PhotoAlbum)
1028+ var realm: RealmMedia {
1029+ return RealmMedia(value: self)
1030+ }
1031+}
1032+
1033+class RealmMedia: Object {
1034+ @objc private dynamic var photo: Photo?
1035+ @objc private dynamic var video: Video?
1036+ @objc private dynamic var audio: Audio?
1037+ @objc private dynamic var doc: Document?
1038+ @objc private dynamic var wall: WallPost?
1039+ @objc private dynamic var wallReply: WallPostReply?
1040+ @objc private dynamic var sticker: Sticker?
1041+ @objc private dynamic var link: Link?
1042+ @objc private dynamic var gift: Gift?
1043+ @objc private dynamic var market: Market?
1044+ @objc private dynamic var marketAlbum: MarketAlbum?
1045+ @objc private dynamic var graffiti: Document?
1046+ @objc private dynamic var note: Note?
1047+ @objc private dynamic var poll: Poll?
1048+ @objc private dynamic var album: PhotoAlbum?
1049+
1050+ var value: Media? {
1051+ get {
1052+ if let sureThing = self.photo { return .photo(sureThing) }
1053+ if let sureThing = self.video { return .video(sureThing) }
1054+ if let sureThing = self.audio { return .audio(sureThing) }
1055+ if let sureThing = self.doc { return .doc(sureThing) }
1056+ if let sureThing = self.wall { return .wall(sureThing) }
1057+ if let sureThing = self.wallReply { return .wallReply(sureThing) }
1058+ if let sureThing = self.sticker { return .sticker(sureThing) }
1059+ if let sureThing = self.link { return .link(sureThing) }
1060+ if let sureThing = self.gift { return .gift(sureThing) }
1061+ if let sureThing = self.market { return .market(sureThing) }
1062+ if let sureThing = self.marketAlbum { return .marketAlbum(sureThing) }
1063+ if let sureThing = self.graffiti { return .graffiti(sureThing) }
1064+ if let sureThing = self.note { return .note(sureThing) }
1065+ if let sureThing = self.poll { return .poll(sureThing) }
1066+ if let sureThing = self.album { return .album(sureThing) }
1067+ return nil
1068+ }
1069+ set {
1070+
1071+ photo = nil
1072+ video = nil
1073+ audio = nil
1074+ doc = nil
1075+ wall = nil
1076+ wallReply = nil
1077+ sticker = nil
1078+ link = nil
1079+ gift = nil
1080+ market = nil
1081+ marketAlbum = nil
1082+ graffiti = nil
1083+ note = nil
1084+ poll = nil
1085+ album = nil
1086+
1087+ if let val = newValue {
1088+ switch val {
1089+ case .photo(let foo): photo = foo
1090+ case .video(let foo): video = foo
1091+ case .audio(let foo): audio = foo
1092+ case .doc(let foo): doc = foo
1093+ case .wall(let foo): wall = foo
1094+ case .wallReply(let foo): wallReply = foo
1095+ case .sticker(let foo): sticker = foo
1096+ case .link(let foo): link = foo
1097+ case .gift(let foo): gift = foo
1098+ case .market(let foo): market = foo
1099+ case .marketAlbum(let foo): marketAlbum = foo
1100+ case .graffiti(let foo): graffiti = foo
1101+ case .note(let foo): note = foo
1102+ case .poll(let foo): poll = foo
1103+ case .album(let foo): album = foo
1104+ }
1105+ }
1106+ }
1107+ }
1108+ convenience init(value: Media) {
1109+ self.init()
1110+ self.value = value
1111+ }
1112+}
1113+
1114+class Poll: Object {
1115+ @objc dynamic var id: PollID = 0
1116+ @objc dynamic var owner: User?
1117+ @objc dynamic var date: Date?
1118+ @objc dynamic var question: String?
1119+ let votes = RealmOptional<Int>()
1120+ let answerID = RealmOptional<PollAnswerID>()
1121+ let answers = List<PollAnswer>()
1122+ convenience init(id: PollID, owner: User, date: Date, question: String, votes: Int) {
1123+ self.init()
1124+ self.id = id
1125+ self.owner = owner
1126+ self.date = date
1127+ self.question = question
1128+ self.votes.value = votes
1129+ // self.answerID.value = answerID
1130+ }
1131+ class func by(id: PollID) -> Poll {
1132+ let realm = try! Realm()
1133+ if let us = realm.object(ofType: Poll.self, forPrimaryKey: id) {
1134+ return us
1135+ } else {
1136+ let us = Poll()
1137+ us.id = id
1138+ realm.add(us)
1139+ return us
1140+ }
1141+ }
1142+
1143+ override class func primaryKey() -> String? {
1144+ return "id"
1145+ }
1146+}
1147+typealias PollID = Int
1148+
1149+class PollAnswer: Object {
1150+ @objc dynamic var id: PollAnswerID = 0
1151+ @objc dynamic var body: String = ""
1152+ @objc dynamic var votes: Int = 0
1153+ @objc dynamic var rating: Int = 0
1154+
1155+ convenience init(id: PollAnswerID, body: String, votes: Int, rating: Int) {
1156+ self.init()
1157+ self.id = id
1158+ self.body = body
1159+ self.votes = votes
1160+ self.rating = rating
1161+ }
1162+}
1163+typealias PollAnswerID = Int
1164+
1165+class Note: Object {
1166+ @objc dynamic var id: NoteID = 0
1167+ @objc dynamic var owner: User?
1168+ @objc dynamic var title: String?
1169+ @objc dynamic var body: String?
1170+ @objc dynamic var date: Date?
1171+ var url: URL? {
1172+ get {
1173+ return surl =>> {URL(string: $0)}
1174+ }
1175+ set {
1176+ surl = newValue >>> {$0.absoluteString}
1177+ }
1178+ }
1179+ @objc dynamic var surl: String?
1180+ let commentCount = RealmOptional<Int>()
1181+ convenience init(id: NoteID, owner: User, title: String, body: String, date: Date, url: URL, commentCount: Int) {
1182+ self.init()
1183+ self.id = id
1184+ self.owner = owner
1185+ self.title = title
1186+ self.body = body
1187+ self.date = date
1188+ self.url = url
1189+ self.commentCount.value = commentCount
1190+ }
1191+ class func by(id: NoteID) -> Note {
1192+ let realm = try! Realm()
1193+ if let us = realm.object(ofType: Note.self, forPrimaryKey: id) {
1194+ return us
1195+ } else {
1196+ let us = Note()
1197+ us.id = id
1198+ realm.add(us)
1199+ return us
1200+ }
1201+ }
1202+ override class func ignoredProperties() -> [String] {
1203+ return ["url"]
1204+ }
1205+
1206+ override class func primaryKey() -> String? {
1207+ return "id"
1208+ }
1209+}
1210+typealias NoteID = Int
1211+
1212+typealias GraffitiID = Int
1213+class Graffiti: Document {
1214+} // Use document.image ?
1215+
1216+class Photo: Object {
1217+ @objc dynamic var id: PhotoID = 0
1218+ @objc dynamic var owner: User?
1219+ @objc dynamic var date: Date?
1220+ @objc dynamic var thumbnail: RealmPhoto?
1221+ var thumbnailID: String {
1222+ return "photo\(id)thumbnail"
1223+ }
1224+ @objc dynamic var image: RealmPhoto?
1225+ var imageID: String {
1226+ return "photo\(id)image"
1227+ }
1228+ let width = RealmOptional<Int>()
1229+ let height = RealmOptional<Int>()
1230+ @objc dynamic var accessKey: Key?
1231+
1232+ convenience init(id: PhotoID, owner: User, date: Date){
1233+ self.init()
1234+ self.id = id
1235+ self.owner = owner
1236+ self.date = date
1237+ }
1238+
1239+ class func by(id: PhotoID) -> Photo {
1240+ let realm = try! Realm()
1241+ if let us = realm.object(ofType: Photo.self, forPrimaryKey: id) {
1242+ return us
1243+ } else {
1244+ let us = Photo()
1245+ us.id = id
1246+ realm.add(us)
1247+ return us
1248+ }
1249+ }
1250+
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+// }
1271+ func get(completion: ((Photo?) -> ())? = nil) {
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)
1276+ }
1277+ }
1278+ }
1279+
1280+ override class func primaryKey() -> String? {
1281+ return "id"
1282+ }
1283+
1284+ override static func ignoredProperties() -> [String] {
1285+ return ["thumbnailID", "imageID"]
1286+ }
1287+}
1288+
1289+class PhotoAlbum: Object {
1290+ @objc dynamic var id: PhotoAlbumID = 0
1291+ @objc dynamic var thumb: Photo?
1292+ @objc dynamic var owner: User?
1293+ @objc dynamic var title: String?
1294+ @objc dynamic var albumDescription: String?
1295+ @objc dynamic var dateCreated: Date?
1296+ @objc dynamic var dateUpdated: Date?
1297+ let count = RealmOptional<Int>()
1298+ let photos = List<Photo>()
1299+ convenience init(id: PhotoAlbumID) {
1300+ self.init()
1301+ self.id = id
1302+ }
1303+
1304+ override class func primaryKey() -> String? {
1305+ return "id"
1306+ }
1307+ class func by(id: PhotoAlbumID) -> PhotoAlbum {
1308+ let realm = try! Realm()
1309+ if let us = realm.object(ofType: PhotoAlbum.self, forPrimaryKey: id) {
1310+ return us
1311+ } else {
1312+ let us = PhotoAlbum()
1313+ us.id = id
1314+ realm.add(us)
1315+ return us
1316+ }
1317+ }
1318+
1319+
1320+ // init(id: PhotoAlbumID, thumbID: PhotoID, ownerID: UserID, title: String, dateCreated: Date, dateUpdated: Date, count: Int) {
1321+ // self.id = id
1322+ // self.thumbID = thumbID
1323+ // self.ownerID = ownerID
1324+ // self.title = title
1325+ // self.dateCreated = dateCreated
1326+ // self.dateUpdated = dateUpdated
1327+ // self.count = count
1328+ // }
1329+
1330+}
1331+typealias PhotoID = Int
1332+typealias PhotoAlbumID = Int
1333+typealias Key = String
1334+
1335+class WallPost: Object {
1336+ @objc dynamic var id: Int = 0
1337+ @objc dynamic var owner: User?
1338+ @objc dynamic var creator: User?
1339+ @objc dynamic var date: Date?
1340+ @objc dynamic var body: String?
1341+ let commentCount = RealmOptional<Int>()
1342+ let canPostComment = RealmOptional<Bool>()
1343+ let likeCount = RealmOptional<Int>()
1344+ @objc dynamic var liked: Bool = false
1345+ let canLike = RealmOptional<Bool>()
1346+ let repostCount = RealmOptional<Int>()
1347+ let canDelete = RealmOptional<Bool>()
1348+ let didRepost = RealmOptional<Bool>()
1349+ // Optional ⬇
1350+ let attachments = List<RealmMedia>()
1351+ @objc dynamic var geo: Geolocation?
1352+ // TODO: let postSource
1353+ @objc dynamic var signed: User?
1354+ @objc dynamic var ownerOfCopy: User?
1355+ @objc dynamic var original: WallPost?
1356+ let historyOfCopy = List<WallPost>()
1357+ let canPin = RealmOptional<Bool>()
1358+ let canEdit = RealmOptional<Bool>()
1359+ let isPinned = RealmOptional<Bool>()
1360+
1361+ // convenience init(id: WallPostID, ownerID: UserID, creatorID: UserID, date: Date, body: String, commentCount: Int, canPostComment: Bool, likeCount: Int, liked: Bool, canLike: Bool, repostCount: Int, didRepost: Bool, attachments: [Media], geo: Geolocation?, signed: UserID?, copyID: WallPostID?, canDelete: Bool, canPin: Bool?, canEdit: Bool?, isPinned: Bool?, copyHistory: [WallPostID], copyOwnerID: UserID?) {
1362+ // self.id = id
1363+ // self.ownerID = ownerID
1364+ // self.creatorID = creatorID
1365+ // self.date = date
1366+ // self.body = body
1367+ // self.commentCount = commentCount
1368+ // self.canPostComment = canPostComment
1369+ // self.likeCount = likeCount
1370+ // self.liked = liked
1371+ // self.canLike = canLike
1372+ // self.repostCount = repostCount
1373+ // self.didRepost = didRepost
1374+ // self.attachments = attachments
1375+ // self.geo = geo
1376+ // self.signed = signed
1377+ // self.copyID = copyID
1378+ // self.canDelete = canDelete
1379+ // self.canPin = canPin
1380+ // self.canEdit = canEdit
1381+ // self.isPinned = isPinned
1382+ // self.copyHistory = copyHistory
1383+ // self.copyOwnerID = copyOwnerID
1384+ // }
1385+
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+// }
1406+ func get(completion: ((WallPost?) -> ())? = 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+ }
1412+ }
1413+ }
1414+
1415+
1416+
1417+ override class func primaryKey() -> String? {
1418+ return "id"
1419+ }
1420+
1421+ class func by(id: WallPostID) -> WallPost {
1422+ let realm = try! Realm()
1423+ if let us = realm.object(ofType: WallPost.self, forPrimaryKey: id) {
1424+ return us
1425+ } else {
1426+ let us = WallPost()
1427+ us.id = id
1428+ realm.add(us)
1429+ return us
1430+ }
1431+ }
1432+
1433+}
1434+typealias WallPostID = Int
1435+
1436+class WallPostReply: Object {
1437+ @objc dynamic var id: WallPostReplyID = 0
1438+ @objc dynamic var author: User?
1439+ @objc dynamic var wallPost: WallPost?
1440+ @objc dynamic var wallOwner: User?
1441+ @objc dynamic var date: Date?
1442+ @objc dynamic var body: String?
1443+ let likeCount = RealmOptional<Int>()
1444+ let didLike = RealmOptional<Bool>()
1445+ let canLike = RealmOptional<Bool>()
1446+ @objc dynamic var inReplyToUser: User?
1447+ let attachments = List<RealmMedia>()
1448+ @objc dynamic var inReplyToComment: WallPostReply?
1449+ class func by(id: WallPostReplyID) -> WallPostReply {
1450+ let realm = try! Realm()
1451+ if let us = realm.object(ofType: WallPostReply.self, forPrimaryKey: id) {
1452+ return us
1453+ } else {
1454+ let us = WallPostReply()
1455+ us.id = id
1456+ realm.add(us)
1457+ return us
1458+ }
1459+ }
1460+
1461+ func get(completion: (() -> ())) {
1462+ // TODO: Get wallPostReply
1463+ }
1464+
1465+ override class func primaryKey() -> String? {
1466+ return "id"
1467+ }
1468+}
1469+typealias WallPostReplyID = Int
1470+
1471+class Sticker: Object {
1472+ @objc dynamic var id: StickerID = 0
1473+ @objc dynamic var packID: StickerPackID = 0
1474+ @objc dynamic var image: RealmPhoto?
1475+ var imageID: String {
1476+ return "sticker\(id)image"
1477+ }
1478+ let width = RealmOptional<Int>()
1479+ let height = RealmOptional<Int>()
1480+
1481+ convenience init(id: StickerID, packID: StickerPackID, width: Int, height: Int) {
1482+ self.init()
1483+ self.id = id
1484+ self.packID = packID
1485+ self.width.value = width
1486+ self.height.value = height
1487+ }
1488+ class func by(id: StickerID) -> Sticker {
1489+ let realm = try! Realm()
1490+ if let us = realm.object(ofType: Sticker.self, forPrimaryKey: id) {
1491+ return us
1492+ } else {
1493+ let us = Sticker()
1494+ us.id = id
1495+ realm.add(us)
1496+ return us
1497+ }
1498+ }
1499+
1500+ override class func primaryKey() -> String? {
1501+ return "id"
1502+ }
1503+ override static func ignoredProperties() -> [String] {
1504+ return ["imageID"]
1505+ }
1506+}
1507+typealias StickerID = Int
1508+typealias StickerPackID = Int
1509+
1510+class Gift: Object {
1511+ @objc dynamic var id: GiftID = 0
1512+ @objc dynamic var image: RealmPhoto?
1513+ var imageID: String {
1514+ return "gift\(id)image"
1515+ }
1516+ convenience init(id: GiftID, surl: String) {
1517+ self.init()
1518+ self.id = id
1519+ self.image = RealmPhoto.by(imageID, surl: surl)
1520+ }
1521+ class func by(id: GiftID) -> Gift {
1522+ let realm = try! Realm()
1523+ if let us = realm.object(ofType: Gift.self, forPrimaryKey: id) {
1524+ return us
1525+ } else {
1526+ let us = Gift()
1527+ us.id = id
1528+ realm.add(us)
1529+ return us
1530+ }
1531+ }
1532+
1533+ override class func primaryKey() -> String? {
1534+ return "id"
1535+ }
1536+ override static func ignoredProperties() -> [String] {
1537+ return ["imageID"]
1538+ }
1539+}
1540+typealias GiftID = Int
1541+
1542+class Link: Object {
1543+ var url: URL? {
1544+ get {
1545+ return surl =>> {URL(string: $0)}
1546+ }
1547+ }
1548+ @objc dynamic var surl: String = ""
1549+ @objc dynamic var title: String?
1550+ @objc dynamic var caption: String?
1551+ @objc dynamic var linkDescription: String?
1552+ @objc dynamic var photo: Photo?
1553+ let isExternal = RealmOptional<Bool>()
1554+ //var product: Market?
1555+ var rating: Rating? {
1556+ get {
1557+ if let stars = ratingStars.value,
1558+ let count = ratingReviewCount.value {
1559+ return Rating(stars: stars, reviewCount: count)
1560+ }
1561+ return nil
1562+ }
1563+ set {
1564+ ratingStars.value = newValue?.stars
1565+ ratingReviewCount.value = newValue?.reviewCount
1566+ }
1567+ }
1568+ private let ratingStars = RealmOptional<Int>()
1569+ private let ratingReviewCount = RealmOptional<Int>()
1570+ //let application: Application?
1571+ var button: LinkButton? {
1572+ get {
1573+ if let title = buttonTitle,
1574+ let url = buttonURL {
1575+ return LinkButton(title: title, url: url)
1576+ }
1577+ return nil
1578+ }
1579+ set {
1580+ buttonURL = newValue?.url
1581+ buttonTitle = newValue?.title
1582+ }
1583+ }
1584+ @objc private dynamic var buttonTitle: String?
1585+ private var buttonURL: URL? {
1586+ get {
1587+ return buttonSURL =>> {URL(string: $0)}
1588+ }
1589+ set {
1590+ buttonSURL = newValue?.absoluteString
1591+ }
1592+ }
1593+ private var buttonSURL: String?
1594+ //let previewURL: URL
1595+
1596+ class func by(surl: String) -> Link {
1597+ let realm = try! Realm()
1598+ if let us = realm.object(ofType: Link.self, forPrimaryKey: surl) {
1599+ return us
1600+ } else {
1601+ let us = Link()
1602+ us.surl = surl
1603+ realm.add(us)
1604+ return us
1605+ }
1606+ }
1607+ override class func primaryKey() -> String? {
1608+ return "surl"
1609+ }
1610+ override static func ignoredProperties() -> [String] {
1611+ return ["url", "rating", "button", "buttonURL"]
1612+ }
1613+}
1614+
1615+
1616+struct Rating {
1617+ let stars: Int
1618+ let reviewCount: Int
1619+}
1620+
1621+struct LinkButton {
1622+ let title: String
1623+ let url: URL
1624+}
1625+
1626+class Market: Object {
1627+ @objc dynamic var id: MarketID = 0
1628+ @objc dynamic var owner: User?
1629+ @objc dynamic var title: String?
1630+ @objc dynamic var marketDescription: String?
1631+ let amount = RealmOptional<Int>()
1632+ let price = RealmOptional<Int>()
1633+ let currencyID = RealmOptional<CurrencyID>()
1634+ @objc dynamic var currencyName: String?
1635+ @objc dynamic var priceString: String?
1636+ let categoryID = RealmOptional<MarketCategoryID>()
1637+ @objc dynamic var categoryName: String?
1638+ let sectionID = RealmOptional<MarketSectionID>()
1639+ @objc dynamic var sectionName: String?
1640+ @objc dynamic var image: RealmPhoto?
1641+ var imageID: String {
1642+ return "market\(id)image"
1643+ }
1644+ @objc dynamic var creationDate: Date?
1645+ var availability: MarketAvailability? {
1646+ set {
1647+ availabilityInt.value = newValue?.rawValue
1648+ }
1649+ get {
1650+ return availabilityInt.value =>> {MarketAvailability(rawValue: $0)}
1651+ }
1652+ }
1653+ private let availabilityInt = RealmOptional<Int>()
1654+ let photos = List<Photo>()
1655+ let canComment = RealmOptional<Bool>()
1656+ let canRepost = RealmOptional<Bool>()
1657+ let likesCount = RealmOptional<Int>()
1658+ let liked = RealmOptional<Bool>()
1659+
1660+ class func by(id: MarketID) -> Market {
1661+ let realm = try! Realm()
1662+ if let us = realm.object(ofType: Market.self, forPrimaryKey: id) {
1663+ return us
1664+ } else {
1665+ let us = Market()
1666+ us.id = id
1667+ realm.add(us)
1668+ return us
1669+ }
1670+ }
1671+
1672+ override class func primaryKey() -> String? {
1673+ return "id"
1674+ }
1675+ override static func ignoredProperties() -> [String] {
1676+ return ["imageID"]
1677+ }
1678+}
1679+typealias CurrencyID = Int
1680+typealias MarketCategoryID = Int
1681+typealias MarketSectionID = Int
1682+enum MarketAvailability: Int {
1683+ case availability = 0
1684+ case deleted
1685+ case notAvailability
1686+}
1687+typealias MarketID = Int
1688+
1689+class MarketAlbum: Object {
1690+ @objc dynamic var id: MarketAlbumID = 0
1691+ @objc dynamic var owner: User?
1692+ @objc dynamic var title: String?
1693+ @objc dynamic var photo: Photo?
1694+ let count = RealmOptional<Int>()
1695+ @objc dynamic var updateTime: Date?
1696+ override class func primaryKey() -> String? {
1697+ return "id"
1698+ }
1699+}
1700+typealias MarketAlbumID = Int
1701+
1702+class Video: Object {
1703+ @objc dynamic var id: VideoID = 0
1704+ @objc dynamic var owner: User?
1705+ @objc dynamic var title: String?
1706+ @objc dynamic var videoDescription: String?
1707+ let duration = RealmOptional<TimeInterval>()
1708+ @objc dynamic var thumbnail: RealmPhoto?
1709+ var thumbnailID: String {
1710+ return "video\(id)thumbnail"
1711+ }
1712+ var player: URL? {
1713+ get {
1714+ return playerSURL =>> {URL(string: $0)}
1715+ }
1716+ set {
1717+ playerSURL = newValue?.absoluteString
1718+ }
1719+ }
1720+ @objc dynamic var playerSURL: String?
1721+ @objc dynamic var accessKey: String?
1722+ @objc dynamic var isProcessing: Bool = false
1723+ @objc dynamic var isLive: Bool = false
1724+ convenience init(id: VideoID, owner: User, title: String, description: String, duration: TimeInterval) {
1725+ self.init()
1726+ self.id = id
1727+ self.owner = owner
1728+ self.title = title
1729+ self.videoDescription = description
1730+ self.duration.value = duration
1731+ }
1732+
1733+ class func by(id: VideoID) -> Video {
1734+ let realm = try! Realm()
1735+ if let us = realm.object(ofType: Video.self, forPrimaryKey: id) {
1736+ return us
1737+ } else {
1738+ let us = Video()
1739+ us.id = id
1740+ realm.add(us)
1741+ return us
1742+ }
1743+ }
1744+
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+// }
1765+ func get(completion: ((Video?) -> ())? = nil) {
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)
1770+ }
1771+ }
1772+ }
1773+
1774+ override class func primaryKey() -> String? {
1775+ return "id"
1776+ }
1777+ override static func ignoredProperties() -> [String] {
1778+ return ["player", "thumbnailID"]
1779+ }
1780+}
1781+typealias VideoID = Int
1782+
1783+class Audio: Object {
1784+ @objc dynamic var id: AudioID = 0
1785+ @objc dynamic var owner: User?
1786+ @objc dynamic var artist: String?
1787+ @objc dynamic var title: String?
1788+ let duration = RealmOptional<TimeInterval>()
1789+ var url: URL? {
1790+ get {
1791+ return surl =>> {URL(string: $0)}
1792+ }
1793+ set {
1794+ surl = newValue?.absoluteString
1795+ }
1796+ }
1797+ @objc dynamic var surl: String?
1798+
1799+ convenience init(id: AudioID) {
1800+ self.init()
1801+ self.id = id
1802+ }
1803+ class func by(id: AudioID) -> Audio {
1804+ let realm = try! Realm()
1805+ if let us = realm.object(ofType: Audio.self, forPrimaryKey: id) {
1806+ return us
1807+ } else {
1808+ let us = Audio()
1809+ us.id = id
1810+ realm.add(us)
1811+ return us
1812+ }
1813+ }
1814+
1815+ override class func primaryKey() -> String? {
1816+ return "id"
1817+ }
1818+ override static func ignoredProperties() -> [String] {
1819+ return ["url"]
1820+ }
1821+}
1822+typealias AudioID = Int
1823+typealias LyricID = Int
1824+typealias AudioAlbumID = Int
1825+typealias GenreID = Int
1826+
1827+class Document: Object {
1828+ @objc dynamic var id: DocumentID = 0
1829+ @objc dynamic var owner: User?
1830+ @objc dynamic var title: String?
1831+ let size = RealmOptional<Int>()
1832+ @objc dynamic var fileExtension: String?
1833+ var url: URL? {
1834+ get {
1835+ return surl =>> {URL(string: $0)}
1836+ }
1837+ set {
1838+ surl = newValue?.absoluteString
1839+ }
1840+ }
1841+ @objc dynamic var surl: String?
1842+ @objc dynamic var image: RealmPhoto?
1843+ var imageID: String {
1844+ return "doc\(id)image"
1845+ }
1846+ var type: DocumentType? {
1847+ get {
1848+ return typeInt.value =>> {DocumentType(rawValue: $0)}
1849+ }
1850+ set {
1851+ typeInt.value = newValue?.rawValue
1852+ }
1853+ }
1854+ private let typeInt = RealmOptional<Int>()
1855+ convenience init(id: DocumentID, owner: User, title: String, size: Int, ext: String, url: URL, type: DocumentType) {
1856+ self.init()
1857+ self.id = id
1858+ self.owner = owner
1859+ self.title = title
1860+ self.size.value = size
1861+ self.fileExtension = ext
1862+ self.url = url
1863+ self.type = type
1864+ }
1865+ class func by(id: DocumentID) -> Document {
1866+ let realm = try! Realm()
1867+ if let us = realm.object(ofType: Document.self, forPrimaryKey: id) {
1868+ return us
1869+ } else {
1870+ let us = Document()
1871+ us.id = id
1872+ realm.add(us)
1873+ return us
1874+ }
1875+ }
1876+
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+// }
1897+ func get(completion: ((Document?) -> ())? = 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+ }
1903+ }
1904+ }
1905+
1906+ override class func primaryKey() -> String? {
1907+ return "id"
1908+ }
1909+ override static func ignoredProperties() -> [String] {
1910+ return ["url", "imageID"]
1911+ }
1912+}
1913+typealias DocumentID = Int
1914+enum DocumentType: Int {
1915+ case text = 1
1916+ case archive
1917+ case gif
1918+ case image
1919+ case audio
1920+ case video
1921+ case eBook
1922+ case unknown
1923+}
1924+
1925+protocol Dialog {
1926+ var firstMessage: Message? { get }
1927+ var important: Bool { get set }
1928+ var name: String? { get }
1929+ func loadImage(into view: UIImageView, completion: (() -> ())?) -> RealmPhotoToken?
1930+ var messages: Messages? { get }
1931+ var id: Int { get }
1932+ var lastReadMessageID: Int { get set }
1933+ var count: RealmOptional<Int> { get }
1934+ var notificationSettings: RealmNotificatonSettings? { get set }
1935+ var peerID: Int { get }
1936+}
1937+
1938+func dialog(for id: DialogID) -> Dialog {
1939+ if id > multichatchatId { // multichat
1940+ let chat = Multichat.by(id: id - multichatchatId)
1941+ return chat
1942+
1943+ //} else if id < 0 { // Pablic
1944+
1945+ } else { // Normal user
1946+ let chat = Chat.by(user: User.by(id: id))
1947+ return chat
1948+ }
1949+}
1950+
1951+// For name conflicts
1952+func dialogFor(_ id: DialogID) -> Dialog {
1953+ return dialog(for: id)
1954+}
1955+
1956+class RealmDialog: Object, Comparable {
1957+
1958+ // TODO: Remove these two
1959+ @objc dynamic var dateLastSent: Date?
1960+ /// Number of times a message has been sent in the dateLastSent second
1961+ @objc dynamic var dateSentNumber = 0
1962+
1963+ var value: Dialog? {
1964+ get {
1965+ return dialog(for: id)
1966+ }
1967+ }
1968+
1969+ var peerID: Int? {
1970+ return value?.peerID
1971+ }
1972+
1973+ @objc dynamic var id: DialogID = 0
1974+
1975+ @objc dynamic var date: Date?
1976+
1977+ class func by(dialog: Dialog) -> RealmDialog {
1978+ return RealmDialog.by(peerID: dialog.peerID)
1979+ }
1980+
1981+ class func by(peerID: Int) -> RealmDialog {
1982+ if let dialog = (try? Realm())?.object(ofType: RealmDialog.self, forPrimaryKey: peerID) {
1983+ return dialog
1984+ } else {
1985+ let dialog = RealmDialog()
1986+ dialog.id = peerID
1987+ realmWrite({ (realm) in
1988+ realm.add(dialog)
1989+ })
1990+ return dialog
1991+ }
1992+ }
1993+
1994+ override class func primaryKey() -> String? {
1995+ return "id"
1996+ }
1997+ override static func ignoredProperties() -> [String] {
1998+ return ["value", "peerID"]
1999+ }
2000+}
2001+func ==(lhs: RealmDialog, rhs: RealmDialog) -> Bool {
2002+ return lhs.date == rhs.date
2003+}
2004+func <(lhs: RealmDialog, rhs: RealmDialog) -> Bool {
2005+ if let l = lhs.date,
2006+ let r = rhs.date {
2007+ return l < r
2008+ } else {
2009+ return false
2010+ }
2011+}
2012+typealias DialogID = Int
2013+
2014+//func ==(lhs: Dialog, rhs: Dialog) -> Bool {
2015+// return lhs.date == rhs.date
2016+//}
2017+//func <(lhs: Dialog, rhs: Dialog) -> Bool {
2018+// return lhs.date < rhs.date
2019+//}
2020+
2021+class RealmNotificatonSettings: Object {
2022+ var value: ChatNotificationSettings? {
2023+ get {
2024+ if let sound = notiSound.value,
2025+ let never = notiDateNever.value {
2026+ if never {
2027+ return ChatNotificationSettings(sound: sound, disabledUntil: .never)
2028+ } else {
2029+ return notiDate >>> {ChatNotificationSettings(sound: sound, disabledUntil: .date($0))}
2030+ }
2031+ }
2032+ return nil
2033+ }
2034+ set {
2035+ notiSound.value = newValue?.sound
2036+ if let newV = newValue {
2037+ switch newV.disabledUntil {
2038+ case .date(let date):
2039+ notiDate = date
2040+ notiDateNever.value = false
2041+ case .never:
2042+ notiDateNever.value = true
2043+ notiDate = nil
2044+ }
2045+ }
2046+ }
2047+ }
2048+ private let notiSound = RealmOptional<Bool>()
2049+ private let notiDateNever = RealmOptional<Bool>()
2050+ @objc private dynamic var notiDate: Date?
2051+ convenience init(value: ChatNotificationSettings?) {
2052+ self.init()
2053+ self.value = value
2054+ }
2055+}
2056+
2057+class Multichat: Object {
2058+ @objc dynamic var id: MultichatID = 0
2059+ @objc dynamic var admin: User?
2060+ var timers: [UserID: Timer] = [:]
2061+ var typingUsers: Set<User> = []
2062+ @objc dynamic var title: String?
2063+ let users = List<MultiChatUser>()
2064+ @objc dynamic var messages: Messages?
2065+ // let messages = List<Message>()
2066+ @objc dynamic var lastReadMessageID = 0
2067+ @objc dynamic var important = false
2068+ @objc dynamic var answered = false
2069+ @objc dynamic var date = Date()
2070+ let count = RealmOptional<Int>()
2071+ @objc dynamic var image: RealmPhoto?
2072+ var imageID: String {
2073+ return "mchat\(id)image"
2074+ }
2075+ let attachments = List<RealmMedia>()
2076+
2077+ @objc dynamic var notificationSettings: RealmNotificatonSettings?
2078+
2079+ var participationStatus: MultichatStatus? {
2080+ get {
2081+ return participationStatusInt.value =>> {MultichatStatus(rawValue: $0)}
2082+ }
2083+ set {
2084+ participationStatusInt.value = newValue?.rawValue
2085+ }
2086+ }
2087+ private let participationStatusInt = RealmOptional<Int>()
2088+
2089+ // convenience init(id: MultichatID, adminID: UserID, title: String, userIDs: [(user: UserID, invitedBy: UserID)]?, notificationSettings: MultichatNotificationSettings?, participationStatus: MultichatStatus?) {
2090+ // self.id = id
2091+ // self.adminID = adminID
2092+ // self.title = title
2093+ // self.userIDs = userIDs
2094+ // self.notificationSetting = notificationSettings
2095+ // self.participationStatus = participationStatus
2096+ // }
2097+ class func by(id: MultichatID) -> Multichat {
2098+ let realm = try! Realm()
2099+ if let us = realm.object(ofType: Multichat.self, forPrimaryKey: id) {
2100+ return us
2101+ } else {
2102+// VkServices.getMultichat(for: id)
2103+ let us = Multichat()
2104+ realmWrite { realm in
2105+ us.id = id
2106+ us.messages = Messages.by(id: us.peerID)
2107+ realm.add(us)
2108+ }
2109+ return us
2110+ }
2111+ }
2112+
2113+ override class func primaryKey() -> String? {
2114+ return "id"
2115+ }
2116+ override static func ignoredProperties() -> [String] {
2117+ return ["imageID", "typingUsers", "timers"]
2118+ }
2119+}
2120+
2121+extension Multichat: Dialog {
2122+ var firstMessage: Message? {
2123+ return messages?.first
2124+ }
2125+
2126+ var name: String? {
2127+ return title
2128+ }
2129+
2130+ func loadImage(into view: UIImageView, completion: (() -> ())? = nil) -> RealmPhotoToken? {
2131+ return image?.load(into: view, completion: completion)
2132+ }
2133+ // func image(completion: @escaping ((UIImage?) -> ())) {
2134+ // if let im = image?.image {
2135+ // im(completion)
2136+ // } else {
2137+ // completion(nil)
2138+ // }
2139+ // }
2140+
2141+ var peerID: Int {
2142+ return id + multichatchatId
2143+ }
2144+}
2145+
2146+class MultiChatUser: Object {
2147+ @objc dynamic var user: User?
2148+ @objc dynamic var invitedBy: User?
2149+ convenience init(_ user: User, invitedBy: User) {
2150+ self.init()
2151+ self.user = user
2152+ self.invitedBy = invitedBy
2153+ }
2154+}
2155+
2156+enum MultichatStatus: Int {
2157+ case active
2158+ case left
2159+ case kicked
2160+}
2161+
2162+class Chat: Object {
2163+ @objc dynamic var user: User? {
2164+ didSet {
2165+ user >>> {userID = $0.id}
2166+ }
2167+ }
2168+ var timer: Timer?
2169+ @objc dynamic var date = Date()
2170+ @objc dynamic var userID = 0
2171+ @objc dynamic var isTyping = false
2172+ @objc dynamic var lastReadMessageID = 0
2173+ let count = RealmOptional<Int>()
2174+ @objc dynamic var messages: Messages?
2175+ // let messages = List<Message>()
2176+ @objc dynamic var important = false
2177+ @objc dynamic var answered = false
2178+ let attachments = List<RealmMedia>()
2179+ @objc dynamic var notificationSettings: RealmNotificatonSettings?
2180+ class func by(user: User) -> Chat {
2181+ let realm = try! Realm()
2182+ if let us = realm.object(ofType: Chat.self, forPrimaryKey: user.id) {
2183+ return us
2184+ } else {
2185+ let us = Chat()
2186+ realmWrite { realm in
2187+ us.user = user
2188+ us.messages = Messages.by(id: us.peerID)
2189+ realm.add(us)
2190+ }
2191+ return us
2192+ }
2193+ }
2194+
2195+ convenience init(user: User) {
2196+ self.init()
2197+ self.user = user
2198+ }
2199+ override static func ignoredProperties() -> [String] {
2200+ return ["timer"]
2201+ }
2202+ override class func primaryKey() -> String? {
2203+ return "userID"
2204+ }
2205+}
2206+
2207+extension Chat: Dialog {
2208+ var firstMessage: Message? {
2209+ return messages?.first
2210+ }
2211+
2212+ var name: String? {
2213+ if let firstName = user?.firstName,
2214+ let lastName = user?.lastName {
2215+ return "\(firstName) \(lastName)"
2216+ } else {
2217+ return nil
2218+ }
2219+ }
2220+
2221+ func loadImage(into view: UIImageView, completion: (() -> ())? = nil) -> RealmPhotoToken? {
2222+ return user?.avatar?.load(into: view, completion: completion)
2223+ }
2224+
2225+ // func image(completion: @escaping ((UIImage?) -> ())) {
2226+ // if let im = user?.avatar?.image {
2227+ // im(completion)
2228+ // } else {
2229+ // completion(nil)
2230+ // }
2231+ // }
2232+
2233+ var id: Int {
2234+ return userID
2235+ }
2236+
2237+ var peerID: Int {
2238+ return userID
2239+ }
2240+}
2241+
2242+struct ChatNotificationSettings {
2243+ var sound: Bool
2244+ var disabledUntil: disableDate
2245+}
2246+
2247+enum disableDate {
2248+ case never
2249+ case date(Date)
2250+ init?(value: Int) {
2251+ switch value {
2252+ case -1:
2253+ self = .never
2254+ case 0:
2255+ return nil
2256+ default:
2257+ self = .date(Date(timeIntervalSince1970: TimeInterval(value)))
2258+ }
2259+ }
2260+}
2261+
2262+func isPhoneNumber(_ phone: String) -> Bool {
2263+ let nums = "1234567890".characters
2264+ let f002 = phone.characters.map{nums.contains($0) ? 1 : 0}
2265+ let foo1 = Double(sum(acc: 0, arr: f002))
2266+ let foo = foo1/Double(phone.characters.count)
2267+ return foo >= 0.4 ? true : false
2268+}
2269+
2270+
Melk/MainCoordinator.swiftnew file+111
@@ -0,0 +1,111 @@
1+//
2+// Coordinator.swift
3+// Melk
4+//
5+// Created by Ilya Kos on 7/1/17.
6+// Copyright © 2017 Ilya Kos. All rights reserved.
7+//
8+
9+import UIKit
10+import RealmSwift
11+//import SwiftyVK
12+
13+class MainCoordinator {
14+ let window: UIWindow
15+ let mainVC = MainViewController()
16+ let loadingVC = UIViewController() // TODO: An actual loading VC
17+ let navC: UINavigationController!
18+ let secondaryCoord = SecondaryCoordinator()
19+ let stream = StreamingService()
20+ init(window: UIWindow) {
21+ self.window = window
22+ window.rootViewController = loadingVC
23+
24+ Realm.Configuration.defaultConfiguration = Realm.Configuration
25+ .init(inMemoryIdentifier: "com.ilyakooo0.Melk.realm",
26+ readOnly: false,
27+ deleteRealmIfMigrationNeeded: true)
28+
29+ navC = UINavigationController(rootViewController: mainVC)
30+
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+ }
46+ }
47+
48+ func start() {
49+// VK.configure(withAppId: appID, delegate: self)
50+// if VK.state != .authorized {
51+// VK.logIn()
52+// }
53+ print("didAuthorize")
54+ window.rootViewController = navC
55+ }
56+
57+ func updateScreens() {
58+ secondaryCoord.updateScreens()
59+ }
60+}
61+/*
62+extension MainCoordinator: VKDelegate {
63+ /** ---DEPRECATED. TOKEN NOW STORED IN KEYCHAIN--- Called when SwiftyVK need know where a token is located
64+ - returns: Path to save/read token or nil if should save token to UserDefaults*/
65+ func vkShouldUseTokenPath() -> String? {
66+ return nil
67+ }
68+
69+ func vkWillAuthorize() -> Set<VK.Scope> {
70+ //Called when SwiftyVK need authorization permissions.
71+ return permissions
72+ }
73+
74+ func vkDidAuthorizeWith(parameters: Dictionary<String, String>) {
75+ //Called when the user is log in.
76+ //Here you can start to send requests to the API.
77+ print("authorized")
78+ mainSync {
79+ self.window.rootViewController = self.navC
80+ }
81+ authorized()
82+ }
83+
84+ func vkAutorizationFailedWith(error: AuthError) {
85+ //Called when SwiftyVK could not authorize. To let the application know that something went wrong.
86+ print("failed auth with error \(error.errorCode): \(error.description)")
87+ mainSync {
88+ self.window.rootViewController = self.navC
89+ }
90+ }
91+
92+ func vkDidUnauthorize() {
93+ print("did unauthorize")
94+ mainSync {
95+ self.window.rootViewController = self.navC
96+ }
97+ //Called when user is log out.
98+ }
99+
100+ func vkWillPresentView() -> UIViewController {
101+ //Only for iOS!
102+ //Called when need to display a view from SwiftyVK.
103+ let authController = UIViewController()
104+ mainSync {
105+ self.window.rootViewController = authController
106+ }
107+ return authController //UIViewController that should present authorization view controller
108+ }
109+
110+}
111+ */
Melk/MainViewController.swiftnew file+107
@@ -0,0 +1,107 @@
1+//
2+// MainViewController.swift
3+// Melk
4+//
5+// Created by Ilya Kos on 7/1/17.
6+// Copyright © 2017 Ilya Kos. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class MainViewController: UIViewController {
12+
13+ var present: (([String]) -> ())?
14+
15+ private let mainView = MainView()
16+
17+ init() {
18+ super.init(nibName: nil, bundle: nil)
19+ self.view = mainView
20+ let rightButton = UIBarButtonItem()
21+ rightButton.title = "Present"
22+ rightButton.target = self
23+ rightButton.action = #selector(MainViewController.presentTags)
24+ self.navigationItem.rightBarButtonItem = rightButton
25+ }
26+
27+ required init?(coder aDecoder: NSCoder) {
28+ fatalError("init(coder:) has not been implemented")
29+ }
30+
31+ @objc private func presentTags() {
32+ present?(
33+ mainView.tags.flatMap({$0}).filter({$0 != ""})
34+ )
35+ }
36+}
37+
38+class MainView: UIView {
39+ let tableView = UITableView()
40+
41+ override init(frame: CGRect) {
42+ super.init(frame: frame)
43+ addSubview(tableView)
44+ tableView.dataSource = self
45+ tableView.delegate = self
46+ tableView.estimatedRowHeight = 30
47+ tableView.rowHeight = UITableViewAutomaticDimension
48+ tableView.register(TitleTableViewCell.self, forCellReuseIdentifier: TitleTableViewCell.identifier)
49+ tableView.register(TagTableViewCell.self, forCellReuseIdentifier: TagTableViewCell.identifier)
50+ tableView.separatorStyle = .none
51+ tableView.allowsSelection = false
52+ setNeedsLayout()
53+ }
54+
55+ override func layoutSubviews() {
56+ tableView.frame = bounds
57+ }
58+
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+
70+ required init?(coder aDecoder: NSCoder) {
71+ fatalError("init(coder:) has not been implemented")
72+ }
73+}
74+
75+extension MainView: UITableViewDataSource {
76+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
77+ if let cell = tableView.dequeueReusableCell(withIdentifier: TagTableViewCell.identifier) as? TagTableViewCell {
78+// cell.titleText = "Testing testing 1 2 3"
79+ 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]
85+ return cell
86+ }
87+ return UITableViewCell()
88+ }
89+
90+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
91+ return tags.count
92+ }
93+
94+ func numberOfSections(in tableView: UITableView) -> Int {
95+ return 1
96+ }
97+
98+ func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
99+ return indexPath.item == 0 ? TagTableViewCell.largeHeight : TagTableViewCell.normalHeight
100+ }
101+
102+}
103+
104+
105+extension MainView: UITableViewDelegate {
106+
107+}
Melk/MyWeirdness.swiftnew file+104
@@ -0,0 +1,104 @@
1+//
2+// MyWierdness.swift
3+// golos
4+//
5+// Created by Ilya Kos on 8/9/16.
6+// Copyright © 2016 Ilya Kos. All rights reserved.
7+//
8+
9+import Foundation
10+
11+func monadate<T, M>(inp: T?, funct: ((T) -> M?)) -> M? {
12+ if let sinp = inp {
13+ return funct(sinp)
14+ } else {
15+ return nil
16+ }
17+}
18+
19+infix operator =>>: Monadation
20+/// Unwraps
21+func =>><T, M>(inp: T?, funct: ((T) -> M?)) -> M? {
22+ return monadate(inp: inp, funct: funct)
23+}
24+
25+precedencegroup Monadation {
26+ higherThan: SortedAppend //AssignmentPrecedence
27+ associativity: left
28+}
29+
30+infix operator >>>: Monadation
31+/// Doesn't unwrap
32+func >>><T, M>(inp: T?, funct: ((T) -> M)) -> M? {
33+ if let sinp = inp {
34+ return funct(sinp)
35+ }
36+ return nil
37+}
38+
39+func sum(acc: Int, arr: [Int]) -> Int {
40+ if arr == [] {
41+ return acc
42+ } else {
43+ var arr = arr
44+ let num = arr.popLast()
45+ return sum(acc: acc + num!, arr: arr)
46+ }
47+}
48+
49+extension Bool {
50+ static func from(num: Int) -> Bool {
51+ return num > 0 ? true : false
52+ }
53+ init(from num: Int) {
54+ self = Bool.from(num: num)
55+ }
56+}
57+
58+extension Int {
59+ var bool: Bool {
60+ return self > 0 ? true : false
61+ }
62+}
63+
64+
65+infix operator |>: Monadation
66+/// runs right after left
67+func |>(lhs: (() -> ())?, rhs: (() -> ())?) -> (() -> ())? {
68+ if lhs != nil || rhs != nil {
69+ return {
70+ lhs?()
71+ rhs?()
72+ }
73+ } else {
74+ return nil
75+ }
76+}
77+func |><T>(lhs: ((T) -> ())?, rhs: (() -> ())?) -> ((T) -> ())? {
78+ if lhs != nil || rhs != nil {
79+ return {
80+ lhs?($0)
81+ rhs?()
82+ }
83+ } else {
84+ return nil
85+ }
86+}
87+func |><T>(lhs: (() -> ())?, rhs: ((T) -> ())?) -> ((T) -> ())? {
88+ if lhs != nil || rhs != nil {
89+ return {
90+ lhs?()
91+ rhs?($0)
92+ }
93+ } else {
94+ return nil
95+ }
96+}
97+
98+func ??<T>(lhs: T?, rhs: (() -> T)) -> T {
99+ if let l = lhs {
100+ return l
101+ } else {
102+ return rhs()
103+ }
104+}
Melk/PublicCrap.swiftnew file+259
@@ -0,0 +1,259 @@
1+//
2+// PublicCrap.swift
3+// golos
4+//
5+// Created by Ilya Kos on 8/29/16.
6+// Copyright © 2016 Ilya Kos. All rights reserved.
7+//
8+
9+import UIKit
10+import RealmSwift
11+
12+//let userQueue = DispatchQueue(label: "io.golos.userRequestedQueue", qos: .userInitiated)
13+let userQueue = DispatchQueue.init(label: "io.golos.userRequestedQueue", qos: .userInitiated, attributes: .concurrent)
14+
15+fileprivate let manager = FileManager.default
16+
17+func url(forName name: String) -> URL? {
18+ return manager.urls(for: .libraryDirectory, in: .userDomainMask)
19+ .first?
20+ .appendingPathComponent("Caches")
21+ .appendingPathComponent(name)
22+}
23+
24+func write(cache data: Data?, withName name: String, completion: (() -> ())? = nil) {
25+ if let url = url(forName: name) {
26+ userQueue.async {
27+ manager.createFile(atPath: url.path, contents: data)
28+ // try! data?.write(to: url)
29+ if let completion = completion {
30+ DispatchQueue.main.async(execute: completion)
31+ }
32+ }
33+ }
34+}
35+
36+func delete(cache name: String) {
37+ if let url = manager.urls(for: .documentDirectory, in: .userDomainMask)
38+ .first?
39+ .appendingPathComponent("Cache")
40+ .appendingPathComponent(name) {
41+ DispatchQueue.main.async {
42+ userQueue.async {
43+ try? manager.removeItem(at: url)
44+ }
45+ }
46+ }
47+}
48+
49+func getLocalImage(fromName name: String) -> UIImage? {
50+ return url(forName: name) =>>
51+ {try? Data(contentsOf: $0)} =>>
52+ {UIImage.init(data: $0)}
53+}
54+
55+func cache(image: UIImage?, withName name: String) -> String? {
56+ return image =>> {return UIImagePNGRepresentation($0)} >>>
57+ { data in
58+ write(cache: data, withName: name)
59+ return name
60+ }
61+}
62+
63+precedencegroup SortedAppend {
64+ higherThan: AssignmentPrecedence
65+ associativity: left
66+}
67+
68+infix operator <-: SortedAppend // Expects all things to be sorted
69+infix operator <~: SortedAppend // Sorts all things it gets regardless
70+
71+protocol SortedArrayItem {
72+ func equal(to: Self) -> Bool
73+ func greater(than: Self) -> Bool
74+}
75+
76+/// Returns: true if added. false if was already in list
77+func <~<T: SortedArrayItem>(lhs: List<T>?, rhs: T?) -> Bool { // Note: Most recent go first (i e bigger ids go first)
78+ if let rhs = rhs,
79+ let lhs = lhs {
80+ if lhs.count == 0 {
81+ lhs.append(rhs)
82+ return true
83+ }
84+ var l = 0
85+ var r = lhs.count - 1
86+ var i = Int((l+r)/2)
87+ while l<r {
88+ i = Int((l+r)/2)
89+ if rhs.greater(than: lhs[i]) {
90+ l = i + 1
91+ } else if rhs.equal(to: lhs[i]) {
92+ return false
93+ } else {
94+ r = i
95+ }
96+ }
97+ if lhs[r].equal(to: rhs) {
98+ return false
99+ }
100+ assert(l == r, "l != r!!!")
101+ if rhs.greater(than: lhs[l]) {
102+ lhs.insert(rhs, at: l + 1)
103+ } else {
104+ lhs.insert(rhs, at: l)
105+ }
106+ return true
107+ }
108+ return false
109+}
110+
111+/// Will silently break if not passed a SORTED list
112+/// Returns: Indexes of added items
113+func <-<T: SortedArrayItem>(lhs: List<T>?, rhs: List<T>?) -> [Int] {
114+ var indexes: [Int] = []
115+ if let lhs = lhs,
116+ let rhs = rhs {
117+ var index = 0
118+ for item in rhs {
119+ while index < lhs.count && item.greater(than: lhs[index]) {
120+ index += 1
121+ }
122+ if index == lhs.count || !item.equal(to: lhs[index]) {
123+ lhs.insert(item, at: index)
124+ indexes.append(index)
125+ }
126+ }
127+ }
128+ return indexes
129+}
130+
131+/// Returns: Indexes of added items
132+func <-<T: SortedArrayItem>(lhs: List<T>?, rhs: [T?]?) -> [Int] {
133+ var indexes: [Int] = []
134+ if let lhs = lhs,
135+ let rhs = rhs?.flatMap({$0}) {
136+ var index = 0
137+ for item in rhs {
138+ while index < lhs.count && item.greater(than: lhs[index]) {
139+ index += 1
140+ }
141+ if index == lhs.count || !item.equal(to: lhs[index]) {
142+ lhs.insert(item, at: index)
143+ indexes.append(index)
144+ }
145+ }
146+ }
147+ return indexes
148+}
149+
150+/// Returns: true if added. false if was already in list
151+func <~(lhs: RealmDialog?, rhs: Message?) -> Bool {
152+ guard let message = rhs,
153+ let dialog = lhs
154+ else { return false }
155+// assert(dialog.date != nil, "Date is nil on dialog!!!")
156+ print(dialog.date ?? "no date found")
157+ var out: Bool!
158+ realmWrite { (_) in
159+ let messages = dialog.value?.messages
160+ out = messages <~ message
161+ dialog.date = messages?.first?.date
162+ }
163+ print(dialog.date ?? "There was no date... \t", message)
164+ return out
165+}
166+
167+func <-(lhs: RealmDialog?, rhs: [Message?]?){
168+ guard let ms = rhs,
169+ let dialog = lhs
170+ else { return }
171+ realmWrite { (_) in
172+ let messages = dialog.value?.messages
173+ messages <- ms
174+ dialog.date = messages?.first?.date
175+ }
176+
177+}
178+
179+/// Returns: true if added. false if was already in list
180+func <~(lhs: Dialog?, rhs: Message?) -> Bool {
181+ guard let message = rhs,
182+ let dialog = lhs
183+ else { return false}
184+ var out: Bool!
185+ realmWrite { (_) in
186+ out = dialog.messages <~ message
187+ }
188+ return out
189+}
190+
191+func <-(lhs: Dialog?, rhs: [Message?]?) {
192+ guard let messages = rhs,
193+ let dialog = lhs
194+ else { return }
195+ realmWrite { (_) in
196+ dialog.messages <- messages
197+ }
198+}
199+
200+/// Appends if not already in list
201+func add<T>(item: T?, toList list: List<T>) {
202+ if let item = item {
203+ if !list.contains(item) {
204+ list.append(item)
205+ }
206+ }
207+}
208+
209+func realmWrite(_ code: ((Realm) -> ())) {
210+ realmWriteWithCompletion { realm, _ in
211+ code(realm)
212+ }
213+}
214+
215+fileprivate var completions: [String: [(()->())]] = [:]
216+
217+
218+/// Passed closure should be called from within realmWrite and should be passed a completion block
219+func realmWriteWithCompletion(_ code: ((Realm, (( @escaping (()->()) )->()) ) -> ())) {
220+ let label = String.init(utf8String: __dispatch_queue_get_label(nil))!
221+ let addCompletion: (( @escaping (()->()) )->()) = { completion in
222+ if !completions.keys.contains(label) {
223+ completions[label] = []
224+ }
225+ completions[label]?.append(completion)
226+ }
227+ do {
228+ let realm = try Realm()
229+ if realm.isInWriteTransaction {
230+ code(realm, addCompletion)
231+ } else {
232+ try realm.write {
233+ code(realm, addCompletion)
234+ }
235+ completions[label]?.map { $0() }
236+ completions[label]?.removeAll()
237+ }
238+ } catch {
239+ // TODO: Handle error. Error closure?
240+ }
241+}
242+
243+struct DecayingAverage {
244+ var weight: CGFloat = 20
245+ var value: CGFloat = 0
246+ mutating func add(_ num: CGFloat) {
247+ value = (value * weight + num) / (weight + 1)
248+ }
249+}
250+
251+func mainSync(_ code: (() -> ())) {
252+ if Thread.isMainThread {
253+ code()
254+ } else {
255+ DispatchQueue.main.sync {
256+ code()
257+ }
258+ }
259+}
Melk/SecondaryCoordinator.swiftnew file+53
@@ -0,0 +1,53 @@
1+//
2+// SecondaryCoordinator.swift
3+// Melk
4+//
5+// Created by Ilya Kos on 7/1/17.
6+// Copyright © 2017 Ilya Kos. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class SecondaryCoordinator {
12+
13+ var windows: [UIWindow] = []
14+
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+
30+ init() {
31+
32+ }
33+
34+ private var VCs: [SecondaryViewController] = []
35+
36+ func updateScreens() {
37+ windows.removeAll()
38+ VCs.removeAll()
39+ for screen in UIScreen.screens.dropFirst() {
40+ let window = UIWindow(frame: screen.bounds)
41+ window.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
42+ window.screen = screen
43+ window.isHidden = false
44+ let VC = SecondaryViewController()
45+ VCs.append(VC)
46+ window.rootViewController = VC
47+ windows.append(window)
48+ if didStartPresenting {
49+ VC.present(tag: tag)
50+ }
51+ }
52+ }
53+}
Melk/SecondaryViewController.swiftnew file+156
@@ -0,0 +1,156 @@
1+//
2+// SecondaryViewController.swift
3+// Melk
4+//
5+// Created by Ilya Kos on 7/5/17.
6+// Copyright © 2017 Ilya Kos. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class SecondaryViewController: UIViewController {
12+
13+ init() {
14+ super.init(nibName: nil, bundle: nil)
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)
24+ }
25+
26+ required init?(coder aDecoder: NSCoder) {
27+ fatalError("init(coder:) has not been implemented")
28+ }
29+
30+ private let sView = SecondaryView()
31+}
32+
33+class 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+
79+ override init(frame: CGRect) {
80+ 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()
128+
129+ melkLabel.sizeToFit()
130+ melkLabel.center = center
131+
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
151+ }
152+
153+ required init?(coder aDecoder: NSCoder) {
154+ fatalError("init(coder:) has not been implemented")
155+ }
156+}
Melk/StackView.swiftnew file+156
@@ -0,0 +1,156 @@
1+//
2+// StackView.swift
3+// Melk
4+//
5+// Created by Ilya Kos on 7/5/17.
6+// Copyright © 2017 Ilya Kos. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class StackView: UIView {
12+ var axis: Axis = .horizontal {
13+ didSet {
14+ setNeedsLayout()
15+ }
16+ }
17+ var alignment: Alignment = .center {
18+ didSet {
19+ setNeedsLayout()
20+ }
21+ }
22+ var padding: CGPoint = CGPoint.zero {
23+ didSet {
24+ setNeedsLayout()
25+ }
26+ }
27+ var arrangedSubviews: [StackViewElement] = [] {
28+ didSet {
29+ setNeedsLayout()
30+ }
31+ }
32+
33+ func addArrangedSubview(_ element: StackViewElement) {
34+ arrangedSubviews.append(element)
35+ }
36+
37+ convenience init(axis: Axis, alignment: Alignment) {
38+ self.init()
39+ self.axis = axis
40+ self.alignment = alignment
41+ }
42+
43+ override func layoutSubviews() {
44+ super.layoutSubviews()
45+ print("laying out subviews")
46+
47+ subviews.map({$0.removeFromSuperview()}) // TODO: There must be a better way!
48+
49+ var height: CGFloat = 0
50+ var width: CGFloat = 0
51+ switch axis {
52+ case .horizontal:
53+ width = padding.x
54+ case .vertical:
55+ height = padding.y
56+ }
57+ for element in arrangedSubviews {
58+ switch element {
59+ case .spacer(let a):
60+ switch axis {
61+ case .horizontal:
62+ width += a
63+ case .vertical:
64+ height += a
65+ }
66+ case .view(let view):
67+ view.layoutIfNeeded()
68+ switch axis {
69+ case .horizontal:
70+ view.frame.origin.x = width
71+ width += view.frame.width
72+ height = max(height, view.frame.height)
73+ if alignment == .top {
74+ view.frame.origin.y = padding.y
75+ }
76+ case .vertical:
77+ view.frame.origin.y = height
78+ height += view.frame.height
79+ width = max(width, view.frame.width)
80+ if alignment == .left {
81+ view.frame.origin.x = padding.x
82+ }
83+ }
84+ addSubview(view)
85+ }
86+ }
87+ switch axis {
88+ case .horizontal:
89+ frame.size = CGSize(width: width + padding.x, height: height + 2*padding.y)
90+ switch alignment {
91+ case .top:
92+ break
93+ case .bottom:
94+ for element in arrangedSubviews {
95+ switch element {
96+ case .view(let view):
97+ view.frame.origin.y = height - view.frame.height + padding.y
98+ default:
99+ break
100+ }
101+ }
102+ default:
103+ for element in arrangedSubviews {
104+ switch element {
105+ case .view(let view):
106+ view.center.y = height/2 + padding.y
107+ default:
108+ break
109+ }
110+ }
111+ }
112+ case .vertical:
113+ frame.size = CGSize(width: width + 2*padding.x, height: height + padding.y)
114+ switch alignment {
115+ case .left:
116+ break
117+ case .right:
118+ for element in arrangedSubviews {
119+ switch element {
120+ case .view(let view):
121+ view.frame.origin.x = width - view.frame.width + padding.x
122+ default:
123+ break
124+ }
125+ }
126+ default:
127+ for element in arrangedSubviews {
128+ switch element {
129+ case .view(let view):
130+ view.center.x = width/2 + padding.x
131+ default:
132+ break
133+ }
134+ }
135+ }
136+ }
137+ }
138+}
139+
140+enum StackViewElement {
141+ case spacer(CGFloat)
142+ case view(UIView)
143+}
144+
145+enum Axis {
146+ case horizontal
147+ case vertical
148+}
149+
150+enum Alignment {
151+ case center
152+ case top
153+ case right
154+ case bottom
155+ case left
156+}
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.swiftnew file+145
@@ -0,0 +1,145 @@
1+//
2+// TableViewCells.swift
3+// Melk
4+//
5+// Created by Ilya Kos on 7/2/17.
6+// Copyright © 2017 Ilya Kos. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class TitleTableViewCell: UITableViewCell {
12+
13+ var titleText: String = "" {
14+ didSet {
15+ title.text = titleText
16+ setNeedsLayout()
17+ }
18+ }
19+
20+ private let title = UILabel()
21+
22+ override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
23+ title.font = UIFont.systemFont(ofSize: 28, weight: UIFontWeightHeavy) // UIFont.preferredFont(forTextStyle: .title1)
24+// print(title.font.pointSize) // 28
25+ super.init(style: style, reuseIdentifier: TitleTableViewCell.identifier)
26+ contentView.addSubview(title)
27+ setNeedsLayout()
28+ }
29+
30+ override func layoutSubviews() {
31+ super.layoutSubviews()
32+ title.sizeToFit()
33+ title.frame.origin = CGPoint(x: 16, y: 16)
34+ print(title.frame.height)
35+ }
36+
37+ override func prepareForReuse() {
38+
39+ }
40+
41+ required init?(coder aDecoder: NSCoder) {
42+ fatalError("init(coder:) has not been implemented")
43+ }
44+
45+ static let identifier = "io.Melk.titleTableViewCell"
46+ static let height: CGFloat = 33.5 + 16 + 8
47+}
48+
49+class TagTableViewCell: UITableViewCell {
50+ var tagString: String? {
51+ set {
52+ textField.text = newValue
53+ setNeedsLayout()
54+ }
55+ get {
56+ return textField.text
57+ }
58+ }
59+
60+ var tagDidChange: ((String?) -> ())?
61+
62+ @objc private func textDidChange() {
63+ tagDidChange?(tagString)
64+ }
65+
66+ var style: TagStyle = .normal {
67+ didSet {
68+ updateStyle()
69+ }
70+ }
71+
72+ private let textField = UITextField()
73+
74+ override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
75+ super.init(style: style, reuseIdentifier: reuseIdentifier)
76+
77+ textField.borderStyle = .roundedRect
78+ textField.adjustsFontSizeToFitWidth = false
79+ textField.autocapitalizationType = .none
80+ textField.autocorrectionType = .no
81+ textField.addTarget(self, action: #selector(self.textDidChange), for: .editingChanged)
82+
83+ contentView.addSubview(textField)
84+// textField.delegate = self
85+// textField.font
86+
87+ }
88+
89+ private func updateStyle() {
90+ textField.font = style.font
91+ setNeedsLayout()
92+ }
93+
94+ override func layoutSubviews() {
95+ super.layoutSubviews()
96+
97+ let label = UILabel()
98+ label.text = "MH"
99+ label.font = style.font
100+ let height = label.textRect(forBounds:
101+ CGRect.infinite, limitedToNumberOfLines: 1)
102+ .height + 2*2
103+
104+ let frame = CGRect(x: 16, y: style == .large ? 32 : 4, width: bounds.width - 16*2, height: height)
105+ textField.frame = frame
106+ print(textField.frame)
107+ }
108+
109+ required init?(coder aDecoder: NSCoder) {
110+ fatalError("init(coder:) has not been implemented")
111+ }
112+
113+
114+ static let identifier = "io.Melk.tagTableViewCell"
115+ static let largeHeight: CGFloat = 37.5 + 16 + 32
116+ static let normalHeight: CGFloat = 28.0 + 4*2
117+}
118+
119+//extension TagTableViewCell: UITextFieldDelegate {
120+// func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
121+//
122+// return true
123+// }
124+//}
125+
126+enum TagStyle {
127+ case normal
128+ case large
129+
130+ private static let largeFont = UIFont.systemFont(ofSize: 28, weight: UIFontWeightHeavy)
131+ private static let normalFont = UIFont.systemFont(ofSize: 20, weight: UIFontWeightSemibold)
132+
133+ var font: UIFont {
134+ switch self {
135+ case .normal:
136+ return TagStyle.normalFont
137+ case .large:
138+ return TagStyle.largeFont
139+ }
140+ }
141+}
142+
143+
144+
145+
Melk/VKData.swiftnew file+672
@@ -0,0 +1,672 @@
1+//
2+// VKData.swift
3+// golos
4+//
5+// Created by Ilya Kos on 7/28/16.
6+// Copyright © 2016 Ilya Kos. All rights reserved.
7+//
8+
9+import Foundation
10+import SwiftyJSON
11+import CoreLocation
12+import RealmSwift
13+//import SwiftyVK
14+
15+class VKData {
16+ // var users: [UserID: User] = [:]
17+ // var multichats: [MultichatID: Multichat] = [:]
18+ // var chats: [UserID: Chat] = [:]
19+ // var dialogs = SortedArray<Dialog>()
20+ // var photos: [PhotoID: Photo] = [:]
21+ // var videos: [VideoID: Video] = [:]
22+ // var audio: [AudioID: Audio] = [:]
23+ // var documents: [DocumentID: Document] = [:]
24+ // var wallPosts: [WallPostID: WallPost] = [:]
25+ // var wallPostReplys: [WallPostReplyID: WallPostReply] = [:]
26+ // var stickers: [StickerID: Sticker] = [:]
27+ // var gifts: [GiftID: Gift] = [:]
28+ // var markets: [MarketID: Market] = [:]
29+ // var marketAlbums: [MarketAlbumID: MarketAlbum] = [:]
30+ // var notes: [NoteID: Note] = [:]
31+ // var polls: [PollID: Poll] = [:]
32+ // var photoAlbums: [PhotoAlbumID: PhotoAlbum] = [:]
33+
34+ class func processUser(json: JSON) -> User? {
35+ print("in")
36+ if let id = json["id"].int, let firstName = json["first_name"].string, let lastName = json["last_name"].string {
37+ var user: User!
38+ realmWrite({_ in
39+ user = User.by(id: id)
40+ user.firstName = firstName
41+ user.lastName = lastName
42+
43+ if let birthday = json["bdate"].string {
44+ let formatter = DateFormatter()
45+ if birthday.characters.split(separator: ".").count == 3 {
46+ formatter.dateFormat = "d.M.y"
47+ } else {
48+ formatter.dateFormat = "d.M"
49+ }
50+ user.birthday = formatter.date(from: birthday)
51+ }
52+ user.avatar = RealmPhoto.by(user.avatarID, surl: json["photo_100"].string)// was "photo_max"
53+ user.photo = RealmPhoto.by(user.photoID, surl: json["photo_max_orig"].string)
54+ if let friendStat = json["friend_status"].int {
55+ user.friendStatus = FriendStatus(rawValue: friendStat)
56+ }
57+ if let online = json["online"].int {
58+ user.online.value = Bool.from(num: online)
59+ }
60+ if let canSendIM = json["can_write_private_message"].int {
61+ user.canSendPrivateMessage.value = Bool.from(num: canSendIM)
62+ }
63+ if let canSendRequest = json["can_send_friend_request"].int {
64+ user.canSendFriendRequest.value = Bool.from(num: canSendRequest)
65+ }
66+ if let phone = json["mobile_phone"].string, isPhoneNumber(phone) {
67+ user.mobilePhone = phone
68+ }
69+ if let phone = json["home_phone"].string, isPhoneNumber(phone) {
70+ user.homePhone = phone
71+ }
72+ if let lastSeen = json["last_seen"].dictionary,
73+ let time = lastSeen["time"]?.int {
74+ user.lastSeen = Date(timeIntervalSince1970: TimeInterval(time))
75+ }
76+ if let deactivated = json["deactivated"].string, let foo = Deactivated(rawValue: deactivated) {
77+ user.deactivated = foo
78+ }
79+ user.about = json["about"].string
80+ if let blacklisted = json["blacklisted_by_me"].int {
81+ user.blacklisted.value = Bool.from(num: blacklisted)
82+ }
83+ user.mutualFriendCount.value = json["common_count"].int
84+ user.siteSURL = json["site"].string
85+ user.status = json["status"].string
86+ print("\(user.id): \(user.firstName ?? "") \(user.lastName ?? "")")
87+ })
88+ return user
89+ }
90+ return nil
91+ }
92+
93+ class func processGeolocation(_ json: JSON) -> Geolocation? {
94+ if let coords = json["coordinates"].string{
95+ let nums = coords.characters.split(separator: " ").map({ (n) -> Double? in
96+ NumberFormatter().number(from: String(n))?.doubleValue
97+ })
98+ let place = json["place"]
99+ let geo = Geolocation()
100+ geo.title = place["title"].string
101+ geo.country = place["country"].string
102+ geo.city = place["city"].string
103+ if let slatt = nums.first,
104+ let slong = nums.last,
105+ let latt = slatt,
106+ let long = slong {
107+ geo.geoCoordinates = CLLocation(latitude: latt, longitude: long)
108+ }
109+ return geo
110+ } else {
111+ return nil
112+ }
113+ }
114+
115+ class func processMedia(_ json: JSON) -> Media? {
116+ if let type = json["type"].string {
117+ switch type {
118+ case "photo":
119+ if let id = processPhoto(json["photo"]) {
120+ return Media.photo(id)
121+ }
122+ case "video":
123+ if let id = processVideo(json["video"]) {
124+ return Media.video(id)
125+ }
126+ case "audio":
127+ if let id = processAudio(json["audio"]) {
128+ return Media.audio(id)
129+ }
130+ case "doc":
131+ if let id = processDocument(json["doc"]) {
132+ if json["title"].string == "graffiti.png" {
133+ return Media.graffiti(id)
134+ } else {
135+ return Media.doc(id)
136+ }
137+ }
138+ case "wall":
139+ if let id = processWallPost(json["wall"]) {
140+ return Media.wall(id)
141+ }
142+ case "wall_reply":
143+ if let id = processWallPostReply(json["wall_reply"]) {
144+ return Media.wallReply(id)
145+ }
146+ case "sticker":
147+ if let id = processSticker(json["sticker"]) {
148+ return Media.sticker(id)
149+ }
150+ case "link":
151+ if let link = processLink(json["link"]) {
152+ return Media.link(link)
153+ }
154+ case "gift":
155+ if let id = processGift(json["gift"]) {
156+ return Media.gift(id)
157+ }
158+ case "poll":
159+ if let id = processPoll(json["poll"]) {
160+ return Media.poll(id)
161+ }
162+ case "note":
163+ if let id = processNote(json["note"]) {
164+ return Media.note(id)
165+ }
166+ case "album":
167+ if let id = processPhotoAlbum(json["album"]) {
168+ return Media.album(id)
169+ }
170+ default:
171+ break
172+ }
173+ }
174+ return nil
175+ }
176+
177+ class func processPhotoAlbum(_ json: JSON) -> PhotoAlbum? {
178+ if let id = json["id"].int{
179+ var album: PhotoAlbum!
180+ realmWrite { _ in
181+ album = PhotoAlbum.by(id: id)
182+ album.thumb = processPhoto(json["thumb"])
183+ album.owner = json["owner_id"].int >>> {User.by(id: $0)}
184+ album.title = json["title"].string
185+ album.dateCreated = json["created"].int >>> {Date(timeIntervalSince1970: TimeInterval($0))}
186+ album.dateUpdated = json["updated"].int >>> {Date(timeIntervalSince1970: TimeInterval($0))}
187+ album.count.value = json["size"].int
188+ album.albumDescription = json["description"].string
189+ }
190+ return album
191+ }
192+ return nil
193+ }
194+
195+ class func processNote(_ json:JSON) -> Note? {
196+ if let id = json["id"].int {
197+ var note: Note!
198+ realmWrite { _ in
199+ note = Note.by(id: id)
200+ note.owner = json["owner_id"].int >>> {User.by(id: $0)}
201+ note.title = json["title"].string
202+ note.body = json["text"].string
203+ note.date = json["date"].int >>> {Date(timeIntervalSince1970: TimeInterval($0))}
204+ note.commentCount.value = json["comments"].int
205+ note.surl = json["view_url"].string
206+ }
207+ return note
208+ }
209+ return nil
210+ }
211+
212+ class func processGift(_ json: JSON) -> Gift? {
213+ if let id = json["id"].int {
214+ var gift: Gift!
215+ realmWrite({ _ in
216+ gift = Gift.by(id: id)
217+ gift.image = RealmPhoto.by(gift.imageID, surl: json["thumb_256"].string)
218+ })
219+ return gift
220+ }
221+ return nil
222+ }
223+
224+ class func processWallPostReply(_ json: JSON) -> WallPostReply? {
225+ let likes = json["likes"]
226+ if let id = json["id"].int {
227+ var reply: WallPostReply!
228+ realmWrite { _ in
229+ reply = WallPostReply.by(id: id)
230+ reply.author = json["from_id"].int >>> {User.by(id: $0)}
231+ reply.date = json["date"].int >>> {Date(timeIntervalSince1970: TimeInterval($0))}
232+ reply.body = json["text"].string
233+ reply.wallPost = json["post_id"].int >>> {WallPost.by(id: $0)}
234+ reply.wallOwner = json["owner_id"].int >>> {User.by(id: $0)}
235+ reply.likeCount.value = likes["count"].int
236+ reply.didLike.value = likes["user_likes"].int?.bool
237+ reply.canLike.value = likes["can_like"].int?.bool
238+ if let attachmentsJSON = json["attachments"].array {
239+ for att in attachmentsJSON {
240+ processMedia(att)?.realm >>> {reply.attachments.append($0)}
241+ }
242+ }
243+ reply.inReplyToUser = json["reply_to_user"].int >>> {User.by(id: $0)}
244+ reply.inReplyToComment = json["reply_to_comment"].int >>> {WallPostReply.by(id: $0)}
245+ }
246+ return reply
247+ }
248+ return nil
249+ }
250+
251+ class func processPollAnswer(_ json: JSON) -> PollAnswer? {
252+ if let id = json["id"].int,
253+ let body = json["text"].string,
254+ let votes = json["votes"].int,
255+ let rate = json["rate"].int {
256+ return PollAnswer(id: id, body: body, votes: votes, rating: rate)
257+ }
258+ return nil
259+ }
260+
261+ class func processPoll(_ json: JSON) -> Poll? {
262+ if let id = json["id"].int {
263+ var poll: Poll!
264+ realmWrite { _ in
265+ poll = Poll.by(id: id)
266+ poll.owner = json["owner_id"].int >>> {User.by(id: $0)}
267+ poll.date = json["created"].int >>> {Date(timeIntervalSince1970: TimeInterval($0))}
268+ poll.question = json["question"].string
269+ poll.votes.value = json["votes"].int
270+ if let answersJSON = json["answers"].array {
271+ for ans in answersJSON {
272+ processPollAnswer(ans) >>> {poll.answers.append($0)}
273+ }
274+ }
275+ poll.answerID.value = json["answer_id"].int
276+ }
277+ return poll
278+ }
279+ return nil
280+ }
281+
282+ class func processPhoto(_ json: JSON) -> Photo? {
283+ if let id = json["id"].int {
284+ var photo: Photo!
285+ realmWrite { _ in
286+ photo = Photo.by(id: id)
287+ photo.owner = json["owner_id"].int >>> {User.by(id: $0)}
288+ photo.date = json["date"].int >>> {Date.init(timeIntervalSince1970: TimeInterval($0))}
289+ photo.width.value = json["width"].int
290+ photo.height.value = json["height"].int
291+ 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 {
299+ surl = imageSurl
300+ } else if let imageSurl = json["photo_130"].string {
301+ surl = imageSurl
302+ } else if let imageSurl = json["photo_75"].string {
303+ surl = imageSurl
304+ }
305+ photo.image = RealmPhoto.by(photo.imageID, surl: surl)
306+ photo.thumbnail = RealmPhoto.by(photo.thumbnailID, surl: json["photo_75"].string)
307+ photo.accessKey = json["access_key"].string
308+ }
309+ return photo
310+ } else {
311+ return nil
312+ }
313+ }
314+
315+ class func processVideo(_ json: JSON) -> Video? {
316+ if let id = json["id"].int {
317+ var video: Video!
318+ realmWrite { _ in
319+ video = Video.by(id: id)
320+ video.owner = json["owner_id"].int >>> {User.by(id: $0)}
321+ video.title = json["title"].string
322+ video.duration.value = json["duartion"].int >>> {TimeInterval.init($0)}
323+ video.videoDescription = json["description"].string
324+ if let _ = json["live"].int {
325+ video.isLive = true
326+ }
327+ if let _ = json["processing"].int {
328+ video.isProcessing = true
329+ }
330+ video.accessKey = json["access_key"].string
331+ video.thumbnail = RealmPhoto.by(video.thumbnailID, surl: json["photo_320"].string)
332+ video.playerSURL = json["player"].string
333+ }
334+ return video
335+ } else {
336+ return nil
337+ }
338+ }
339+
340+ class func processAudio(_ json: JSON) -> Audio? {
341+ if let id = json["id"].int {
342+ var audio: Audio!
343+ realmWrite { _ in
344+ audio = Audio.by(id: id)
345+ audio.owner = json["owner_id"].int >>> {User.by(id: $0)}
346+ audio.artist = json["artist"].string
347+ audio.title = json["title"].string
348+ audio.duration.value = json["duration"].int >>> {TimeInterval($0)}
349+ audio.surl = json["url"].string
350+ }
351+ return audio
352+ } else {
353+ return nil
354+ }
355+ }
356+
357+ class func processDocument(_ json: JSON) -> Document? {
358+ if let id = json["id"].int {
359+ var doc: Document!
360+ realmWrite { _ in
361+ doc = Document.by(id: id)
362+ doc.owner = json["owner_id"].int >>> {User.by(id: $0)}
363+ doc.title = json["title"].string
364+ doc.size.value = json["size"].int
365+ doc.fileExtension = json["ext"].string
366+ doc.surl = json["url"].string
367+ doc.type = json["type"].int =>> {DocumentType(rawValue: $0)}
368+ }
369+ return doc
370+ }
371+ return nil
372+ }
373+
374+ class func processSticker(_ json: JSON) -> Sticker? {
375+ if let id = json["id"].int,
376+ let pack = json["product_id"].int {
377+ var sticker: Sticker!
378+ realmWrite { _ in
379+ sticker = Sticker.by(id: id)
380+ sticker.packID = pack
381+ sticker.image = RealmPhoto.by(sticker.imageID, surl: json["photo_352"].string)
382+ sticker.width.value = json["width"].int
383+ sticker.height.value = json["height"].int
384+ }
385+ return sticker
386+ }
387+ return nil
388+ }
389+
390+ class func processRating(_ json: JSON) -> Rating? {
391+ if let stars = json["stars"].int,
392+ let reviewCount = json["reviews_count"].int {
393+ return Rating(stars: stars, reviewCount: reviewCount)
394+ }
395+ return nil
396+ }
397+
398+ class func processButton(_ json: JSON) -> LinkButton? {
399+ if let title = json["title"].string,
400+ let surl = json["url"].string,
401+ let url = URL(string: surl) {
402+ return LinkButton(title: title, url: url)
403+ }
404+ return nil
405+ }
406+
407+ class func processLink(_ json: JSON) -> Link? {
408+ if let surl = json["url"].string {
409+ var link: Link!
410+ realmWrite({ (_) in
411+ link = Link.by(surl: surl)
412+ link.title = json["title"].string
413+ link.linkDescription = json["description"].string
414+ link.caption = json["caption"].string
415+ link.photo = processPhoto(json["photo"])
416+ link.isExternal.value = json["is_external"].int?.bool
417+ link.rating = processRating(json["rating"])
418+ link.button = processButton(json["button"])
419+ })
420+ return link
421+ }
422+ return nil
423+ }
424+
425+ class func processWallPost(_ json: JSON) -> WallPost? {
426+ let comments = json["comments"]
427+ let likes = json["likes"]
428+ let reposts = json["reposts"]
429+ if let id = json["id"].int {
430+ var post: WallPost!
431+ realmWrite { _ in
432+ post = WallPost.by(id: id)
433+ post.owner = json["owner_id"].int >>> {User.by(id: $0)}
434+ post.creator = json["from_id"].int >>> {User.by(id: $0)}
435+ post.date = json["date"].int >>> {Date.init(timeIntervalSince1970: TimeInterval($0))}
436+ post.body = json["text"].string
437+ post.commentCount.value = comments["count"].int
438+ post.canPostComment.value = comments["can_post"].int?.bool
439+ post.likeCount.value = likes["count"].int
440+ if let sLiked = likes["user_likes"].int?.bool {
441+ post.liked = sLiked
442+ }
443+ post.canLike.value = likes["can_like"].int?.bool
444+ post.repostCount.value = reposts["count"].int
445+ post.didRepost.value = reposts["user_resposted"].int?.bool
446+ post.canDelete.value = json["can_delete"].int?.bool
447+ if let attachmentsJSON = json["attachments"].array {
448+ for att in attachmentsJSON {
449+ processMedia(att) >>> {post.attachments.append(RealmMedia(value: $0))}
450+ }
451+ }
452+ post.geo = processGeolocation(json["geo"])
453+ post.signed = json["signer_id"].int >>> {User.by(id: $0)}
454+ post.original = json["reply_post_id"].int >>> {WallPost.by(id: $0)}
455+ post.ownerOfCopy = json["reply_owner_id"].int >>> {User.by(id: $0)}
456+ if let sCopyHistory = json["copy_history"].array {
457+ for pos in sCopyHistory {
458+ if let po = processWallPost(pos) {
459+ post.historyOfCopy.append(po)
460+ }
461+ }
462+ }
463+ post.canPin.value = json["can_pin"].int?.bool
464+ post.canEdit.value = json["can_edit"].int?.bool
465+ post.isPinned.value = json["is_pinned"].int?.bool
466+ }
467+ return post
468+ }
469+ return nil
470+ }
471+
472+ class func prpocessServiceMessageAction(_ json: JSON) -> ServiceMessageAction? {
473+ if let sAction = json["action"].string {
474+ switch sAction {
475+ case "chat_photo_update":
476+ return ServiceMessageAction.photoUpdate
477+ case "chat_photo_remove":
478+ return ServiceMessageAction.photoRemove
479+ case "chat_create":
480+ if let title = json["action_text"].string {
481+ return ServiceMessageAction.create(title)
482+ }
483+ case "chat_title_update":
484+ if let title = json["action_text"].string {
485+ return ServiceMessageAction.titleUpdate(title)
486+ }
487+ case "chat_invite_user":
488+ if let id = json["action_mid"].int {
489+ if id < 0 {
490+ if let email = json["action_email"].string {
491+ return ServiceMessageAction.inviteUser(.email(email))
492+ }
493+ } else {
494+ var act: ServiceMessageAction!
495+ realmWrite { _ in
496+ act = ServiceMessageAction.inviteUser(.user(User.by(id: id)))
497+ }
498+ return act
499+ }
500+ }
501+ case "chat_kick_user":
502+ if let id = json["action_mid"].int {
503+ if id < 0 {
504+ if let email = json["action_email"].string {
505+ return ServiceMessageAction.kickUser(.email(email))
506+ }
507+ } else {
508+ var act: ServiceMessageAction!
509+ realmWrite { _ in
510+ act = ServiceMessageAction.kickUser(.user(User.by(id: id)))
511+ }
512+ return act
513+ }
514+ }
515+ default:
516+ break
517+ }
518+ }
519+ return nil
520+ }
521+
522+ class func processMessage(_ json: JSON) -> Message? {
523+ if let user = json["user_id"].int >>> {User.by(id: $0)},
524+ let date = json["date"].int >>> {Date(timeIntervalSince1970: TimeInterval($0))},
525+ let id = json["id"].int {
526+ let message = Message.by(id: id)
527+ realmWrite { _ in
528+ message.user = user
529+ message.body = json["body"].string?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
530+ message.date = date
531+ message.randomID.value = json["random_id"].int
532+ message.read = json["read_state"].int?.bool ?? message.read
533+ message.geolocation = json["geo"] =>> processGeolocation
534+ message.attachments.removeAll()
535+ if let atts = json["attachments"].array {
536+ for att in atts {
537+ processMedia(att) >>> {message.attachments.append(RealmMedia.init(value: $0))}
538+ }
539+ }
540+ message.forwardedMessages.removeAll()
541+ if let messes = json["fwd_messages"].array {
542+ for mess in messes {
543+ processGenericMessage(mess) >>> { message.forwardedMessages.append(RealmForwardedMessage(value: .genericMessage($0))) }
544+ }
545+ }
546+ message.chatID.value = json["chat_id"].int
547+ message.action = prpocessServiceMessageAction(json)
548+ message.photoSURL = json["photo_200"].string
549+ message.out = json["out"].int?.bool ?? message.read
550+
551+ }
552+ print(message.body)
553+ return message
554+ } else {
555+ return nil
556+ }
557+ }
558+
559+ class func processGenericMessage(_ json: JSON) -> GenericMessage? {
560+ if let user = json["user_id"].int >>> {User.by(id: $0)},
561+ let date = json["date"].int >>> {Date(timeIntervalSince1970: TimeInterval($0))} {
562+ let message = GenericMessage()
563+ message.user = user
564+ message.body = json["body"].string?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
565+ message.date = date
566+ message.geolocation = json["geo"] =>> processGeolocation
567+ if let atts = json["attachments"].array {
568+ for att in atts {
569+ processMedia(att) >>> {message.attachments.append(RealmMedia.init(value: $0))}
570+ }
571+ }
572+ if let messes = json["fwd_messages"].array {
573+ for mess in messes {
574+ processGenericMessage(mess) >>> {message.forwardedMessages
575+ .append(RealmForwardedMessage(value: .genericMessage($0))) }
576+ }
577+ }
578+
579+ realmWrite { realm in
580+ realm.add(message)
581+ }
582+ print(message.body)
583+ return message
584+ }
585+ return nil
586+ }
587+
588+
589+
590+ class func processNotificationSettings(_ json: JSON) -> ChatNotificationSettings? {
591+ if let sound = json["sound"].int,
592+ let date = json["disabled_until"].int {
593+ if date == -1 {
594+ return ChatNotificationSettings(sound: Bool.from(num: sound), disabledUntil: .never)
595+ } else {
596+ return ChatNotificationSettings(sound: Bool.from(num: sound), disabledUntil: .date(Date(timeIntervalSince1970: TimeInterval(date))))
597+ }
598+ } else {
599+ return nil
600+ }
601+ }
602+
603+ class func processMultichat(_ json: JSON) -> Multichat? { // from .getChat
604+ if let id = json["id"].int {
605+ var chat: Multichat!
606+ realmWrite { _ in
607+ chat = Multichat.by(id: id)
608+ chat.admin = json["admin_id"].int >>> {User.by(id: $0)}
609+ chat.title = json["title"].string
610+ // TODO: let type = json["type"].string
611+ // TODO: type == "chat"
612+ if let uss = json["users"].array {
613+ for user in uss {
614+ if let invitedBy = user["invited_by"].int,
615+ let us = processUser(json: user) {
616+ add(item: MultiChatUser(us, invitedBy: User.by(id: invitedBy)), toList: chat.users)
617+ }
618+ }
619+ }
620+ chat.notificationSettings = RealmNotificatonSettings
621+ .init(value: processNotificationSettings(json["push_settings"]))
622+ chat.image = RealmPhoto.by(chat.imageID, surl: json["photo_200"].string)
623+
624+ var status: MultichatStatus = .active
625+ if let _ = json["kicked"].int {
626+ status = .kicked
627+ } else if let _ = json["left"].int {
628+ status = .left
629+ }
630+ chat.participationStatus = status
631+ }
632+ return chat
633+ }
634+ return nil
635+ }
636+
637+ class func processDialog(_ json: JSON) -> Dialog? {
638+ let message = json["message"]
639+ if let id = message["chat_id"].int { // Multichat
640+ if let sMessage = processMessage(message) {
641+ var mChat: Multichat!
642+ var dialog: Dialog!
643+ realmWrite { _ in
644+ mChat = Multichat.by(id: id)
645+ mChat.admin = message["admin_id"].int >>> {User.by(id: $0)}
646+ mChat.title = message["title"].string
647+ mChat.notificationSettings = RealmNotificatonSettings
648+ .init(value: processNotificationSettings(message["push_settings"]))
649+ dialog = mChat
650+// dialog.date = sMessage.date
651+ RealmDialog.by(dialog: dialog) <~ sMessage
652+ }
653+ // print(dialog)
654+ return dialog
655+ }
656+ } else if let id = message["user_id"].int { // Chat
657+ if let sMessage = processMessage(json["message"]) {
658+ var chat: Chat!
659+ var dialog: Dialog!
660+ realmWrite { _ in
661+ chat = Chat.by(user: User.by(id: id))
662+ dialog = chat
663+// dialog.date = sMessage.date
664+ RealmDialog.by(dialog: dialog) <~ sMessage
665+ }
666+ // print(dialog)
667+ return dialog
668+ }
669+ }
670+ return nil
671+ }
672+}
Melk/VKPublicCrap.swiftnew file+43
@@ -0,0 +1,43 @@
1+//
2+// File.swift
3+// golos
4+//
5+// Created by Ilya Kos on 12/10/16.
6+// Copyright © 2016 Ilya Kos. All rights reserved.
7+//
8+
9+//import SwiftyVK
10+/*
11+let permissions: Set<VK.Scope> = [
12+// .notify,
13+// .friends,
14+// .photos,
15+// .audio,
16+// .video,
17+// .docs,
18+// .notes,
19+ .pages,
20+// .status,
21+// .offers,
22+// .questions,
23+ .wall,
24+ .groups,
25+// .messages,
26+// .email,
27+// .notifications,
28+// .stats,
29+// .ads,
30+// .offline,
31+// .market,
32+]
33+ */
34+let vkAPIVersion = "5.67"
35+let appID = "6097091"
36+
37+let multichatchatId = 2000000000
38+
39+enum Methods {
40+ static let getDialogs = "getDialogs"
41+}
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/ViewController.swiftdeleted−25
@@ -1,25 +0,0 @@
1−//
2−// ViewController.swift
3−// Melk
4−//
5−// Created by Ilya Kos on 7/1/17.
6−// Copyright © 2017 Ilya Kos. All rights reserved.
7−//
8−
9−import UIKit
10−
11−class ViewController: UIViewController {
12−
13− override func viewDidLoad() {
14− super.viewDidLoad()
15− // Do any additional setup after loading the view, typically from a nib.
16− }
17−
18− override func didReceiveMemoryWarning() {
19− super.didReceiveMemoryWarning()
20− // Dispose of any resources that can be recreated.
21− }
22−
23−
24−}
25−
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”, записи с основным хештегом будут выводиться на любые подключенные к устройству экраны.