git.iko.soy/dashared/hookah-tau
on develop help sign in
Merge pull request #16 from dashared/client/api-registration

Client/api registration: API регистрации + хранение данных + обработки ввода пользователя

commit3a43f0ec20d4fff139c808643a623dc79e06e50c authorDarya Rednikina <31587869+dashared@users.noreply.github.com> dateMon 4 Nov 2019, 17:18 (7 years ago) parentbd3410e8cbc5a3
34 files changed+955−109
DataStorage.swift+55 Hookah Tau.xcodeproj/project.pbxproj+97−6 Hookah Tau.xcodeproj/project.xcworkspace/contents.xcworkspacedata+1−1 Hookah Tau.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved+16 Hookah Tau.xcodeproj/xcshareddata/xcschemes/Hookah Tau.xcscheme+98 Hookah Tau/API/APIClient.swift+64 Hookah Tau/API/ApiRequest.swift+43 Hookah Tau/API/ApiResolver.swift+19 Hookah Tau/API/Authorization api/AuthorizationService.swift+109 Hookah Tau/API/Authorization api/Resolvers/AuthResolver.swift+50 Hookah Tau/API/Authorization api/Resolvers/PhoneCodeResolver.swift+48 Hookah Tau/API/Authorization api/Resolvers/RegistrationResolver.swift+52 Hookah Tau/API/MyCodable.swift+25 Hookah Tau/API/ServerError.swift+21 Hookah Tau/Controllers/ApplicationCoordinator.swift+1−1 Hookah Tau/Controllers/Authentication/Code from sms/CodeCoordinator.swift+3−10 Hookah Tau/Controllers/Authentication/Code from sms/CodeSmsViewController.swift+38−3 Hookah Tau/Controllers/Authentication/Name/NameCoordinator.swift+6−7 Hookah Tau/Controllers/Authentication/Name/NameViewController.swift+9−1 Hookah Tau/Controllers/Authentication/Phone/PhoneCoordinator.swift+16−9 Hookah Tau/Controllers/Authentication/Phone/PhoneNumberViewController.swift+23−1 Hookah Tau/Controllers/BaseCoordinator.swift+4 Hookah Tau/Controllers/BaseViewController.swift+16−1 Hookah Tau/Controllers/Client/Profile/ProfileChangeCoordinator.swift+1−3 Hookah Tau/Controllers/Client/Profile/ProfileChangeViewController.swift+2−2 Hookah Tau/Controllers/Client/Profile/ProfileClientViewController.swift+2−2 Hookah Tau/Controllers/Client/Profile/ProfileCoordinator.swift+1−1 Hookah Tau/Controllers/Client/Profile/Views/ChangeView.swift+1−1 Hookah Tau/Controllers/Client/Profile/Views/ProfileView.swift+2−2 Hookah Tau/Controllers/Client/Tab bar/ClientTabbarCoordinator.swift−4 Hookah Tau/Models/UserModel.swift → Hookah Tau/Models/User.swift+2−2 Hookah Tau/Views/Code/CodeView.swift+52−20 Hookah Tau/Views/Code/CodeView.xib+33−31 Hookah Tau/Views/Phone/PhoneView.swift+45−1
DataStorage.swiftnew file+55
@@ -0,0 +1,55 @@
1+//
2+// DataStorage.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 01/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+/// Класс для хранения промежуточного состояния регистрации,
12+/// данных пользователя, ...
13+class DataStorage {
14+
15+ enum Keys: String {
16+ case name
17+ case phone
18+ case isAdmin
19+ case loggedIn
20+ }
21+
22+ private init() {}
23+
24+ static let standard = DataStorage()
25+
26+ /// Номер телефона для процесса регистрации/авторизации
27+ var phone: String?
28+ /// Имя введенное при регистрации
29+ var name: String?
30+
31+ /// Метод для сохранения данных пользователя в приложении,
32+ /// используется для сохранения объекта, полученного после успешной
33+ /// авторизации, регистрации
34+ func saveUserModel(_ user: User) {
35+ let name = user.name
36+ let phone = user.phoneNumber
37+ let isAdmin = user.isAdmin
38+
39+ UserDefaults.standard.setValue(name, forKey: Keys.name.rawValue)
40+ UserDefaults.standard.setValue(phone, forKey: Keys.phone.rawValue)
41+ UserDefaults.standard.setValue(isAdmin, forKey: Keys.isAdmin.rawValue)
42+ }
43+
44+ func getUserModel() -> User? {
45+ guard
46+ let name = UserDefaults.standard.string(forKey: Keys.name.rawValue),
47+ let phone = UserDefaults.standard.string(forKey: Keys.phone.rawValue)
48+ else { return nil }
49+
50+ let isAdmin = UserDefaults.standard.bool(forKey: Keys.isAdmin.rawValue)
51+
52+ return User(name: name, phoneNumber: phone, isAdmin: isAdmin)
53+ }
54+
55+}
Hookah Tau.xcodeproj/project.pbxproj+97−6
@@ -3,10 +3,12 @@
33 archiveVersion = 1;
44 classes = {
55 };
6− objectVersion = 50;
6+ objectVersion = 52;
77 objects = {
88
99/* Begin PBXBuildFile section */
10+ 12118B9C23682022006BAEE3 /* RegistrationResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12118B9B23682022006BAEE3 /* RegistrationResolver.swift */; };
11+ 12118B9E23682049006BAEE3 /* PhoneCodeResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12118B9D23682049006BAEE3 /* PhoneCodeResolver.swift */; };
1012 12219CEF2353A63F0057DDEE /* ReservationsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12219CEE2353A63E0057DDEE /* ReservationsCoordinator.swift */; };
1113 123615CF235510BC00D1122A /* ProfileView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 123615CE235510BC00D1122A /* ProfileView.xib */; };
1214 123615D12355141600D1122A /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 123615D02355141600D1122A /* ProfileView.swift */; };
@@ -16,8 +18,16 @@
1618 1246B71C235CDF3A004CBFA1 /* ReservationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1246B71B235CDF3A004CBFA1 /* ReservationCell.swift */; };
1719 1246B71E235CDF8A004CBFA1 /* ReservationView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1246B71D235CDF8A004CBFA1 /* ReservationView.xib */; };
1820 1246B720235CDF9E004CBFA1 /* ReservationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1246B71F235CDF9E004CBFA1 /* ReservationView.swift */; };
21+ 12838EA9236764A600CEF108 /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12838EA8236764A600CEF108 /* APIClient.swift */; };
22+ 12838EAC2367689600CEF108 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 12838EAB2367689600CEF108 /* Alamofire */; };
23+ 12838EAE2367701300CEF108 /* AuthorizationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12838EAD2367701300CEF108 /* AuthorizationService.swift */; };
24+ 12838EB0236777D400CEF108 /* ServerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12838EAF236777D400CEF108 /* ServerError.swift */; };
25+ 12838EB2236779A000CEF108 /* AuthResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12838EB1236779A000CEF108 /* AuthResolver.swift */; };
26+ 12838EB4236779C700CEF108 /* ApiResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12838EB3236779C700CEF108 /* ApiResolver.swift */; };
27+ 12838EB623677FB900CEF108 /* ApiRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12838EB523677FB900CEF108 /* ApiRequest.swift */; };
28+ 12838EB82367834900CEF108 /* MyCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12838EB72367834900CEF108 /* MyCodable.swift */; };
1929 12B6A60F235B857A00898B31 /* ProfileChangeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B6A60E235B857A00898B31 /* ProfileChangeCoordinator.swift */; };
20− 12B6A611235B8EC500898B31 /* UserModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B6A610235B8EC500898B31 /* UserModel.swift */; };
30+ 12B6A611235B8EC500898B31 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B6A610235B8EC500898B31 /* User.swift */; };
2131 12B6A613235B9B4300898B31 /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B6A612235B9B4300898B31 /* BaseViewController.swift */; };
2232 12B6A615235BB36C00898B31 /* ChangeView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 12B6A614235BB36C00898B31 /* ChangeView.xib */; };
2333 12B6A617235BB37D00898B31 /* ChangeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B6A616235BB37D00898B31 /* ChangeView.swift */; };
@@ -32,6 +42,7 @@
3242 12EB698123661CB300E4D1CE /* AddressCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12EB698023661CB300E4D1CE /* AddressCell.swift */; };
3343 12EE4A76234BB8FE005B4D1A /* CodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12EE4A75234BB8FE005B4D1A /* CodeView.swift */; };
3444 12F73B022357AD7B007D4476 /* ProfileChangeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F73B012357AD7B007D4476 /* ProfileChangeViewController.swift */; };
45+ 12FE4042236B8B050012391E /* DataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12FE4041236B8B050012391E /* DataStorage.swift */; };
3546 F30369B5233679EA00A8EA84 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F30369B4233679EA00A8EA84 /* LaunchScreen.storyboard */; };
3647 F30369BE2336924D00A8EA84 /* ClientTabbarCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30369BD2336924D00A8EA84 /* ClientTabbarCoordinator.swift */; };
3748 F30369C02336925800A8EA84 /* ClientTabbarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30369BF2336925800A8EA84 /* ClientTabbarController.swift */; };
@@ -80,6 +91,8 @@
8091/* End PBXContainerItemProxy section */
8192
8293/* Begin PBXFileReference section */
94+ 12118B9B23682022006BAEE3 /* RegistrationResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationResolver.swift; sourceTree = "<group>"; };
95+ 12118B9D23682049006BAEE3 /* PhoneCodeResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneCodeResolver.swift; sourceTree = "<group>"; };
8396 12219CEE2353A63E0057DDEE /* ReservationsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReservationsCoordinator.swift; sourceTree = "<group>"; };
8497 123615CE235510BC00D1122A /* ProfileView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ProfileView.xib; sourceTree = "<group>"; };
8598 123615D02355141600D1122A /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
@@ -89,8 +102,15 @@
89102 1246B71B235CDF3A004CBFA1 /* ReservationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReservationCell.swift; sourceTree = "<group>"; };
90103 1246B71D235CDF8A004CBFA1 /* ReservationView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ReservationView.xib; sourceTree = "<group>"; };
91104 1246B71F235CDF9E004CBFA1 /* ReservationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReservationView.swift; sourceTree = "<group>"; };
105+ 12838EA8236764A600CEF108 /* APIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIClient.swift; sourceTree = "<group>"; };
106+ 12838EAD2367701300CEF108 /* AuthorizationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationService.swift; sourceTree = "<group>"; };
107+ 12838EAF236777D400CEF108 /* ServerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerError.swift; sourceTree = "<group>"; };
108+ 12838EB1236779A000CEF108 /* AuthResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthResolver.swift; sourceTree = "<group>"; };
109+ 12838EB3236779C700CEF108 /* ApiResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiResolver.swift; sourceTree = "<group>"; };
110+ 12838EB523677FB900CEF108 /* ApiRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiRequest.swift; sourceTree = "<group>"; };
111+ 12838EB72367834900CEF108 /* MyCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyCodable.swift; sourceTree = "<group>"; };
92112 12B6A60E235B857A00898B31 /* ProfileChangeCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileChangeCoordinator.swift; sourceTree = "<group>"; };
93− 12B6A610235B8EC500898B31 /* UserModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserModel.swift; sourceTree = "<group>"; };
113+ 12B6A610235B8EC500898B31 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
94114 12B6A612235B9B4300898B31 /* BaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = "<group>"; };
95115 12B6A614235BB36C00898B31 /* ChangeView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ChangeView.xib; sourceTree = "<group>"; };
96116 12B6A616235BB37D00898B31 /* ChangeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeView.swift; sourceTree = "<group>"; };
@@ -105,6 +125,7 @@
105125 12EB698023661CB300E4D1CE /* AddressCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressCell.swift; sourceTree = "<group>"; };
106126 12EE4A75234BB8FE005B4D1A /* CodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeView.swift; sourceTree = "<group>"; };
107127 12F73B012357AD7B007D4476 /* ProfileChangeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileChangeViewController.swift; sourceTree = "<group>"; };
128+ 12FE4041236B8B050012391E /* DataStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStorage.swift; sourceTree = SOURCE_ROOT; };
108129 F30369B4233679EA00A8EA84 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
109130 F30369BD2336924D00A8EA84 /* ClientTabbarCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientTabbarCoordinator.swift; sourceTree = "<group>"; };
110131 F30369BF2336925800A8EA84 /* ClientTabbarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientTabbarController.swift; sourceTree = "<group>"; };
@@ -146,6 +167,7 @@
146167 isa = PBXFrameworksBuildPhase;
147168 buildActionMask = 2147483647;
148169 files = (
170+ 12838EAC2367689600CEF108 /* Alamofire in Frameworks */,
149171 );
150172 runOnlyForDeploymentPostprocessing = 0;
151173 };
@@ -166,6 +188,25 @@
166188/* End PBXFrameworksBuildPhase section */
167189
168190/* Begin PBXGroup section */
191+ 12118B9A2368187B006BAEE3 /* Authorization api */ = {
192+ isa = PBXGroup;
193+ children = (
194+ 12838EAD2367701300CEF108 /* AuthorizationService.swift */,
195+ 12118B9F23682061006BAEE3 /* Resolvers */,
196+ );
197+ path = "Authorization api";
198+ sourceTree = "<group>";
199+ };
200+ 12118B9F23682061006BAEE3 /* Resolvers */ = {
201+ isa = PBXGroup;
202+ children = (
203+ 12838EB1236779A000CEF108 /* AuthResolver.swift */,
204+ 12118B9D23682049006BAEE3 /* PhoneCodeResolver.swift */,
205+ 12118B9B23682022006BAEE3 /* RegistrationResolver.swift */,
206+ );
207+ path = Resolvers;
208+ sourceTree = "<group>";
209+ };
169210 1212F1312353A5F6005BEF8A /* Profile */ = {
170211 isa = PBXGroup;
171212 children = (
@@ -218,6 +259,19 @@
218259 path = Cells;
219260 sourceTree = "<group>";
220261 };
262+ 12838EB923679AEB00CEF108 /* API */ = {
263+ isa = PBXGroup;
264+ children = (
265+ 12838EA8236764A600CEF108 /* APIClient.swift */,
266+ 12838EB3236779C700CEF108 /* ApiResolver.swift */,
267+ 12838EAF236777D400CEF108 /* ServerError.swift */,
268+ 12838EB523677FB900CEF108 /* ApiRequest.swift */,
269+ 12838EB72367834900CEF108 /* MyCodable.swift */,
270+ 12118B9A2368187B006BAEE3 /* Authorization api */,
271+ );
272+ path = API;
273+ sourceTree = "<group>";
274+ };
221275 12C72EDB235367CF00AD7004 /* Tab bar */ = {
222276 isa = PBXGroup;
223277 children = (
@@ -302,8 +356,8 @@
302356 children = (
303357 F31E20C82348E0F300BEA215 /* AuthorizationViewController.swift */,
304358 F3895F51233D354300FA6A37 /* Phone */,
305− F3895F50233D353600FA6A37 /* Code from sms */,
306359 F3895F4F233D352400FA6A37 /* Name */,
360+ F3895F50233D353600FA6A37 /* Code from sms */,
307361 );
308362 path = Authentication;
309363 sourceTree = "<group>";
@@ -339,7 +393,7 @@
339393 isa = PBXGroup;
340394 children = (
341395 F3851B2F2340A513009EE0B6 /* RegistationViewModel.swift */,
342− 12B6A610235B8EC500898B31 /* UserModel.swift */,
396+ 12B6A610235B8EC500898B31 /* User.swift */,
343397 );
344398 path = Models;
345399 sourceTree = "<group>";
@@ -432,9 +486,11 @@
432486 F3FC7DCB2315BE610080D851 /* Hookah Tau */ = {
433487 isa = PBXGroup;
434488 children = (
489+ 12FE4041236B8B050012391E /* DataStorage.swift */,
435490 F3FC7DCC2315BE610080D851 /* AppDelegate.swift */,
436491 F3DC59FE233E893400B5E751 /* Extensions+UIFont.swift */,
437492 F31E20C62348DDB900BEA215 /* Extensions+UIView.swift */,
493+ 12838EB923679AEB00CEF108 /* API */,
438494 F30369BC2336861100A8EA84 /* Models */,
439495 F3199E59233FD7F700F3C17A /* Views */,
440496 F30369B92336844500A8EA84 /* Components */,
@@ -479,6 +535,9 @@
479535 dependencies = (
480536 );
481537 name = "Hookah Tau";
538+ packageProductDependencies = (
539+ 12838EAB2367689600CEF108 /* Alamofire */,
540+ );
482541 productName = "Hookah Tau";
483542 productReference = F3FC7DC92315BE610080D851 /* Hookah Tau.app */;
484543 productType = "com.apple.product-type.application";
@@ -551,6 +610,9 @@
551610 Base,
552611 );
553612 mainGroup = F3FC7DC02315BE610080D851;
613+ packageReferences = (
614+ 12838EAA2367689600CEF108 /* XCRemoteSwiftPackageReference "Alamofire" */,
615+ );
554616 productRefGroup = F3FC7DCA2315BE610080D851 /* Products */;
555617 projectDirPath = "";
556618 projectRoot = "";
@@ -606,6 +668,8 @@
606668 12EA99482362B684005D9FE9 /* AddresViewController.swift in Sources */,
607669 1246B719235CD3AD004CBFA1 /* NoReservationsView.swift in Sources */,
608670 F3895F4A233D323700FA6A37 /* CodeCoordinator.swift in Sources */,
671+ 12838EB4236779C700CEF108 /* ApiResolver.swift in Sources */,
672+ 12838EA9236764A600CEF108 /* APIClient.swift in Sources */,
609673 123615D12355141600D1122A /* ProfileView.swift in Sources */,
610674 12F73B022357AD7B007D4476 /* ProfileChangeViewController.swift in Sources */,
611675 F3851B2E2340A444009EE0B6 /* TitleTextView.swift in Sources */,
@@ -613,23 +677,30 @@
613677 12B6A613235B9B4300898B31 /* BaseViewController.swift in Sources */,
614678 F3FC7DFF2315C8D70080D851 /* NameCoordinator.swift in Sources */,
615679 F3FC7DCF2315BE610080D851 /* NameViewController.swift in Sources */,
680+ 12838EB82367834900CEF108 /* MyCodable.swift in Sources */,
616681 F3DC5A05233E89D500B5E751 /* Button.swift in Sources */,
617682 F3FC7DFB2315C25D0080D851 /* ApplicationCoordinator.swift in Sources */,
618683 12EB698123661CB300E4D1CE /* AddressCell.swift in Sources */,
619684 F3895F48233D2EEA00FA6A37 /* CodeSmsViewController.swift in Sources */,
620685 12219CEF2353A63F0057DDEE /* ReservationsCoordinator.swift in Sources */,
686+ 12838EB2236779A000CEF108 /* AuthResolver.swift in Sources */,
687+ 12FE4042236B8B050012391E /* DataStorage.swift in Sources */,
621688 1246B71C235CDF3A004CBFA1 /* ReservationCell.swift in Sources */,
622689 1246B720235CDF9E004CBFA1 /* ReservationView.swift in Sources */,
623690 F3895F4E233D348B00FA6A37 /* PhoneCoordinator.swift in Sources */,
624691 12B6A617235BB37D00898B31 /* ChangeView.swift in Sources */,
625692 F3FC7DCD2315BE610080D851 /* AppDelegate.swift in Sources */,
693+ 12838EB623677FB900CEF108 /* ApiRequest.swift in Sources */,
694+ 12118B9E23682049006BAEE3 /* PhoneCodeResolver.swift in Sources */,
626695 F30369BE2336924D00A8EA84 /* ClientTabbarCoordinator.swift in Sources */,
627696 F3FC7DFD2315C4710080D851 /* BaseCoordinator.swift in Sources */,
628697 F3DC59FF233E893400B5E751 /* Extensions+UIFont.swift in Sources */,
629698 F3895F53233D5EEB00FA6A37 /* BlackButtonStyle.swift in Sources */,
699+ 12118B9C23682022006BAEE3 /* RegistrationResolver.swift in Sources */,
630700 F30369C02336925800A8EA84 /* ClientTabbarController.swift in Sources */,
631701 F31E20C72348DDB900BEA215 /* Extensions+UIView.swift in Sources */,
632702 F3851B302340A513009EE0B6 /* RegistationViewModel.swift in Sources */,
703+ 12838EAE2367701300CEF108 /* AuthorizationService.swift in Sources */,
633704 12C72EDF23537D1B00AD7004 /* ProfileCoordinator.swift in Sources */,
634705 12EA994E2362FCE8005D9FE9 /* AddressView.swift in Sources */,
635706 F3895F4C233D347E00FA6A37 /* PhoneNumberViewController.swift in Sources */,
@@ -640,8 +711,9 @@
640711 F31E20C92348E0F300BEA215 /* AuthorizationViewController.swift in Sources */,
641712 F3851B2923409F8A009EE0B6 /* PhoneView.swift in Sources */,
642713 12C72EE3235380EB00AD7004 /* ReservationsViewController.swift in Sources */,
643− 12B6A611235B8EC500898B31 /* UserModel.swift in Sources */,
714+ 12B6A611235B8EC500898B31 /* User.swift in Sources */,
644715 12B6A60F235B857A00898B31 /* ProfileChangeCoordinator.swift in Sources */,
716+ 12838EB0236777D400CEF108 /* ServerError.swift in Sources */,
645717 );
646718 runOnlyForDeploymentPostprocessing = 0;
647719 };
@@ -953,6 +1025,25 @@
9531025 defaultConfigurationName = Release;
9541026 };
9551027/* End XCConfigurationList section */
1028+
1029+/* Begin XCRemoteSwiftPackageReference section */
1030+ 12838EAA2367689600CEF108 /* XCRemoteSwiftPackageReference "Alamofire" */ = {
1031+ isa = XCRemoteSwiftPackageReference;
1032+ repositoryURL = "https://github.com/Alamofire/Alamofire";
1033+ requirement = {
1034+ kind = exactVersion;
1035+ version = "5.0.0-rc.3";
1036+ };
1037+ };
1038+/* End XCRemoteSwiftPackageReference section */
1039+
1040+/* Begin XCSwiftPackageProductDependency section */
1041+ 12838EAB2367689600CEF108 /* Alamofire */ = {
1042+ isa = XCSwiftPackageProductDependency;
1043+ package = 12838EAA2367689600CEF108 /* XCRemoteSwiftPackageReference "Alamofire" */;
1044+ productName = Alamofire;
1045+ };
1046+/* End XCSwiftPackageProductDependency section */
9561047 };
9571048 rootObject = F3FC7DC12315BE610080D851 /* Project object */;
9581049}
Hookah Tau.xcodeproj/project.xcworkspace/contents.xcworkspacedata+1−1
@@ -2,6 +2,6 @@
22<Workspace
33 version = "1.0">
44 <FileRef
5− location = "self:Hookah Tau.xcodeproj">
5+ location = "self:">
66 </FileRef>
77</Workspace>
Hookah Tau.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolvednew file+16
@@ -0,0 +1,16 @@
1+{
2+ "object": {
3+ "pins": [
4+ {
5+ "package": "Alamofire",
6+ "repositoryURL": "https://github.com/Alamofire/Alamofire",
7+ "state": {
8+ "branch": null,
9+ "revision": "2cbf59935fbb1f26e352ce4db53f1cf9408d5313",
10+ "version": "5.0.0-rc.3"
11+ }
12+ }
13+ ]
14+ },
15+ "version": 1
16+}
Hookah Tau.xcodeproj/xcshareddata/xcschemes/Hookah Tau.xcschemenew file+98
@@ -0,0 +1,98 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<Scheme
3+ LastUpgradeVersion = "1100"
4+ version = "1.3">
5+ <BuildAction
6+ parallelizeBuildables = "YES"
7+ buildImplicitDependencies = "YES">
8+ <BuildActionEntries>
9+ <BuildActionEntry
10+ buildForTesting = "YES"
11+ buildForRunning = "YES"
12+ buildForProfiling = "YES"
13+ buildForArchiving = "YES"
14+ buildForAnalyzing = "YES">
15+ <BuildableReference
16+ BuildableIdentifier = "primary"
17+ BlueprintIdentifier = "F3FC7DC82315BE610080D851"
18+ BuildableName = "Hookah Tau.app"
19+ BlueprintName = "Hookah Tau"
20+ ReferencedContainer = "container:Hookah Tau.xcodeproj">
21+ </BuildableReference>
22+ </BuildActionEntry>
23+ </BuildActionEntries>
24+ </BuildAction>
25+ <TestAction
26+ buildConfiguration = "Debug"
27+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
28+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29+ shouldUseLaunchSchemeArgsEnv = "YES">
30+ <Testables>
31+ <TestableReference
32+ skipped = "NO">
33+ <BuildableReference
34+ BuildableIdentifier = "primary"
35+ BlueprintIdentifier = "F3FC7DDC2315BE630080D851"
36+ BuildableName = "Hookah TauTests.xctest"
37+ BlueprintName = "Hookah TauTests"
38+ ReferencedContainer = "container:Hookah Tau.xcodeproj">
39+ </BuildableReference>
40+ </TestableReference>
41+ <TestableReference
42+ skipped = "NO">
43+ <BuildableReference
44+ BuildableIdentifier = "primary"
45+ BlueprintIdentifier = "F3FC7DE72315BE630080D851"
46+ BuildableName = "Hookah TauUITests.xctest"
47+ BlueprintName = "Hookah TauUITests"
48+ ReferencedContainer = "container:Hookah Tau.xcodeproj">
49+ </BuildableReference>
50+ </TestableReference>
51+ </Testables>
52+ </TestAction>
53+ <LaunchAction
54+ buildConfiguration = "Debug"
55+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
56+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
57+ launchStyle = "0"
58+ useCustomWorkingDirectory = "NO"
59+ ignoresPersistentStateOnLaunch = "NO"
60+ debugDocumentVersioning = "YES"
61+ debugServiceExtension = "internal"
62+ allowLocationSimulation = "YES">
63+ <BuildableProductRunnable
64+ runnableDebuggingMode = "0">
65+ <BuildableReference
66+ BuildableIdentifier = "primary"
67+ BlueprintIdentifier = "F3FC7DC82315BE610080D851"
68+ BuildableName = "Hookah Tau.app"
69+ BlueprintName = "Hookah Tau"
70+ ReferencedContainer = "container:Hookah Tau.xcodeproj">
71+ </BuildableReference>
72+ </BuildableProductRunnable>
73+ </LaunchAction>
74+ <ProfileAction
75+ buildConfiguration = "Release"
76+ shouldUseLaunchSchemeArgsEnv = "YES"
77+ savedToolIdentifier = ""
78+ useCustomWorkingDirectory = "NO"
79+ debugDocumentVersioning = "YES">
80+ <BuildableProductRunnable
81+ runnableDebuggingMode = "0">
82+ <BuildableReference
83+ BuildableIdentifier = "primary"
84+ BlueprintIdentifier = "F3FC7DC82315BE610080D851"
85+ BuildableName = "Hookah Tau.app"
86+ BlueprintName = "Hookah Tau"
87+ ReferencedContainer = "container:Hookah Tau.xcodeproj">
88+ </BuildableReference>
89+ </BuildableProductRunnable>
90+ </ProfileAction>
91+ <AnalyzeAction
92+ buildConfiguration = "Debug">
93+ </AnalyzeAction>
94+ <ArchiveAction
95+ buildConfiguration = "Release"
96+ revealArchiveInOrganizer = "YES">
97+ </ArchiveAction>
98+</Scheme>
Hookah Tau/API/APIClient.swiftnew file+64
@@ -0,0 +1,64 @@
1+//
2+// APIClient.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 28/10/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+import Alamofire
11+
12+/// http://iko.soy/hookah-tau-docs/latest/index.html
13+class APIClient {
14+ typealias CompletionBlock = (Result<AFDataResponse<Data?>, GeneralError>) -> Void
15+
16+ private init() {}
17+
18+ /// Shared instance of the client
19+ static let shared = APIClient()
20+
21+ enum BaseUrls {
22+ static let staging = "https://hookah-tau-staging.herokuapp.com"
23+ // static let production = ""
24+ }
25+
26+ /// Basic
27+ func load(request: URLRequest, completion: @escaping CompletionBlock) {
28+ AF.request(request).response { response in
29+
30+ if let error = self.getErrorFromResponce(response) {
31+ completion(.failure(error))
32+ return
33+ }
34+
35+ completion(.success(response))
36+ }
37+ }
38+
39+ /// Обработка ошибок которую мы заслужили
40+ private func getErrorFromResponce(_ response: AFDataResponse<Data?>) -> GeneralError? {
41+ if let statusCode = response.response?.statusCode {
42+ switch statusCode {
43+ case 200...299:
44+ return nil
45+ case 400:
46+ // спасибо илья я печатаю ошибки хаскеля в консоль ура
47+ if let data = response.data {
48+ let json = String(data: data, encoding: String.Encoding.utf8)
49+ print("Failure Response: \(String(describing: json))")
50+ }
51+ case 422:
52+ if let data = response.data, let error = SE.fromJSONToSelf(data: data) {
53+ return GeneralError.serverError(error)
54+ }
55+
56+ fallthrough // прекрасно, я знаю
57+ default:
58+ return GeneralError.somethingWentCompletelyWrong
59+ }
60+ }
61+
62+ return GeneralError.somethingWentCompletelyWrong
63+ }
64+}
Hookah Tau/API/ApiRequest.swiftnew file+43
@@ -0,0 +1,43 @@
1+//
2+// ApiRequest.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 28/10/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+enum CrudMethod: String {
12+ case post = "POST"
13+ case put = "PUT"
14+ case delete = "DELETE"
15+ case get = "GET"
16+}
17+
18+class ApiRequest {
19+
20+ // MARK: - Properties
21+
22+ var request: URLRequest
23+
24+ var httpMehtod: CrudMethod
25+
26+ var resolver: ApiResolver
27+
28+ // MARK: - Init
29+
30+ init(resolver: ApiResolver, httpMethod: CrudMethod) {
31+ self.resolver = resolver
32+ self.httpMehtod = httpMethod
33+
34+ let url = URL(string: APIClient.BaseUrls.staging + (resolver.groupName ?? "") + resolver.name)!
35+ self.request = URLRequest(url: url)
36+
37+ self.request.httpBody = resolver.parameters().toJSONData()
38+ self.request.setValue("application/json;charset=utf-8",
39+ forHTTPHeaderField: "Content-Type")
40+ self.request.httpMethod = httpMethod.rawValue
41+ }
42+
43+}
Hookah Tau/API/ApiResolver.swiftnew file+19
@@ -0,0 +1,19 @@
1+//
2+// Resolver.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 28/10/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+protocol ApiResolver: class {
12+
13+ var name: String { get }
14+
15+ var groupName: String? { get }
16+
17+ /// Parameter for request
18+ func parameters() -> MyCodable
19+}
Hookah Tau/API/Authorization api/AuthorizationService.swiftnew file+109
@@ -0,0 +1,109 @@
1+//
2+// AuthorizationService.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 28/10/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+import Alamofire
11+
12+/// Слой методов для авторизации пользователя в приложении
13+class AuthorizationService {
14+ private var apiClient: APIClient
15+
16+ init(apiClient: APIClient) {
17+ self.apiClient = apiClient
18+ }
19+
20+ /// Check if phone number is registered and send verification code to that number.
21+ /// - Parameter phoneNumber: (_xx) xxx xx xx - 9 numbers withount +7 9...
22+ /// - Parameter completion: isUserRegistered and Error
23+ func authenticate(withPhone phoneNumber: String,
24+ completion: @escaping (Result<Bool, GeneralError>) -> Void) {
25+ let resolver = AuthResolver<AuthResponse>(phoneNumber: phoneNumber)
26+ let request = ApiRequest(resolver: resolver, httpMethod: .post)
27+
28+ apiClient.load(request: request.request) { (result) in
29+ switch result {
30+ case .failure(let err):
31+ completion(.failure(err))
32+ case .success(let response):
33+ guard
34+ let d = response.data,
35+ let decodedData = resolver.targetClass().fromJSONToSelf(data: d)
36+ else {
37+ completion(.failure(GeneralError.decodeError))
38+ return
39+ }
40+
41+ completion(.success(decodedData.isUserRegistered))
42+ }
43+ }
44+ }
45+
46+ /// Verify login of **already existing user** with phone number and verification code.
47+ func phonecode(phoneNumber: String,
48+ code: String,
49+ completion: @escaping (Result<User, GeneralError>) -> Void) {
50+ let resolver = PhoneCodeResolver<User>(phoneNumber: phoneNumber, code: code)
51+ let request = ApiRequest(resolver: resolver, httpMethod: .post)
52+
53+ apiClient.load(request: request.request) { result in
54+ switch result {
55+ case .failure(let err):
56+ completion(.failure(err))
57+ case .success(let response):
58+ guard
59+ let unwrappedData = response.data,
60+ let decodedData = resolver.targetClass().fromJSONToSelf(data: unwrappedData)
61+ else {
62+ completion(.failure(GeneralError.decodeError))
63+ return
64+ }
65+
66+ self.setCookies(response: response)
67+ completion(.success(decodedData))
68+ }
69+ }
70+ }
71+
72+
73+ /// Register new user with a given name, phone number and verification code.
74+ func register(name: String,
75+ code: String,
76+ phoneNumber: String, completion: @escaping (Result<User, GeneralError>) -> Void) {
77+ let resolver = RegistrationResolver<User>(name: name, code: code, phoneNumber: phoneNumber)
78+ let request = ApiRequest(resolver: resolver, httpMethod: .post)
79+
80+ apiClient.load(request: request.request) { result in
81+ switch result {
82+ case .failure(let err):
83+ completion(.failure(err))
84+ case .success(let response):
85+ guard
86+ let unwrappedData = response.data,
87+ let decodedData = resolver.targetClass().fromJSONToSelf(data: unwrappedData)
88+ else {
89+ completion(.failure(GeneralError.decodeError))
90+ return
91+ }
92+
93+ self.setCookies(response: response)
94+ completion(.success(decodedData))
95+ }
96+ }
97+ }
98+
99+ // MARK: - Helpers
100+
101+ private func setCookies(response: AFDataResponse<Data?>) {
102+ let fields = response.response?.allHeaderFields as? [String :String]
103+ guard let fieldss = fields, let url = response.response?.url else {
104+ return
105+ }
106+ let cookies = HTTPCookie.cookies(withResponseHeaderFields: fieldss, for: url)
107+ HTTPCookieStorage.shared.setCookie(cookies[0])
108+ }
109+}
Hookah Tau/API/Authorization api/Resolvers/AuthResolver.swiftnew file+50
@@ -0,0 +1,50 @@
1+//
2+// AuthResolvers.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 28/10/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+struct AuthResponse: MyCodable {
12+ var isUserRegistered: Bool
13+}
14+
15+class AuthResolver<Response: MyCodable>: ApiResolver {
16+
17+ // MARK: - Request/Response types
18+
19+ struct Request: MyCodable {
20+ var phoneNumber: String
21+ }
22+
23+ // MARK: - Properties
24+
25+ private var phoneNumber: String
26+
27+ var name: String {
28+ return "/login"
29+ }
30+
31+ var groupName: String? {
32+ return "/auth"
33+ }
34+
35+ // MARK: - Init
36+
37+ init(phoneNumber: String) {
38+ self.phoneNumber = phoneNumber
39+ }
40+
41+ func parameters() -> MyCodable {
42+ return Request(phoneNumber: phoneNumber)
43+ }
44+
45+ func targetClass() -> Response.Type {
46+ return Response.self
47+ }
48+}
49+
50+
Hookah Tau/API/Authorization api/Resolvers/PhoneCodeResolver.swiftnew file+48
@@ -0,0 +1,48 @@
1+//
2+// PhoneCodeResolver.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 29/10/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+class PhoneCodeResolver<Response: MyCodable>: ApiResolver {
12+
13+ // MARK: - types
14+
15+ struct Request: MyCodable {
16+ var phoneNumber: String
17+ var code: String
18+ }
19+
20+ // MARK: - Properties
21+
22+ var name: String {
23+ return "/phonecode"
24+ }
25+
26+ var groupName: String? {
27+ return "/auth"
28+ }
29+
30+ private var phoneNumber: String
31+ private var code: String
32+
33+ // MARK: - Init
34+
35+ init(phoneNumber: String, code: String) {
36+ self.phoneNumber = phoneNumber
37+ self.code = code
38+ }
39+
40+ func parameters() -> MyCodable {
41+ return Request(phoneNumber: phoneNumber,
42+ code: code)
43+ }
44+
45+ func targetClass() -> Response.Type {
46+ return Response.self
47+ }
48+}
Hookah Tau/API/Authorization api/Resolvers/RegistrationResolver.swiftnew file+52
@@ -0,0 +1,52 @@
1+//
2+// RegistrationResolver.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 29/10/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+class RegistrationResolver<Response: MyCodable>: ApiResolver {
12+
13+ // MARK: - types
14+
15+ struct Request: MyCodable {
16+ var name: String
17+ var phoneNumber: String
18+ var code: String
19+ }
20+
21+ // MARK: - Properties
22+
23+ var name: String {
24+ return "/register"
25+ }
26+
27+ var groupName: String? {
28+ return "/auth"
29+ }
30+
31+ private var uname: String
32+ private var code: String
33+ private var phoneNumber: String
34+
35+ // MARK: - Init
36+
37+ init(name: String, code: String, phoneNumber: String) {
38+ self.uname = name
39+ self.code = code
40+ self.phoneNumber = phoneNumber
41+ }
42+
43+ func parameters() -> MyCodable {
44+ return Request(name: uname,
45+ phoneNumber: phoneNumber,
46+ code: code)
47+ }
48+
49+ func targetClass() -> Response.Type {
50+ return Response.self
51+ }
52+}
Hookah Tau/API/MyCodable.swiftnew file+25
@@ -0,0 +1,25 @@
1+//
2+// ApiParameters.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 28/10/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+protocol MyCodable: Codable {
12+ func toJSONData() -> Data?
13+
14+ static func fromJSONToSelf(data: Data) -> Self?
15+}
16+
17+extension MyCodable {
18+ func toJSONData() -> Data? {
19+ return try? JSONEncoder().encode(self)
20+ }
21+
22+ static func fromJSONToSelf(data: Data) -> Self? {
23+ return try? JSONDecoder().decode(self, from: data)
24+ }
25+}
Hookah Tau/API/ServerError.swiftnew file+21
@@ -0,0 +1,21 @@
1+//
2+// ServerError.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 28/10/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+struct SE: MyCodable {
12+ var error: String?
13+}
14+
15+enum GeneralError: Error {
16+ case serverError(SE)
17+ case decodeError
18+ case noData
19+ case Error400
20+ case somethingWentCompletelyWrong
21+}
Hookah Tau/Controllers/ApplicationCoordinator.swift+1−1
@@ -63,7 +63,7 @@ extension ApplicationCoordinator {
6363
6464 static func configure() -> LaunchOptions {
6565 print("configuring...")
66− return .main
66+ return .auth
6767 }
6868 }
6969}
Hookah Tau/Controllers/Authentication/Code from sms/CodeCoordinator.swift+3−10
@@ -12,10 +12,6 @@ import UIKit
1212class CodeCoordinator: BaseCoordinator {
1313
1414 // MARK: - Properties
15−
16− var didEndFlow: (() -> Void)?
17−
18− weak var parentCoordinator: PhoneCoordinator?
1915
2016 override func start() {
2117 let smsInput = CodeSmsViewController()
@@ -28,12 +24,9 @@ class CodeCoordinator: BaseCoordinator {
2824 navigationController?.popViewController(animated: true)
2925 }
3026
31− /// - warning: Depends on weather is authorized or not, mb need parameter
27+ /// Last step of authorization process
3228 func goToNextStep() {
33− let nameCoordinator = NameCoordinator(navigationController: navigationController)
34− nameCoordinator.didEndFlow = { [weak self] in self?.didEndFlow?() }
35− nameCoordinator.parentCoordinator = self
36− addDependency(nameCoordinator)
37− nameCoordinator.start()
29+ print("Did end auth flow...")
30+ didEndFlow?()
3831 }
3932}
Hookah Tau/Controllers/Authentication/Code from sms/CodeSmsViewController.swift+38−3
@@ -15,6 +15,8 @@ final class CodeSmsViewController: AuthorizationViewController {
1515 weak var coordinator: CodeCoordinator?
1616
1717 weak var codeView: CodeView?
18+
19+ var authService: AuthorizationService?
1820
1921 let nextButton: Button = {
2022 let button = Button(frame: CGRect.zero)
@@ -33,18 +35,22 @@ final class CodeSmsViewController: AuthorizationViewController {
3335
3436 setUpButtons()
3537 setUpContentView()
38+
39+ authService = AuthorizationService(apiClient: APIClient.shared)
40+
41+ codeView?.onComplete = tapHandlerNextButton
42+ codeView?.firstTextField?.becomeFirstResponder()
3643 }
3744
3845 override func viewWillAppear(_ animated: Bool) {
3946 super.viewWillAppear(animated)
4047
41− codeView?.fst?.becomeFirstResponder()
4248 }
4349
4450 override func viewWillDisappear(_ animated: Bool) {
4551 super.viewWillDisappear(animated)
4652
47− codeView?.fst?.resignFirstResponder()
53+ codeView?.lastTextField?.resignFirstResponder()
4854 }
4955
5056 // MARK: - Setup
@@ -71,7 +77,36 @@ final class CodeSmsViewController: AuthorizationViewController {
7177
7278 @objc
7379 func tapHandlerNextButton() {
74− coordinator?.goToNextStep()
80+ guard let code = codeView?.getFullCode() else {
81+ displayAlert(forError: GeneralError.noData)
82+ return
83+ }
84+
85+ guard let phone = DataStorage.standard.phone else { return /* TODO обработка */ }
86+
87+ let handleCompletion: (Result<User, GeneralError>) -> Void = { [weak self] result in
88+ self?.nextButton.loading = false
89+ switch result {
90+ case .failure(let err):
91+ self?.displayAlert(forError: err)
92+ case .success(let user):
93+ DataStorage.standard.saveUserModel(user)
94+ self?.coordinator?.goToNextStep()
95+ }
96+ }
97+
98+ nextButton.loading = true
99+ if let name = DataStorage.standard.name {
100+ authService?.register(name: name,
101+ code: code,
102+ phoneNumber: phone,
103+ completion: handleCompletion)
104+ return
105+ }
106+
107+ authService?.phonecode(phoneNumber: phone,
108+ code: code,
109+ completion: handleCompletion)
75110 }
76111
77112 @objc
Hookah Tau/Controllers/Authentication/Name/NameCoordinator.swift+6−7
@@ -10,11 +10,7 @@ import UIKit
1010
1111class NameCoordinator: BaseCoordinator {
1212
13− // MARK: - Properties
14−
15− var didEndFlow: (() -> Void)?
16−
17− var parentCoordinator: CodeCoordinator?
13+ // MARK: - Lifecycle
1814
1915 override func start() {
2016 let nameViewController = NameViewController()
@@ -23,8 +19,11 @@ class NameCoordinator: BaseCoordinator {
2319 }
2420
2521 func goToNextStep() {
26− print("Did end auth flow...")
27− didEndFlow?()
22+ let codeCoordinator = CodeCoordinator(navigationController: navigationController)
23+ codeCoordinator.didEndFlow = { [weak self] in self?.didEndFlow?() }
24+ codeCoordinator.parentCoordinator = self
25+ addDependency(codeCoordinator)
26+ codeCoordinator.start()
2827 }
2928
3029 func goBack() {
Hookah Tau/Controllers/Authentication/Name/NameViewController.swift+9−1
@@ -68,8 +68,16 @@ class NameViewController: AuthorizationViewController {
6868 // MARK: - Handlers
6969
7070 @objc func tapHandlerNextButton() {
71+ guard
72+ let name = nameView?.nameTextField?.text,
73+ name.count > 1
74+ else {
75+ displayAlert(forError: GeneralError.noData)
76+ return
77+ }
78+
79+ DataStorage.standard.name = name
7180 coordinator?.goToNextStep()
72− //nextButton.loading = !nextButton.loading
7381 }
7482
7583 @objc func tapHandlerReturnButton() {
Hookah Tau/Controllers/Authentication/Phone/PhoneCoordinator.swift+16−9
@@ -10,9 +10,7 @@ import Foundation
1010
1111class PhoneCoordinator: BaseCoordinator {
1212
13− // MARK: - Properties
14−
15− var didEndFlow: (() -> Void)?
13+ // MARK: - Lifecycle
1614
1715 override func start() {
1816 let phoneVC = PhoneNumberViewController()
@@ -20,11 +18,20 @@ class PhoneCoordinator: BaseCoordinator {
2018 navigationController?.viewControllers = [ phoneVC ]
2119 }
2220
23− func goToNextStep() {
24− let codeCoordinator = CodeCoordinator(navigationController: navigationController)
25− codeCoordinator.didEndFlow = { [weak self] in self?.didEndFlow?() }
26− codeCoordinator.parentCoordinator = self
27− addDependency(codeCoordinator)
28− codeCoordinator.start()
21+ /// Method chooses next step. Depends on weather user was previously registered
22+ /// - Parameter isUserRegistered: was user registered
23+ func goToNextStep(isUserRegistered: Bool) {
24+ let coordinator: BaseCoordinator!
25+
26+ if isUserRegistered {
27+ coordinator = CodeCoordinator(navigationController: navigationController)
28+ } else {
29+ coordinator = NameCoordinator(navigationController: navigationController)
30+ }
31+
32+ coordinator.didEndFlow = { [weak self] in self?.didEndFlow?() }
33+ coordinator.parentCoordinator = self
34+ addDependency(coordinator)
35+ coordinator.start()
2936 }
3037}
Hookah Tau/Controllers/Authentication/Phone/PhoneNumberViewController.swift+23−1
@@ -16,6 +16,8 @@ final class PhoneNumberViewController: AuthorizationViewController {
1616
1717 var phoneView: PhoneView?
1818
19+ var authService: AuthorizationService?
20+
1921 let nextButton: Button = {
2022 let button = Button()
2123 return button
@@ -29,6 +31,8 @@ final class PhoneNumberViewController: AuthorizationViewController {
2931
3032 setUpButtons()
3133 setUpContentView()
34+
35+ authService = AuthorizationService(apiClient: APIClient.shared)
3236 }
3337
3438 override func viewWillAppear(_ animated: Bool) {
@@ -63,6 +67,24 @@ final class PhoneNumberViewController: AuthorizationViewController {
6367
6468 @objc
6569 func tapHandlerNextButton() {
66− coordinator?.goToNextStep()
70+ nextButton.loading = true
71+
72+ guard let phoneText = phoneView?.getFormattedNumber() else {
73+ self.displayAlert(forError: GeneralError.noData)
74+ nextButton.loading = false
75+ return
76+ }
77+
78+ authService?.authenticate(withPhone: phoneText, completion: { [weak self] (result) in
79+ self?.nextButton.loading = false
80+
81+ switch result {
82+ case .failure(let err):
83+ self?.displayAlert(forError: err)
84+ case .success(let isUserRegistered):
85+ DataStorage.standard.phone = phoneText
86+ self?.coordinator?.goToNextStep(isUserRegistered: isUserRegistered)
87+ }
88+ })
6789 }
6890}
Hookah Tau/Controllers/BaseCoordinator.swift+4
@@ -31,6 +31,10 @@ class BaseCoordinator: Coordinator {
3131 var childCoordinators = [Coordinator]()
3232
3333 var navigationController: UINavigationController?
34+
35+ var didEndFlow: (() -> Void)?
36+
37+ weak var parentCoordinator: BaseCoordinator?
3438
3539 func start() { } // should be overriden
3640
Hookah Tau/Controllers/BaseViewController.swift+16−1
@@ -97,5 +97,20 @@ class BaseViewController: UIViewController {
9797 })
9898 }
9999 }
100−
100+
101+ // MARK: - Alert
102+
103+ func displayAlert(forError error: GeneralError) {
104+ var title: String? = "MEOW"
105+ switch error {
106+ case .serverError(let se):
107+ title = se.error
108+ fallthrough // да тоже произведение искусства
109+ default:
110+ let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
111+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
112+ self.present(alert, animated: true, completion: nil)
113+ }
114+ }
115+
101116}
Hookah Tau/Controllers/Client/Profile/ProfileChangeCoordinator.swift+1−3
@@ -16,8 +16,6 @@ class ProfileChangeCoordinator: BaseCoordinator {
1616
1717 // MARK: - Properties
1818
19− var didEndFlow: (() -> Void)?
20−
2119 var changeModel: ChangeModel?
2220
2321 // MARK: - Lifecycle
@@ -31,7 +29,7 @@ class ProfileChangeCoordinator: BaseCoordinator {
3129 }
3230
3331 /// Done
34− func update(withModel model: UserModel) {
32+ func update(withModel model: User) {
3533 for controller in navigationController?.viewControllers ?? [] {
3634 guard let updatableController = controller as? UserUpdate else { continue }
3735 updatableController.updateUser(withModel: model)
Hookah Tau/Controllers/Client/Profile/ProfileChangeViewController.swift+2−2
@@ -9,7 +9,7 @@
99import UIKit
1010
1111struct ChangeModel {
12− var userModel: UserModel
12+ var userModel: User
1313 var editingOption: EditingOption
1414}
1515
@@ -91,7 +91,7 @@ class ProfileChangeViewController: BaseViewController {
9191 case .name:
9292 userModel.name = text
9393 case .phone:
94− userModel.phone = text
94+ userModel.phoneNumber = text
9595 }
9696
9797 coordinator?.update(withModel: userModel)
Hookah Tau/Controllers/Client/Profile/ProfileClientViewController.swift+2−2
@@ -14,7 +14,7 @@ class ProfileClientViewController: UIViewController {
1414
1515 weak var coordinator: ProfileCoordinator?
1616
17− var model: UserModel = UserModel(name: "Стасик", phone: "+ 8 901 733 01 79", isAdmin: false) {
17+ var model: User = User(name: "Стасик", phoneNumber: "+ 8 901 733 01 79", isAdmin: false) {
1818 didSet {
1919 profileContentView?.bind(withModel: model)
2020 }
@@ -59,7 +59,7 @@ class ProfileClientViewController: UIViewController {
5959// MARK: - UserUpdate ext
6060
6161extension ProfileClientViewController: UserUpdate {
62− func updateUser(withModel model: UserModel) {
62+ func updateUser(withModel model: User) {
6363 self.model = model
6464 }
6565}
Hookah Tau/Controllers/Client/Profile/ProfileCoordinator.swift+1−1
@@ -9,7 +9,7 @@
99import UIKit
1010
1111protocol UserUpdate: class {
12− func updateUser(withModel model: UserModel)
12+ func updateUser(withModel model: User)
1313}
1414
1515class ProfileCoordinator: BaseCoordinator {
Hookah Tau/Controllers/Client/Profile/Views/ChangeView.swift+1−1
@@ -27,7 +27,7 @@ class ChangeView: UIView {
2727 textField?.keyboardType = .alphabet
2828 case .phone:
2929 titleLabel?.text = "Номер телефона"
30− textField?.text = model.userModel.phone
30+ textField?.text = model.userModel.phoneNumber
3131 textField?.keyboardType = .numberPad
3232 }
3333 }
Hookah Tau/Controllers/Client/Profile/Views/ProfileView.swift+2−2
@@ -66,9 +66,9 @@ class ProfileView: UIView {
6666 }
6767
6868 // TODO: - change in future
69− func bind(withModel model: UserModel) {
69+ func bind(withModel model: User) {
7070 nameView?.bind(withModel: model.name)
71− phoneView?.bind(withModel: model.phone)
71+ phoneView?.bind(withModel: model.phoneNumber)
7272
7373 if !model.isAdmin {
7474 inviteStackView?.isHidden = true
Hookah Tau/Controllers/Client/Tab bar/ClientTabbarCoordinator.swift−4
@@ -12,10 +12,6 @@ class ClientTabbarCoordinator: BaseCoordinator {
1212
1313 typealias TabbarClosure = ((UINavigationController) -> ())
1414
15− // MARK: - Properties
16−
17− var didEndFlow: (()-> Void)?
18−
1915 // MARK: - Lifecycle
2016
2117 override func start() {
Hookah Tau/Models/UserModel.swift → Hookah Tau/Models/User.swiftrenamed+2−2
@@ -8,8 +8,8 @@
88
99import Foundation
1010
11−struct UserModel {
11+struct User: MyCodable {
1212 var name: String
13− var phone: String
13+ var phoneNumber: String
1414 var isAdmin: Bool
1515}
Hookah Tau/Views/Code/CodeView.swift+52−20
@@ -11,37 +11,69 @@ import UIKit
1111class CodeView: UIView {
1212
1313 // MARK: - Properties
14+
15+ @IBOutlet weak var firstTextField: UITextField?
1416
15− @IBOutlet weak var fst: UITextField?
16−
17− @IBOutlet weak var snd: UITextField?
18−
19− @IBOutlet weak var trd: UITextField?
20−
21− @IBOutlet weak var frt: UITextField?
17+ @IBOutlet weak var lastTextField: UITextField?
2218
23− @IBOutlet weak var fif: UITextField?
19+ @IBOutlet var codeTextFields: [UITextField]?
2420
25− @IBOutlet weak var six: UITextField?
26−
27− private var allNumbers: [UITextField] = []
21+ var onComplete: (() -> Void)?
2822
2923 // MARK: - Lifecycle
3024
3125 override func awakeFromNib() {
3226 super.awakeFromNib()
3327
34− allNumbers = [fst,
35− snd,
36− trd,
37− frt,
38− fif,
39− six].compactMap{ $0 }
28+ _ = codeTextFields?.map {
29+ $0.delegate = self
30+ $0.keyboardType = .asciiCapableNumberPad
31+ }
32+ }
33+
34+ func getFullCode() -> String? {
35+ guard let codeArray = codeTextFields else { return nil }
36+ let code = codeArray.map { $0.text ?? "" }.reduce("", +)
4037
41− _ = allNumbers.map {
42− $0.keyboardType = .numberPad
43− $0.attributedPlaceholder = NSAttributedString(string:"8", attributes: [NSAttributedString.Key.foregroundColor: UIColor.lightGray])
38+ return code
39+ }
40+}
41+
42+// MARK: - UITextFieldDelegate
43+
44+extension CodeView: UITextFieldDelegate {
45+ func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
46+
47+ let char = string.cString(using: String.Encoding.utf8)!
48+ let isBackSpace = strcmp(char, "\\b")
49+
50+ if (isBackSpace == -92) {
51+ getBackwards(textField)
52+ return false
53+ } else {
54+ let newString = ((textField.text)! as NSString).replacingCharacters(in: range, with: string).last
55+ if let newFirstString = newString {
56+ textFieldShouldReturnSingle(textField, newString: String(newFirstString))
57+ return false
58+ }
4459 }
60+ return true
4561 }
4662
63+ func textFieldShouldReturnSingle(_ textField: UITextField, newString: String)
64+ {
65+ textField.text = newString
66+ if let nextTextField = textField.superview?.viewWithTag(textField.tag + 1) {
67+ nextTextField.becomeFirstResponder()
68+ } else {
69+ onComplete?()
70+ }
71+ }
72+
73+ func getBackwards(_ textField: UITextField) {
74+ textField.text = ""
75+ if let nextTextField = textField.superview?.viewWithTag(textField.tag - 1) {
76+ nextTextField.becomeFirstResponder()
77+ }
78+ }
4779}
Hookah Tau/Views/Code/CodeView.xib+33−31
@@ -15,69 +15,69 @@
1515 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
1616 <subviews>
1717 <stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="AvJ-ix-PaS">
18− <rect key="frame" x="68" y="4" width="238" height="34"/>
18+ <rect key="frame" x="32" y="3" width="310" height="36"/>
1919 <subviews>
20− <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="center" minimumFontSize="28" translatesAutoresizingMaskIntoConstraints="NO" id="bN8-Dx-tUT">
21− <rect key="frame" x="0.0" y="0.0" width="33" height="34"/>
20+ <textField opaque="NO" tag="1" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="center" minimumFontSize="28" translatesAutoresizingMaskIntoConstraints="NO" id="bN8-Dx-tUT">
21+ <rect key="frame" x="0.0" y="0.0" width="45" height="36"/>
2222 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
2323 <constraints>
24− <constraint firstAttribute="width" constant="33" id="Nob-jT-cth"/>
25− <constraint firstAttribute="height" constant="34" id="ifR-1l-U2V"/>
24+ <constraint firstAttribute="width" constant="45" id="Nob-jT-cth"/>
25+ <constraint firstAttribute="height" constant="36" id="ifR-1l-U2V"/>
2626 </constraints>
2727 <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
2828 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
2929 <textInputTraits key="textInputTraits"/>
3030 </textField>
31− <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="center" minimumFontSize="28" translatesAutoresizingMaskIntoConstraints="NO" id="FY0-pn-fCC">
32− <rect key="frame" x="41" y="0.0" width="33" height="34"/>
31+ <textField opaque="NO" tag="2" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="center" minimumFontSize="28" translatesAutoresizingMaskIntoConstraints="NO" id="FY0-pn-fCC">
32+ <rect key="frame" x="53" y="0.0" width="45" height="36"/>
3333 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
3434 <constraints>
35− <constraint firstAttribute="height" constant="34" id="Ucv-mT-fyj"/>
36− <constraint firstAttribute="width" constant="33" id="ck1-wZ-IBT"/>
35+ <constraint firstAttribute="height" constant="36" id="Ucv-mT-fyj"/>
36+ <constraint firstAttribute="width" constant="45" id="ck1-wZ-IBT"/>
3737 </constraints>
3838 <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
3939 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
4040 <textInputTraits key="textInputTraits"/>
4141 </textField>
42− <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="center" minimumFontSize="28" translatesAutoresizingMaskIntoConstraints="NO" id="JKD-8I-UNU">
43− <rect key="frame" x="82" y="0.0" width="33" height="34"/>
42+ <textField opaque="NO" tag="3" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="center" minimumFontSize="28" translatesAutoresizingMaskIntoConstraints="NO" id="JKD-8I-UNU">
43+ <rect key="frame" x="106" y="0.0" width="45" height="36"/>
4444 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
4545 <constraints>
46− <constraint firstAttribute="height" constant="34" id="ACy-Gx-Fuc"/>
47− <constraint firstAttribute="width" constant="33" id="N0g-Tj-jiT"/>
46+ <constraint firstAttribute="height" constant="36" id="ACy-Gx-Fuc"/>
47+ <constraint firstAttribute="width" constant="45" id="N0g-Tj-jiT"/>
4848 </constraints>
4949 <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
5050 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
5151 <textInputTraits key="textInputTraits"/>
5252 </textField>
53− <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="center" minimumFontSize="28" translatesAutoresizingMaskIntoConstraints="NO" id="m6q-G8-Kj1">
54− <rect key="frame" x="123" y="0.0" width="33" height="34"/>
53+ <textField opaque="NO" tag="4" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="center" minimumFontSize="28" translatesAutoresizingMaskIntoConstraints="NO" id="m6q-G8-Kj1">
54+ <rect key="frame" x="159" y="0.0" width="45" height="36"/>
5555 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
5656 <constraints>
57− <constraint firstAttribute="width" constant="33" id="doW-6X-9HY"/>
58− <constraint firstAttribute="height" constant="34" id="yjR-7R-KES"/>
57+ <constraint firstAttribute="width" constant="45" id="doW-6X-9HY"/>
58+ <constraint firstAttribute="height" constant="36" id="yjR-7R-KES"/>
5959 </constraints>
6060 <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
6161 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
6262 <textInputTraits key="textInputTraits"/>
6363 </textField>
64− <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="center" minimumFontSize="28" translatesAutoresizingMaskIntoConstraints="NO" id="FEc-xO-TH4">
65− <rect key="frame" x="164" y="0.0" width="33" height="34"/>
64+ <textField opaque="NO" tag="5" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="center" minimumFontSize="28" translatesAutoresizingMaskIntoConstraints="NO" id="FEc-xO-TH4">
65+ <rect key="frame" x="212" y="0.0" width="45" height="36"/>
6666 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
6767 <constraints>
68− <constraint firstAttribute="width" constant="33" id="7tz-Mb-4xo"/>
69− <constraint firstAttribute="height" constant="34" id="sLJ-BN-xvF"/>
68+ <constraint firstAttribute="width" constant="45" id="7tz-Mb-4xo"/>
69+ <constraint firstAttribute="height" constant="36" id="sLJ-BN-xvF"/>
7070 </constraints>
7171 <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
7272 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
7373 <textInputTraits key="textInputTraits"/>
7474 </textField>
75− <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="center" minimumFontSize="28" translatesAutoresizingMaskIntoConstraints="NO" id="apy-G3-6LG">
76− <rect key="frame" x="205" y="0.0" width="33" height="34"/>
75+ <textField opaque="NO" tag="6" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="center" minimumFontSize="28" translatesAutoresizingMaskIntoConstraints="NO" id="apy-G3-6LG">
76+ <rect key="frame" x="265" y="0.0" width="45" height="36"/>
7777 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
7878 <constraints>
79− <constraint firstAttribute="width" constant="33" id="2jK-JD-vJb"/>
80− <constraint firstAttribute="height" constant="34" id="vmy-eE-VGc"/>
79+ <constraint firstAttribute="width" constant="45" id="2jK-JD-vJb"/>
80+ <constraint firstAttribute="height" constant="36" id="vmy-eE-VGc"/>
8181 </constraints>
8282 <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
8383 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
@@ -94,12 +94,14 @@
9494 <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
9595 <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
9696 <connections>
97− <outlet property="fif" destination="FEc-xO-TH4" id="c6h-yp-y1w"/>
98− <outlet property="frt" destination="m6q-G8-Kj1" id="FOj-Db-SUl"/>
99− <outlet property="fst" destination="bN8-Dx-tUT" id="Poq-KO-RY3"/>
100− <outlet property="six" destination="apy-G3-6LG" id="Mkt-JK-pQ3"/>
101− <outlet property="snd" destination="FY0-pn-fCC" id="BVY-Ym-f69"/>
102− <outlet property="trd" destination="JKD-8I-UNU" id="aFJ-hK-7k7"/>
97+ <outlet property="firstTextField" destination="bN8-Dx-tUT" id="NtS-pl-Cv9"/>
98+ <outlet property="lastTextField" destination="apy-G3-6LG" id="k3e-7U-of7"/>
99+ <outletCollection property="codeTextFields" destination="bN8-Dx-tUT" collectionClass="NSMutableArray" id="HVa-7S-Mbr"/>
100+ <outletCollection property="codeTextFields" destination="FY0-pn-fCC" collectionClass="NSMutableArray" id="Yg8-ng-V2S"/>
101+ <outletCollection property="codeTextFields" destination="JKD-8I-UNU" collectionClass="NSMutableArray" id="fN4-Ah-Adg"/>
102+ <outletCollection property="codeTextFields" destination="m6q-G8-Kj1" collectionClass="NSMutableArray" id="CyO-Lm-FRN"/>
103+ <outletCollection property="codeTextFields" destination="FEc-xO-TH4" collectionClass="NSMutableArray" id="2Ep-c4-qGM"/>
104+ <outletCollection property="codeTextFields" destination="apy-G3-6LG" collectionClass="NSMutableArray" id="BuX-CG-0tX"/>
103105 </connections>
104106 <point key="canvasLocation" x="138.40000000000001" y="372.41379310344831"/>
105107 </view>
Hookah Tau/Views/Phone/PhoneView.swift+45−1
@@ -19,13 +19,57 @@ class PhoneView: UIView {
1919 override func awakeFromNib() {
2020 super.awakeFromNib()
2121
22+ phone?.delegate = self
2223 phone?.keyboardType = .numberPad
23− phone?.attributedPlaceholder = NSAttributedString(string:"+ 7 901 733 01 79", attributes: [NSAttributedString.Key.foregroundColor: UIColor.lightGray])
24+ phone?.attributedPlaceholder = NSAttributedString(string:"+ 7 (901) 733 01 79", attributes: [NSAttributedString.Key.foregroundColor: UIColor.lightGray])
2425 }
2526
2627 // TODO: - change in future
2728 func bind(withModel model: String) {
2829 phone?.text = model
2930 }
31+}
32+
33+// MARK: - UITextFieldDelegate
3034
35+extension PhoneView: UITextFieldDelegate {
36+ func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
37+ let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
38+ textField.text = formattedNumber(newString)
39+ return false
40+ }
41+
42+ private func formattedNumber(_ number: String) -> String {
43+ let cleanPhoneNumber = number.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
44+
45+ var result = ""
46+ let phoneMask = "+X (XXX) XXX XX XX"
47+ var index = cleanPhoneNumber.startIndex
48+ for ch in phoneMask where index < cleanPhoneNumber.endIndex {
49+ if ch == "X" {
50+ result.append(cleanPhoneNumber[index])
51+ index = cleanPhoneNumber.index(after: index)
52+ } else {
53+ result.append(ch)
54+ }
55+ }
56+ return result
57+ }
58+
59+ /// +o (oxx) xxx xx xx
60+ ///
61+ /// need tor return only `x`, length is 9. And it has to have prefix +7 (9...) or +8 (9..)
62+ func getFormattedNumber() -> String? {
63+ guard let number = phone?.text else { return nil }
64+
65+ let cleanPhoneNumber = String(number.components(separatedBy: CharacterSet.decimalDigits.inverted).joined())
66+ let hasPrefix = cleanPhoneNumber.hasPrefix("79") || cleanPhoneNumber.hasPrefix("89")
67+
68+ let result = String(cleanPhoneNumber.dropFirst(2))
69+ if result.count == 9 && hasPrefix {
70+ return result
71+ }
72+
73+ return nil
74+ }
3175}