Merge lp:~dude-from-by/openlp/my-ios-remote into lp:~danielborges93/openlp/ios-remote

Proposed by andrei
Status: Needs review
Proposed branch: lp:~dude-from-by/openlp/my-ios-remote
Merge into: lp:~danielborges93/openlp/ios-remote
Diff against target: 588 lines (+295/-48)
10 files modified
OLP RemoteTests/SettingsViewModelTests.swift (+77/-0)
Podfile (+1/-0)
Podfile.lock (+4/-1)
Remote.xcodeproj/project.pbxproj (+14/-0)
Remote/AppDelegate.swift (+11/-1)
Remote/Classes/Controller/Settings/SettingsControllerPresenter.swift (+2/-4)
Remote/Classes/Controller/Settings/SettingsViewController.swift (+24/-40)
Remote/Classes/Controller/Settings/SettingsViewModel.swift (+73/-0)
Remote/Classes/Network/Service.swift (+4/-2)
Remote/Classes/Util/CountlyClientSideUserPaths.swift (+85/-0)
To merge this branch: bzr merge lp:~dude-from-by/openlp/my-ios-remote
Reviewer Review Type Date Requested Status
Daniel Borges Needs Fixing
Review via email: mp+324257@code.launchpad.net

This proposal supersedes a proposal from 2017-05-16.

Description of the change

Moved SettingsViewController to MVC and added tests
Added Countly and events

To post a comment you must log in.
Revision history for this message
andrei (dude-from-by) wrote :

Events are described in CountlyClientSideUserPaths.swift

first app launch start with no server ip
settings screen was opened without server ip filled
settings screen was closed with server ip filled
received server response - setup complete

Revision history for this message
Daniel Borges (danielborges93) wrote :

The application works well and the analytics too, but the tests aren't running. Can you take a look about this?

OBS: I put the current events on Wekan card.

review: Needs Fixing

Unmerged revisions

65. By andrei

Added countly analytics pod and added events to track users from from start to getting response from server

64. By andrei

