on master help sign in
Copied files and started UI
commitda6d4a515414154b9868e136537e0ad93b09d801 authorilyakooo0 <ilyakooo0@gmail.com> dateSat 8 Jul 2017, 22:07 (9 years ago) parent3f03b5e
23 files changed+4334−70
.gitignore+1 Cartfile+1 Cartfile.resolved+5 Melk.xcodeproj/project.pbxproj+139−16 Melk/AppDelegate.swift+21−1 Melk/Assets.xcassets/AppIcon.appiconset/Contents.json+10 Melk/Base.lproj/Main.storyboard−26 Melk/CardView.swift+80 Melk/CollectionView.swift+110 Melk/Info.plist−2 Melk/LocalVKDataTypes.swift+2224 Melk/MainCoordinator.swift+26 Melk/MainViewController.swift+85 Melk/MyWeirdness.swift+104 Melk/PublicCrap.swift+249 Melk/SecondaryCoordinator.swift+30 Melk/SecondaryViewController.swift+40 Melk/StackView.swift+154 Melk/TableViewCells.swift+136 Melk/VKData.swift+672 Melk/VKPublicCrap.swift+40 Melk/ViewController.swift−25 Melk/VkServices.swift+207
.gitignorenew file+1
@@ -0,0 +1 @@
1+Carthage/
Cartfile+1
@@ -2,3 +2,4 @@ github "Alamofire/Alamofire"
22github "WE-St0r/SwiftyVK"
33github "kean/Nuke" ~> 4.0
44github "realm/realm-cocoa.git"
5+github "tidwall/SwiftWebSocket"
Cartfile.resolvednew file+5
@@ -0,0 +1,5 @@
1+github "Alamofire/Alamofire" "4.5.0"
2+github "WE-St0r/SwiftyVK" "2.0.8"
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+139−16
@@ -7,23 +7,87 @@
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+ 09939A561F097DA90014719D /* TableViewCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09939A551F097DA90014719D /* TableViewCells.swift */; };
14+ 09A456361F0ED9A800BAC32D /* CollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09A456351F0ED9A800BAC32D /* CollectionView.swift */; };
15+ 09B781591F11630D0007D2BF /* .gitignore in Resources */ = {isa = PBXBuildFile; fileRef = 09B781581F11630D0007D2BF /* .gitignore */; };
1016 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 */; };
1317 09BF41F61F07F71F00AFAD57 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 09BF41F51F07F71F00AFAD57 /* Assets.xcassets */; };
1418 09BF41F91F07F71F00AFAD57 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 09BF41F71F07F71F00AFAD57 /* LaunchScreen.storyboard */; };
1519 09BF42011F07F75800AFAD57 /* Cartfile in Resources */ = {isa = PBXBuildFile; fileRef = 09BF42001F07F75800AFAD57 /* Cartfile */; };
20+ 09BF420E1F0833A700AFAD57 /* LocalVKDataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF420D1F0833A700AFAD57 /* LocalVKDataTypes.swift */; };
21+ 09BF42101F0833C100AFAD57 /* MainCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF420F1F0833C100AFAD57 /* MainCoordinator.swift */; };
22+ 09BF42131F08341B00AFAD57 /* MyWeirdness.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF42111F08341B00AFAD57 /* MyWeirdness.swift */; };
23+ 09BF42141F08341B00AFAD57 /* PublicCrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF42121F08341B00AFAD57 /* PublicCrap.swift */; };
24+ 09BF42161F0834D400AFAD57 /* VKPublicCrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF42151F0834D400AFAD57 /* VKPublicCrap.swift */; };
25+ 09BF42181F08352E00AFAD57 /* VKData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF42171F08352E00AFAD57 /* VKData.swift */; };
26+ 09BF421A1F08357E00AFAD57 /* VkServices.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF42191F08357E00AFAD57 /* VkServices.swift */; };
27+ 09BF42261F0835B200AFAD57 /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42021F080A5700AFAD57 /* Alamofire.framework */; };
28+ 09BF42271F0835B200AFAD57 /* Alamofire.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42021F080A5700AFAD57 /* Alamofire.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
29+ 09BF42281F0835B200AFAD57 /* Nuke.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42031F080A5700AFAD57 /* Nuke.framework */; };
30+ 09BF42291F0835B200AFAD57 /* Nuke.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42031F080A5700AFAD57 /* Nuke.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
31+ 09BF422A1F0835B200AFAD57 /* Realm.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42041F080A5700AFAD57 /* Realm.framework */; };
32+ 09BF422B1F0835B200AFAD57 /* Realm.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42041F080A5700AFAD57 /* Realm.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
33+ 09BF422C1F0835B200AFAD57 /* RealmSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42051F080A5700AFAD57 /* RealmSwift.framework */; };
34+ 09BF422D1F0835B200AFAD57 /* RealmSwift.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42051F080A5700AFAD57 /* RealmSwift.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
35+ 09BF422E1F0835B200AFAD57 /* SwiftyVK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42061F080A5700AFAD57 /* SwiftyVK.framework */; };
36+ 09BF422F1F0835B200AFAD57 /* SwiftyVK.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42061F080A5700AFAD57 /* SwiftyVK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
37+ 09BF42321F08388100AFAD57 /* SecondaryCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF42311F08388100AFAD57 /* SecondaryCoordinator.swift */; };
38+ 09BF42341F083B5800AFAD57 /* SwiftWebSocket.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42331F083B5800AFAD57 /* SwiftWebSocket.framework */; };
39+ 09BF42351F083B6000AFAD57 /* SwiftWebSocket.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42331F083B5800AFAD57 /* SwiftWebSocket.framework */; };
40+ 09BF42361F083B6000AFAD57 /* SwiftWebSocket.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 09BF42331F083B5800AFAD57 /* SwiftWebSocket.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
41+ 09BF42561F08413000AFAD57 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09BF42551F08413000AFAD57 /* MainViewController.swift */; };
1642/* End PBXBuildFile section */
1743
44+/* Begin PBXCopyFilesBuildPhase section */
45+ 09BF42301F0835B200AFAD57 /* Embed Frameworks */ = {
46+ isa = PBXCopyFilesBuildPhase;
47+ buildActionMask = 2147483647;
48+ dstPath = "";
49+ dstSubfolderSpec = 10;
50+ files = (
51+ 09BF422B1F0835B200AFAD57 /* Realm.framework in Embed Frameworks */,
52+ 09BF42271F0835B200AFAD57 /* Alamofire.framework in Embed Frameworks */,
53+ 09BF42361F083B6000AFAD57 /* SwiftWebSocket.framework in Embed Frameworks */,
54+ 09BF422F1F0835B200AFAD57 /* SwiftyVK.framework in Embed Frameworks */,
55+ 09BF422D1F0835B200AFAD57 /* RealmSwift.framework in Embed Frameworks */,
56+ 09BF42291F0835B200AFAD57 /* Nuke.framework in Embed Frameworks */,
57+ );
58+ name = "Embed Frameworks";
59+ runOnlyForDeploymentPostprocessing = 0;
60+ };
61+/* End PBXCopyFilesBuildPhase section */
62+
1863/* Begin PBXFileReference section */
64+ 0919AEA61F0D3D9D00894FFA /* SecondaryViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondaryViewController.swift; sourceTree = "<group>"; };
65+ 0919AEA81F0D459600894FFA /* CardView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CardView.swift; sourceTree = "<group>"; };
66+ 0919AEAC1F0D706700894FFA /* StackView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackView.swift; sourceTree = "<group>"; };
67+ 09939A551F097DA90014719D /* TableViewCells.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableViewCells.swift; sourceTree = "<group>"; };
68+ 09A456351F0ED9A800BAC32D /* CollectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionView.swift; sourceTree = "<group>"; };
69+ 09B781581F11630D0007D2BF /* .gitignore */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = .gitignore; path = ../.gitignore; sourceTree = "<group>"; };
1970 09BF41EB1F07F71F00AFAD57 /* Melk.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Melk.app; sourceTree = BUILT_PRODUCTS_DIR; };
2071 09BF41EE1F07F71F00AFAD57 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
21− 09BF41F01F07F71F00AFAD57 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
22− 09BF41F31F07F71F00AFAD57 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
2372 09BF41F51F07F71F00AFAD57 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
2473 09BF41F81F07F71F00AFAD57 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
2574 09BF41FA1F07F71F00AFAD57 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
2675 09BF42001F07F75800AFAD57 /* Cartfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Cartfile; sourceTree = SOURCE_ROOT; };
76+ 09BF42021F080A5700AFAD57 /* Alamofire.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Alamofire.framework; path = Carthage/Build/iOS/Alamofire.framework; sourceTree = SOURCE_ROOT; };
77+ 09BF42031F080A5700AFAD57 /* Nuke.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nuke.framework; path = Carthage/Build/iOS/Nuke.framework; sourceTree = SOURCE_ROOT; };
78+ 09BF42041F080A5700AFAD57 /* Realm.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Realm.framework; path = Carthage/Build/iOS/Realm.framework; sourceTree = SOURCE_ROOT; };
79+ 09BF42051F080A5700AFAD57 /* RealmSwift.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RealmSwift.framework; path = Carthage/Build/iOS/RealmSwift.framework; sourceTree = SOURCE_ROOT; };
80+ 09BF42061F080A5700AFAD57 /* SwiftyVK.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftyVK.framework; path = Carthage/Build/iOS/SwiftyVK.framework; sourceTree = SOURCE_ROOT; };
81+ 09BF420D1F0833A700AFAD57 /* LocalVKDataTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalVKDataTypes.swift; sourceTree = "<group>"; };
82+ 09BF420F1F0833C100AFAD57 /* MainCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainCoordinator.swift; sourceTree = "<group>"; };
83+ 09BF42111F08341B00AFAD57 /* MyWeirdness.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyWeirdness.swift; sourceTree = "<group>"; };
84+ 09BF42121F08341B00AFAD57 /* PublicCrap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PublicCrap.swift; sourceTree = "<group>"; };
85+ 09BF42151F0834D400AFAD57 /* VKPublicCrap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VKPublicCrap.swift; sourceTree = "<group>"; };
86+ 09BF42171F08352E00AFAD57 /* VKData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VKData.swift; sourceTree = "<group>"; };
87+ 09BF42191F08357E00AFAD57 /* VkServices.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VkServices.swift; sourceTree = "<group>"; };
88+ 09BF42311F08388100AFAD57 /* SecondaryCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondaryCoordinator.swift; sourceTree = "<group>"; };
89+ 09BF42331F083B5800AFAD57 /* SwiftWebSocket.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftWebSocket.framework; path = Carthage/Build/iOS/SwiftWebSocket.framework; sourceTree = SOURCE_ROOT; };
90+ 09BF42551F08413000AFAD57 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
2791/* End PBXFileReference section */
2892
2993/* Begin PBXFrameworksBuildPhase section */
@@ -31,12 +95,28 @@
3195 isa = PBXFrameworksBuildPhase;
3296 buildActionMask = 2147483647;
3397 files = (
98+ 09BF422A1F0835B200AFAD57 /* Realm.framework in Frameworks */,
99+ 09BF42261F0835B200AFAD57 /* Alamofire.framework in Frameworks */,
100+ 09BF42341F083B5800AFAD57 /* SwiftWebSocket.framework in Frameworks */,
101+ 09BF422E1F0835B200AFAD57 /* SwiftyVK.framework in Frameworks */,
102+ 09BF422C1F0835B200AFAD57 /* RealmSwift.framework in Frameworks */,
103+ 09BF42281F0835B200AFAD57 /* Nuke.framework in Frameworks */,
104+ 09BF42351F083B6000AFAD57 /* SwiftWebSocket.framework in Frameworks */,
34105 );
35106 runOnlyForDeploymentPostprocessing = 0;
36107 };
37108/* End PBXFrameworksBuildPhase section */
38109
39110/* Begin PBXGroup section */
111+ 09A456341F0ED98800BAC32D /* Public */ = {
112+ isa = PBXGroup;
113+ children = (
114+ 0919AEAC1F0D706700894FFA /* StackView.swift */,
115+ 09A456351F0ED9A800BAC32D /* CollectionView.swift */,
116+ );
117+ name = Public;
118+ sourceTree = "<group>";
119+ };
40120 09BF41E21F07F71F00AFAD57 = {
41121 isa = PBXGroup;
42122 children = (
@@ -57,16 +137,42 @@
57137 isa = PBXGroup;
58138 children = (
59139 09BF41EE1F07F71F00AFAD57 /* AppDelegate.swift */,
60− 09BF41F01F07F71F00AFAD57 /* ViewController.swift */,
61− 09BF41F21F07F71F00AFAD57 /* Main.storyboard */,
140+ 09BF42111F08341B00AFAD57 /* MyWeirdness.swift */,
141+ 09BF42121F08341B00AFAD57 /* PublicCrap.swift */,
142+ 09BF420F1F0833C100AFAD57 /* MainCoordinator.swift */,
143+ 09BF42311F08388100AFAD57 /* SecondaryCoordinator.swift */,
144+ 0919AEA81F0D459600894FFA /* CardView.swift */,
145+ 0919AEA61F0D3D9D00894FFA /* SecondaryViewController.swift */,
146+ 09BF42551F08413000AFAD57 /* MainViewController.swift */,
147+ 09939A551F097DA90014719D /* TableViewCells.swift */,
148+ 09BF420D1F0833A700AFAD57 /* LocalVKDataTypes.swift */,
149+ 09BF42151F0834D400AFAD57 /* VKPublicCrap.swift */,
150+ 09BF42191F08357E00AFAD57 /* VkServices.swift */,
151+ 09BF42171F08352E00AFAD57 /* VKData.swift */,
62152 09BF41F51F07F71F00AFAD57 /* Assets.xcassets */,
63153 09BF41F71F07F71F00AFAD57 /* LaunchScreen.storyboard */,
64154 09BF41FA1F07F71F00AFAD57 /* Info.plist */,
65155 09BF42001F07F75800AFAD57 /* Cartfile */,
156+ 09A456341F0ED98800BAC32D /* Public */,
157+ 09B781581F11630D0007D2BF /* .gitignore */,
158+ 09BF420C1F080A5F00AFAD57 /* Dependencies */,
66159 );
67160 path = Melk;
68161 sourceTree = "<group>";
69162 };
163+ 09BF420C1F080A5F00AFAD57 /* Dependencies */ = {
164+ isa = PBXGroup;
165+ children = (
166+ 09BF42331F083B5800AFAD57 /* SwiftWebSocket.framework */,
167+ 09BF42021F080A5700AFAD57 /* Alamofire.framework */,
168+ 09BF42031F080A5700AFAD57 /* Nuke.framework */,
169+ 09BF42041F080A5700AFAD57 /* Realm.framework */,
170+ 09BF42051F080A5700AFAD57 /* RealmSwift.framework */,
171+ 09BF42061F080A5700AFAD57 /* SwiftyVK.framework */,
172+ );
173+ name = Dependencies;
174+ sourceTree = "<group>";
175+ };
70176/* End PBXGroup section */
71177
72178/* Begin PBXNativeTarget section */
@@ -77,6 +183,7 @@
77183 09BF41E71F07F71F00AFAD57 /* Sources */,
78184 09BF41E81F07F71F00AFAD57 /* Frameworks */,
79185 09BF41E91F07F71F00AFAD57 /* Resources */,
186+ 09BF42301F0835B200AFAD57 /* Embed Frameworks */,
80187 );
81188 buildRules = (
82189 );
@@ -127,10 +234,10 @@
127234 isa = PBXResourcesBuildPhase;
128235 buildActionMask = 2147483647;
129236 files = (
237+ 09B781591F11630D0007D2BF /* .gitignore in Resources */,
130238 09BF41F91F07F71F00AFAD57 /* LaunchScreen.storyboard in Resources */,
131239 09BF41F61F07F71F00AFAD57 /* Assets.xcassets in Resources */,
132240 09BF42011F07F75800AFAD57 /* Cartfile in Resources */,
133− 09BF41F41F07F71F00AFAD57 /* Main.storyboard in Resources */,
134241 );
135242 runOnlyForDeploymentPostprocessing = 0;
136243 };
@@ -141,22 +248,27 @@
141248 isa = PBXSourcesBuildPhase;
142249 buildActionMask = 2147483647;
143250 files = (
144− 09BF41F11F07F71F00AFAD57 /* ViewController.swift in Sources */,
251+ 09939A561F097DA90014719D /* TableViewCells.swift in Sources */,
252+ 0919AEAD1F0D706700894FFA /* StackView.swift in Sources */,
253+ 09BF42141F08341B00AFAD57 /* PublicCrap.swift in Sources */,
254+ 09BF42161F0834D400AFAD57 /* VKPublicCrap.swift in Sources */,
255+ 09BF42181F08352E00AFAD57 /* VKData.swift in Sources */,
256+ 09BF42101F0833C100AFAD57 /* MainCoordinator.swift in Sources */,
257+ 09A456361F0ED9A800BAC32D /* CollectionView.swift in Sources */,
258+ 09BF421A1F08357E00AFAD57 /* VkServices.swift in Sources */,
259+ 09BF42131F08341B00AFAD57 /* MyWeirdness.swift in Sources */,
260+ 0919AEA91F0D459600894FFA /* CardView.swift in Sources */,
261+ 09BF42321F08388100AFAD57 /* SecondaryCoordinator.swift in Sources */,
145262 09BF41EF1F07F71F00AFAD57 /* AppDelegate.swift in Sources */,
263+ 09BF420E1F0833A700AFAD57 /* LocalVKDataTypes.swift in Sources */,
264+ 09BF42561F08413000AFAD57 /* MainViewController.swift in Sources */,
265+ 0919AEA71F0D3D9D00894FFA /* SecondaryViewController.swift in Sources */,
146266 );
147267 runOnlyForDeploymentPostprocessing = 0;
148268 };
149269/* End PBXSourcesBuildPhase section */
150270
151271/* Begin PBXVariantGroup section */
152− 09BF41F21F07F71F00AFAD57 /* Main.storyboard */ = {
153− isa = PBXVariantGroup;
154− children = (
155− 09BF41F31F07F71F00AFAD57 /* Base */,
156− );
157− name = Main.storyboard;
158− sourceTree = "<group>";
159− };
160272 09BF41F71F07F71F00AFAD57 /* LaunchScreen.storyboard */ = {
161273 isa = PBXVariantGroup;
162274 children = (
@@ -266,7 +378,12 @@
266378 buildSettings = {
267379 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
268380 DEVELOPMENT_TEAM = 8J63JZYYHZ;
381+ FRAMEWORK_SEARCH_PATHS = (
382+ "$(inherited)",
383+ "$(PROJECT_DIR)/Carthage/Build/iOS",
384+ );
269385 INFOPLIST_FILE = Melk/Info.plist;
386+ IPHONEOS_DEPLOYMENT_TARGET = 10.0;
270387 LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
271388 PRODUCT_BUNDLE_IDENTIFIER = com.ilyakooo0.Melk;
272389 PRODUCT_NAME = "$(TARGET_NAME)";
@@ -279,7 +396,12 @@
279396 buildSettings = {
280397 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
281398 DEVELOPMENT_TEAM = 8J63JZYYHZ;
399+ FRAMEWORK_SEARCH_PATHS = (
400+ "$(inherited)",
401+ "$(PROJECT_DIR)/Carthage/Build/iOS",
402+ );
282403 INFOPLIST_FILE = Melk/Info.plist;
404+ IPHONEOS_DEPLOYMENT_TARGET = 10.0;
283405 LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
284406 PRODUCT_BUNDLE_IDENTIFIER = com.ilyakooo0.Melk;
285407 PRODUCT_NAME = "$(TARGET_NAME)";
@@ -306,6 +428,7 @@
306428 09BF41FF1F07F71F00AFAD57 /* Release */,
307429 );
308430 defaultConfigurationIsVisible = 0;
431+ defaultConfigurationName = Release;
309432 };
310433/* End XCConfigurationList section */
311434 };
Melk/AppDelegate.swift+21−1
@@ -10,11 +10,27 @@ import UIKit
1010
1111@UIApplicationMain
1212class AppDelegate: UIResponder, UIApplicationDelegate {
13−
13+
1414 var window: UIWindow?
15+
16+ var coordinator: MainCoordinator!
1517
1618
1719 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
20+ let mainWindow = UIWindow(frame: UIScreen.main.bounds)
21+ mainWindow.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
22+ mainWindow.screen = UIScreen.main
23+ mainWindow.makeKeyAndVisible()
24+ coordinator = MainCoordinator(window: mainWindow)
25+
26+ NotificationCenter.default.addObserver(forName: NSNotification.Name.UIScreenDidConnect, object: nil, queue: OperationQueue.main) { (_) in
27+ self.updateScreens()
28+ }
29+
30+ NotificationCenter.default.addObserver(forName: NSNotification.Name.UIScreenDidDisconnect, object: nil, queue: OperationQueue.main) { (_) in
31+ self.updateScreens()
32+ }
33+
1834 // Override point for customization after application launch.
1935 return true
2036 }
@@ -40,6 +56,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
4056 func applicationWillTerminate(_ application: UIApplication) {
4157 // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
4258 }
59+
60+ private func updateScreens() {
61+ coordinator.updateScreens()
62+ }
4363
4464
4565}
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/CardView.swiftnew file+80
@@ -0,0 +1,80 @@
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+ update()
16+ }
17+ }
18+
19+ override init(frame: CGRect) {
20+ super.init(frame: frame)
21+ layer.masksToBounds = true
22+ layer.cornerRadius = 20
23+ backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)
24+ }
25+
26+ required init?(coder aDecoder: NSCoder) {
27+ fatalError("init(coder:) has not been implemented")
28+ }
29+
30+// private let avatar = UIImage()
31+// private let name = UIImage()
32+
33+ private func update() {
34+ subviews.map {$0.removeFromSuperview()}
35+
36+ let largeStack = StackView(axis: .vertical, alignment: .left)
37+ largeStack.padding = CGPoint(x: 32, y: 32)
38+ addSubview(largeStack)
39+
40+ let userView = StackView(axis: .horizontal, alignment: .center)
41+// userView.backgroundColor = #colorLiteral(red: 0.5568627715, green: 0.3529411852, blue: 0.9686274529, alpha: 1)
42+
43+
44+ let avatarDim: CGFloat = 50
45+ let avatar = UIImageView()
46+ post?.owner?.avatar?.load(into: avatar)
47+ avatar.layer.cornerRadius = avatarDim/2
48+ avatar.clipsToBounds = true
49+ avatar.contentMode = .scaleAspectFill
50+ avatar.layer.minificationFilter = kCAFilterTrilinear
51+ avatar.frame.size = CGSize(width: avatarDim, height: avatarDim)
52+ userView.addArrangedSubview(.view(avatar))
53+
54+ userView.addArrangedSubview(.spacer(8))
55+
56+ let userName = UILabel()
57+ let firstName = post?.owner?.firstName ?? ""
58+ let lastName = post?.owner?.lastName ?? ""
59+ userName.text = "\(firstName) \(lastName)"
60+ userName.font = UIFont.systemFont(ofSize: 28, weight: UIFontWeightHeavy)
61+ userName.sizeToFit()
62+ userView.addArrangedSubview(.view(userName))
63+ userView.frame.origin = CGPoint(x: 16, y: 16)
64+
65+ largeStack.addArrangedSubview(.view(userView))
66+
67+ largeStack.addArrangedSubview(.spacer(16))
68+
69+ let body = UILabel()
70+ body.numberOfLines = 0
71+ let bodyWidth: Double = 300
72+ body.font = UIFont.systemFont(ofSize: 22, weight: UIFontWeightSemibold)
73+ body.text = post?.body ?? "Spicy jalapeno flank landjaeger sausage tongue tail fatback frankfurter. Meatball tongue tail short ribs pastrami. Short ribs chicken pig tenderloin sausage pork. Corned beef t-bone pancetta jerky chuck picanha venison, turducken tenderloin shoulder boudin drumstick hamburger. Pork meatball meatloaf prosciutto pig andouille. Sirloin bacon hamburger short ribs pork cupim shank biltong turducken bresaola kevin prosciutto shoulder meatball. Strip steak frankfurter pork chop biltong jowl beef rump venison pancetta turkey tongue."
74+ let size = body.textRect(forBounds: CGRect(x: 0, y: 0, width: bodyWidth, height: Double.infinity), limitedToNumberOfLines: 0).size
75+ body.frame.size = size
76+
77+ largeStack.addArrangedSubview(.view(body))
78+
79+ }
80+}
Melk/CollectionView.swiftnew file+110
@@ -0,0 +1,110 @@
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+ largeStack.addArrangedSubview(.spacer(verticalPadding))
98+ largeStack.addArrangedSubview(.view(lastStack))
99+ largeStack.frame.origin = CGPoint.zero
100+ largeStack.layoutIfNeeded()
101+ addSubview(largeStack)
102+ frame.size = largeStack.frame.size
103+ }
104+}
105+
106+fileprivate extension UIView {
107+ var width: CGFloat {
108+ return self.frame.width
109+ }
110+}
Melk/Info.plist−2
@@ -22,8 +22,6 @@
2222 <true/>
2323 <key>UILaunchStoryboardName</key>
2424 <string>LaunchScreen</string>
25− <key>UIMainStoryboardFile</key>
26− <string>Main</string>
2725 <key>UIRequiredDeviceCapabilities</key>
2826 <array>
2927 <string>armv7</string>
Melk/LocalVKDataTypes.swiftnew file+2224
@@ -0,0 +1,2224 @@
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 SwiftyVK
15+
16+
17+typealias Token = String
18+typealias UserID = Int
19+typealias MessageID = Int
20+typealias MultichatID = Int
21+
22+class RealmPhoto: Object {
23+
24+ convenience init(surl: String, id: String) {
25+ self.init()
26+ self.imageSURL = surl
27+ self.id = id
28+ }
29+
30+ @objc dynamic var id: String = ""
31+ var imageURL: URL? {
32+ get {
33+ return imageSURL =>> {URL(string: $0)}
34+ }
35+ set {
36+ imageSURL = newValue?.absoluteString
37+ }
38+ }
39+ @objc dynamic var imageSURL: String? {
40+ didSet {
41+ if oldValue != imageSURL {
42+ localImageSURL = nil
43+ }
44+ }
45+ }
46+ @objc private dynamic var localImageSURL: String? {
47+ didSet {
48+ if oldValue != localImageSURL {
49+ imageCache = nil
50+ }
51+ }
52+ }
53+ private var imageCache: UIImage?
54+
55+ convenience init(id: String, surl: String?) {
56+ self.init()
57+ self.id = id
58+ self.imageSURL = surl
59+ }
60+
61+ class func by(_ id: String, surl: String?) -> RealmPhoto {
62+ do {
63+ let realm = try Realm()
64+ if let ob = realm.object(ofType: RealmPhoto.self, forPrimaryKey: id) {
65+ if realm.isInWriteTransaction {
66+ surl =>> {ob.imageSURL = $0}
67+ } else {
68+ try? realm.write {
69+ surl =>> {ob.imageSURL = $0}
70+ }
71+ }
72+ return ob
73+ } else {
74+ let ob = RealmPhoto(id: id, surl: surl)
75+ if realm.isInWriteTransaction {
76+ realm.add(ob)
77+ } else {
78+ try? realm.write {
79+ realm.add(ob)
80+ }
81+ }
82+ return ob
83+ }
84+ } catch {
85+ let ob = RealmPhoto(id: id, surl: surl)
86+ return ob
87+ }
88+ }
89+
90+ override static func primaryKey() -> String? {
91+ return "id"
92+ }
93+
94+ override static func ignoredProperties() -> [String] {
95+ return ["imageURL", "imageCache", "completions", "request"]
96+ }
97+
98+ var completions: [((() -> ()), RealmPhotoToken)] = []
99+ var views: [(UIImageView?, RealmPhotoToken)] = []
100+ var request: DataRequest?
101+
102+ @discardableResult func load(into view: UIImageView, completion: (() -> ())? = nil, error errorClosure: (() -> ())? = nil) -> RealmPhotoToken {
103+
104+ let token = RealmPhotoToken()
105+
106+ func getFromServer() {
107+ if request == nil {
108+ let surl = "\(id).png"
109+ if let url = imageURL {
110+ print("getting from network")
111+ request = Alamofire.request(url, method: .get).validate().responseData(queue: userQueue) { (responce) in
112+ self.request = nil
113+ switch responce.result {
114+ case .success(let data):
115+ UIImage(data: data) >>> {image in
116+ write(cache: UIImagePNGRepresentation(image), withName: surl) {
117+ realmWrite({ (realm) in
118+ realm.object(ofType: RealmPhoto.self, forPrimaryKey: self.id)?.localImageSURL = surl
119+ })
120+ }
121+ self.imageCache = image
122+ DispatchQueue.main.async {
123+ if token.isValid {
124+ self.views.map { if $0.1.isValid {$0.0?.image = image} }
125+ self.completions.map { if $0.1.isValid { $0.0() } }
126+ self.completions = []
127+ }
128+ }
129+ }
130+ case .failure(let error):
131+ errorClosure?()
132+ // TODO: Error handling
133+ break
134+ }
135+ }
136+ }
137+ }
138+ }
139+
140+ if let surl = localImageSURL, // TODO: Should probrably get thing from server ???
141+ let url = url(forName: surl) {
142+ Nuke.loadImage(with: url, into: view) { [weak view] (response, fromMemory) in
143+ switch response {
144+ case .fulfilled(let image):
145+ if token.isValid {
146+ view?.image = image
147+ completion?()
148+ }
149+ case .rejected(let error): // TODO: Handle error?
150+ completion >>> {self.completions.append(($0, token))}
151+ self.views.append((view, token))
152+ getFromServer()
153+ }
154+ }
155+ } else {
156+ completion >>> {self.completions.append(($0, token))}
157+ self.views.append((view, token))
158+ getFromServer()
159+ }
160+
161+ return token
162+ }
163+
164+ // func image(_ completion: @escaping ((UIImage?) -> ())) {
165+ // print(id)
166+ // let surl = "\(id).png"
167+ // if imageCache != nil {
168+ // print("we have caching!")
169+ // }
170+ // imageCache = imageCache ?? (localImageSURL =>> getLocalImage) // TODO: imageCache always seems to be nil... ie always loading from disk for some reason
171+ // if let sImage = imageCache {
172+ // DispatchQueue.main.async { completion(sImage) }
173+ // } else if let url = imageURL {
174+ // print("getting from network")
175+ // Alamofire.request(url, method: .get).validate().responseData(queue: userQueue) { (responce) in
176+ // switch responce.result {
177+ // case .success(let data):
178+ // UIImage(data: data) >>> {image in
179+ // write(cache: UIImagePNGRepresentation(image), withName: surl) {
180+ // DispatchQueue.main.async {
181+ // do {
182+ // let realm = try Realm()
183+ // if !realm.isInWriteTransaction {
184+ // try realm.write {
185+ // self.localImageSURL = surl
186+ // }
187+ // } else {
188+ // self.localImageSURL = surl
189+ // }
190+ // } catch { }
191+ // }
192+ // }
193+ // self.imageCache = image
194+ // DispatchQueue.main.async {completion(image)}
195+ // }
196+ // case .failure(let error):
197+ // // TODO: Error handling
198+ // break
199+ // }
200+ // }.resume()
201+ // }
202+ // }
203+}
204+
205+public class RealmPhotoToken {
206+ public func cancel() {
207+ isValid = false
208+ }
209+ fileprivate var isValid = true
210+}
211+
212+
213+class User: Object {
214+ @objc dynamic var id: UserID = 0
215+ @objc dynamic var firstName: String?
216+ @objc dynamic var lastName: String?
217+ var deactivated: Deactivated? {
218+ get {
219+ return deactivatedStr =>> {Deactivated.init(rawValue: $0)}
220+ }
221+ set {
222+ deactivatedStr = newValue >>> {$0.rawValue}
223+ }
224+ }
225+ @objc private dynamic var deactivatedStr: String?
226+
227+ @objc dynamic var about: String?
228+ @objc dynamic var birthday: Date?
229+ let blacklisted = RealmOptional<Bool>()
230+ let canSendFriendRequest = RealmOptional<Bool>()
231+ let canSendPrivateMessage = RealmOptional<Bool>()
232+ let mutualFriendCount = RealmOptional<Int>()
233+ // TODO: var connections:
234+ @objc dynamic var mobilePhone: String?
235+ @objc dynamic var homePhone: String?
236+ // TODO: var exports
237+ var friendStatus: FriendStatus? {
238+ get {
239+ return friendStatusInt.value =>> {FriendStatus(rawValue: $0)}
240+ }
241+ set {
242+ friendStatusInt.value = newValue >>> {$0.rawValue}
243+ }
244+ }
245+ private let friendStatusInt = RealmOptional<Int>()
246+ let online = RealmOptional<Bool>()
247+ @objc dynamic var avatar: RealmPhoto?
248+ var avatarID: String {
249+ return "user\(id)avatar"
250+ }
251+ @objc dynamic var photo: RealmPhoto?
252+ var photoID: String {
253+ return "user\(id)photo"
254+ }
255+ var site: URL? {
256+ get {
257+ return siteSURL =>> {URL.init(string: $0)}
258+ }
259+ set {
260+ siteSURL = newValue >>> {$0.absoluteString}
261+ }
262+ }
263+ @objc dynamic var siteSURL: String?
264+ @objc dynamic var status: String?
265+ @objc dynamic var lastSeen: Date?
266+ // dynamic var lastUpdated: Date = Date()
267+ let friendCount = RealmOptional<Int>()
268+
269+ convenience init(id: UserID, firstName: String, lastName: String) {
270+ self.init()
271+ self.id = id
272+ self.firstName = firstName
273+ self.lastName = lastName
274+ // self.lastUpdated = Date()
275+ }
276+ class func by(id: UserID) -> User {
277+ let realm = try! Realm()
278+ if let us = realm.object(ofType: User.self, forPrimaryKey: id) {
279+ return us
280+ } else {
281+ let us = User()
282+ us.id = id
283+ if realm.isInWriteTransaction {
284+ realm.add(us)
285+ } else {
286+ try? realm.write {
287+ realm.add(us)
288+
289+ }
290+ }
291+ return us
292+ }
293+ }
294+
295+ func get(completion: ((User?) -> ())? = nil) {
296+ VK.API.Users.get([VK.Arg.userIDs: "\(id)"]).send(onSuccess: { (response) in
297+ if let json = response.array?.first {
298+ let us = VKData.processUser(json: json)
299+ completion?(us)
300+ } else {
301+ completion?(nil)
302+ }
303+ }, onError: { (error) in
304+ completion?(nil)
305+ })
306+ }
307+
308+
309+ override static func primaryKey() -> String? {
310+ return "id"
311+ }
312+
313+ override static func ignoredProperties() -> [String] {
314+ return ["site", "photoID", "avatarID"]
315+ }
316+ // func update() { lastUpdated = Date() }
317+
318+ // MARK: Hashable override
319+
320+ override var hashValue: Int {
321+ return id
322+ }
323+}
324+
325+class Geolocation: Object {
326+ var geoCoordinates: CLLocation? {
327+ get {
328+ if let long = long.value, let latt = latt.value {
329+ return CLLocation(latitude: latt, longitude: long)
330+ }
331+ return nil
332+ }
333+ set {
334+ if let latt = newValue?.coordinate.latitude,
335+ let long = newValue?.coordinate.longitude {
336+ self.latt.value = latt
337+ self.long.value = long
338+ } else {
339+ self.long.value = nil
340+ self.latt.value = nil
341+ }
342+ }
343+ }
344+ private let latt = RealmOptional<Double>()
345+ private let long = RealmOptional<Double>()
346+ let id = RealmOptional<Int>()
347+ @objc dynamic var title: String?
348+ @objc dynamic var image: RealmPhoto?
349+ var imageID: String {
350+ return "geo_lat\(latt.value ?? 0)_long\(long.value ?? 0)image"
351+ }
352+ var city: String?
353+ var country: String?
354+
355+ override static func ignoredProperties() -> [String] {
356+ return ["geoCoordinates", "imageID"]
357+ }
358+ convenience init(long: Double, latt: Double) {
359+ self.init()
360+ self.long.value = long
361+ self.latt.value = latt
362+ }
363+
364+}
365+
366+class GenericMessage: Object {
367+ @objc dynamic var date = Date() //
368+ @objc dynamic var body = "" //
369+ @objc dynamic var user: User?
370+
371+ var outPhotos: [UIImage] = [] // TODO: ~
372+
373+ @objc dynamic var geolocation: Geolocation?
374+
375+ let attachments = List<RealmMedia>()
376+ let forwardedMessages = List<RealmForwardedMessage>()
377+ //let sentMessageId: MessageID? I DONT KNOW WHY I PUT THIS HERE
378+
379+ // convenience init(body: String) {
380+ // self.init()
381+ // self.body = body
382+ // self.out.value = true
383+ // }
384+ // convenience init(image: UIImage) {
385+ // self.init()
386+ // self.outPhotos.append(image)
387+ // }
388+ override static func ignoredProperties() -> [String] {
389+ return ["outPhotos"]
390+ }
391+}
392+
393+class Message: GenericMessage, Comparable, SortedArrayItem {
394+ @objc dynamic var id = 0
395+ let randomID = RealmOptional<MessageID>()
396+ @objc dynamic var read = false
397+ @objc dynamic var replied = false
398+ @objc dynamic var important = false
399+ @objc dynamic var deleted = false
400+
401+ func equal(to message: Message) -> Bool {
402+ // print("\(lhs.id.value) \(rhs.id.value) \(lhs.id.value == rhs.id.value)")
403+ if let a = self.randomID.value,
404+ let b = message.randomID.value {
405+ if a == b {
406+ return true
407+ }
408+ }
409+ return self.id == message.id
410+ }
411+ internal func greater(than message: Message) -> Bool {
412+ return self.id < message.id
413+ }
414+
415+ static func ==(lhs: Message, rhs: Message) -> Bool {
416+ print("\(lhs.id) \(rhs.id) \(lhs.id == rhs.id)")
417+ return lhs.id == rhs.id
418+ }
419+
420+ @objc dynamic var out = false
421+
422+ // Multichat stuff
423+
424+ @objc dynamic var photoSURL: String?
425+ let chatID = RealmOptional<MultichatID>()
426+ @objc private dynamic var serviceMessageString: String?
427+ private var serviceMessageUser: User?
428+ private let serviceMessage = RealmOptional<Int>()
429+
430+ var action: ServiceMessageAction? {
431+ get {
432+ func getUser() -> ServiceMessageUser? {
433+ if let user = self.serviceMessageUser {
434+ return .user(user)
435+ } else if let email = self.serviceMessageString {
436+ return .email(email)
437+ } else {
438+ return nil
439+ }
440+ }
441+
442+ return serviceMessage.value =>> { service in
443+ switch service {
444+ case 0:
445+ return .photoUpdate
446+ case 1:
447+ return .photoRemove
448+ case 2:
449+ return self.serviceMessageString >>> { .create($0) }
450+ case 3:
451+ return self.serviceMessageString >>> { .titleUpdate($0) }
452+ case 4:
453+ return getUser() >>> { .inviteUser($0) }
454+ case 5:
455+ return getUser() >>> { .kickUser($0) }
456+ default:
457+ return nil
458+ }
459+ }
460+ }
461+ set {
462+ func set(user: ServiceMessageUser) {
463+ switch user {
464+ case .user(let use):
465+ self.serviceMessageUser = use
466+ case .email(let str):
467+ self.serviceMessageString = str
468+ }
469+ }
470+
471+ serviceMessageString = nil
472+ serviceMessageUser = nil
473+ serviceMessage.value = newValue >>> { new in
474+ switch new {
475+ case .photoUpdate:
476+ return 0
477+ case .photoRemove:
478+ return 1
479+ case .create(let str):
480+ self.serviceMessageString = str
481+ return 2
482+ case .titleUpdate(let str):
483+ self.serviceMessageString = str
484+ return 3
485+ case .inviteUser(let user):
486+ set(user: user)
487+ return 4
488+ case .kickUser(let user):
489+ set(user: user)
490+ return 5
491+ }
492+ }
493+ }
494+ }
495+
496+ func get(completion: ((Message?) -> ())? = nil) {
497+ VK.API.Messages.getById([VK.Arg.messageIds: String(id)]).send(onSuccess: { (response) in
498+ if let json = response.array?.first {
499+ let mes = VKData.processMessage(response)
500+ completion?(mes)
501+ } else {
502+ completion?(nil)
503+ }
504+ }, onError: { (error) in
505+ completion?(nil)
506+ })
507+ // TODO: Implement propperly?
508+ }
509+
510+ class func by(id: MessageID) -> Message {
511+ let realm = try! Realm()
512+ if let us = realm.object(ofType: Message.self, forPrimaryKey: id) {
513+ return us
514+ } else {
515+ let us = Message()
516+ us.id = id
517+ realmWrite({ (_) in
518+ realm.add(us)
519+ })
520+ return us
521+ }
522+ }
523+
524+ override static func primaryKey() -> String? {
525+ return "id"
526+ }
527+}
528+//func ==(lhs: Message, rhs: Message) -> Bool {
529+// print("\(lhs.id.value) \(rhs.id.value) \(lhs.id.value == rhs.id.value)")
530+// return lhs.id.value == rhs.id.value
531+//}
532+func <(lhs: Message, rhs: Message) -> Bool {
533+ return lhs.id < rhs.id
534+}
535+
536+enum forwardedMessage {
537+ case genericMessage(GenericMessage)
538+ case message(Message)
539+}
540+
541+class RealmForwardedMessage: Object {
542+ var value: forwardedMessage? {
543+ get {
544+ if let gm = genericMessage {
545+ return .genericMessage(gm)
546+ } else if let m = message {
547+ return .message(m)
548+ } else {
549+ return nil
550+ }
551+ }
552+ set {
553+ realmWrite { _ in
554+ if let fm = newValue {
555+ switch fm {
556+ case .genericMessage(let gm):
557+ genericMessage = gm
558+ message = nil
559+ case .message(let m):
560+ genericMessage = nil
561+ message = m
562+ }
563+ } else {
564+ message = nil
565+ genericMessage = nil
566+ }
567+ }
568+ }
569+ }
570+ @objc private dynamic var genericMessage: GenericMessage?
571+ @objc private dynamic var message: Message?
572+
573+ var id: MessageID? {
574+ if let m = message {
575+ return m.id
576+ } else {
577+ return nil
578+ }
579+ }
580+
581+ convenience init(value: forwardedMessage) {
582+ self.init()
583+ self.value = value
584+ }
585+
586+ override static func ignoredProperties() -> [String] {
587+ return ["value"]
588+ }
589+}
590+
591+//struct Geolocation {
592+// var coordinates: CLLocation
593+// var title: String?
594+// var imageURL: URL? {
595+// get {
596+// return imageSURL =>> {URL.init(string: $0)}
597+// }
598+// set {
599+// imageSURL = newValue?.absoluteString
600+// }
601+// }
602+// var imageSURL: String?
603+// // func image(completion: ((UIImage) -> ())) {
604+// // imageFunc =>> {$0(completion)}
605+// // }
606+// var image: (@escaping ((UIImage) -> ()) -> ())?
607+// var city: String?
608+// var country: String?
609+// init(coordinates: CLLocation, title: String?, imageSURL: String?, image: (@escaping ((UIImage) -> ()) -> ()), city: String?, country: String?) {
610+// self.coordinates = coordinates
611+// self.title = title
612+// self.imageSURL = imageSURL
613+// self.image = image
614+// self.city = city
615+// self.country = country
616+// }
617+//}
618+typealias GeolocationID = Int
619+
620+enum Deactivated: String {
621+ case banned = "banned"
622+ case deleted = "deleted"
623+ case normal = "normal"
624+}
625+
626+enum FriendStatus: Int {
627+ case notFriends = 0
628+ case requestSent
629+ case requestRecieved
630+ case fiend
631+}
632+
633+enum MessageReadState: Int {
634+ case read = 0
635+ case notRead
636+}
637+enum MessageSendState: Int {
638+ case received = 0
639+ case sent
640+}
641+
642+enum MultichatNotificationSetting {
643+ case sound
644+ case disabled_until
645+}
646+
647+enum ServiceMessageAction {
648+ case photoUpdate // 0
649+ case photoRemove // 1
650+ case create(String) // 2
651+ case titleUpdate(String) // 3
652+ case inviteUser(ServiceMessageUser) // 4
653+ case kickUser(ServiceMessageUser) // 5
654+}
655+
656+enum ServiceMessageUser {
657+ case user(User)
658+ case email(String)
659+}
660+
661+typealias MessageChunkID = String
662+class MessageChunk: Object { // message ids go down
663+ subscript(index: Int) -> Message {
664+ return messages[index]
665+ }
666+
667+ @objc dynamic var id: MessageChunkID = ""
668+
669+ fileprivate let messages = List<Message>()
670+
671+ var firstMessage: Message? {
672+ return messages.first
673+ }
674+
675+ var lastMessage: Message? {
676+ return messages.last
677+ }
678+
679+ var firstID: MessageID? {
680+ return firstMessage?.id
681+ }
682+
683+ var lastID: MessageID? {
684+ return lastMessage?.id
685+ }
686+
687+ var count: Int {
688+ return messages.count
689+ }
690+
691+ fileprivate func append(message: Message?) {
692+ message >>> {messages.append($0)}
693+ }
694+
695+ override static func primaryKey() -> String? {
696+ return "id"
697+ }
698+
699+ override static func ignoredProperties() -> [String] {
700+ return ["firstMessage", "lastMessage", "firstID", "lastID", "count"]
701+ }
702+}
703+
704+func +=(lhs: MessageChunk, rhs: MessageChunk) {
705+ lhs.messages.append(objectsIn: rhs.messages)
706+}
707+
708+/// Returns: true if added. false if was already in list
709+func <~(lhs: MessageChunk?, rhs: Message?) -> Bool {
710+ var out: Bool = false
711+ realmWrite { (_) in
712+ out = lhs?.messages <~ rhs
713+ }
714+ return out
715+}
716+/// Returns: Indexes of added items
717+func <-(lhs: MessageChunk?, rhs: [Message?]?) -> [Int] {
718+ var out: [Int] = []
719+ realmWrite { (_) in
720+ out = lhs?.messages <- rhs
721+ }
722+ return out
723+}
724+/// Returns: Indexes of added items
725+func <-(lhs: MessageChunk?, rhs: MessageChunk?) -> [Int] {
726+ var out: [Int] = []
727+ realmWrite { (_) in
728+ out = lhs?.messages <- rhs?.messages
729+ }
730+ return out
731+}
732+
733+enum MessageEntry {
734+ case message(Message)
735+ case spacer
736+}
737+
738+typealias MessagesID = Int
739+class Messages: Object {
740+ subscript(ind: Int) -> MessageEntry? {
741+ assert(ind >= 0, "index out of bounds")
742+ if ind >= count || ind < 0 {
743+ return nil
744+ }
745+ var index = ind
746+ var i = 0
747+ while messageChunks[i].count < index {
748+ index -= messageChunks[i].count + 1
749+ i += 1
750+ }
751+ if index == messageChunks[i].count {
752+ return .spacer
753+ } else {
754+ return .message(messageChunks[i][index])
755+ }
756+ }
757+
758+ @objc dynamic var id: MessagesID = 0
759+
760+ @objc fileprivate dynamic var lastChunkID = 0
761+
762+ var endIndex: Int {
763+ return messageChunks.endIndex
764+ }
765+
766+ var first: Message? {
767+ return messageChunks.first?.firstMessage
768+ }
769+
770+ func count(upToChunk index: Int) -> Int {
771+ var n = 0
772+ for i in 0..<index {
773+ n += messageChunks[i].count + 1
774+ }
775+ return n
776+ }
777+
778+ public var count: Int {
779+ var n = messageChunks.count - 1
780+ for chunk in messageChunks {
781+ n += chunk.count
782+ }
783+ return n
784+ }
785+
786+ fileprivate let messageChunks = List<MessageChunk>()
787+
788+ public var chunks: List<MessageChunk> {
789+ return messageChunks
790+ }
791+
792+ fileprivate enum ChunkIndex {
793+ case inside(Int)
794+ case after(Int)
795+ case beforeFirst
796+ }
797+
798+ fileprivate func createChunk(at index: Int = 0) -> MessageChunk {
799+ let chunk = MessageChunk()
800+ realmWrite { (realm) in
801+ chunk.id = "\(id)_\(lastChunkID)"
802+ lastChunkID += 1
803+ realm.add(chunk)
804+ messageChunks.insert(chunk, at: index)
805+ }
806+ return chunk
807+ }
808+
809+ /// Return nil if there are no chunks
810+ fileprivate func chunkIndex(of message: Message) -> ChunkIndex? {
811+ if messageChunks.count == 0 {
812+ return nil
813+ }
814+ if let first = messageChunks.first?.firstMessage {
815+ if first.greater(than: message) {
816+ return .beforeFirst
817+ }
818+ }
819+ for (i, chunk) in messageChunks.enumerated() {
820+ if let lastMessage = chunk.lastMessage { // It should never be nil since a chunk is never empty
821+ if !message.greater(than: lastMessage) { // [ ... | ()] [...] ...
822+ if i == 0 {
823+ return .inside(i)
824+ } else {
825+ if let firstMessage = chunk.firstMessage { // It should never be nil since a chunk is never empty
826+ if message.greater(than: firstMessage) || message.equal(to: firstMessage) {
827+ return .inside(i)
828+ } else {
829+ return .after(i-1)
830+ }
831+ }
832+ }
833+ }
834+ }
835+ }
836+ return .after(endIndex-1)
837+ }
838+
839+
840+ override static func primaryKey() -> String? {
841+ return "id"
842+ }
843+
844+ override static func ignoredProperties() -> [String] {
845+ return ["endIndex"]
846+ }
847+
848+ class func by(id: MessagesID) -> Messages {
849+ let realm = try! Realm()
850+ if let us = realm.object(ofType: Messages.self, forPrimaryKey: id) {
851+ return us
852+ } else {
853+ let us = Messages()
854+ us.id = id
855+ realm.add(us)
856+ return us
857+ }
858+ }
859+
860+ // MARK: Messages obesrver
861+
862+ static let notificationName: Notification.Name = Notification.Name(rawValue: "MessagesWereModifiedNotificationName")
863+
864+ enum Changes {
865+ case added
866+ case modified
867+ case deleted
868+ }
869+}
870+
871+
872+
873+/// Returns: true if added. false if was already in list
874+func <~(lhs: Messages?, rhs: Message?) -> Bool {
875+ var out: Bool = false
876+ if let lhs = lhs,
877+ let rhs = rhs {
878+ realmWrite { (_) in
879+ if let index = lhs.chunkIndex(of: rhs) {
880+ switch index {
881+ case .after(let i):
882+ lhs.messageChunks[i].append(message: rhs)
883+ out = true
884+ case .inside(let i):
885+ out = lhs.messageChunks[i] <~ rhs
886+ case .beforeFirst:
887+ out = lhs.createChunk(at: 0) <~ rhs
888+ }
889+ } else {
890+ out = lhs.createChunk() <~ rhs
891+ }
892+ }
893+ }
894+ return out
895+}
896+/// Only accepts a list of SEQUENTIAL messages. Will silently break if not passed sequentially.
897+/// Returns: Indexes of added items
898+func <-(lhs: Messages?, rhs: [Message?]?) /*-> [Int]*/ {
899+ print(rhs)
900+ print(lhs)
901+ if let lhs = lhs,
902+ let rhs = rhs?.flatMap({$0}),
903+ let first = rhs.first,
904+ let last = rhs.last {
905+
906+ var deleted: [Int] = []
907+ var modified: [Int] = []
908+ var added: [Int] = []
909+ if let startI = lhs.chunkIndex(of: first),
910+ let endI = lhs.chunkIndex(of: last) {
911+ var start: Int!
912+ var end: Int!
913+ print("\t", startI, endI)
914+ switch startI {
915+ case .inside(let i):
916+ start = i
917+ case .after(let i):
918+ start = i + 1
919+ case .beforeFirst:
920+ start = -1
921+ }
922+ switch endI {
923+ case .after(let i):
924+ end = i
925+ case .inside(let i):
926+ end = i
927+ case .beforeFirst:
928+ end = -1
929+ }
930+
931+ if start < end {
932+ print(start, end)
933+ start = max(start, 0)
934+ var spacerIndexes = (start+1...end).map({lhs.count(upToChunk: $0) - 1})
935+ // deleted += spacerIndexes
936+ let count = lhs.count(upToChunk: start)
937+ var dIndex = 0
938+ for i in 0..<spacerIndexes.count {
939+ spacerIndexes[i] -= dIndex
940+ deleted.append(spacerIndexes[i])
941+ spacerIndexes[i] -= count
942+ dIndex += 1
943+ }
944+ var addedMs: [Int]!
945+ realmWriteWithCompletion { realm, completion in
946+ for x in start+1...end {
947+ print(lhs.chunks.count, x)
948+ lhs.chunks[start] += lhs.chunks[start+1]
949+ realm.delete(lhs.chunks[start+1])
950+ // lhs.chunks.remove(objectAtIndex: start+1)
951+ }
952+ let chunk = lhs.chunks[start]
953+ addedMs = chunk <- rhs
954+
955+ addedMs.map {$0 + count}
956+ added += addedMs
957+
958+ completion {
959+ print("after notification", lhs.chunks)
960+ NotificationCenter.default.post(name: Messages.notificationName,
961+ object: lhs.id,
962+ userInfo: [Messages.Changes.added: added,
963+ Messages.Changes.modified: modified,
964+ Messages.Changes.deleted: deleted])
965+ }
966+ }
967+ } else { // within one
968+ assert(start == end, "start index is larger than end index!")
969+ realmWriteWithCompletion { _, completion in
970+ if start == -1 {
971+ added += lhs.createChunk(at: 0) <- rhs
972+ added.append(0) // IDK o[]o[]o []o[]o
973+ } else {
974+ added += (lhs.messageChunks[start] <- rhs).map {$0 + lhs.count(upToChunk: start)}
975+ }
976+ completion {
977+ NotificationCenter.default.post(name: Messages.notificationName,
978+ object: lhs.id,
979+ userInfo: [Messages.Changes.added: added,
980+ Messages.Changes.modified: modified,
981+ Messages.Changes.deleted: deleted])
982+ }
983+ }
984+ }
985+ } else { // empty
986+ realmWriteWithCompletion { _, completion in
987+ added += lhs.createChunk() <- rhs
988+ completion {
989+ NotificationCenter.default.post(name: Messages.notificationName,
990+ object: lhs.id,
991+ userInfo: [Messages.Changes.added: added,
992+ Messages.Changes.modified: modified,
993+ Messages.Changes.deleted: deleted])
994+ }
995+ }
996+ }
997+ print(lhs)
998+ print(added)
999+ print(deleted)
1000+ }
1001+}
1002+
1003+enum Media {
1004+ case photo(Photo)
1005+ case video(Video)
1006+ case audio(Audio)
1007+ case doc(Document)
1008+ case wall(WallPost)
1009+ case wallReply(WallPostReply)
1010+ case sticker(Sticker)
1011+ case link(Link)
1012+ case gift(Gift)
1013+ case market(Market)
1014+ case marketAlbum(MarketAlbum)
1015+ case graffiti(Document)
1016+ case note(Note)
1017+ case poll(Poll)
1018+ case album(PhotoAlbum)
1019+ var realm: RealmMedia {
1020+ return RealmMedia(value: self)
1021+ }
1022+}
1023+
1024+class RealmMedia: Object {
1025+ @objc private dynamic var photo: Photo?
1026+ @objc private dynamic var video: Video?
1027+ @objc private dynamic var audio: Audio?
1028+ @objc private dynamic var doc: Document?
1029+ @objc private dynamic var wall: WallPost?
1030+ @objc private dynamic var wallReply: WallPostReply?
1031+ @objc private dynamic var sticker: Sticker?
1032+ @objc private dynamic var link: Link?
1033+ @objc private dynamic var gift: Gift?
1034+ @objc private dynamic var market: Market?
1035+ @objc private dynamic var marketAlbum: MarketAlbum?
1036+ @objc private dynamic var graffiti: Document?
1037+ @objc private dynamic var note: Note?
1038+ @objc private dynamic var poll: Poll?
1039+ @objc private dynamic var album: PhotoAlbum?
1040+
1041+ var value: Media? {
1042+ get {
1043+ if let sureThing = self.photo { return .photo(sureThing) }
1044+ if let sureThing = self.video { return .video(sureThing) }
1045+ if let sureThing = self.audio { return .audio(sureThing) }
1046+ if let sureThing = self.doc { return .doc(sureThing) }
1047+ if let sureThing = self.wall { return .wall(sureThing) }
1048+ if let sureThing = self.wallReply { return .wallReply(sureThing) }
1049+ if let sureThing = self.sticker { return .sticker(sureThing) }
1050+ if let sureThing = self.link { return .link(sureThing) }
1051+ if let sureThing = self.gift { return .gift(sureThing) }
1052+ if let sureThing = self.market { return .market(sureThing) }
1053+ if let sureThing = self.marketAlbum { return .marketAlbum(sureThing) }
1054+ if let sureThing = self.graffiti { return .graffiti(sureThing) }
1055+ if let sureThing = self.note { return .note(sureThing) }
1056+ if let sureThing = self.poll { return .poll(sureThing) }
1057+ if let sureThing = self.album { return .album(sureThing) }
1058+ return nil
1059+ }
1060+ set {
1061+
1062+ photo = nil
1063+ video = nil
1064+ audio = nil
1065+ doc = nil
1066+ wall = nil
1067+ wallReply = nil
1068+ sticker = nil
1069+ link = nil
1070+ gift = nil
1071+ market = nil
1072+ marketAlbum = nil
1073+ graffiti = nil
1074+ note = nil
1075+ poll = nil
1076+ album = nil
1077+
1078+ if let val = newValue {
1079+ switch val {
1080+ case .photo(let foo): photo = foo
1081+ case .video(let foo): video = foo
1082+ case .audio(let foo): audio = foo
1083+ case .doc(let foo): doc = foo
1084+ case .wall(let foo): wall = foo
1085+ case .wallReply(let foo): wallReply = foo
1086+ case .sticker(let foo): sticker = foo
1087+ case .link(let foo): link = foo
1088+ case .gift(let foo): gift = foo
1089+ case .market(let foo): market = foo
1090+ case .marketAlbum(let foo): marketAlbum = foo
1091+ case .graffiti(let foo): graffiti = foo
1092+ case .note(let foo): note = foo
1093+ case .poll(let foo): poll = foo
1094+ case .album(let foo): album = foo
1095+ }
1096+ }
1097+ }
1098+ }
1099+ convenience init(value: Media) {
1100+ self.init()
1101+ self.value = value
1102+ }
1103+}
1104+
1105+class Poll: Object {
1106+ @objc dynamic var id: PollID = 0
1107+ @objc dynamic var owner: User?
1108+ @objc dynamic var date: Date?
1109+ @objc dynamic var question: String?
1110+ let votes = RealmOptional<Int>()
1111+ let answerID = RealmOptional<PollAnswerID>()
1112+ let answers = List<PollAnswer>()
1113+ convenience init(id: PollID, owner: User, date: Date, question: String, votes: Int) {
1114+ self.init()
1115+ self.id = id
1116+ self.owner = owner
1117+ self.date = date
1118+ self.question = question
1119+ self.votes.value = votes
1120+ // self.answerID.value = answerID
1121+ }
1122+ class func by(id: PollID) -> Poll {
1123+ let realm = try! Realm()
1124+ if let us = realm.object(ofType: Poll.self, forPrimaryKey: id) {
1125+ return us
1126+ } else {
1127+ let us = Poll()
1128+ us.id = id
1129+ realm.add(us)
1130+ return us
1131+ }
1132+ }
1133+
1134+ override class func primaryKey() -> String? {
1135+ return "id"
1136+ }
1137+}
1138+typealias PollID = Int
1139+
1140+class PollAnswer: Object {
1141+ @objc dynamic var id: PollAnswerID = 0
1142+ @objc dynamic var body: String = ""
1143+ @objc dynamic var votes: Int = 0
1144+ @objc dynamic var rating: Int = 0
1145+
1146+ convenience init(id: PollAnswerID, body: String, votes: Int, rating: Int) {
1147+ self.init()
1148+ self.id = id
1149+ self.body = body
1150+ self.votes = votes
1151+ self.rating = rating
1152+ }
1153+}
1154+typealias PollAnswerID = Int
1155+
1156+class Note: Object {
1157+ @objc dynamic var id: NoteID = 0
1158+ @objc dynamic var owner: User?
1159+ @objc dynamic var title: String?
1160+ @objc dynamic var body: String?
1161+ @objc dynamic var date: Date?
1162+ var url: URL? {
1163+ get {
1164+ return surl =>> {URL(string: $0)}
1165+ }
1166+ set {
1167+ surl = newValue >>> {$0.absoluteString}
1168+ }
1169+ }
1170+ @objc dynamic var surl: String?
1171+ let commentCount = RealmOptional<Int>()
1172+ convenience init(id: NoteID, owner: User, title: String, body: String, date: Date, url: URL, commentCount: Int) {
1173+ self.init()
1174+ self.id = id
1175+ self.owner = owner
1176+ self.title = title
1177+ self.body = body
1178+ self.date = date
1179+ self.url = url
1180+ self.commentCount.value = commentCount
1181+ }
1182+ class func by(id: NoteID) -> Note {
1183+ let realm = try! Realm()
1184+ if let us = realm.object(ofType: Note.self, forPrimaryKey: id) {
1185+ return us
1186+ } else {
1187+ let us = Note()
1188+ us.id = id
1189+ realm.add(us)
1190+ return us
1191+ }
1192+ }
1193+ override class func ignoredProperties() -> [String] {
1194+ return ["url"]
1195+ }
1196+
1197+ override class func primaryKey() -> String? {
1198+ return "id"
1199+ }
1200+}
1201+typealias NoteID = Int
1202+
1203+typealias GraffitiID = Int
1204+class Graffiti: Document {
1205+} // Use document.image ?
1206+
1207+class Photo: Object {
1208+ @objc dynamic var id: PhotoID = 0
1209+ @objc dynamic var owner: User?
1210+ @objc dynamic var date: Date?
1211+ @objc dynamic var thumbnail: RealmPhoto?
1212+ var thumbnailID: String {
1213+ return "photo\(id)thumbnail"
1214+ }
1215+ @objc dynamic var image: RealmPhoto?
1216+ var imageID: String {
1217+ return "photo\(id)image"
1218+ }
1219+ let width = RealmOptional<Int>()
1220+ let height = RealmOptional<Int>()
1221+ @objc dynamic var accessKey: Key?
1222+
1223+ convenience init(id: PhotoID, owner: User, date: Date){
1224+ self.init()
1225+ self.id = id
1226+ self.owner = owner
1227+ self.date = date
1228+ }
1229+
1230+ class func by(id: PhotoID) -> Photo {
1231+ let realm = try! Realm()
1232+ if let us = realm.object(ofType: Photo.self, forPrimaryKey: id) {
1233+ return us
1234+ } else {
1235+ let us = Photo()
1236+ us.id = id
1237+ realm.add(us)
1238+ return us
1239+ }
1240+ }
1241+
1242+ func get(completion: ((Photo?) -> ())? = nil) {
1243+ if let ownerID = owner?.id {
1244+ var ph = "\(ownerID)_\(id)"
1245+ if let key = accessKey {
1246+ ph.append("_\(key)")
1247+ }
1248+ VK.API.Photos.getById([VK.Arg.photos: ph]).send(onSuccess: { (response) in
1249+ if let json = response.array?.first {
1250+ let photo = VKData.processPhoto(json)
1251+ completion?(photo)
1252+ } else {
1253+ completion?(nil)
1254+ }
1255+ }, onError: { (error) in
1256+ completion?(nil)
1257+ })
1258+ } else {
1259+ completion?(nil)
1260+ }
1261+ }
1262+
1263+ override class func primaryKey() -> String? {
1264+ return "id"
1265+ }
1266+
1267+ override static func ignoredProperties() -> [String] {
1268+ return ["thumbnailID", "imageID"]
1269+ }
1270+}
1271+
1272+class PhotoAlbum: Object {
1273+ @objc dynamic var id: PhotoAlbumID = 0
1274+ @objc dynamic var thumb: Photo?
1275+ @objc dynamic var owner: User?
1276+ @objc dynamic var title: String?
1277+ @objc dynamic var albumDescription: String?
1278+ @objc dynamic var dateCreated: Date?
1279+ @objc dynamic var dateUpdated: Date?
1280+ let count = RealmOptional<Int>()
1281+ let photos = List<Photo>()
1282+ convenience init(id: PhotoAlbumID) {
1283+ self.init()
1284+ self.id = id
1285+ }
1286+
1287+ override class func primaryKey() -> String? {
1288+ return "id"
1289+ }
1290+ class func by(id: PhotoAlbumID) -> PhotoAlbum {
1291+ let realm = try! Realm()
1292+ if let us = realm.object(ofType: PhotoAlbum.self, forPrimaryKey: id) {
1293+ return us
1294+ } else {
1295+ let us = PhotoAlbum()
1296+ us.id = id
1297+ realm.add(us)
1298+ return us
1299+ }
1300+ }
1301+
1302+
1303+ // init(id: PhotoAlbumID, thumbID: PhotoID, ownerID: UserID, title: String, dateCreated: Date, dateUpdated: Date, count: Int) {
1304+ // self.id = id
1305+ // self.thumbID = thumbID
1306+ // self.ownerID = ownerID
1307+ // self.title = title
1308+ // self.dateCreated = dateCreated
1309+ // self.dateUpdated = dateUpdated
1310+ // self.count = count
1311+ // }
1312+
1313+}
1314+typealias PhotoID = Int
1315+typealias PhotoAlbumID = Int
1316+typealias Key = String
1317+
1318+class WallPost: Object {
1319+ @objc dynamic var id: Int = 0
1320+ @objc dynamic var owner: User?
1321+ @objc dynamic var creator: User?
1322+ @objc dynamic var date: Date?
1323+ @objc dynamic var body: String?
1324+ let commentCount = RealmOptional<Int>()
1325+ let canPostComment = RealmOptional<Bool>()
1326+ let likeCount = RealmOptional<Int>()
1327+ @objc dynamic var liked: Bool = false
1328+ let canLike = RealmOptional<Bool>()
1329+ let repostCount = RealmOptional<Int>()
1330+ let canDelete = RealmOptional<Bool>()
1331+ let didRepost = RealmOptional<Bool>()
1332+ // Optional ⬇
1333+ let attachments = List<RealmMedia>()
1334+ @objc dynamic var geo: Geolocation?
1335+ // TODO: let postSource
1336+ @objc dynamic var signed: User?
1337+ @objc dynamic var ownerOfCopy: User?
1338+ @objc dynamic var original: WallPost?
1339+ let historyOfCopy = List<WallPost>()
1340+ let canPin = RealmOptional<Bool>()
1341+ let canEdit = RealmOptional<Bool>()
1342+ let isPinned = RealmOptional<Bool>()
1343+
1344+ // 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?) {
1345+ // self.id = id
1346+ // self.ownerID = ownerID
1347+ // self.creatorID = creatorID
1348+ // self.date = date
1349+ // self.body = body
1350+ // self.commentCount = commentCount
1351+ // self.canPostComment = canPostComment
1352+ // self.likeCount = likeCount
1353+ // self.liked = liked
1354+ // self.canLike = canLike
1355+ // self.repostCount = repostCount
1356+ // self.didRepost = didRepost
1357+ // self.attachments = attachments
1358+ // self.geo = geo
1359+ // self.signed = signed
1360+ // self.copyID = copyID
1361+ // self.canDelete = canDelete
1362+ // self.canPin = canPin
1363+ // self.canEdit = canEdit
1364+ // self.isPinned = isPinned
1365+ // self.copyHistory = copyHistory
1366+ // self.copyOwnerID = copyOwnerID
1367+ // }
1368+
1369+ func get(completion: ((WallPost?) -> ())? = nil) {
1370+ if let ownerID = owner?.id {
1371+ var ph = "\(ownerID)_\(id)"
1372+ // if let key = accessKey {
1373+ // ph.append("_\(key)")
1374+ // }
1375+ VK.API.Wall.getById([VK.Arg.posts: ph]).send(onSuccess: { (response) in
1376+ if let json = response.array?.first {
1377+ let photo = VKData.processWallPost(json)
1378+ completion?(photo)
1379+ } else {
1380+ completion?(nil)
1381+ }
1382+ }, onError: { (error) in
1383+ completion?(nil)
1384+ })
1385+ } else {
1386+ completion?(nil)
1387+ }
1388+ }
1389+
1390+
1391+ override class func primaryKey() -> String? {
1392+ return "id"
1393+ }
1394+
1395+ class func by(id: UserID) -> WallPost {
1396+ let realm = try! Realm()
1397+ if let us = realm.object(ofType: WallPost.self, forPrimaryKey: id) {
1398+ return us
1399+ } else {
1400+ let us = WallPost()
1401+ us.id = id
1402+ realm.add(us)
1403+ return us
1404+ }
1405+ }
1406+
1407+}
1408+typealias WallPostID = Int
1409+
1410+class WallPostReply: Object {
1411+ @objc dynamic var id: WallPostReplyID = 0
1412+ @objc dynamic var author: User?
1413+ @objc dynamic var wallPost: WallPost?
1414+ @objc dynamic var wallOwner: User?
1415+ @objc dynamic var date: Date?
1416+ @objc dynamic var body: String?
1417+ let likeCount = RealmOptional<Int>()
1418+ let didLike = RealmOptional<Bool>()
1419+ let canLike = RealmOptional<Bool>()
1420+ @objc dynamic var inReplyToUser: User?
1421+ let attachments = List<RealmMedia>()
1422+ @objc dynamic var inReplyToComment: WallPostReply?
1423+ class func by(id: WallPostReplyID) -> WallPostReply {
1424+ let realm = try! Realm()
1425+ if let us = realm.object(ofType: WallPostReply.self, forPrimaryKey: id) {
1426+ return us
1427+ } else {
1428+ let us = WallPostReply()
1429+ us.id = id
1430+ realm.add(us)
1431+ return us
1432+ }
1433+ }
1434+
1435+ override class func primaryKey() -> String? {
1436+ return "id"
1437+ }
1438+}
1439+typealias WallPostReplyID = Int
1440+
1441+class Sticker: Object {
1442+ @objc dynamic var id: StickerID = 0
1443+ @objc dynamic var packID: StickerPackID = 0
1444+ @objc dynamic var image: RealmPhoto?
1445+ var imageID: String {
1446+ return "sticker\(id)image"
1447+ }
1448+ let width = RealmOptional<Int>()
1449+ let height = RealmOptional<Int>()
1450+
1451+ convenience init(id: StickerID, packID: StickerPackID, width: Int, height: Int) {
1452+ self.init()
1453+ self.id = id
1454+ self.packID = packID
1455+ self.width.value = width
1456+ self.height.value = height
1457+ }
1458+ class func by(id: StickerID) -> Sticker {
1459+ let realm = try! Realm()
1460+ if let us = realm.object(ofType: Sticker.self, forPrimaryKey: id) {
1461+ return us
1462+ } else {
1463+ let us = Sticker()
1464+ us.id = id
1465+ realm.add(us)
1466+ return us
1467+ }
1468+ }
1469+
1470+ override class func primaryKey() -> String? {
1471+ return "id"
1472+ }
1473+ override static func ignoredProperties() -> [String] {
1474+ return ["imageID"]
1475+ }
1476+}
1477+typealias StickerID = Int
1478+typealias StickerPackID = Int
1479+
1480+class Gift: Object {
1481+ @objc dynamic var id: GiftID = 0
1482+ @objc dynamic var image: RealmPhoto?
1483+ var imageID: String {
1484+ return "gift\(id)image"
1485+ }
1486+ convenience init(id: GiftID, surl: String) {
1487+ self.init()
1488+ self.id = id
1489+ self.image = RealmPhoto.by(imageID, surl: surl)
1490+ }
1491+ class func by(id: GiftID) -> Gift {
1492+ let realm = try! Realm()
1493+ if let us = realm.object(ofType: Gift.self, forPrimaryKey: id) {
1494+ return us
1495+ } else {
1496+ let us = Gift()
1497+ us.id = id
1498+ realm.add(us)
1499+ return us
1500+ }
1501+ }
1502+
1503+ override class func primaryKey() -> String? {
1504+ return "id"
1505+ }
1506+ override static func ignoredProperties() -> [String] {
1507+ return ["imageID"]
1508+ }
1509+}
1510+typealias GiftID = Int
1511+
1512+class Link: Object {
1513+ var url: URL? {
1514+ get {
1515+ return surl =>> {URL(string: $0)}
1516+ }
1517+ }
1518+ @objc dynamic var surl: String = ""
1519+ @objc dynamic var title: String?
1520+ @objc dynamic var caption: String?
1521+ @objc dynamic var linkDescription: String?
1522+ @objc dynamic var photo: Photo?
1523+ let isExternal = RealmOptional<Bool>()
1524+ //var product: Market?
1525+ var rating: Rating? {
1526+ get {
1527+ if let stars = ratingStars.value,
1528+ let count = ratingReviewCount.value {
1529+ return Rating(stars: stars, reviewCount: count)
1530+ }
1531+ return nil
1532+ }
1533+ set {
1534+ ratingStars.value = newValue?.stars
1535+ ratingReviewCount.value = newValue?.reviewCount
1536+ }
1537+ }
1538+ private let ratingStars = RealmOptional<Int>()
1539+ private let ratingReviewCount = RealmOptional<Int>()
1540+ //let application: Application?
1541+ var button: LinkButton? {
1542+ get {
1543+ if let title = buttonTitle,
1544+ let url = buttonURL {
1545+ return LinkButton(title: title, url: url)
1546+ }
1547+ return nil
1548+ }
1549+ set {
1550+ buttonURL = newValue?.url
1551+ buttonTitle = newValue?.title
1552+ }
1553+ }
1554+ @objc private dynamic var buttonTitle: String?
1555+ private var buttonURL: URL? {
1556+ get {
1557+ return buttonSURL =>> {URL(string: $0)}
1558+ }
1559+ set {
1560+ buttonSURL = newValue?.absoluteString
1561+ }
1562+ }
1563+ private var buttonSURL: String?
1564+ //let previewURL: URL
1565+
1566+ class func by(surl: String) -> Link {
1567+ let realm = try! Realm()
1568+ if let us = realm.object(ofType: Link.self, forPrimaryKey: surl) {
1569+ return us
1570+ } else {
1571+ let us = Link()
1572+ us.surl = surl
1573+ realm.add(us)
1574+ return us
1575+ }
1576+ }
1577+ override class func primaryKey() -> String? {
1578+ return "surl"
1579+ }
1580+ override static func ignoredProperties() -> [String] {
1581+ return ["url", "rating", "button", "buttonURL"]
1582+ }
1583+}
1584+
1585+
1586+struct Rating {
1587+ let stars: Int
1588+ let reviewCount: Int
1589+}
1590+
1591+struct LinkButton {
1592+ let title: String
1593+ let url: URL
1594+}
1595+
1596+class Market: Object {
1597+ @objc dynamic var id: MarketID = 0
1598+ @objc dynamic var owner: User?
1599+ @objc dynamic var title: String?
1600+ @objc dynamic var marketDescription: String?
1601+ let amount = RealmOptional<Int>()
1602+ let price = RealmOptional<Int>()
1603+ let currencyID = RealmOptional<CurrencyID>()
1604+ @objc dynamic var currencyName: String?
1605+ @objc dynamic var priceString: String?
1606+ let categoryID = RealmOptional<MarketCategoryID>()
1607+ @objc dynamic var categoryName: String?
1608+ let sectionID = RealmOptional<MarketSectionID>()
1609+ @objc dynamic var sectionName: String?
1610+ @objc dynamic var image: RealmPhoto?
1611+ var imageID: String {
1612+ return "market\(id)image"
1613+ }
1614+ @objc dynamic var creationDate: Date?
1615+ var availability: MarketAvailability? {
1616+ set {
1617+ availabilityInt.value = newValue?.rawValue
1618+ }
1619+ get {
1620+ return availabilityInt.value =>> {MarketAvailability(rawValue: $0)}
1621+ }
1622+ }
1623+ private let availabilityInt = RealmOptional<Int>()
1624+ let photos = List<Photo>()
1625+ let canComment = RealmOptional<Bool>()
1626+ let canRepost = RealmOptional<Bool>()
1627+ let likesCount = RealmOptional<Int>()
1628+ let liked = RealmOptional<Bool>()
1629+
1630+ class func by(id: MarketID) -> Market {
1631+ let realm = try! Realm()
1632+ if let us = realm.object(ofType: Market.self, forPrimaryKey: id) {
1633+ return us
1634+ } else {
1635+ let us = Market()
1636+ us.id = id
1637+ realm.add(us)
1638+ return us
1639+ }
1640+ }
1641+
1642+ override class func primaryKey() -> String? {
1643+ return "id"
1644+ }
1645+ override static func ignoredProperties() -> [String] {
1646+ return ["imageID"]
1647+ }
1648+}
1649+typealias CurrencyID = Int
1650+typealias MarketCategoryID = Int
1651+typealias MarketSectionID = Int
1652+enum MarketAvailability: Int {
1653+ case availability = 0
1654+ case deleted
1655+ case notAvailability
1656+}
1657+typealias MarketID = Int
1658+
1659+class MarketAlbum: Object {
1660+ @objc dynamic var id: MarketAlbumID = 0
1661+ @objc dynamic var owner: User?
1662+ @objc dynamic var title: String?
1663+ @objc dynamic var photo: Photo?
1664+ let count = RealmOptional<Int>()
1665+ @objc dynamic var updateTime: Date?
1666+ override class func primaryKey() -> String? {
1667+ return "id"
1668+ }
1669+}
1670+typealias MarketAlbumID = Int
1671+
1672+class Video: Object {
1673+ @objc dynamic var id: VideoID = 0
1674+ @objc dynamic var owner: User?
1675+ @objc dynamic var title: String?
1676+ @objc dynamic var videoDescription: String?
1677+ let duration = RealmOptional<TimeInterval>()
1678+ @objc dynamic var thumbnail: RealmPhoto?
1679+ var thumbnailID: String {
1680+ return "video\(id)thumbnail"
1681+ }
1682+ var player: URL? {
1683+ get {
1684+ return playerSURL =>> {URL(string: $0)}
1685+ }
1686+ set {
1687+ playerSURL = newValue?.absoluteString
1688+ }
1689+ }
1690+ @objc dynamic var playerSURL: String?
1691+ @objc dynamic var accessKey: String?
1692+ @objc dynamic var isProcessing: Bool = false
1693+ @objc dynamic var isLive: Bool = false
1694+ convenience init(id: VideoID, owner: User, title: String, description: String, duration: TimeInterval) {
1695+ self.init()
1696+ self.id = id
1697+ self.owner = owner
1698+ self.title = title
1699+ self.videoDescription = description
1700+ self.duration.value = duration
1701+ }
1702+
1703+ class func by(id: VideoID) -> Video {
1704+ let realm = try! Realm()
1705+ if let us = realm.object(ofType: Video.self, forPrimaryKey: id) {
1706+ return us
1707+ } else {
1708+ let us = Video()
1709+ us.id = id
1710+ realm.add(us)
1711+ return us
1712+ }
1713+ }
1714+
1715+ func get(completion: ((Video?) -> ())? = nil) {
1716+ if let peerID = owner?.id {
1717+ var vi = "\(peerID)_\(id)"
1718+ if let key = accessKey {
1719+ vi.append("_\(key)")
1720+ }
1721+ VK.API.Video.get([VK.Arg.videos: vi]).send(onSuccess: { (response) in
1722+ if let json = response["items"].array?.first {
1723+ let video = VKData.processVideo(json)
1724+ completion?(video)
1725+ } else {
1726+ completion?(nil)
1727+ }
1728+ }, onError: { (error) in
1729+ completion?(nil)
1730+ })
1731+ } else {
1732+ completion?(nil)
1733+ }
1734+ }
1735+
1736+ override class func primaryKey() -> String? {
1737+ return "id"
1738+ }
1739+ override static func ignoredProperties() -> [String] {
1740+ return ["player", "thumbnailID"]
1741+ }
1742+}
1743+typealias VideoID = Int
1744+
1745+class Audio: Object {
1746+ @objc dynamic var id: AudioID = 0
1747+ @objc dynamic var owner: User?
1748+ @objc dynamic var artist: String?
1749+ @objc dynamic var title: String?
1750+ let duration = RealmOptional<TimeInterval>()
1751+ var url: URL? {
1752+ get {
1753+ return surl =>> {URL(string: $0)}
1754+ }
1755+ set {
1756+ surl = newValue?.absoluteString
1757+ }
1758+ }
1759+ @objc dynamic var surl: String?
1760+
1761+ convenience init(id: AudioID) {
1762+ self.init()
1763+ self.id = id
1764+ }
1765+ class func by(id: AudioID) -> Audio {
1766+ let realm = try! Realm()
1767+ if let us = realm.object(ofType: Audio.self, forPrimaryKey: id) {
1768+ return us
1769+ } else {
1770+ let us = Audio()
1771+ us.id = id
1772+ realm.add(us)
1773+ return us
1774+ }
1775+ }
1776+
1777+ override class func primaryKey() -> String? {
1778+ return "id"
1779+ }
1780+ override static func ignoredProperties() -> [String] {
1781+ return ["url"]
1782+ }
1783+}
1784+typealias AudioID = Int
1785+typealias LyricID = Int
1786+typealias AudioAlbumID = Int
1787+typealias GenreID = Int
1788+
1789+class Document: Object {
1790+ @objc dynamic var id: DocumentID = 0
1791+ @objc dynamic var owner: User?
1792+ @objc dynamic var title: String?
1793+ let size = RealmOptional<Int>()
1794+ @objc dynamic var fileExtension: String?
1795+ var url: URL? {
1796+ get {
1797+ return surl =>> {URL(string: $0)}
1798+ }
1799+ set {
1800+ surl = newValue?.absoluteString
1801+ }
1802+ }
1803+ @objc dynamic var surl: String?
1804+ @objc dynamic var image: RealmPhoto?
1805+ var imageID: String {
1806+ return "doc\(id)image"
1807+ }
1808+ var type: DocumentType? {
1809+ get {
1810+ return typeInt.value =>> {DocumentType(rawValue: $0)}
1811+ }
1812+ set {
1813+ typeInt.value = newValue?.rawValue
1814+ }
1815+ }
1816+ private let typeInt = RealmOptional<Int>()
1817+ convenience init(id: DocumentID, owner: User, title: String, size: Int, ext: String, url: URL, type: DocumentType) {
1818+ self.init()
1819+ self.id = id
1820+ self.owner = owner
1821+ self.title = title
1822+ self.size.value = size
1823+ self.fileExtension = ext
1824+ self.url = url
1825+ self.type = type
1826+ }
1827+ class func by(id: DocumentID) -> Document {
1828+ let realm = try! Realm()
1829+ if let us = realm.object(ofType: Document.self, forPrimaryKey: id) {
1830+ return us
1831+ } else {
1832+ let us = Document()
1833+ us.id = id
1834+ realm.add(us)
1835+ return us
1836+ }
1837+ }
1838+
1839+ func get(completion: ((Document?) -> ())? = nil) {
1840+ if let ownerID = owner?.id {
1841+ var doc = "\(ownerID)_\(id)"
1842+ // if let key = accessKey {
1843+ // doc.append("_\(key)")
1844+ // }
1845+ VK.API.Docs.getById([VK.Arg.docs: doc]).send(onSuccess: { response in
1846+ if let json = response.array?.first {
1847+ let document = VKData.processDocument(json)
1848+ completion?(document)
1849+ } else {
1850+ completion?(nil)
1851+ }
1852+ }, onError: { (error) in
1853+ completion?(nil)
1854+ })
1855+ } else {
1856+ completion?(nil)
1857+ }
1858+ }
1859+
1860+ override class func primaryKey() -> String? {
1861+ return "id"
1862+ }
1863+ override static func ignoredProperties() -> [String] {
1864+ return ["url", "imageID"]
1865+ }
1866+}
1867+typealias DocumentID = Int
1868+enum DocumentType: Int {
1869+ case text = 1
1870+ case archive
1871+ case gif
1872+ case image
1873+ case audio
1874+ case video
1875+ case eBook
1876+ case unknown
1877+}
1878+
1879+protocol Dialog {
1880+ var firstMessage: Message? { get }
1881+ var important: Bool { get set }
1882+ var name: String? { get }
1883+ func loadImage(into view: UIImageView, completion: (() -> ())?) -> RealmPhotoToken?
1884+ var messages: Messages? { get }
1885+ var id: Int { get }
1886+ var lastReadMessageID: Int { get set }
1887+ var count: RealmOptional<Int> { get }
1888+ var notificationSettings: RealmNotificatonSettings? { get set }
1889+ var peerID: Int { get }
1890+}
1891+
1892+func dialog(for id: DialogID) -> Dialog {
1893+ if id > multichatchatId { // multichat
1894+ let chat = Multichat.by(id: id - multichatchatId)
1895+ return chat
1896+
1897+ //} else if id < 0 { // Pablic
1898+
1899+ } else { // Normal user
1900+ let chat = Chat.by(user: User.by(id: id))
1901+ return chat
1902+ }
1903+}
1904+
1905+// For name conflicts
1906+func dialogFor(_ id: DialogID) -> Dialog {
1907+ return dialog(for: id)
1908+}
1909+
1910+class RealmDialog: Object, Comparable {
1911+
1912+ // TODO: Remove these two
1913+ @objc dynamic var dateLastSent: Date?
1914+ /// Number of times a message has been sent in the dateLastSent second
1915+ @objc dynamic var dateSentNumber = 0
1916+
1917+ var value: Dialog? {
1918+ get {
1919+ return dialog(for: id)
1920+ }
1921+ }
1922+
1923+ var peerID: Int? {
1924+ return value?.peerID
1925+ }
1926+
1927+ @objc dynamic var id: DialogID = 0
1928+
1929+ @objc dynamic var date: Date?
1930+
1931+ class func by(dialog: Dialog) -> RealmDialog {
1932+ return RealmDialog.by(peerID: dialog.peerID)
1933+ }
1934+
1935+ class func by(peerID: Int) -> RealmDialog {
1936+ if let dialog = (try? Realm())?.object(ofType: RealmDialog.self, forPrimaryKey: peerID) {
1937+ return dialog
1938+ } else {
1939+ let dialog = RealmDialog()
1940+ dialog.id = peerID
1941+ realmWrite({ (realm) in
1942+ realm.add(dialog)
1943+ })
1944+ return dialog
1945+ }
1946+ }
1947+
1948+ override class func primaryKey() -> String? {
1949+ return "id"
1950+ }
1951+ override static func ignoredProperties() -> [String] {
1952+ return ["value", "peerID"]
1953+ }
1954+}
1955+func ==(lhs: RealmDialog, rhs: RealmDialog) -> Bool {
1956+ return lhs.date == rhs.date
1957+}
1958+func <(lhs: RealmDialog, rhs: RealmDialog) -> Bool {
1959+ if let l = lhs.date,
1960+ let r = rhs.date {
1961+ return l < r
1962+ } else {
1963+ return false
1964+ }
1965+}
1966+typealias DialogID = Int
1967+
1968+//func ==(lhs: Dialog, rhs: Dialog) -> Bool {
1969+// return lhs.date == rhs.date
1970+//}
1971+//func <(lhs: Dialog, rhs: Dialog) -> Bool {
1972+// return lhs.date < rhs.date
1973+//}
1974+
1975+class RealmNotificatonSettings: Object {
1976+ var value: ChatNotificationSettings? {
1977+ get {
1978+ if let sound = notiSound.value,
1979+ let never = notiDateNever.value {
1980+ if never {
1981+ return ChatNotificationSettings(sound: sound, disabledUntil: .never)
1982+ } else {
1983+ return notiDate >>> {ChatNotificationSettings(sound: sound, disabledUntil: .date($0))}
1984+ }
1985+ }
1986+ return nil
1987+ }
1988+ set {
1989+ notiSound.value = newValue?.sound
1990+ if let newV = newValue {
1991+ switch newV.disabledUntil {
1992+ case .date(let date):
1993+ notiDate = date
1994+ notiDateNever.value = false
1995+ case .never:
1996+ notiDateNever.value = true
1997+ notiDate = nil
1998+ }
1999+ }
2000+ }
2001+ }
2002+ private let notiSound = RealmOptional<Bool>()
2003+ private let notiDateNever = RealmOptional<Bool>()
2004+ @objc private dynamic var notiDate: Date?
2005+ convenience init(value: ChatNotificationSettings?) {
2006+ self.init()
2007+ self.value = value
2008+ }
2009+}
2010+
2011+class Multichat: Object {
2012+ @objc dynamic var id: MultichatID = 0
2013+ @objc dynamic var admin: User?
2014+ var timers: [UserID: Timer] = [:]
2015+ var typingUsers: Set<User> = []
2016+ @objc dynamic var title: String?
2017+ let users = List<MultiChatUser>()
2018+ @objc dynamic var messages: Messages?
2019+ // let messages = List<Message>()
2020+ @objc dynamic var lastReadMessageID = 0
2021+ @objc dynamic var important = false
2022+ @objc dynamic var answered = false
2023+ @objc dynamic var date = Date()
2024+ let count = RealmOptional<Int>()
2025+ @objc dynamic var image: RealmPhoto?
2026+ var imageID: String {
2027+ return "mchat\(id)image"
2028+ }
2029+ let attachments = List<RealmMedia>()
2030+
2031+ @objc dynamic var notificationSettings: RealmNotificatonSettings?
2032+
2033+ var participationStatus: MultichatStatus? {
2034+ get {
2035+ return participationStatusInt.value =>> {MultichatStatus(rawValue: $0)}
2036+ }
2037+ set {
2038+ participationStatusInt.value = newValue?.rawValue
2039+ }
2040+ }
2041+ private let participationStatusInt = RealmOptional<Int>()
2042+
2043+ // convenience init(id: MultichatID, adminID: UserID, title: String, userIDs: [(user: UserID, invitedBy: UserID)]?, notificationSettings: MultichatNotificationSettings?, participationStatus: MultichatStatus?) {
2044+ // self.id = id
2045+ // self.adminID = adminID
2046+ // self.title = title
2047+ // self.userIDs = userIDs
2048+ // self.notificationSetting = notificationSettings
2049+ // self.participationStatus = participationStatus
2050+ // }
2051+ class func by(id: MultichatID) -> Multichat {
2052+ let realm = try! Realm()
2053+ if let us = realm.object(ofType: Multichat.self, forPrimaryKey: id) {
2054+ return us
2055+ } else {
2056+ VkServices.getMultichat(for: id)
2057+ let us = Multichat()
2058+ realmWrite { realm in
2059+ us.id = id
2060+ us.messages = Messages.by(id: us.peerID)
2061+ realm.add(us)
2062+ }
2063+ return us
2064+ }
2065+ }
2066+
2067+ override class func primaryKey() -> String? {
2068+ return "id"
2069+ }
2070+ override static func ignoredProperties() -> [String] {
2071+ return ["imageID", "typingUsers", "timers"]
2072+ }
2073+}
2074+
2075+extension Multichat: Dialog {
2076+ var firstMessage: Message? {
2077+ return messages?.first
2078+ }
2079+
2080+ var name: String? {
2081+ return title
2082+ }
2083+
2084+ func loadImage(into view: UIImageView, completion: (() -> ())? = nil) -> RealmPhotoToken? {
2085+ return image?.load(into: view, completion: completion)
2086+ }
2087+ // func image(completion: @escaping ((UIImage?) -> ())) {
2088+ // if let im = image?.image {
2089+ // im(completion)
2090+ // } else {
2091+ // completion(nil)
2092+ // }
2093+ // }
2094+
2095+ var peerID: Int {
2096+ return id + multichatchatId
2097+ }
2098+}
2099+
2100+class MultiChatUser: Object {
2101+ @objc dynamic var user: User?
2102+ @objc dynamic var invitedBy: User?
2103+ convenience init(_ user: User, invitedBy: User) {
2104+ self.init()
2105+ self.user = user
2106+ self.invitedBy = invitedBy
2107+ }
2108+}
2109+
2110+enum MultichatStatus: Int {
2111+ case active
2112+ case left
2113+ case kicked
2114+}
2115+
2116+class Chat: Object {
2117+ @objc dynamic var user: User? {
2118+ didSet {
2119+ user >>> {userID = $0.id}
2120+ }
2121+ }
2122+ var timer: Timer?
2123+ @objc dynamic var date = Date()
2124+ @objc dynamic var userID = 0
2125+ @objc dynamic var isTyping = false
2126+ @objc dynamic var lastReadMessageID = 0
2127+ let count = RealmOptional<Int>()
2128+ @objc dynamic var messages: Messages?
2129+ // let messages = List<Message>()
2130+ @objc dynamic var important = false
2131+ @objc dynamic var answered = false
2132+ let attachments = List<RealmMedia>()
2133+ @objc dynamic var notificationSettings: RealmNotificatonSettings?
2134+ class func by(user: User) -> Chat {
2135+ let realm = try! Realm()
2136+ if let us = realm.object(ofType: Chat.self, forPrimaryKey: user.id) {
2137+ return us
2138+ } else {
2139+ let us = Chat()
2140+ realmWrite { realm in
2141+ us.user = user
2142+ us.messages = Messages.by(id: us.peerID)
2143+ realm.add(us)
2144+ }
2145+ return us
2146+ }
2147+ }
2148+
2149+ convenience init(user: User) {
2150+ self.init()
2151+ self.user = user
2152+ }
2153+ override static func ignoredProperties() -> [String] {
2154+ return ["timer"]
2155+ }
2156+ override class func primaryKey() -> String? {
2157+ return "userID"
2158+ }
2159+}
2160+
2161+extension Chat: Dialog {
2162+ var firstMessage: Message? {
2163+ return messages?.first
2164+ }
2165+
2166+ var name: String? {
2167+ if let firstName = user?.firstName,
2168+ let lastName = user?.lastName {
2169+ return "\(firstName) \(lastName)"
2170+ } else {
2171+ return nil
2172+ }
2173+ }
2174+
2175+ func loadImage(into view: UIImageView, completion: (() -> ())? = nil) -> RealmPhotoToken? {
2176+ return user?.avatar?.load(into: view, completion: completion)
2177+ }
2178+
2179+ // func image(completion: @escaping ((UIImage?) -> ())) {
2180+ // if let im = user?.avatar?.image {
2181+ // im(completion)
2182+ // } else {
2183+ // completion(nil)
2184+ // }
2185+ // }
2186+
2187+ var id: Int {
2188+ return userID
2189+ }
2190+
2191+ var peerID: Int {
2192+ return userID
2193+ }
2194+}
2195+
2196+struct ChatNotificationSettings {
2197+ var sound: Bool
2198+ var disabledUntil: disableDate
2199+}
2200+
2201+enum disableDate {
2202+ case never
2203+ case date(Date)
2204+ init?(value: Int) {
2205+ switch value {
2206+ case -1:
2207+ self = .never
2208+ case 0:
2209+ return nil
2210+ default:
2211+ self = .date(Date(timeIntervalSince1970: TimeInterval(value)))
2212+ }
2213+ }
2214+}
2215+
2216+func isPhoneNumber(_ phone: String) -> Bool {
2217+ let nums = "1234567890".characters
2218+ let f002 = phone.characters.map{nums.contains($0) ? 1 : 0}
2219+ let foo1 = Double(sum(acc: 0, arr: f002))
2220+ let foo = foo1/Double(phone.characters.count)
2221+ return foo >= 0.4 ? true : false
2222+}
2223+
2224+
Melk/MainCoordinator.swiftnew file+26
@@ -0,0 +1,26 @@
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+
11+class MainCoordinator {
12+ let window: UIWindow
13+ let mainVC = MainViewController()
14+ let navC: UINavigationController!
15+ let secondaryCoord = SecondaryCoordinator()
16+ init(window: UIWindow) {
17+ self.window = window
18+ navC = UINavigationController(rootViewController: mainVC)
19+ window.rootViewController = navC
20+ updateScreens()
21+ }
22+
23+ func updateScreens() {
24+ secondaryCoord.updateScreens()
25+ }
26+}
Melk/MainViewController.swiftnew file+85
@@ -0,0 +1,85 @@
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+ let mainView = MainView()
14+
15+ init() {
16+ super.init(nibName: nil, bundle: nil)
17+ self.view = mainView
18+ let rightButton = UIBarButtonItem()
19+ rightButton.title = "Present"
20+ rightButton.target = self
21+ rightButton.action = #selector(MainViewController.presentTags)
22+ self.navigationItem.rightBarButtonItem = rightButton
23+ }
24+
25+ required init?(coder aDecoder: NSCoder) {
26+ fatalError("init(coder:) has not been implemented")
27+ }
28+
29+ func presentTags() {
30+
31+ }
32+}
33+
34+class MainView: UIView {
35+ let tableView = UITableView()
36+
37+ override init(frame: CGRect) {
38+ super.init(frame: frame)
39+ addSubview(tableView)
40+ tableView.dataSource = self
41+ tableView.delegate = self
42+ tableView.estimatedRowHeight = 30
43+ tableView.rowHeight = UITableViewAutomaticDimension
44+ tableView.register(TitleTableViewCell.self, forCellReuseIdentifier: TitleTableViewCell.identifier)
45+ tableView.register(TagTableViewCell.self, forCellReuseIdentifier: TagTableViewCell.identifier)
46+ setNeedsLayout()
47+ }
48+
49+ override func layoutSubviews() {
50+ tableView.frame = bounds
51+ }
52+
53+ required init?(coder aDecoder: NSCoder) {
54+ fatalError("init(coder:) has not been implemented")
55+ }
56+}
57+
58+extension MainView: UITableViewDataSource {
59+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
60+ if let cell = tableView.dequeueReusableCell(withIdentifier: TagTableViewCell.identifier) as? TagTableViewCell {
61+// cell.titleText = "Testing testing 1 2 3"
62+ cell.style = indexPath.item == 0 ? .large : .normal
63+ return cell
64+ }
65+ return UITableViewCell()
66+ }
67+
68+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
69+ return 3
70+ }
71+
72+ func numberOfSections(in tableView: UITableView) -> Int {
73+ return 1
74+ }
75+
76+ func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
77+ return indexPath.item == 0 ? TagTableViewCell.largeHeight : TagTableViewCell.normalHeight
78+ }
79+
80+}
81+
82+
83+extension MainView: UITableViewDelegate {
84+
85+}
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+249
@@ -0,0 +1,249 @@
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+}
Melk/SecondaryCoordinator.swiftnew file+30
@@ -0,0 +1,30 @@
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+ init() {
16+
17+ }
18+
19+ func updateScreens() {
20+ windows.removeAll()
21+ for screen in UIScreen.screens.dropFirst() {
22+ let window = UIWindow(frame: screen.bounds)
23+ window.backgroundColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
24+ window.screen = screen
25+ window.isHidden = false
26+ window.rootViewController = SecondaryViewController()
27+ windows.append(window)
28+ }
29+ }
30+}
Melk/SecondaryViewController.swiftnew file+40
@@ -0,0 +1,40 @@
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+ init() {
13+ super.init(nibName: nil, bundle: nil)
14+ self.view = SecondaryView(frame: view.bounds)
15+ }
16+
17+ required init?(coder aDecoder: NSCoder) {
18+ fatalError("init(coder:) has not been implemented")
19+ }
20+}
21+
22+class SecondaryView: UIView {
23+ override init(frame: CGRect) {
24+ super.init(frame: frame)
25+
26+ let card = CardView(frame: CGRect(x: 20, y: 20, width: 600, height: 300))
27+ let post = WallPost()
28+ post.owner = User()
29+ post.owner?.firstName = "David"
30+ post.owner?.avatar = RealmPhoto()
31+ post.owner?.avatar?.imageSURL = "https://static.independent.co.uk/s3fs-public/styles/article_small/public/thumbnails/image/2017/04/01/17/twitter-egg.jpg"
32+ card.post = post
33+ addSubview(card)
34+
35+ }
36+
37+ required init?(coder aDecoder: NSCoder) {
38+ fatalError("init(coder:) has not been implemented")
39+ }
40+}
Melk/StackView.swiftnew file+154
@@ -0,0 +1,154 @@
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+ var height: CGFloat = 0
48+ var width: CGFloat = 0
49+ switch axis {
50+ case .horizontal:
51+ width = padding.x
52+ case .vertical:
53+ height = padding.y
54+ }
55+ for element in arrangedSubviews {
56+ switch element {
57+ case .spacer(let a):
58+ switch axis {
59+ case .horizontal:
60+ width += a
61+ case .vertical:
62+ height += a
63+ }
64+ case .view(let view):
65+
66+ switch axis {
67+ case .horizontal:
68+ view.frame.origin.x = width
69+ width += view.frame.width
70+ height = max(height, view.frame.height)
71+ if alignment == .top {
72+ view.frame.origin.y = padding.y
73+ }
74+ case .vertical:
75+ view.frame.origin.y = height
76+ height += view.frame.height
77+ width = max(width, view.frame.width)
78+ if alignment == .left {
79+ view.frame.origin.x = padding.x
80+ }
81+ }
82+ addSubview(view)
83+ }
84+ }
85+ switch axis {
86+ case .horizontal:
87+ frame.size = CGSize(width: width + padding.x, height: height + 2*padding.y)
88+ switch alignment {
89+ case .top:
90+ break
91+ case .bottom:
92+ for element in arrangedSubviews {
93+ switch element {
94+ case .view(let view):
95+ view.frame.origin.y = height - view.frame.height + padding.y
96+ default:
97+ break
98+ }
99+ }
100+ default:
101+ for element in arrangedSubviews {
102+ switch element {
103+ case .view(let view):
104+ view.center.y = height/2 + padding.y
105+ default:
106+ break
107+ }
108+ }
109+ }
110+ case .vertical:
111+ frame.size = CGSize(width: width + 2*padding.x, height: height + padding.y)
112+ switch alignment {
113+ case .left:
114+ break
115+ case .right:
116+ for element in arrangedSubviews {
117+ switch element {
118+ case .view(let view):
119+ view.frame.origin.x = width - view.frame.width + padding.x
120+ default:
121+ break
122+ }
123+ }
124+ default:
125+ for element in arrangedSubviews {
126+ switch element {
127+ case .view(let view):
128+ view.center.x = width/2 + padding.x
129+ default:
130+ break
131+ }
132+ }
133+ }
134+ }
135+ }
136+}
137+
138+enum StackViewElement {
139+ case spacer(CGFloat)
140+ case view(UIView)
141+}
142+
143+enum Axis {
144+ case horizontal
145+ case vertical
146+}
147+
148+enum Alignment {
149+ case center
150+ case top
151+ case right
152+ case bottom
153+ case left
154+}
Melk/TableViewCells.swiftnew file+136
@@ -0,0 +1,136 @@
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 style: TagStyle = .normal {
61+ didSet {
62+ updateStyle()
63+ }
64+ }
65+
66+ private let textField = UITextField()
67+
68+ override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
69+ super.init(style: style, reuseIdentifier: reuseIdentifier)
70+
71+ textField.borderStyle = .roundedRect
72+ textField.adjustsFontSizeToFitWidth = false
73+
74+ contentView.addSubview(textField)
75+// textField.delegate = self
76+// textField.font
77+
78+ }
79+
80+ private func updateStyle() {
81+ textField.font = style.font
82+ setNeedsLayout()
83+ }
84+
85+ override func layoutSubviews() {
86+ super.layoutSubviews()
87+
88+ let label = UILabel()
89+ label.text = "MH"
90+ label.font = style.font
91+ let height = label.textRect(forBounds:
92+ CGRect.infinite, limitedToNumberOfLines: 1)
93+ .height + 2*2
94+
95+ let frame = CGRect(x: 16, y: 8, width: bounds.width - 16*2, height: height)
96+ textField.frame = frame
97+ print(textField.frame)
98+ }
99+
100+ required init?(coder aDecoder: NSCoder) {
101+ fatalError("init(coder:) has not been implemented")
102+ }
103+
104+
105+ static let identifier = "io.Melk.tagTableViewCell"
106+ static let largeHeight: CGFloat = 37.5 + 8*2
107+ static let normalHeight: CGFloat = 28.0 + 8*2
108+}
109+
110+//extension TagTableViewCell: UITextFieldDelegate {
111+// func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
112+//
113+// return true
114+// }
115+//}
116+
117+enum TagStyle {
118+ case normal
119+ case large
120+
121+ private static let largeFont = UIFont.systemFont(ofSize: 28, weight: UIFontWeightHeavy)
122+ private static let normalFont = UIFont.systemFont(ofSize: 20, weight: UIFontWeightSemibold)
123+
124+ var font: UIFont {
125+ switch self {
126+ case .normal:
127+ return TagStyle.normalFont
128+ case .large:
129+ return TagStyle.largeFont
130+ }
131+ }
132+}
133+
134+
135+
136+
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: SwiftyVK.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_max"].string)
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: SwiftyVK.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: SwiftyVK.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: SwiftyVK.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: SwiftyVK.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: SwiftyVK.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: SwiftyVK.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: SwiftyVK.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: SwiftyVK.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: SwiftyVK.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: SwiftyVK.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: SwiftyVK.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: SwiftyVK.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: SwiftyVK.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: SwiftyVK.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: SwiftyVK.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: SwiftyVK.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["to_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: SwiftyVK.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: SwiftyVK.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: SwiftyVK.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: SwiftyVK.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: SwiftyVK.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: SwiftyVK.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+40
@@ -0,0 +1,40 @@
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+let vkAPIVersion = "5.60"
34+let appID = "5529752"
35+
36+let multichatchatId = 2000000000
37+
38+enum Methods {
39+ static let getDialogs = "getDialogs"
40+}
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−
Melk/VkServices.swiftnew file+207
@@ -0,0 +1,207 @@
1+//
2+// VkServices.swift
3+// golos
4+//
5+// Created by Ilya Kos on 7/24/16.
6+// Copyright © 2016 Ilya Kos. All rights reserved.
7+//
8+
9+import UIKit
10+import Alamofire
11+import SwiftyVK
12+
13+// let realm = try! Realm()
14+
15+class VkServices {
16+ class func getDialogs(offset: Int = 0, count: Int = 200, completion: (() -> ())? = nil) { // Messages
17+ VK.API.remote(method: Methods.getDialogs, parameters:
18+ [VK.Arg.offset: String(offset),
19+ VK.Arg.count: String(count)])
20+ .send(onSuccess: { (response) in
21+ if let usersJSON = response[1].array,
22+ let chatUsersJSON = response[2].array,
23+ let dialogs = response[0]["items"].array,
24+ let chatUserInvitersJSON = response[3].array,
25+ let multichatsJSON = response[4].array { // TODO: Catch error from vk
26+ userQueue.async {
27+ let _ = usersJSON.map(VKData.processUser)
28+ let _ = chatUsersJSON.map(VKData.processUser)
29+ let _ = chatUserInvitersJSON.flatMap(VKData.processUser)
30+ let _ = multichatsJSON.map(VKData.processMultichat)
31+ let _ = dialogs.map(VKData.processDialog)
32+ print("DONEZO")
33+ DispatchQueue.main.async {
34+ completion?()
35+ }
36+ }
37+ }
38+ }, onError: { error in
39+ print(error)
40+ }) // TODO: Handle error
41+ }
42+
43+
44+ /// - Parameters:
45+ /// - dialog: in the `200000000` and `-` fomat
46+ /// - count: count
47+ /// - offset: offset
48+ /// - completion: completion handler
49+ class func getMessages(forDialog dialog: DialogID, count: Int = 20, offset: Int = 0, completion: (() -> ())? = nil) {
50+ var params: [VK.Arg: String] = [
51+ VK.Arg.peerId: String(dialog),
52+ VK.Arg.offset: String(offset),
53+ VK.Arg.count: String(count)]
54+ var request = VK.API.Messages.getHistory()
55+ request.add(parameters: params)
56+ request.send(onSuccess: { (response) in
57+ userQueue.async {
58+ realmWrite { _ in
59+ let rDialog = RealmDialog.by(peerID: dialog)
60+ var f = rDialog.value
61+ f <- response["items"].array?.flatMap(VKData.processMessage)
62+ f?.lastReadMessageID = max(response["out_read"].intValue, response["in_read"].intValue) // TODO: Probrably should use seperate things for readin and readout
63+ response["count"].int >>> { rDialog.value?.count.value = $0 }
64+ }
65+ }
66+ }, onError: nil) // TODO: Handle error
67+
68+ completion?()
69+ }
70+
71+ class func send(_ message: Message, to dialog: RealmDialog, completion: (() -> ())? = nil) {
72+ let date = Date()
73+ let time = Int(date.timeIntervalSince1970) * 100
74+ var params: [VK.Arg: String] = [:]
75+ if let d = dialog.value {
76+ switch d {
77+ case let chat as Chat:
78+ params[VK.Arg.peerId] = String(chat.userID)
79+ case let mchat as Multichat:
80+ params[VK.Arg.chatId] = String(mchat.id)
81+ default:
82+ break
83+ }
84+ }
85+ if dialog.dateLastSent == date {
86+ realmWrite({ (_) in
87+ dialog.dateSentNumber += 1
88+ })
89+ } else {
90+ realmWrite({ (_) in
91+ dialog.dateSentNumber = 1
92+ dialog.dateLastSent = date
93+ })
94+ }
95+
96+ var i = 0
97+ func photoUploadRequest() {
98+ if i < message.outPhotos.count {
99+ if let data = UIImagePNGRepresentation(message.outPhotos[i]) {
100+ let request = VK.API.Upload.Photo.toMessage(SwiftyVK.Media.init(imageData: data, type: .PNG))
101+ request.send(onSuccess: { (response) in
102+ if let photo = VKData.processPhoto(response) { // TODO: this probrably shouldnt happen on the main queue
103+ message.attachments.append(RealmMedia(value: Media.photo(photo)))
104+ }
105+ i += 1
106+ photoUploadRequest()
107+ }, onError: nil) // TODO: Handle error
108+ } else {
109+ i += 1
110+ photoUploadRequest()
111+ }
112+ }
113+ }
114+
115+
116+
117+ params[VK.Arg.randomId] = String(time + dialog.dateSentNumber)
118+ params[VK.Arg.message] = String(message.body)
119+ message.geolocation?.geoCoordinates?.coordinate.latitude >>> {params[VK.Arg.lat] = String($0)}
120+ message.geolocation?.geoCoordinates?.coordinate.longitude >>> {params[VK.Arg.long] = String($0)}
121+ var att = ""
122+ var stickerID: Int?
123+ for m in message.attachments {
124+ m.string >>> { att.append("\($0),") }
125+ if stickerID == nil {
126+ if let v = m.value {
127+ switch v {
128+ case .sticker(let s):
129+ stickerID = s.id
130+ default:
131+ break
132+ }
133+ }
134+ }
135+ }
136+ params[VK.Arg.attachments] = att
137+ var fm = ""
138+ for m in message.forwardedMessages {
139+ m.id >>> {fm.append("\($0),")}
140+ }
141+ params[VK.Arg.forwardMessages] = fm
142+ stickerID >>> {params[VK.Arg.stickerId] = String($0)}
143+ VK.API.Messages.send(params).send(onSuccess: { (response) in
144+ if let id = response.int {
145+ realmWrite({ (_) in
146+ message.id = id
147+ dialog.value?.messages <~ message // TODO: Uncomment this
148+ })
149+ }
150+ completion?()
151+ }, onError: nil) // TODO: Handle error
152+ }
153+ class func getMultichat(for id: MultichatID) {
154+ let params: [VK.Arg: String] = [
155+ VK.Arg.chatId: String(id),
156+ VK.Arg.fields: "nickname, screen_name, sex, bdate, city, country, timezone, photo_50, photo_100, photo_200_orig, has_mobile, contacts, education, online, counters, relation, last_seen, status, can_write_private_message, can_see_all_posts, can_post, universities",
157+
158+ ]
159+ VK.API.Messages.getChat(params).send(onSuccess: { (response) in
160+ VKData.processMultichat(response)
161+ }, onError: nil) // TODO: Handle error
162+ }
163+
164+}
165+
166+fileprivate extension RealmMedia {
167+ var string: String? {
168+ if let v = value {
169+ switch v {
170+ case .photo(let p):
171+ if let o = p.owner {
172+ var out = "photo\(o.id)_\(p.id)"
173+ p.accessKey >>> {out.append("_\($0)")}
174+ return out
175+ }
176+ case .video(let p):
177+ if let o = p.owner {
178+ var out = "video\(o.id)_\(p.id)"
179+ p.accessKey >>> {out.append("_\($0)")}
180+ return out
181+ }
182+ case .audio(let p):
183+ if let o = p.owner {
184+ return "audio\(o.id)_\(p.id)"
185+ }
186+ case .doc(let p), .graffiti(let p):
187+ if let o = p.owner {
188+ return "doc\(o.id)_\(p.id)"
189+ }
190+ case .wall(let p):
191+ if let o = p.owner {
192+ return "photo\(o.id)_\(p.id)"
193+ }
194+ case .market(let p):
195+ if let o = p.owner {
196+ return "photo\(o.id)_\(p.id)"
197+ }
198+ default: break
199+ }
200+ }
201+ return nil
202+ }
203+}
204+
205+private func URLFor(method: String) -> String {
206+ return "https://api.vk.com/method/" + method
207+}