Moved SettingsViewController.swift to MVC, added tests

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'OLP RemoteTests/SettingsViewModelTests.swift'
2--- OLP RemoteTests/SettingsViewModelTests.swift 1970-01-01 00:00:00 +0000
3+++ OLP RemoteTests/SettingsViewModelTests.swift 2017-05-18 14:05:49 +0000
4@@ -0,0 +1,77 @@
5+//
6+// SettingsViewModelTests.swift
7+// Remote
8+//
9+// Created by andrei shender on 5/16/17.
10+// Copyright © 2017 OpenLP. All rights reserved.
11+//
12+
13+import XCTest
14+import InAppSettingsKit
15+
16+class SettingsViewModelTests: XCTestCase {
17+
18+ func createModel() -> (defs: UserDefaults, sett: UserSettings, mod: SettingsViewModel) {
19+ let userDefaults = UserDefaults()
20+ let userSettings = UserSettings(defaults: userDefaults)
21+ let settingsViewModel = SettingsViewModel(settings: userSettings)
22+ return (userDefaults, userSettings, settingsViewModel)
23+ }
24+
25+ func testNoAuth() {
26+ let (_, _, settingsViewModel) = createModel()
27+ XCTAssert(settingsViewModel.needAuth.value == false)
28+ }
29+
30+ func testNoCrashNotificationWithoutUserInfo() {
31+ let (_, _, _) = createModel()
32+ NotificationCenter.default.post(Notification(name: NSNotification.Name(rawValue: kIASKAppSettingChanged), object: nil, userInfo: [:]))
33+ NotificationCenter.default.post(Notification(name: NSNotification.Name(rawValue: kIASKAppSettingChanged), object: nil, userInfo: nil))
34+ }
35+
36+ func testSettingNeedAuth() {
37+ let (_, _, settingsViewModel) = createModel()
38+ let userInfo = ["auth.needsAuth" : true]
39+ NotificationCenter.default.post(Notification(name: NSNotification.Name(rawValue: kIASKAppSettingChanged), object: nil, userInfo: userInfo))
40+ XCTAssert(settingsViewModel.needAuth.value)
41+ }
42+
43+ func testDismissWithEmptyServerIP() {
44+ let (userDefaults, _, settingsViewModel) = createModel()
45+ userDefaults.set("", forKey: "server.ip")
46+ settingsViewModel.dismiss()
47+ XCTAssert(settingsViewModel.alert.value.message == Localizable.Error.validIP)
48+ }
49+
50+ func testDismissWithInvalidServerIP() {
51+ let (userDefaults, _, settingsViewModel) = createModel()
52+ userDefaults.set("±±±±", forKey: "server.ip")
53+ settingsViewModel.dismiss()
54+ XCTAssert(settingsViewModel.alert.value.message == Localizable.Error.validIP)
55+ }
56+
57+ func testDismissWithNoServerPort() {
58+ let (userDefaults, _, settingsViewModel) = createModel()
59+ userDefaults.set("1", forKey: "server.ip")
60+ userDefaults.set("", forKey: "server.port")
61+ settingsViewModel.dismiss()
62+ XCTAssert(settingsViewModel.alert.value.message == Localizable.Error.validPort)
63+ }
64+
65+ func testDismissWithInvalidServerPort() {
66+ let (userDefaults, _, settingsViewModel) = createModel()
67+ userDefaults.set("1", forKey: "server.ip")
68+ userDefaults.set("invalid", forKey: "server.port")
69+ settingsViewModel.dismiss()
70+ XCTAssert(settingsViewModel.alert.value.message == Localizable.Error.validPort)
71+ }
72+
73+ func testDismiss() {
74+ let (userDefaults, _, settingsViewModel) = createModel()
75+ userDefaults.set("1", forKey: "server.ip")
76+ userDefaults.set("2", forKey: "server.port")
77+ settingsViewModel.dismiss()
78+ XCTAssert(settingsViewModel.alert.value.message == "")
79+ XCTAssert(settingsViewModel.dismissController.value)
80+ }
81+ }
82
83=== modified file 'Podfile'
84--- Podfile 2017-03-02 07:16:31 +0000
85+++ Podfile 2017-05-18 14:05:49 +0000
86@@ -12,6 +12,7 @@
87 pod 'BlockLooper', :git=>'https://github.com/ashender/BlockLooper.git'
88 pod 'CWStatusBarNotification'
89 pod 'ReactiveCocoa'
90+ pod 'Countly'
91 end
92
93 target 'OLP RemoteTests' do
94
95=== modified file 'Podfile.lock'
96--- Podfile.lock 2017-03-02 07:16:31 +0000
97+++ Podfile.lock 2017-05-18 14:05:49 +0000
98@@ -4,6 +4,7 @@
99 - Alamofire (~> 4.1)
100 - ObjectMapper (~> 2.0)
101 - BlockLooper (0.0.3)
102+ - Countly (16.12)
103 - CWStatusBarNotification (2.3.5)
104 - InAppSettingsKit (2.8.1)
105 - Nimble (5.1.1)
106@@ -35,6 +36,7 @@
107 - Alamofire
108 - AlamofireObjectMapper
109 - BlockLooper (from `https://github.com/ashender/BlockLooper.git`)
110+ - Countly
111 - CWStatusBarNotification
112 - InAppSettingsKit
113 - Nimble
114@@ -57,6 +59,7 @@
115 Alamofire: 856a113053a7bc9cbe5d6367a555d773fc5cfef7
116 AlamofireObjectMapper: 842d2eed43a4d0593ff0110d54ac86a170c4519c
117 BlockLooper: 8bba05fef76734e8e52e39412a0212ac3474776c
118+ Countly: 517a5c221463b29854e214b2193620690b5edb1a
119 CWStatusBarNotification: 3d2738b25c3207f60cc50201388d3c96182545ff
120 InAppSettingsKit: 94d2fba3ccd700fc4e32c6d09fabcc0af2426744
121 Nimble: 415e3aa3267e7bc2c96b05fa814ddea7bb686a29
122@@ -68,6 +71,6 @@
123 TPKeyboardAvoiding: 0d6af20e95e2850f4c621841225b59ec7d8dd852
124 UITextView+Placeholder: 77680995fcdd07c3f52ec92fe1150874a2ac89ff
125
126-PODFILE CHECKSUM: 8bcb0a7a00d7e61acff50967f2778c47912a0880
127+PODFILE CHECKSUM: 315619d4b8576ea53bb0e33054782ee9669fb788
128
129 COCOAPODS: 1.1.1
130
131=== modified file 'Remote.xcodeproj/project.pbxproj'
132--- Remote.xcodeproj/project.pbxproj 2017-04-05 15:55:26 +0000
133+++ Remote.xcodeproj/project.pbxproj 2017-05-18 14:05:49 +0000
134@@ -83,6 +83,10 @@
135 759464A51CEB8493001A56BF /* SettingsControllerPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 759464A41CEB8493001A56BF /* SettingsControllerPresenter.swift */; };
136 759778AE1CF83AC400C2E4B4 /* APITest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 759778AD1CF83AC400C2E4B4 /* APITest.swift */; };
137 75AF930F1E9297220051EB82 /* SearchResultsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 755B3FE11E8CFEE000DA570C /* SearchResultsViewModel.swift */; };
138+ 75BC63271ECDCAB500A21BF2 /* CountlyClientSideUserPaths.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75BC63261ECDCAB500A21BF2 /* CountlyClientSideUserPaths.swift */; };
139+ 75C1BF671ECA1D9C00939F73 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75C1BF661ECA1D9C00939F73 /* SettingsViewModel.swift */; };
140+ 75D100C61ECB2D0B00818402 /* SettingsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75D100C51ECB2D0B00818402 /* SettingsViewModelTests.swift */; };
141+ 75D100C71ECB2E0000818402 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75C1BF661ECA1D9C00939F73 /* SettingsViewModel.swift */; };
142 863574EB78CF355A8463055D /* Pods_Remote.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3194E32378DD6BB8E273BB47 /* Pods_Remote.framework */; };
143 /* End PBXBuildFile section */
144
145@@ -147,6 +151,9 @@
146 759778A31CF839C200C2E4B4 /* OLP RemoteTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "OLP RemoteTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
147 759778A71CF839C200C2E4B4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
148 759778AD1CF83AC400C2E4B4 /* APITest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APITest.swift; sourceTree = "<group>"; };
149+ 75BC63261ECDCAB500A21BF2 /* CountlyClientSideUserPaths.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CountlyClientSideUserPaths.swift; sourceTree = "<group>"; };
150+ 75C1BF661ECA1D9C00939F73 /* SettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = "<group>"; };
151+ 75D100C51ECB2D0B00818402 /* SettingsViewModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewModelTests.swift; sourceTree = "<group>"; };
152 BF194B8FEA335C68D241C125 /* Pods-OLP RemoteTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OLP RemoteTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-OLP RemoteTests/Pods-OLP RemoteTests.release.xcconfig"; sourceTree = "<group>"; };
153 /* End PBXFileReference section */
154
155@@ -217,6 +224,7 @@
156 children = (
157 759464A41CEB8493001A56BF /* SettingsControllerPresenter.swift */,
158 462395801BE505EC00B90146 /* SettingsViewController.swift */,
159+ 75C1BF661ECA1D9C00939F73 /* SettingsViewModel.swift */,
160 );
161 path = Settings;
162 sourceTree = "<group>";
163@@ -278,6 +286,7 @@
164 46A612301C78E75D00B61DC8 /* ColorUtil.swift */,
165 46FD378E1C79867C00DED423 /* ErrorUtil.swift */,
166 46753A9A1E593EAC007872C0 /* LocalizationUtil.swift */,
167+ 75BC63261ECDCAB500A21BF2 /* CountlyClientSideUserPaths.swift */,
168 );
169 path = Util;
170 sourceTree = "<group>";
171@@ -367,6 +376,7 @@
172 750831BA1CFF594400CA9299 /* service_response.json */,
173 750831BC1CFF597D00CA9299 /* slides_response.json */,
174 757326FF1E8C06B7001E618B /* SearchViewModelTests.swift */,
175+ 75D100C51ECB2D0B00818402 /* SettingsViewModelTests.swift */,
176 );
177 path = "OLP RemoteTests";
178 sourceTree = "<group>";
179@@ -595,10 +605,12 @@
180 4623B6971C07BC2E007FFE2B /* SlidesViewController.swift in Sources */,
181 46A9EED81BFD89DF00E8D520 /* ServiceAPI.swift in Sources */,
182 46FD37871C7959EF00DED423 /* SearchAPI.swift in Sources */,
183+ 75C1BF671ECA1D9C00939F73 /* SettingsViewModel.swift in Sources */,
184 4623B69C1C07C569007FFE2B /* ControllerAPI.swift in Sources */,
185 463A91851C092E6B00D572E6 /* DictionaryUtil.swift in Sources */,
186 462395831BE50C5A00B90146 /* ViewControllerUtil.swift in Sources */,
187 46CC0CED1BE6972300423EB4 /* UserSettings.swift in Sources */,
188+ 75BC63271ECDCAB500A21BF2 /* CountlyClientSideUserPaths.swift in Sources */,
189 46FD37891C796C9B00DED423 /* SearchResultViewController.swift in Sources */,
190 463A918D1C093DD500D572E6 /* AlertsViewController.swift in Sources */,
191 755B3FE21E8CFEE000DA570C /* SearchResultsViewModel.swift in Sources */,
192@@ -636,6 +648,7 @@
193 75667B561CFEE949008FDFE0 /* ItemModel.swift in Sources */,
194 75667B591CFEE949008FDFE0 /* LiveModel.swift in Sources */,
195 75667B521CFEE938008FDFE0 /* AddAPI.swift in Sources */,
196+ 75D100C61ECB2D0B00818402 /* SettingsViewModelTests.swift in Sources */,
197 75667B5C1CFEFF7E008FDFE0 /* DictionaryUtil.swift in Sources */,
198 757327001E8C06B7001E618B /* SearchViewModelTests.swift in Sources */,
199 75667B5E1CFEFF86008FDFE0 /* ErrorUtil.swift in Sources */,
200@@ -648,6 +661,7 @@
201 757326FE1E8C060F001E618B /* LocalizationUtil.swift in Sources */,
202 75667B5F1CFEFFA7008FDFE0 /* DisplayAPI.swift in Sources */,
203 75667B4E1CFEE930008FDFE0 /* PollAPI.swift in Sources */,
204+ 75D100C71ECB2E0000818402 /* SettingsViewModel.swift in Sources */,
205 75667B541CFEE93D008FDFE0 /* AlertAPI.swift in Sources */,
206 75667B531CFEE938008FDFE0 /* Service.swift in Sources */,
207 75667B291CFCA8C9008FDFE0 /* UserSettings.swift in Sources */,
208
209=== modified file 'Remote/AppDelegate.swift'
210--- Remote/AppDelegate.swift 2017-04-05 15:55:26 +0000
211+++ Remote/AppDelegate.swift 2017-05-18 14:05:49 +0000
212@@ -24,6 +24,7 @@
213
214 import UIKit
215 import BlockLooper
216+import Countly
217
218 @UIApplicationMain
219 class AppDelegate: UIResponder, UIApplicationDelegate, UITabBarControllerDelegate, ServiceViewControllerDelegate {
220@@ -42,9 +43,10 @@
221 }
222
223 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
224-
225+ startAnalytics()
226 settings = UserSettings(defaults: UserDefaults.standard)
227 settings.registerDefaults()
228+ Countly.sharedInstance().applicationStartWithSettings(settings)
229 service = Service(settings: settings)
230
231 var rootViewControllers = [UINavigationController]()
232@@ -94,6 +96,14 @@
233 useAnimator = false
234 }
235
236+ func startAnalytics() {
237+ let config = CountlyConfig()
238+ config.appKey = "f057136e457a5d2da05c3b569a48caaee9a8644b"
239+ config.host = "https://stats.openlp.io"
240+ config.features = [CLYCrashReporting]
241+ Countly.sharedInstance().start(with: config)
242+ }
243+
244
245 // MARK: - ServiceViewControllerDelegate
246 func serviceViewControllerDidSelectItem(_ item: ItemModel) {
247
248=== modified file 'Remote/Classes/Controller/Settings/SettingsControllerPresenter.swift'
249--- Remote/Classes/Controller/Settings/SettingsControllerPresenter.swift 2017-02-05 02:25:57 +0000
250+++ Remote/Classes/Controller/Settings/SettingsControllerPresenter.swift 2017-05-18 14:05:49 +0000
251@@ -26,13 +26,10 @@
252 import UIKit
253
254 class SettingsControllerPresenter: NSObject {
255-
256- let pollAPI: PollAPI
257 let settings: UserSettings
258 var fromController: UIViewController?
259
260 init(settings: UserSettings, pollAPI: PollAPI) {
261- self.pollAPI = pollAPI
262 self.settings = settings
263 super.init()
264 }
265@@ -46,7 +43,8 @@
266
267
268 func presentSettingViewController() {
269- let settingsController = SettingsViewController(settings:settings, pollAPI: pollAPI)
270+ let settingsViewModel = SettingsViewModel(settings: settings)
271+ let settingsController = SettingsViewController(viewModel: settingsViewModel)
272 let navigationController = UINavigationController(rootViewController: settingsController)
273 guard let fromController = self.fromController else { return }
274 if #available(iOS 9.0, *) {
275
276=== modified file 'Remote/Classes/Controller/Settings/SettingsViewController.swift'
277--- Remote/Classes/Controller/Settings/SettingsViewController.swift 2017-02-19 05:21:00 +0000
278+++ Remote/Classes/Controller/Settings/SettingsViewController.swift 2017-05-18 14:05:49 +0000
279@@ -24,24 +24,37 @@
280
281 import UIKit
282 import InAppSettingsKit
283+import Countly
284
285 class SettingsViewController: IASKAppSettingsViewController {
286
287 fileprivate let hiddenSettingsKeys = ["auth.userID" as NSObject, "auth.password" as NSObject] as Set<NSObject>
288- fileprivate let pollAPI: PollAPI!
289- fileprivate let settings: UserSettings!
290+ fileprivate let viewModel: SettingsViewModel?
291
292- init(settings: UserSettings, pollAPI: PollAPI) {
293- self.pollAPI = pollAPI
294- self.settings = settings
295+ init(viewModel: SettingsViewModel) {
296+ self.viewModel = viewModel
297 super.init(nibName:nil, bundle:nil)
298+ viewModel.needAuth.producer.startWithValues { (needAuth) in
299+ self.setHiddenKeys(needAuth ? [] : self.hiddenSettingsKeys, animated: true)
300+ self.synchronizeSettings()
301+ }
302+
303+ viewModel.alert.producer.skip(first: 1).startWithValues { (alert) in
304+ UIAlertView(title: alert.title, message: alert.message, delegate: nil, cancelButtonTitle: "OK")
305+ .show()
306+ }
307+
308+ viewModel.dismissController.producer.skip(first: 1).startWithValues { (shouldDismiss) in
309+ self.synchronizeSettings()
310+ self.dismiss(animated: true, completion: nil)
311+ }
312+
313 self.setupSettingViewController()
314 self.setupNavigationBar()
315 }
316
317 required init?(coder aDecoder: NSCoder) {
318- self.pollAPI = nil
319- self.settings = nil
320+ viewModel = nil
321 super.init(coder: aDecoder)
322 }
323
324@@ -53,55 +66,26 @@
325 self.tableView.delegate = self
326 self.tableView.dataSource = self
327 }
328-
329+
330 // MARK: My methods
331
332 fileprivate func setupSettingViewController() {
333 self.showDoneButton = false
334 self.showCreditsFooter = false
335 self.delegate = self
336-
337- let needsAuth = settings.needsAuth
338- self.hiddenKeys = needsAuth ? [] : self.hiddenSettingsKeys
339-
340- NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewController.settingsDidChange(_:)), name: NSNotification.Name(rawValue: kIASKAppSettingChanged), object: nil)
341- }
342-
343- @objc fileprivate func settingsDidChange(_ notification: Notification) {
344- if let object = notification.userInfo {
345- if object["auth.needsAuth"] != nil {
346- let enabled = settings.needsAuth
347- let hiddenKeys = enabled ? [] : self.hiddenSettingsKeys
348- self.setHiddenKeys(hiddenKeys, animated: true)
349- }
350- }
351+
352 }
353
354 fileprivate func setupNavigationBar() {
355 self.navigationItem.title = Localizable.settings
356-
357 let strSave = Localizable.ok
358 let btSave = UIBarButtonItem(title: strSave, style: .done, target: self, action: #selector(SettingsViewController.dismissViewController))
359 self.navigationItem.rightBarButtonItem = btSave
360 }
361-
362+
363 @objc fileprivate func dismissViewController() {
364- if !settings.validateServerIP() {
365- let title = Localizable.error
366- let message = Localizable.Error.validIP
367- UIAlertView(title: title, message: message, delegate: nil, cancelButtonTitle: "OK")
368- .show()
369- } else if !settings.validateServerPort() {
370- let title = Localizable.error
371- let message = Localizable.Error.validPort
372- UIAlertView(title: title, message: message, delegate: nil, cancelButtonTitle: "OK")
373- .show()
374- } else {
375- self.synchronizeSettings()
376- self.dismiss(animated: true, completion: nil)
377- }
378+ viewModel?.dismiss()
379 }
380-
381 }
382
383 // MARK: - IASKSettingsDelegate
384
385=== added file 'Remote/Classes/Controller/Settings/SettingsViewModel.swift'
386--- Remote/Classes/Controller/Settings/SettingsViewModel.swift 1970-01-01 00:00:00 +0000
387+++ Remote/Classes/Controller/Settings/SettingsViewModel.swift 2017-05-18 14:05:49 +0000
388@@ -0,0 +1,73 @@
389+/******************************************************************************
390+ * OpenLP iOS Remote *
391+ * --------------------------------------------------------------------------- *
392+ * Copyright (c) 2008-2016 OpenLP Developers *
393+ * --------------------------------------------------------------------------- *
394+ * Permission is hereby granted, free of charge, to any person obtaining a *
395+ * copy of this software and associated documentation files (the "Software"), *
396+ * to deal in the Software without restriction, including without limitation *
397+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, *
398+ * and/or sell copies of the Software, and to permit persons to whom the *
399+ * Software is furnished to do so, subject to the following conditions: *
400+ * *
401+ * The above copyright notice and this permission notice shall be included in *
402+ * all copies or substantial portions of the Software. *
403+ * *
404+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
405+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
406+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
407+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
408+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *
409+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *
410+ * DEALINGS IN THE SOFTWARE. *
411+ ******************************************************************************/
412+
413+
414+import UIKit
415+import ReactiveCocoa
416+import ReactiveSwift
417+import InAppSettingsKit
418+import Countly
419+
420+struct Alert {
421+ let title: String
422+ let message: String
423+}
424+
425+class SettingsViewModel: NSObject {
426+ let needAuth: MutableProperty<Bool>
427+ let settings: UserSettings
428+ let alert: MutableProperty<Alert> = MutableProperty<Alert>(Alert(title: "", message: ""))
429+ let dismissController = MutableProperty<Bool>(false)
430+
431+ init(settings: UserSettings) {
432+ self.needAuth = MutableProperty<Bool>(settings.needsAuth)
433+ self.settings = settings
434+ super.init()
435+ NotificationCenter.default.addObserver(self, selector: #selector(SettingsViewModel.settingsDidChange(_:)), name: NSNotification.Name(rawValue: kIASKAppSettingChanged), object: nil)
436+ Countly.sharedInstance().settingsScreenOpenedWithUserSettings(settings)
437+ }
438+
439+ @objc fileprivate func settingsDidChange(_ notification: Notification) {
440+ if let object = notification.userInfo, let needAuth = object["auth.needsAuth"] as? Bool {
441+ self.needAuth.value = needAuth
442+ }
443+ }
444+
445+ func dismiss() {
446+ if !settings.validateServerIP() {
447+ let title = Localizable.error
448+ let message = Localizable.Error.validIP
449+ alert.value = Alert(title: title, message: message)
450+ } else if !settings.validateServerPort() {
451+ let title = Localizable.error
452+ let message = Localizable.Error.validPort
453+ alert.value = Alert(title: title, message: message)
454+ } else {
455+ dismissController.value = true
456+ if self.settings.serverIP.characters.count != 0 {
457+ Countly.sharedInstance().settingsScreenClosedWithUserSettings(settings)
458+ }
459+ }
460+ }
461+}
462
463=== modified file 'Remote/Classes/Network/Service.swift'
464--- Remote/Classes/Network/Service.swift 2017-04-05 15:55:26 +0000
465+++ Remote/Classes/Network/Service.swift 2017-05-18 14:05:49 +0000
466@@ -27,6 +27,7 @@
467 import Result
468 import ReactiveCocoa
469 import BlockLooper
470+import Countly
471
472
473 protocol PollAPIServiceDelegate {
474@@ -109,14 +110,14 @@
475
476
477 pollSignal
478- .filter({ (result) -> Bool in
479+ .filter() { (result) -> Bool in
480 switch result {
481 case .success:
482 return true
483 default:
484 return false
485 }
486- })
487+ }
488 .observe { (event) in
489 self.handlePollResponse((event.value?.value)!)
490 }
491@@ -157,6 +158,7 @@
492
493 fileprivate func handlePollResponse(_ poll: PollModel?) {
494 if let poll = poll {
495+ Countly.sharedInstance().serverSentNonEmptyResponse()
496 if self.poll.service != poll.service {
497 self.updateService({
498 self.serviceDelegate?.updateSelectedItem(itemId: poll.item)
499
500=== added file 'Remote/Classes/Util/CountlyClientSideUserPaths.swift'
501--- Remote/Classes/Util/CountlyClientSideUserPaths.swift 1970-01-01 00:00:00 +0000
502+++ Remote/Classes/Util/CountlyClientSideUserPaths.swift 2017-05-18 14:05:49 +0000
503@@ -0,0 +1,85 @@
504+/******************************************************************************
505+ * OpenLP iOS Remote *
506+ * --------------------------------------------------------------------------- *
507+ * Copyright (c) 2008-2016 OpenLP Developers *
508+ * --------------------------------------------------------------------------- *
509+ * Permission is hereby granted, free of charge, to any person obtaining a *
510+ * copy of this software and associated documentation files (the "Software"), *
511+ * to deal in the Software without restriction, including without limitation *
512+ * the rights to use, copy, modify, merge, publish, distribute, sublicense, *
513+ * and/or sell copies of the Software, and to permit persons to whom the *
514+ * Software is furnished to do so, subject to the following conditions: *
515+ * *
516+ * The above copyright notice and this permission notice shall be included in *
517+ * all copies or substantial portions of the Software. *
518+ * *
519+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
520+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
521+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE *
522+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
523+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *
524+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *
525+ * DEALINGS IN THE SOFTWARE. *
526+ ******************************************************************************/
527+
528+import UIKit
529+import Countly
530+
531+extension Countly {
532+
533+ enum UserPathFirstExperienceState : String {
534+ case Started = "started"
535+ case SettingsOpenedWithEmptyServerAddress = "SettingsOpenedWithEmptyServerAddress"
536+ case SettingsClosedWithFilledServerAddress = "SettingsClosedWithFilledServerAddress"
537+ case GotNonEmptyResponseFromServer = "GotNonEmptyResponseFromServer"
538+ }
539+
540+ static let firstExperiencePathStartedKey = "userPath_firstExperienceState"
541+
542+ func applicationStartWithSettings(_ settings: UserSettings) {
543+ if firstExperiencePathState() == .none, settings.serverIP.characters.count == 0 {
544+ startFirstExperienceUserPath()
545+ type(of: self).sharedInstance().recordEvent("first app launch start with no server ip")
546+ }
547+ }
548+
549+ func settingsScreenOpenedWithUserSettings(_ settings: UserSettings) {
550+ if let state = firstExperiencePathState(), state == .Started, settings.serverIP.characters.count == 0 {
551+ updateFirstExperiencePathState(newState: .SettingsOpenedWithEmptyServerAddress)
552+ type(of: self).sharedInstance().recordEvent("settings screen was opened without server ip filled")
553+ }
554+ }
555+
556+ func settingsScreenClosedWithUserSettings(_ settings: UserSettings) {
557+ if let state = firstExperiencePathState(), state == .SettingsOpenedWithEmptyServerAddress, settings.serverIP.characters.count != 0 {
558+ updateFirstExperiencePathState(newState: .SettingsClosedWithFilledServerAddress)
559+ type(of: self).sharedInstance().recordEvent("settings screen was closed with server ip filled")
560+ }
561+ }
562+
563+ func serverSentNonEmptyResponse() {
564+ if let state = firstExperiencePathState(), state == .SettingsClosedWithFilledServerAddress {
565+ updateFirstExperiencePathState(newState: .GotNonEmptyResponseFromServer)
566+ type(of: self).sharedInstance().recordEvent("received server response - setup complete")
567+ }
568+ }
569+
570+ // MARK: private
571+
572+ fileprivate func startFirstExperienceUserPath() {
573+ updateFirstExperiencePathState(newState: .Started)
574+ }
575+
576+ fileprivate func firstExperiencePathState() -> UserPathFirstExperienceState? {
577+ if let state = UserDefaults.standard.object(forKey: type(of: self).firstExperiencePathStartedKey) as? String {
578+ return UserPathFirstExperienceState(rawValue:state)
579+ }
580+ else {
581+ return Optional.none
582+ }
583+ }
584+
585+ fileprivate func updateFirstExperiencePathState(newState: UserPathFirstExperienceState) {
586+ UserDefaults.standard.set(newState.rawValue, forKey: type(of: self).firstExperiencePathStartedKey)
587+ }
588+}

Subscribers

People subscribed via source and target branches

to all changes: