git.iko.soy/dashared/hookah-tau
on develop help sign in
Hookah Tau.xcodeproj/project.pbxproj+608−6
since splitsnapshots
unifiedsplit
@@ -7,17 +7,46 @@
7 objects = {
7 objects = {
8
8
9/* Begin PBXBuildFile section */
9/* Begin PBXBuildFile section */
10+ 12050569238048CE00801618 /* Extensions+Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12050568238048CE00801618 /* Extensions+Date.swift */; };
11+ 1205056B2380588C00801618 /* Extenstions+IBDesignable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1205056A2380588C00801618 /* Extenstions+IBDesignable.swift */; };
12+ 120BFC7723891925001EBE89 /* ThirdStepReservationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 120BFC7623891925001EBE89 /* ThirdStepReservationViewController.swift */; };
13+ 120BFC7923891942001EBE89 /* ThirdStepReservationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 120BFC7823891942001EBE89 /* ThirdStepReservationCoordinator.swift */; };
14+ 120BFC7C23891965001EBE89 /* ThirdStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 120BFC7B23891965001EBE89 /* ThirdStepView.swift */; };
15+ 120BFC7E23891975001EBE89 /* ThirdStepView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 120BFC7D23891975001EBE89 /* ThirdStepView.xib */; };
10 12118B9C23682022006BAEE3 /* RegistrationResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12118B9B23682022006BAEE3 /* RegistrationResolver.swift */; };
16 12118B9C23682022006BAEE3 /* RegistrationResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12118B9B23682022006BAEE3 /* RegistrationResolver.swift */; };
11 12118B9E23682049006BAEE3 /* PhoneCodeResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12118B9D23682049006BAEE3 /* PhoneCodeResolver.swift */; };
17 12118B9E23682049006BAEE3 /* PhoneCodeResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12118B9D23682049006BAEE3 /* PhoneCodeResolver.swift */; };
18+ 1212048C23914CFD00CAF162 /* DeleteReservationResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1212048B23914CFD00CAF162 /* DeleteReservationResolver.swift */; };
19+ 1212048E23915A0A00CAF162 /* ChangeReservationResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1212048D23915A0A00CAF162 /* ChangeReservationResolver.swift */; };
20+ 1220A6F423CDB1AD00365E36 /* GetClientByPhoneNumberResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1220A6F323CDB1AD00365E36 /* GetClientByPhoneNumberResolver.swift */; };
21+ 1220A6F723CDB64A00365E36 /* AdminSecondStepReservationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1220A6F623CDB64A00365E36 /* AdminSecondStepReservationViewController.swift */; };
22+ 1220A6FA23CDBE7E00365E36 /* AdminFinalStepReservationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1220A6F923CDBE7E00365E36 /* AdminFinalStepReservationCoordinator.swift */; };
12 12219CEF2353A63F0057DDEE /* ReservationsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12219CEE2353A63E0057DDEE /* ReservationsCoordinator.swift */; };
23 12219CEF2353A63F0057DDEE /* ReservationsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12219CEE2353A63E0057DDEE /* ReservationsCoordinator.swift */; };
24+ 12230AEA238FD8B3004A01E5 /* MapView2.xib in Resources */ = {isa = PBXBuildFile; fileRef = 12230AE9238FD8B3004A01E5 /* MapView2.xib */; };
25+ 12230AEC238FD8C5004A01E5 /* MapView2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12230AEB238FD8C5004A01E5 /* MapView2.swift */; };
26+ 12230AF123900974004A01E5 /* TotalStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12230AF023900974004A01E5 /* TotalStorage.swift */; };
27+ 12230AF423900BCB004A01E5 /* EstablishmentService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12230AF323900BCB004A01E5 /* EstablishmentService.swift */; };
28+ 12230AF723900D44004A01E5 /* EstablishmentResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12230AF623900D44004A01E5 /* EstablishmentResolver.swift */; };
29+ 12230AFA23901029004A01E5 /* DataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12230AF923901029004A01E5 /* DataStorage.swift */; };
30+ 12230AFC239034D3004A01E5 /* SecondStepReservationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12230AFB239034D3004A01E5 /* SecondStepReservationViewController.swift */; };
13 123615CF235510BC00D1122A /* ProfileView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 123615CE235510BC00D1122A /* ProfileView.xib */; };
31 123615CF235510BC00D1122A /* ProfileView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 123615CE235510BC00D1122A /* ProfileView.xib */; };
14 123615D12355141600D1122A /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 123615D02355141600D1122A /* ProfileView.swift */; };
32 123615D12355141600D1122A /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 123615D02355141600D1122A /* ProfileView.swift */; };
15 12387337234BB98700D1F1AB /* CodeView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 12387336234BB98700D1F1AB /* CodeView.xib */; };
33 12387337234BB98700D1F1AB /* CodeView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 12387336234BB98700D1F1AB /* CodeView.xib */; };
34+ 123D25F0239E3E11002FA32B /* AdminAddressCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 123D25EF239E3E11002FA32B /* AdminAddressCoordinator.swift */; };
35+ 123D25F2239E3E44002FA32B /* ClientsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 123D25F1239E3E44002FA32B /* ClientsCoordinator.swift */; };
36+ 123D25F4239E3FF5002FA32B /* ClientsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 123D25F3239E3FF5002FA32B /* ClientsViewController.swift */; };
37+ 123D25F7239E5839002FA32B /* ClientTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 123D25F6239E5839002FA32B /* ClientTableViewCell.swift */; };
16 1246B717235CD186004CBFA1 /* NoReservationsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1246B716235CD186004CBFA1 /* NoReservationsView.xib */; };
38 1246B717235CD186004CBFA1 /* NoReservationsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1246B716235CD186004CBFA1 /* NoReservationsView.xib */; };
17 1246B719235CD3AD004CBFA1 /* NoReservationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1246B718235CD3AD004CBFA1 /* NoReservationsView.swift */; };
39 1246B719235CD3AD004CBFA1 /* NoReservationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1246B718235CD3AD004CBFA1 /* NoReservationsView.swift */; };
18 1246B71C235CDF3A004CBFA1 /* ReservationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1246B71B235CDF3A004CBFA1 /* ReservationCell.swift */; };
40 1246B71C235CDF3A004CBFA1 /* ReservationCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1246B71B235CDF3A004CBFA1 /* ReservationCell.swift */; };
19 1246B71E235CDF8A004CBFA1 /* ReservationView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1246B71D235CDF8A004CBFA1 /* ReservationView.xib */; };
41 1246B71E235CDF8A004CBFA1 /* ReservationView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 1246B71D235CDF8A004CBFA1 /* ReservationView.xib */; };
20 1246B720235CDF9E004CBFA1 /* ReservationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1246B71F235CDF9E004CBFA1 /* ReservationView.swift */; };
42 1246B720235CDF9E004CBFA1 /* ReservationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1246B71F235CDF9E004CBFA1 /* ReservationView.swift */; };
43+ 124D007C23721FD400AE76B9 /* Reservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 124D007B23721FD400AE76B9 /* Reservation.swift */; };
44+ 124D007F237222B200AE76B9 /* ReservationsResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 124D007E237222B200AE76B9 /* ReservationsResolver.swift */; };
45+ 124D0081237222C900AE76B9 /* ReservationsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 124D0080237222C900AE76B9 /* ReservationsService.swift */; };
46+ 1252D9E523CDCDB300446B6F /* ACreateReservationResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1252D9E423CDCDB300446B6F /* ACreateReservationResolver.swift */; };
47+ 125AE51D2395583E00F847BD /* ChangeReservationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 125AE51C2395583E00F847BD /* ChangeReservationCoordinator.swift */; };
48+ 125AE51F2395585300F847BD /* ChangeReservationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 125AE51E2395585300F847BD /* ChangeReservationViewController.swift */; };
49+ 125AE5212395753400F847BD /* HFunc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 125AE5202395753400F847BD /* HFunc.swift */; };
21 12838EA9236764A600CEF108 /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12838EA8236764A600CEF108 /* APIClient.swift */; };
50 12838EA9236764A600CEF108 /* APIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12838EA8236764A600CEF108 /* APIClient.swift */; };
22 12838EAC2367689600CEF108 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 12838EAB2367689600CEF108 /* Alamofire */; };
51 12838EAC2367689600CEF108 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 12838EAB2367689600CEF108 /* Alamofire */; };
23 12838EAE2367701300CEF108 /* AuthorizationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12838EAD2367701300CEF108 /* AuthorizationService.swift */; };
52 12838EAE2367701300CEF108 /* AuthorizationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12838EAD2367701300CEF108 /* AuthorizationService.swift */; };
@@ -26,15 +55,62 @@
26 12838EB4236779C700CEF108 /* ApiResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12838EB3236779C700CEF108 /* ApiResolver.swift */; };
55 12838EB4236779C700CEF108 /* ApiResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12838EB3236779C700CEF108 /* ApiResolver.swift */; };
27 12838EB623677FB900CEF108 /* ApiRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12838EB523677FB900CEF108 /* ApiRequest.swift */; };
56 12838EB623677FB900CEF108 /* ApiRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12838EB523677FB900CEF108 /* ApiRequest.swift */; };
28 12838EB82367834900CEF108 /* MyCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12838EB72367834900CEF108 /* MyCodable.swift */; };
57 12838EB82367834900CEF108 /* MyCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12838EB72367834900CEF108 /* MyCodable.swift */; };
58+ 129F162723CC838200EE13FC /* AdminFirstStepReservationViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 129F162623CC838200EE13FC /* AdminFirstStepReservationViewCoordinator.swift */; };
59+ 129F162C23CC8D2E00EE13FC /* AdminSecondStepReservationViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 129F162B23CC8D2E00EE13FC /* AdminSecondStepReservationViewCoordinator.swift */; };
60+ 129F163023CC8EF600EE13FC /* PhoneTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 129F162F23CC8EF600EE13FC /* PhoneTextField.swift */; };
61+ 12A075F72373535C00D749BB /* FirstStepReservationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12A075F62373535C00D749BB /* FirstStepReservationViewController.swift */; };
62+ 12A075F9237353F500D749BB /* MapView1.xib in Resources */ = {isa = PBXBuildFile; fileRef = 12A075F8237353F500D749BB /* MapView1.xib */; };
63+ 12A075FB2373561A00D749BB /* Reservation.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 12A075FA2373561A00D749BB /* Reservation.storyboard */; };
64+ 12A075FD237357E900D749BB /* FirstStepReservationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12A075FC237357E900D749BB /* FirstStepReservationCoordinator.swift */; };
65+ 12A075FF237358F200D749BB /* MapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12A075FE237358F200D749BB /* MapView.swift */; };
66+ 12A07601237359BB00D749BB /* MapView1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12A07600237359BB00D749BB /* MapView1.swift */; };
67+ 12B3A79823ABADB9009E2FA6 /* AdminReservationView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 12B3A79723ABADB9009E2FA6 /* AdminReservationView.xib */; };
68+ 12B3A79A23ABB4E2009E2FA6 /* IntervalPickerView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 12B3A79923ABB4E2009E2FA6 /* IntervalPickerView.xib */; };
69+ 12B3A79C23ABB8ED009E2FA6 /* IntervalPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B3A79B23ABB8ED009E2FA6 /* IntervalPickerView.swift */; };
70+ 12B3A79F23ABCA21009E2FA6 /* AdminReservationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B3A79E23ABCA21009E2FA6 /* AdminReservationView.swift */; };
71+ 12B3A7A323ABD594009E2FA6 /* AdminReservationsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B3A7A223ABD594009E2FA6 /* AdminReservationsController.swift */; };
72+ 12B3A7A523ABDA25009E2FA6 /* AdminReservationsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B3A7A423ABDA25009E2FA6 /* AdminReservationsCoordinator.swift */; };
73+ 12B3A7A823ABDDB1009E2FA6 /* AdminReservationCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B3A7A723ABDDB1009E2FA6 /* AdminReservationCellView.swift */; };
74+ 12B3A7AA23ABDDC1009E2FA6 /* AdminReservations.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 12B3A7A923ABDDC1009E2FA6 /* AdminReservations.storyboard */; };
75+ 12B3A7AD23ABEABF009E2FA6 /* AReservationsResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B3A7AC23ABEABF009E2FA6 /* AReservationsResolver.swift */; };
76+ 12B3A7AF23AC1872009E2FA6 /* AdminNoReservationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B3A7AE23AC1872009E2FA6 /* AdminNoReservationsView.swift */; };
77+ 12B3A7B123AC1885009E2FA6 /* AdminNoReservationsView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 12B3A7B023AC1885009E2FA6 /* AdminNoReservationsView.xib */; };
78+ 12B3A7B423AC1D09009E2FA6 /* AdminReservationCellView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 12B3A7B323AC1D09009E2FA6 /* AdminReservationCellView.xib */; };
79+ 12B3A7B923ACCD83009E2FA6 /* AddOtherAdminResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B3A7B823ACCD83009E2FA6 /* AddOtherAdminResolver.swift */; };
80+ 12B3A7BC23ACFD75009E2FA6 /* ReservationsAroundResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B3A7BB23ACFD75009E2FA6 /* ReservationsAroundResolver.swift */; };
81+ 12B3A7BE23AD0049009E2FA6 /* AdminReservationEditingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B3A7BD23AD0048009E2FA6 /* AdminReservationEditingViewController.swift */; };
82+ 12B3A7C023AD0749009E2FA6 /* AdminReservationEditingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B3A7BF23AD0749009E2FA6 /* AdminReservationEditingCoordinator.swift */; };
83+ 12B3A7C423AD2FCD009E2FA6 /* AChangeReservationResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B3A7C323AD2FCD009E2FA6 /* AChangeReservationResolver.swift */; };
84+ 12B4447C238EF25C000B6B16 /* CreateReservationResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B4447B238EF25C000B6B16 /* CreateReservationResolver.swift */; };
29 12B6A60F235B857A00898B31 /* ProfileChangeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B6A60E235B857A00898B31 /* ProfileChangeCoordinator.swift */; };
85 12B6A60F235B857A00898B31 /* ProfileChangeCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B6A60E235B857A00898B31 /* ProfileChangeCoordinator.swift */; };
30 12B6A611235B8EC500898B31 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B6A610235B8EC500898B31 /* User.swift */; };
86 12B6A611235B8EC500898B31 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B6A610235B8EC500898B31 /* User.swift */; };
31 12B6A613235B9B4300898B31 /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B6A612235B9B4300898B31 /* BaseViewController.swift */; };
87 12B6A613235B9B4300898B31 /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B6A612235B9B4300898B31 /* BaseViewController.swift */; };
32 12B6A615235BB36C00898B31 /* ChangeView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 12B6A614235BB36C00898B31 /* ChangeView.xib */; };
88 12B6A615235BB36C00898B31 /* ChangeView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 12B6A614235BB36C00898B31 /* ChangeView.xib */; };
33 12B6A617235BB37D00898B31 /* ChangeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B6A616235BB37D00898B31 /* ChangeView.swift */; };
89 12B6A617235BB37D00898B31 /* ChangeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12B6A616235BB37D00898B31 /* ChangeView.swift */; };
90+ 12BD95B0237EBEDF009614D8 /* FirstStepView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 12BD95AF237EBEDE009614D8 /* FirstStepView.xib */; };
91+ 12BD95B2237EC22B009614D8 /* FirstStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12BD95B1237EC22B009614D8 /* FirstStepView.swift */; };
92+ 12BD95B6237ED8AC009614D8 /* TimePointView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 12BD95B5237ED8AB009614D8 /* TimePointView.xib */; };
93+ 12BD95B8237EDB01009614D8 /* TimePointView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12BD95B7237EDB01009614D8 /* TimePointView.swift */; };
94+ 12BE4B332399A52E00FC974B /* AdminTabbarCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12BE4B322399A52E00FC974B /* AdminTabbarCoordinator.swift */; };
95+ 12BE4B352399A60A00FC974B /* AdminTabbarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12BE4B342399A60A00FC974B /* AdminTabbarController.swift */; };
96+ 12BE4B372399A98200FC974B /* Admin.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 12BE4B362399A98200FC974B /* Admin.storyboard */; };
97+ 12C0B4BB239E5A3A00562BA3 /* ClientList.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 12C0B4BA239E5A3A00562BA3 /* ClientList.storyboard */; };
34 12C72EDF23537D1B00AD7004 /* ProfileCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12C72EDE23537D1B00AD7004 /* ProfileCoordinator.swift */; };
98 12C72EDF23537D1B00AD7004 /* ProfileCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12C72EDE23537D1B00AD7004 /* ProfileCoordinator.swift */; };
35 12C72EE1235380D400AD7004 /* ProfileClientViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12C72EE0235380D400AD7004 /* ProfileClientViewController.swift */; };
99 12C72EE1235380D400AD7004 /* ProfileClientViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12C72EE0235380D400AD7004 /* ProfileClientViewController.swift */; };
36 12C72EE3235380EB00AD7004 /* ReservationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12C72EE2235380EB00AD7004 /* ReservationsViewController.swift */; };
100 12C72EE3235380EB00AD7004 /* ReservationsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12C72EE2235380EB00AD7004 /* ReservationsViewController.swift */; };
101+ 12D8FA2D237F003800305F57 /* DateView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 12D8FA2C237F003800305F57 /* DateView.xib */; };
102+ 12D8FA2F237F00E200305F57 /* DateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12D8FA2E237F00E200305F57 /* DateView.swift */; };
103+ 12D8FA31237F08C700305F57 /* AllReservations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12D8FA30237F08C700305F57 /* AllReservations.swift */; };
104+ 12D928AF23C47DCF0051724D /* ADeleteReservationResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12D928AE23C47DCF0051724D /* ADeleteReservationResolver.swift */; };
105+ 12D928B123C486380051724D /* BaseTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12D928B023C486380051724D /* BaseTableViewController.swift */; };
106+ 12D928B323C7464B0051724D /* BlocklistPutResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12D928B223C7464B0051724D /* BlocklistPutResolver.swift */; };
37 12DBEEB223539F2100AF1BB6 /* Client.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 12DBEEB123539F2100AF1BB6 /* Client.storyboard */; };
107 12DBEEB223539F2100AF1BB6 /* Client.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 12DBEEB123539F2100AF1BB6 /* Client.storyboard */; };
108+ 12E35071239FA8CA004BB188 /* Extensions+String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E35070239FA8CA004BB188 /* Extensions+String.swift */; };
109+ 12E35074239FAF52004BB188 /* ClientsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E35073239FAF52004BB188 /* ClientsService.swift */; };
110+ 12E35078239FAFA4004BB188 /* ClientsListResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E35077239FAFA4004BB188 /* ClientsListResolver.swift */; };
111+ 12E3507B239FCFB2004BB188 /* ProfileTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E3507A239FCFB2004BB188 /* ProfileTableViewController.swift */; };
112+ 12E3507D239FD1F3004BB188 /* ClientReservationsResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E3507C239FD1F3004BB188 /* ClientReservationsResolver.swift */; };
113+ 12E3507F239FDFC2004BB188 /* ClientProfileCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12E3507E239FDFC2004BB188 /* ClientProfileCoordinator.swift */; };
38 12EA99462362B674005D9FE9 /* AddressCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12EA99452362B674005D9FE9 /* AddressCoordinator.swift */; };
114 12EA99462362B674005D9FE9 /* AddressCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12EA99452362B674005D9FE9 /* AddressCoordinator.swift */; };
39 12EA99482362B684005D9FE9 /* AddresViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12EA99472362B684005D9FE9 /* AddresViewController.swift */; };
115 12EA99482362B684005D9FE9 /* AddresViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12EA99472362B684005D9FE9 /* AddresViewController.swift */; };
40 12EA994C2362F8EF005D9FE9 /* AddressView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 12EA994B2362F8EF005D9FE9 /* AddressView.xib */; };
116 12EA994C2362F8EF005D9FE9 /* AddressView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 12EA994B2362F8EF005D9FE9 /* AddressView.xib */; };
@@ -42,6 +118,12 @@
42 12EB698123661CB300E4D1CE /* AddressCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12EB698023661CB300E4D1CE /* AddressCell.swift */; };
118 12EB698123661CB300E4D1CE /* AddressCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12EB698023661CB300E4D1CE /* AddressCell.swift */; };
43 12EE4A76234BB8FE005B4D1A /* CodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12EE4A75234BB8FE005B4D1A /* CodeView.swift */; };
119 12EE4A76234BB8FE005B4D1A /* CodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12EE4A75234BB8FE005B4D1A /* CodeView.swift */; };
44 12F73B022357AD7B007D4476 /* ProfileChangeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F73B012357AD7B007D4476 /* ProfileChangeViewController.swift */; };
120 12F73B022357AD7B007D4476 /* ProfileChangeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F73B012357AD7B007D4476 /* ProfileChangeViewController.swift */; };
121+ 12F9652A23868E6000FCA696 /* SecondStepView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 12F9652923868E6000FCA696 /* SecondStepView.xib */; };
122+ 12F9652C23868E7200FCA696 /* SecondStepView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F9652B23868E7200FCA696 /* SecondStepView.swift */; };
123+ 12F9652E23869F3100FCA696 /* SecondStepReservationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12F9652D23869F3100FCA696 /* SecondStepReservationCoordinator.swift */; };
124+ 12FC07B923C8D17600EDC55B /* DeleteOtherAdminResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12FC07B823C8D17600EDC55B /* DeleteOtherAdminResolver.swift */; };
125+ 12FC40382382C93D00AB593B /* TableButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12FC40372382C93C00AB593B /* TableButtonStyle.swift */; };
126+ 12FC403A2382CB6F00AB593B /* TableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 12FC40392382CB6F00AB593B /* TableButton.swift */; };
45 F30369B5233679EA00A8EA84 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F30369B4233679EA00A8EA84 /* LaunchScreen.storyboard */; };
127 F30369B5233679EA00A8EA84 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F30369B4233679EA00A8EA84 /* LaunchScreen.storyboard */; };
46 F30369BE2336924D00A8EA84 /* ClientTabbarCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30369BD2336924D00A8EA84 /* ClientTabbarCoordinator.swift */; };
128 F30369BE2336924D00A8EA84 /* ClientTabbarCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30369BD2336924D00A8EA84 /* ClientTabbarCoordinator.swift */; };
47 F30369C02336925800A8EA84 /* ClientTabbarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30369BF2336925800A8EA84 /* ClientTabbarController.swift */; };
129 F30369C02336925800A8EA84 /* ClientTabbarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F30369BF2336925800A8EA84 /* ClientTabbarController.swift */; };
@@ -90,17 +172,46 @@
90/* End PBXContainerItemProxy section */
172/* End PBXContainerItemProxy section */
91
173
92/* Begin PBXFileReference section */
174/* Begin PBXFileReference section */
175+ 12050568238048CE00801618 /* Extensions+Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extensions+Date.swift"; sourceTree = "<group>"; };
176+ 1205056A2380588C00801618 /* Extenstions+IBDesignable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extenstions+IBDesignable.swift"; sourceTree = "<group>"; };
177+ 120BFC7623891925001EBE89 /* ThirdStepReservationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdStepReservationViewController.swift; sourceTree = "<group>"; };
178+ 120BFC7823891942001EBE89 /* ThirdStepReservationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdStepReservationCoordinator.swift; sourceTree = "<group>"; };
179+ 120BFC7B23891965001EBE89 /* ThirdStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdStepView.swift; sourceTree = "<group>"; };
180+ 120BFC7D23891975001EBE89 /* ThirdStepView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ThirdStepView.xib; sourceTree = "<group>"; };
93 12118B9B23682022006BAEE3 /* RegistrationResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationResolver.swift; sourceTree = "<group>"; };
181 12118B9B23682022006BAEE3 /* RegistrationResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegistrationResolver.swift; sourceTree = "<group>"; };
94 12118B9D23682049006BAEE3 /* PhoneCodeResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneCodeResolver.swift; sourceTree = "<group>"; };
182 12118B9D23682049006BAEE3 /* PhoneCodeResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneCodeResolver.swift; sourceTree = "<group>"; };
183+ 1212048B23914CFD00CAF162 /* DeleteReservationResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteReservationResolver.swift; sourceTree = "<group>"; };
184+ 1212048D23915A0A00CAF162 /* ChangeReservationResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeReservationResolver.swift; sourceTree = "<group>"; };
185+ 1220A6F323CDB1AD00365E36 /* GetClientByPhoneNumberResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetClientByPhoneNumberResolver.swift; sourceTree = "<group>"; };
186+ 1220A6F623CDB64A00365E36 /* AdminSecondStepReservationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AdminSecondStepReservationViewController.swift; sourceTree = "<group>"; };
187+ 1220A6F923CDBE7E00365E36 /* AdminFinalStepReservationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminFinalStepReservationCoordinator.swift; sourceTree = "<group>"; };
95 12219CEE2353A63E0057DDEE /* ReservationsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReservationsCoordinator.swift; sourceTree = "<group>"; };
188 12219CEE2353A63E0057DDEE /* ReservationsCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReservationsCoordinator.swift; sourceTree = "<group>"; };
189+ 12230AE9238FD8B3004A01E5 /* MapView2.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MapView2.xib; sourceTree = "<group>"; };
190+ 12230AEB238FD8C5004A01E5 /* MapView2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView2.swift; sourceTree = "<group>"; };
191+ 12230AF023900974004A01E5 /* TotalStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TotalStorage.swift; sourceTree = "<group>"; };
192+ 12230AF323900BCB004A01E5 /* EstablishmentService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EstablishmentService.swift; sourceTree = "<group>"; };
193+ 12230AF623900D44004A01E5 /* EstablishmentResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EstablishmentResolver.swift; sourceTree = "<group>"; };
194+ 12230AF923901029004A01E5 /* DataStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataStorage.swift; sourceTree = "<group>"; };
195+ 12230AFB239034D3004A01E5 /* SecondStepReservationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecondStepReservationViewController.swift; sourceTree = "<group>"; };
96 123615CE235510BC00D1122A /* ProfileView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ProfileView.xib; sourceTree = "<group>"; };
196 123615CE235510BC00D1122A /* ProfileView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ProfileView.xib; sourceTree = "<group>"; };
97 123615D02355141600D1122A /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
197 123615D02355141600D1122A /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; };
98 12387336234BB98700D1F1AB /* CodeView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CodeView.xib; sourceTree = "<group>"; };
198 12387336234BB98700D1F1AB /* CodeView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CodeView.xib; sourceTree = "<group>"; };
199+ 123D25EF239E3E11002FA32B /* AdminAddressCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminAddressCoordinator.swift; sourceTree = "<group>"; };
200+ 123D25F1239E3E44002FA32B /* ClientsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientsCoordinator.swift; sourceTree = "<group>"; };
201+ 123D25F3239E3FF5002FA32B /* ClientsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientsViewController.swift; sourceTree = "<group>"; };
202+ 123D25F6239E5839002FA32B /* ClientTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientTableViewCell.swift; sourceTree = "<group>"; };
99 1246B716235CD186004CBFA1 /* NoReservationsView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NoReservationsView.xib; sourceTree = "<group>"; };
203 1246B716235CD186004CBFA1 /* NoReservationsView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NoReservationsView.xib; sourceTree = "<group>"; };
100 1246B718235CD3AD004CBFA1 /* NoReservationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoReservationsView.swift; sourceTree = "<group>"; };
204 1246B718235CD3AD004CBFA1 /* NoReservationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoReservationsView.swift; sourceTree = "<group>"; };
101 1246B71B235CDF3A004CBFA1 /* ReservationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReservationCell.swift; sourceTree = "<group>"; };
205 1246B71B235CDF3A004CBFA1 /* ReservationCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReservationCell.swift; sourceTree = "<group>"; };
102 1246B71D235CDF8A004CBFA1 /* ReservationView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ReservationView.xib; sourceTree = "<group>"; };
206 1246B71D235CDF8A004CBFA1 /* ReservationView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ReservationView.xib; sourceTree = "<group>"; };
103 1246B71F235CDF9E004CBFA1 /* ReservationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReservationView.swift; sourceTree = "<group>"; };
207 1246B71F235CDF9E004CBFA1 /* ReservationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReservationView.swift; sourceTree = "<group>"; };
208+ 124D007B23721FD400AE76B9 /* Reservation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reservation.swift; sourceTree = "<group>"; };
209+ 124D007E237222B200AE76B9 /* ReservationsResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReservationsResolver.swift; sourceTree = "<group>"; };
210+ 124D0080237222C900AE76B9 /* ReservationsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReservationsService.swift; sourceTree = "<group>"; };
211+ 1252D9E423CDCDB300446B6F /* ACreateReservationResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ACreateReservationResolver.swift; sourceTree = "<group>"; };
212+ 125AE51C2395583E00F847BD /* ChangeReservationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeReservationCoordinator.swift; sourceTree = "<group>"; };
213+ 125AE51E2395585300F847BD /* ChangeReservationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeReservationViewController.swift; sourceTree = "<group>"; };
214+ 125AE5202395753400F847BD /* HFunc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HFunc.swift; sourceTree = "<group>"; };
104 12838EA8236764A600CEF108 /* APIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIClient.swift; sourceTree = "<group>"; };
215 12838EA8236764A600CEF108 /* APIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIClient.swift; sourceTree = "<group>"; };
105 12838EAD2367701300CEF108 /* AuthorizationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationService.swift; sourceTree = "<group>"; };
216 12838EAD2367701300CEF108 /* AuthorizationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthorizationService.swift; sourceTree = "<group>"; };
106 12838EAF236777D400CEF108 /* ServerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerError.swift; sourceTree = "<group>"; };
217 12838EAF236777D400CEF108 /* ServerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerError.swift; sourceTree = "<group>"; };
@@ -108,15 +219,62 @@
108 12838EB3236779C700CEF108 /* ApiResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiResolver.swift; sourceTree = "<group>"; };
219 12838EB3236779C700CEF108 /* ApiResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiResolver.swift; sourceTree = "<group>"; };
109 12838EB523677FB900CEF108 /* ApiRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiRequest.swift; sourceTree = "<group>"; };
220 12838EB523677FB900CEF108 /* ApiRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApiRequest.swift; sourceTree = "<group>"; };
110 12838EB72367834900CEF108 /* MyCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyCodable.swift; sourceTree = "<group>"; };
221 12838EB72367834900CEF108 /* MyCodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MyCodable.swift; sourceTree = "<group>"; };
222+ 129F162623CC838200EE13FC /* AdminFirstStepReservationViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminFirstStepReservationViewCoordinator.swift; sourceTree = "<group>"; };
223+ 129F162B23CC8D2E00EE13FC /* AdminSecondStepReservationViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminSecondStepReservationViewCoordinator.swift; sourceTree = "<group>"; };
224+ 129F162F23CC8EF600EE13FC /* PhoneTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhoneTextField.swift; sourceTree = "<group>"; };
225+ 12A075F62373535C00D749BB /* FirstStepReservationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstStepReservationViewController.swift; sourceTree = "<group>"; };
226+ 12A075F8237353F500D749BB /* MapView1.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MapView1.xib; sourceTree = "<group>"; };
227+ 12A075FA2373561A00D749BB /* Reservation.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Reservation.storyboard; sourceTree = "<group>"; };
228+ 12A075FC237357E900D749BB /* FirstStepReservationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstStepReservationCoordinator.swift; sourceTree = "<group>"; };
229+ 12A075FE237358F200D749BB /* MapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView.swift; sourceTree = "<group>"; };
230+ 12A07600237359BB00D749BB /* MapView1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapView1.swift; sourceTree = "<group>"; };
231+ 12B3A79723ABADB9009E2FA6 /* AdminReservationView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AdminReservationView.xib; sourceTree = "<group>"; };
232+ 12B3A79923ABB4E2009E2FA6 /* IntervalPickerView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = IntervalPickerView.xib; sourceTree = "<group>"; };
233+ 12B3A79B23ABB8ED009E2FA6 /* IntervalPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntervalPickerView.swift; sourceTree = "<group>"; };
234+ 12B3A79E23ABCA21009E2FA6 /* AdminReservationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminReservationView.swift; sourceTree = "<group>"; };
235+ 12B3A7A223ABD594009E2FA6 /* AdminReservationsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminReservationsController.swift; sourceTree = "<group>"; };
236+ 12B3A7A423ABDA25009E2FA6 /* AdminReservationsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminReservationsCoordinator.swift; sourceTree = "<group>"; };
237+ 12B3A7A723ABDDB1009E2FA6 /* AdminReservationCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminReservationCellView.swift; sourceTree = "<group>"; };
238+ 12B3A7A923ABDDC1009E2FA6 /* AdminReservations.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = AdminReservations.storyboard; sourceTree = "<group>"; };
239+ 12B3A7AC23ABEABF009E2FA6 /* AReservationsResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AReservationsResolver.swift; sourceTree = "<group>"; };
240+ 12B3A7AE23AC1872009E2FA6 /* AdminNoReservationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminNoReservationsView.swift; sourceTree = "<group>"; };
241+ 12B3A7B023AC1885009E2FA6 /* AdminNoReservationsView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AdminNoReservationsView.xib; sourceTree = "<group>"; };
242+ 12B3A7B323AC1D09009E2FA6 /* AdminReservationCellView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AdminReservationCellView.xib; sourceTree = "<group>"; };
243+ 12B3A7B823ACCD83009E2FA6 /* AddOtherAdminResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddOtherAdminResolver.swift; sourceTree = "<group>"; };
244+ 12B3A7BB23ACFD75009E2FA6 /* ReservationsAroundResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReservationsAroundResolver.swift; sourceTree = "<group>"; };
245+ 12B3A7BD23AD0048009E2FA6 /* AdminReservationEditingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminReservationEditingViewController.swift; sourceTree = "<group>"; };
246+ 12B3A7BF23AD0749009E2FA6 /* AdminReservationEditingCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminReservationEditingCoordinator.swift; sourceTree = "<group>"; };
247+ 12B3A7C323AD2FCD009E2FA6 /* AChangeReservationResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AChangeReservationResolver.swift; sourceTree = "<group>"; };
248+ 12B4447B238EF25C000B6B16 /* CreateReservationResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateReservationResolver.swift; sourceTree = "<group>"; };
111 12B6A60E235B857A00898B31 /* ProfileChangeCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileChangeCoordinator.swift; sourceTree = "<group>"; };
249 12B6A60E235B857A00898B31 /* ProfileChangeCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileChangeCoordinator.swift; sourceTree = "<group>"; };
112 12B6A610235B8EC500898B31 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
250 12B6A610235B8EC500898B31 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
113 12B6A612235B9B4300898B31 /* BaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = "<group>"; };
251 12B6A612235B9B4300898B31 /* BaseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = "<group>"; };
114 12B6A614235BB36C00898B31 /* ChangeView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ChangeView.xib; sourceTree = "<group>"; };
252 12B6A614235BB36C00898B31 /* ChangeView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ChangeView.xib; sourceTree = "<group>"; };
115 12B6A616235BB37D00898B31 /* ChangeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeView.swift; sourceTree = "<group>"; };
253 12B6A616235BB37D00898B31 /* ChangeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeView.swift; sourceTree = "<group>"; };
254+ 12BD95AF237EBEDE009614D8 /* FirstStepView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FirstStepView.xib; sourceTree = "<group>"; };
255+ 12BD95B1237EC22B009614D8 /* FirstStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirstStepView.swift; sourceTree = "<group>"; };
256+ 12BD95B5237ED8AB009614D8 /* TimePointView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TimePointView.xib; sourceTree = "<group>"; };
257+ 12BD95B7237EDB01009614D8 /* TimePointView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimePointView.swift; sourceTree = "<group>"; };
258+ 12BE4B322399A52E00FC974B /* AdminTabbarCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminTabbarCoordinator.swift; sourceTree = "<group>"; };
259+ 12BE4B342399A60A00FC974B /* AdminTabbarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdminTabbarController.swift; sourceTree = "<group>"; };
260+ 12BE4B362399A98200FC974B /* Admin.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Admin.storyboard; sourceTree = "<group>"; };
261+ 12C0B4BA239E5A3A00562BA3 /* ClientList.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = ClientList.storyboard; sourceTree = "<group>"; };
116 12C72EDE23537D1B00AD7004 /* ProfileCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileCoordinator.swift; sourceTree = "<group>"; };
262 12C72EDE23537D1B00AD7004 /* ProfileCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileCoordinator.swift; sourceTree = "<group>"; };
117 12C72EE0235380D400AD7004 /* ProfileClientViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileClientViewController.swift; sourceTree = "<group>"; };
263 12C72EE0235380D400AD7004 /* ProfileClientViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileClientViewController.swift; sourceTree = "<group>"; };
118 12C72EE2235380EB00AD7004 /* ReservationsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReservationsViewController.swift; sourceTree = "<group>"; };
264 12C72EE2235380EB00AD7004 /* ReservationsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReservationsViewController.swift; sourceTree = "<group>"; };
265+ 12D8FA2C237F003800305F57 /* DateView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DateView.xib; sourceTree = "<group>"; };
266+ 12D8FA2E237F00E200305F57 /* DateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateView.swift; sourceTree = "<group>"; };
267+ 12D8FA30237F08C700305F57 /* AllReservations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllReservations.swift; sourceTree = "<group>"; };
268+ 12D928AE23C47DCF0051724D /* ADeleteReservationResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ADeleteReservationResolver.swift; sourceTree = "<group>"; };
269+ 12D928B023C486380051724D /* BaseTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTableViewController.swift; sourceTree = "<group>"; };
270+ 12D928B223C7464B0051724D /* BlocklistPutResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlocklistPutResolver.swift; sourceTree = "<group>"; };
119 12DBEEB123539F2100AF1BB6 /* Client.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Client.storyboard; sourceTree = "<group>"; };
271 12DBEEB123539F2100AF1BB6 /* Client.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Client.storyboard; sourceTree = "<group>"; };
272+ 12E35070239FA8CA004BB188 /* Extensions+String.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extensions+String.swift"; sourceTree = "<group>"; };
273+ 12E35073239FAF52004BB188 /* ClientsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientsService.swift; sourceTree = "<group>"; };
274+ 12E35077239FAFA4004BB188 /* ClientsListResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientsListResolver.swift; sourceTree = "<group>"; };
275+ 12E3507A239FCFB2004BB188 /* ProfileTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileTableViewController.swift; sourceTree = "<group>"; };
276+ 12E3507C239FD1F3004BB188 /* ClientReservationsResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientReservationsResolver.swift; sourceTree = "<group>"; };
277+ 12E3507E239FDFC2004BB188 /* ClientProfileCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientProfileCoordinator.swift; sourceTree = "<group>"; };
120 12EA99452362B674005D9FE9 /* AddressCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressCoordinator.swift; sourceTree = "<group>"; };
278 12EA99452362B674005D9FE9 /* AddressCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressCoordinator.swift; sourceTree = "<group>"; };
121 12EA99472362B684005D9FE9 /* AddresViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddresViewController.swift; sourceTree = "<group>"; };
279 12EA99472362B684005D9FE9 /* AddresViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddresViewController.swift; sourceTree = "<group>"; };
122 12EA994B2362F8EF005D9FE9 /* AddressView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AddressView.xib; sourceTree = "<group>"; };
280 12EA994B2362F8EF005D9FE9 /* AddressView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AddressView.xib; sourceTree = "<group>"; };
@@ -124,6 +282,12 @@
124 12EB698023661CB300E4D1CE /* AddressCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressCell.swift; sourceTree = "<group>"; };
282 12EB698023661CB300E4D1CE /* AddressCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressCell.swift; sourceTree = "<group>"; };
125 12EE4A75234BB8FE005B4D1A /* CodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeView.swift; sourceTree = "<group>"; };
283 12EE4A75234BB8FE005B4D1A /* CodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeView.swift; sourceTree = "<group>"; };
126 12F73B012357AD7B007D4476 /* ProfileChangeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileChangeViewController.swift; sourceTree = "<group>"; };
284 12F73B012357AD7B007D4476 /* ProfileChangeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileChangeViewController.swift; sourceTree = "<group>"; };
285+ 12F9652923868E6000FCA696 /* SecondStepView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SecondStepView.xib; sourceTree = "<group>"; };
286+ 12F9652B23868E7200FCA696 /* SecondStepView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondStepView.swift; sourceTree = "<group>"; };
287+ 12F9652D23869F3100FCA696 /* SecondStepReservationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondStepReservationCoordinator.swift; sourceTree = "<group>"; };
288+ 12FC07B823C8D17600EDC55B /* DeleteOtherAdminResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteOtherAdminResolver.swift; sourceTree = "<group>"; };
289+ 12FC40372382C93C00AB593B /* TableButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableButtonStyle.swift; sourceTree = "<group>"; };
290+ 12FC40392382CB6F00AB593B /* TableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableButton.swift; sourceTree = "<group>"; };
127 F30369B4233679EA00A8EA84 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
291 F30369B4233679EA00A8EA84 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
128 F30369BD2336924D00A8EA84 /* ClientTabbarCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientTabbarCoordinator.swift; sourceTree = "<group>"; };
292 F30369BD2336924D00A8EA84 /* ClientTabbarCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientTabbarCoordinator.swift; sourceTree = "<group>"; };
129 F30369BF2336925800A8EA84 /* ClientTabbarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientTabbarController.swift; sourceTree = "<group>"; };
293 F30369BF2336925800A8EA84 /* ClientTabbarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientTabbarController.swift; sourceTree = "<group>"; };
@@ -186,6 +350,17 @@
186/* End PBXFrameworksBuildPhase section */
350/* End PBXFrameworksBuildPhase section */
187
351
188/* Begin PBXGroup section */
352/* Begin PBXGroup section */
353+ 120BFC7A2389194B001EBE89 /* 3 */ = {
354+ isa = PBXGroup;
355+ children = (
356+ 120BFC7623891925001EBE89 /* ThirdStepReservationViewController.swift */,
357+ 120BFC7823891942001EBE89 /* ThirdStepReservationCoordinator.swift */,
358+ 120BFC7B23891965001EBE89 /* ThirdStepView.swift */,
359+ 120BFC7D23891975001EBE89 /* ThirdStepView.xib */,
360+ );
361+ path = 3;
362+ sourceTree = "<group>";
363+ };
189 12118B9A2368187B006BAEE3 /* Authorization api */ = {
364 12118B9A2368187B006BAEE3 /* Authorization api */ = {
190 isa = PBXGroup;
365 isa = PBXGroup;
191 children = (
366 children = (
@@ -220,6 +395,9 @@
220 1212F1322353A603005BEF8A /* Reservations */ = {
395 1212F1322353A603005BEF8A /* Reservations */ = {
221 isa = PBXGroup;
396 isa = PBXGroup;
222 children = (
397 children = (
398+ 12BD95AE237EBE52009614D8 /* Maps */,
399+ 12BD95B3237EC231009614D8 /* Steps */,
400+ 12A075FA2373561A00D749BB /* Reservation.storyboard */,
223 1246B721235CDFC2004CBFA1 /* Cells */,
401 1246B721235CDFC2004CBFA1 /* Cells */,
224 1246B71A235CD4C5004CBFA1 /* Views */,
402 1246B71A235CD4C5004CBFA1 /* Views */,
225 12EA994A2362BA11005D9FE9 /* Active Reservations */,
403 12EA994A2362BA11005D9FE9 /* Active Reservations */,
@@ -228,6 +406,86 @@
228 path = Reservations;
406 path = Reservations;
229 sourceTree = "<group>";
407 sourceTree = "<group>";
230 };
408 };
409+ 1220A6F523CDB63000365E36 /* 2 */ = {
410+ isa = PBXGroup;
411+ children = (
412+ 1220A6F623CDB64A00365E36 /* AdminSecondStepReservationViewController.swift */,
413+ 129F162B23CC8D2E00EE13FC /* AdminSecondStepReservationViewCoordinator.swift */,
414+ );
415+ path = 2;
416+ sourceTree = "<group>";
417+ };
418+ 1220A6F823CDB65C00365E36 /* 1 */ = {
419+ isa = PBXGroup;
420+ children = (
421+ 129F162623CC838200EE13FC /* AdminFirstStepReservationViewCoordinator.swift */,
422+ );
423+ path = 1;
424+ sourceTree = "<group>";
425+ };
426+ 1220A6FB23CDBED900365E36 /* 3 */ = {
427+ isa = PBXGroup;
428+ children = (
429+ 1220A6F923CDBE7E00365E36 /* AdminFinalStepReservationCoordinator.swift */,
430+ );
431+ path = 3;
432+ sourceTree = "<group>";
433+ };
434+ 12230AED238FD8D5004A01E5 /* 1 */ = {
435+ isa = PBXGroup;
436+ children = (
437+ 12A075F8237353F500D749BB /* MapView1.xib */,
438+ 12A07600237359BB00D749BB /* MapView1.swift */,
439+ );
440+ path = 1;
441+ sourceTree = "<group>";
442+ };
443+ 12230AEE238FD8DE004A01E5 /* 2 */ = {
444+ isa = PBXGroup;
445+ children = (
446+ 12230AE9238FD8B3004A01E5 /* MapView2.xib */,
447+ 12230AEB238FD8C5004A01E5 /* MapView2.swift */,
448+ );
449+ path = 2;
450+ sourceTree = "<group>";
451+ };
452+ 12230AEF239008F8004A01E5 /* Common */ = {
453+ isa = PBXGroup;
454+ children = (
455+ 12BD95B7237EDB01009614D8 /* TimePointView.swift */,
456+ 12BD95B5237ED8AB009614D8 /* TimePointView.xib */,
457+ 12D8FA2E237F00E200305F57 /* DateView.swift */,
458+ 12D8FA2C237F003800305F57 /* DateView.xib */,
459+ );
460+ path = Common;
461+ sourceTree = "<group>";
462+ };
463+ 12230AF223900BB6004A01E5 /* Establishments api */ = {
464+ isa = PBXGroup;
465+ children = (
466+ 12230AF323900BCB004A01E5 /* EstablishmentService.swift */,
467+ 12230AF523900D32004A01E5 /* Resolvers */,
468+ );
469+ path = "Establishments api";
470+ sourceTree = "<group>";
471+ };
472+ 12230AF523900D32004A01E5 /* Resolvers */ = {
473+ isa = PBXGroup;
474+ children = (
475+ 12230AF623900D44004A01E5 /* EstablishmentResolver.swift */,
476+ );
477+ path = Resolvers;
478+ sourceTree = "<group>";
479+ };
480+ 12230AF823901000004A01E5 /* DB */ = {
481+ isa = PBXGroup;
482+ children = (
483+ 12230AF923901029004A01E5 /* DataStorage.swift */,
484+ 12230AF023900974004A01E5 /* TotalStorage.swift */,
485+ );
486+ path = DB;
487+ sourceTree = "<group>";
488+ };
231 123615D22355141D00D1122A /* Views */ = {
489 123615D22355141D00D1122A /* Views */ = {
232 isa = PBXGroup;
490 isa = PBXGroup;
233 children = (
491 children = (
@@ -239,6 +497,46 @@
239 path = Views;
497 path = Views;
240 sourceTree = "<group>";
498 sourceTree = "<group>";
241 };
499 };
500+ 123D25EC239E3D47002FA32B /* Profile */ = {
501+ isa = PBXGroup;
502+ children = (
503+ );
504+ path = Profile;
505+ sourceTree = "<group>";
506+ };
507+ 123D25ED239E3D50002FA32B /* Reservations */ = {
508+ isa = PBXGroup;
509+ children = (
510+ 129F162523CC836600EE13FC /* Making a reeservation */,
511+ 12B3A79623ABAD9A009E2FA6 /* Views */,
512+ 12B3A7C123AD0751009E2FA6 /* Editing */,
513+ 12B3A7C223AD0762009E2FA6 /* List of reservations */,
514+ 123D25EF239E3E11002FA32B /* AdminAddressCoordinator.swift */,
515+ 12B3A7A923ABDDC1009E2FA6 /* AdminReservations.storyboard */,
516+ );
517+ path = Reservations;
518+ sourceTree = "<group>";
519+ };
520+ 123D25EE239E3D61002FA32B /* Clients */ = {
521+ isa = PBXGroup;
522+ children = (
523+ 12E35079239FCF91004BB188 /* Profile */,
524+ 123D25F5239E57D6002FA32B /* Cells */,
525+ 123D25F1239E3E44002FA32B /* ClientsCoordinator.swift */,
526+ 123D25F3239E3FF5002FA32B /* ClientsViewController.swift */,
527+ 12C0B4BA239E5A3A00562BA3 /* ClientList.storyboard */,
528+ );
529+ path = Clients;
530+ sourceTree = "<group>";
531+ };
532+ 123D25F5239E57D6002FA32B /* Cells */ = {
533+ isa = PBXGroup;
534+ children = (
535+ 123D25F6239E5839002FA32B /* ClientTableViewCell.swift */,
536+ );
537+ path = Cells;
538+ sourceTree = "<group>";
539+ };
242 1246B71A235CD4C5004CBFA1 /* Views */ = {
540 1246B71A235CD4C5004CBFA1 /* Views */ = {
243 isa = PBXGroup;
541 isa = PBXGroup;
244 children = (
542 children = (
@@ -257,6 +555,37 @@
257 path = Cells;
555 path = Cells;
258 sourceTree = "<group>";
556 sourceTree = "<group>";
259 };
557 };
558+ 124D007D2372229500AE76B9 /* Reservations api */ = {
559+ isa = PBXGroup;
560+ children = (
561+ 124D0080237222C900AE76B9 /* ReservationsService.swift */,
562+ 124D00822372233000AE76B9 /* Resolvers */,
563+ );
564+ path = "Reservations api";
565+ sourceTree = "<group>";
566+ };
567+ 124D00822372233000AE76B9 /* Resolvers */ = {
568+ isa = PBXGroup;
569+ children = (
570+ 12B3A7AB23ABEAAB009E2FA6 /* Admin */,
571+ 124D007E237222B200AE76B9 /* ReservationsResolver.swift */,
572+ 12B4447B238EF25C000B6B16 /* CreateReservationResolver.swift */,
573+ 1212048B23914CFD00CAF162 /* DeleteReservationResolver.swift */,
574+ 1212048D23915A0A00CAF162 /* ChangeReservationResolver.swift */,
575+ 12B3A7BB23ACFD75009E2FA6 /* ReservationsAroundResolver.swift */,
576+ );
577+ path = Resolvers;
578+ sourceTree = "<group>";
579+ };
580+ 125AE51B2395582900F847BD /* change */ = {
581+ isa = PBXGroup;
582+ children = (
583+ 125AE51C2395583E00F847BD /* ChangeReservationCoordinator.swift */,
584+ 125AE51E2395585300F847BD /* ChangeReservationViewController.swift */,
585+ );
586+ path = "change ";
587+ sourceTree = "<group>";
588+ };
260 12838EB923679AEB00CEF108 /* API */ = {
589 12838EB923679AEB00CEF108 /* API */ = {
261 isa = PBXGroup;
590 isa = PBXGroup;
262 children = (
591 children = (
@@ -265,11 +594,142 @@
265 12838EAF236777D400CEF108 /* ServerError.swift */,
594 12838EAF236777D400CEF108 /* ServerError.swift */,
266 12838EB523677FB900CEF108 /* ApiRequest.swift */,
595 12838EB523677FB900CEF108 /* ApiRequest.swift */,
267 12838EB72367834900CEF108 /* MyCodable.swift */,
596 12838EB72367834900CEF108 /* MyCodable.swift */,
597+ 12E35072239FAF41004BB188 /* ClientsService */,
598+ 12230AF223900BB6004A01E5 /* Establishments api */,
599+ 124D007D2372229500AE76B9 /* Reservations api */,
268 12118B9A2368187B006BAEE3 /* Authorization api */,
600 12118B9A2368187B006BAEE3 /* Authorization api */,
269 );
601 );
270 path = API;
602 path = API;
271 sourceTree = "<group>";
603 sourceTree = "<group>";
272 };
604 };
605+ 129F162523CC836600EE13FC /* Making a reeservation */ = {
606+ isa = PBXGroup;
607+ children = (
608+ 1220A6F823CDB65C00365E36 /* 1 */,
609+ 1220A6F523CDB63000365E36 /* 2 */,
610+ 1220A6FB23CDBED900365E36 /* 3 */,
611+ );
612+ path = "Making a reeservation";
613+ sourceTree = "<group>";
614+ };
615+ 129F162823CC89EA00EE13FC /* Phone number */ = {
616+ isa = PBXGroup;
617+ children = (
618+ );
619+ path = "Phone number";
620+ sourceTree = "<group>";
621+ };
622+ 12B3A79623ABAD9A009E2FA6 /* Views */ = {
623+ isa = PBXGroup;
624+ children = (
625+ 129F162823CC89EA00EE13FC /* Phone number */,
626+ 12B3A7B223AC1A1F009E2FA6 /* No reserv */,
627+ 12B3A79723ABADB9009E2FA6 /* AdminReservationView.xib */,
628+ 12B3A79E23ABCA21009E2FA6 /* AdminReservationView.swift */,
629+ );
630+ path = Views;
631+ sourceTree = "<group>";
632+ };
633+ 12B3A79D23ABB8F4009E2FA6 /* Picker view */ = {
634+ isa = PBXGroup;
635+ children = (
636+ 12B3A79923ABB4E2009E2FA6 /* IntervalPickerView.xib */,
637+ 12B3A79B23ABB8ED009E2FA6 /* IntervalPickerView.swift */,
638+ );
639+ path = "Picker view";
640+ sourceTree = "<group>";
641+ };
642+ 12B3A7A623ABDD92009E2FA6 /* Cell */ = {
643+ isa = PBXGroup;
644+ children = (
645+ 12B3A7A723ABDDB1009E2FA6 /* AdminReservationCellView.swift */,
646+ 12B3A7B323AC1D09009E2FA6 /* AdminReservationCellView.xib */,
647+ );
648+ path = Cell;
649+ sourceTree = "<group>";
650+ };
651+ 12B3A7AB23ABEAAB009E2FA6 /* Admin */ = {
652+ isa = PBXGroup;
653+ children = (
654+ 12B3A7AC23ABEABF009E2FA6 /* AReservationsResolver.swift */,
655+ 12B3A7C323AD2FCD009E2FA6 /* AChangeReservationResolver.swift */,
656+ 12D928AE23C47DCF0051724D /* ADeleteReservationResolver.swift */,
657+ 1252D9E423CDCDB300446B6F /* ACreateReservationResolver.swift */,
658+ );
659+ path = Admin;
660+ sourceTree = "<group>";
661+ };
662+ 12B3A7B223AC1A1F009E2FA6 /* No reserv */ = {
663+ isa = PBXGroup;
664+ children = (
665+ 12B3A7AE23AC1872009E2FA6 /* AdminNoReservationsView.swift */,
666+ 12B3A7B023AC1885009E2FA6 /* AdminNoReservationsView.xib */,
667+ );
668+ path = "No reserv";
669+ sourceTree = "<group>";
670+ };
671+ 12B3A7C123AD0751009E2FA6 /* Editing */ = {
672+ isa = PBXGroup;
673+ children = (
674+ 12B3A7BF23AD0749009E2FA6 /* AdminReservationEditingCoordinator.swift */,
675+ 12B3A7BD23AD0048009E2FA6 /* AdminReservationEditingViewController.swift */,
676+ );
677+ name = Editing;
678+ sourceTree = "<group>";
679+ };
680+ 12B3A7C223AD0762009E2FA6 /* List of reservations */ = {
681+ isa = PBXGroup;
682+ children = (
683+ 12B3A7A623ABDD92009E2FA6 /* Cell */,
684+ 12B3A7A223ABD594009E2FA6 /* AdminReservationsController.swift */,
685+ 12B3A7A423ABDA25009E2FA6 /* AdminReservationsCoordinator.swift */,
686+ );
687+ path = "List of reservations";
688+ sourceTree = "<group>";
689+ };
690+ 12BD95AE237EBE52009614D8 /* Maps */ = {
691+ isa = PBXGroup;
692+ children = (
693+ 12A075FE237358F200D749BB /* MapView.swift */,
694+ 12230AED238FD8D5004A01E5 /* 1 */,
695+ 12230AEE238FD8DE004A01E5 /* 2 */,
696+ );
697+ path = Maps;
698+ sourceTree = "<group>";
699+ };
700+ 12BD95B3237EC231009614D8 /* Steps */ = {
701+ isa = PBXGroup;
702+ children = (
703+ 125AE51B2395582900F847BD /* change */,
704+ 12230AEF239008F8004A01E5 /* Common */,
705+ 12BD95B4237EC23A009614D8 /* 1 */,
706+ 12F9652823868E4900FCA696 /* 2 */,
707+ 120BFC7A2389194B001EBE89 /* 3 */,
708+ );
709+ path = Steps;
710+ sourceTree = "<group>";
711+ };
712+ 12BD95B4237EC23A009614D8 /* 1 */ = {
713+ isa = PBXGroup;
714+ children = (
715+ 12A075F62373535C00D749BB /* FirstStepReservationViewController.swift */,
716+ 12A075FC237357E900D749BB /* FirstStepReservationCoordinator.swift */,
717+ 12BD95B1237EC22B009614D8 /* FirstStepView.swift */,
718+ 12BD95AF237EBEDE009614D8 /* FirstStepView.xib */,
719+ );
720+ path = 1;
721+ sourceTree = "<group>";
722+ };
723+ 12BE4B312399A51500FC974B /* Tab bar */ = {
724+ isa = PBXGroup;
725+ children = (
726+ 12BE4B322399A52E00FC974B /* AdminTabbarCoordinator.swift */,
727+ 12BE4B342399A60A00FC974B /* AdminTabbarController.swift */,
728+ 12BE4B362399A98200FC974B /* Admin.storyboard */,
729+ );
730+ path = "Tab bar";
731+ sourceTree = "<group>";
732+ };
273 12C72EDB235367CF00AD7004 /* Tab bar */ = {
733 12C72EDB235367CF00AD7004 /* Tab bar */ = {
274 isa = PBXGroup;
734 isa = PBXGroup;
275 children = (
735 children = (
@@ -280,6 +740,37 @@
280 path = "Tab bar";
740 path = "Tab bar";
281 sourceTree = "<group>";
741 sourceTree = "<group>";
282 };
742 };
743+ 12E35072239FAF41004BB188 /* ClientsService */ = {
744+ isa = PBXGroup;
745+ children = (
746+ 12E35073239FAF52004BB188 /* ClientsService.swift */,
747+ 12E35075239FAF8F004BB188 /* Resolvers */,
748+ );
749+ path = ClientsService;
750+ sourceTree = "<group>";
751+ };
752+ 12E35075239FAF8F004BB188 /* Resolvers */ = {
753+ isa = PBXGroup;
754+ children = (
755+ 12FC07B823C8D17600EDC55B /* DeleteOtherAdminResolver.swift */,
756+ 12B3A7B823ACCD83009E2FA6 /* AddOtherAdminResolver.swift */,
757+ 12E35077239FAFA4004BB188 /* ClientsListResolver.swift */,
758+ 12E3507C239FD1F3004BB188 /* ClientReservationsResolver.swift */,
759+ 12D928B223C7464B0051724D /* BlocklistPutResolver.swift */,
760+ 1220A6F323CDB1AD00365E36 /* GetClientByPhoneNumberResolver.swift */,
761+ );
762+ path = Resolvers;
763+ sourceTree = "<group>";
764+ };
765+ 12E35079239FCF91004BB188 /* Profile */ = {
766+ isa = PBXGroup;
767+ children = (
768+ 12E3507A239FCFB2004BB188 /* ProfileTableViewController.swift */,
769+ 12E3507E239FDFC2004BB188 /* ClientProfileCoordinator.swift */,
770+ );
771+ path = Profile;
772+ sourceTree = "<group>";
773+ };
283 12EA99492362B9F5005D9FE9 /* Adress of reservation */ = {
774 12EA99492362B9F5005D9FE9 /* Adress of reservation */ = {
284 isa = PBXGroup;
775 isa = PBXGroup;
285 children = (
776 children = (
@@ -327,6 +818,17 @@
327 path = Code;
818 path = Code;
328 sourceTree = "<group>";
819 sourceTree = "<group>";
329 };
820 };
821+ 12F9652823868E4900FCA696 /* 2 */ = {
822+ isa = PBXGroup;
823+ children = (
824+ 12230AFB239034D3004A01E5 /* SecondStepReservationViewController.swift */,
825+ 12F9652D23869F3100FCA696 /* SecondStepReservationCoordinator.swift */,
826+ 12F9652B23868E7200FCA696 /* SecondStepView.swift */,
827+ 12F9652923868E6000FCA696 /* SecondStepView.xib */,
828+ );
829+ path = 2;
830+ sourceTree = "<group>";
831+ };
330 F30369B623367B2300A8EA84 /* Supporting Files */ = {
832 F30369B623367B2300A8EA84 /* Supporting Files */ = {
331 isa = PBXGroup;
833 isa = PBXGroup;
332 children = (
834 children = (
@@ -342,6 +844,8 @@
342 F3FC7DFC2315C4710080D851 /* BaseCoordinator.swift */,
844 F3FC7DFC2315C4710080D851 /* BaseCoordinator.swift */,
343 F3FC7DFA2315C25D0080D851 /* ApplicationCoordinator.swift */,
845 F3FC7DFA2315C25D0080D851 /* ApplicationCoordinator.swift */,
344 12B6A612235B9B4300898B31 /* BaseViewController.swift */,
846 12B6A612235B9B4300898B31 /* BaseViewController.swift */,
847+ 12D928B023C486380051724D /* BaseTableViewController.swift */,
848+ 12B3A79D23ABB8F4009E2FA6 /* Picker view */,
345 F30369BB2336845F00A8EA84 /* Admin */,
849 F30369BB2336845F00A8EA84 /* Admin */,
346 F30369BA2336845400A8EA84 /* Client */,
850 F30369BA2336845400A8EA84 /* Client */,
347 F30369B82336842600A8EA84 /* Authentication */,
851 F30369B82336842600A8EA84 /* Authentication */,
@@ -354,8 +858,8 @@
354 children = (
858 children = (
355 F31E20C82348E0F300BEA215 /* AuthorizationViewController.swift */,
859 F31E20C82348E0F300BEA215 /* AuthorizationViewController.swift */,
356 F3895F51233D354300FA6A37 /* Phone */,
860 F3895F51233D354300FA6A37 /* Phone */,
357− F3895F50233D353600FA6A37 /* Code from sms */,
358 F3895F4F233D352400FA6A37 /* Name */,
861 F3895F4F233D352400FA6A37 /* Name */,
862+ F3895F50233D353600FA6A37 /* Code from sms */,
359 );
863 );
360 path = Authentication;
864 path = Authentication;
361 sourceTree = "<group>";
865 sourceTree = "<group>";
@@ -366,6 +870,9 @@
366 F3DC5A00233E896300B5E751 /* ButtonStyle.swift */,
870 F3DC5A00233E896300B5E751 /* ButtonStyle.swift */,
367 F3895F52233D5EEB00FA6A37 /* BlackButtonStyle.swift */,
871 F3895F52233D5EEB00FA6A37 /* BlackButtonStyle.swift */,
368 F3DC5A04233E89D500B5E751 /* Button.swift */,
872 F3DC5A04233E89D500B5E751 /* Button.swift */,
873+ 12FC40372382C93C00AB593B /* TableButtonStyle.swift */,
874+ 12FC40392382CB6F00AB593B /* TableButton.swift */,
875+ 129F162F23CC8EF600EE13FC /* PhoneTextField.swift */,
369 );
876 );
370 path = Components;
877 path = Components;
371 sourceTree = "<group>";
878 sourceTree = "<group>";
@@ -383,6 +890,10 @@
383 F30369BB2336845F00A8EA84 /* Admin */ = {
890 F30369BB2336845F00A8EA84 /* Admin */ = {
384 isa = PBXGroup;
891 isa = PBXGroup;
385 children = (
892 children = (
893+ 123D25ED239E3D50002FA32B /* Reservations */,
894+ 123D25EE239E3D61002FA32B /* Clients */,
895+ 123D25EC239E3D47002FA32B /* Profile */,
896+ 12BE4B312399A51500FC974B /* Tab bar */,
386 );
897 );
387 path = Admin;
898 path = Admin;
388 sourceTree = "<group>";
899 sourceTree = "<group>";
@@ -392,6 +903,8 @@
392 children = (
903 children = (
393 F3851B2F2340A513009EE0B6 /* RegistationViewModel.swift */,
904 F3851B2F2340A513009EE0B6 /* RegistationViewModel.swift */,
394 12B6A610235B8EC500898B31 /* User.swift */,
905 12B6A610235B8EC500898B31 /* User.swift */,
906+ 124D007B23721FD400AE76B9 /* Reservation.swift */,
907+ 12D8FA30237F08C700305F57 /* AllReservations.swift */,
395 );
908 );
396 path = Models;
909 path = Models;
397 sourceTree = "<group>";
910 sourceTree = "<group>";
@@ -484,10 +997,15 @@
484 F3FC7DCB2315BE610080D851 /* Hookah Tau */ = {
997 F3FC7DCB2315BE610080D851 /* Hookah Tau */ = {
485 isa = PBXGroup;
998 isa = PBXGroup;
486 children = (
999 children = (
487− 12838EB923679AEB00CEF108 /* API */,
1000+ 12230AF823901000004A01E5 /* DB */,
488 F3FC7DCC2315BE610080D851 /* AppDelegate.swift */,
1001 F3FC7DCC2315BE610080D851 /* AppDelegate.swift */,
489 F3DC59FE233E893400B5E751 /* Extensions+UIFont.swift */,
1002 F3DC59FE233E893400B5E751 /* Extensions+UIFont.swift */,
490 F31E20C62348DDB900BEA215 /* Extensions+UIView.swift */,
1003 F31E20C62348DDB900BEA215 /* Extensions+UIView.swift */,
1004+ 12050568238048CE00801618 /* Extensions+Date.swift */,
1005+ 1205056A2380588C00801618 /* Extenstions+IBDesignable.swift */,
1006+ 12E35070239FA8CA004BB188 /* Extensions+String.swift */,
1007+ 125AE5202395753400F847BD /* HFunc.swift */,
1008+ 12838EB923679AEB00CEF108 /* API */,
491 F30369BC2336861100A8EA84 /* Models */,
1009 F30369BC2336861100A8EA84 /* Models */,
492 F3199E59233FD7F700F3C17A /* Views */,
1010 F3199E59233FD7F700F3C17A /* Views */,
493 F30369B92336844500A8EA84 /* Components */,
1011 F30369B92336844500A8EA84 /* Components */,
@@ -627,13 +1145,28 @@
627 buildActionMask = 2147483647;
1145 buildActionMask = 2147483647;
628 files = (
1146 files = (
629 F3199E5B233FE08200F3C17A /* PhoneView.xib in Resources */,
1147 F3199E5B233FE08200F3C17A /* PhoneView.xib in Resources */,
1148+ 12B3A7AA23ABDDC1009E2FA6 /* AdminReservations.storyboard in Resources */,
1149+ 12B3A7B423AC1D09009E2FA6 /* AdminReservationCellView.xib in Resources */,
1150+ 12C0B4BB239E5A3A00562BA3 /* ClientList.storyboard in Resources */,
1151+ 12BE4B372399A98200FC974B /* Admin.storyboard in Resources */,
630 12DBEEB223539F2100AF1BB6 /* Client.storyboard in Resources */,
1152 12DBEEB223539F2100AF1BB6 /* Client.storyboard in Resources */,
1153+ 12D8FA2D237F003800305F57 /* DateView.xib in Resources */,
1154+ 12B3A79A23ABB4E2009E2FA6 /* IntervalPickerView.xib in Resources */,
631 12387337234BB98700D1F1AB /* CodeView.xib in Resources */,
1155 12387337234BB98700D1F1AB /* CodeView.xib in Resources */,
632 12B6A615235BB36C00898B31 /* ChangeView.xib in Resources */,
1156 12B6A615235BB36C00898B31 /* ChangeView.xib in Resources */,
633 F3851B2C2340A280009EE0B6 /* TitleTextView.xib in Resources */,
1157 F3851B2C2340A280009EE0B6 /* TitleTextView.xib in Resources */,
1158+ 12F9652A23868E6000FCA696 /* SecondStepView.xib in Resources */,
1159+ 120BFC7E23891975001EBE89 /* ThirdStepView.xib in Resources */,
1160+ 12230AEA238FD8B3004A01E5 /* MapView2.xib in Resources */,
634 1246B71E235CDF8A004CBFA1 /* ReservationView.xib in Resources */,
1161 1246B71E235CDF8A004CBFA1 /* ReservationView.xib in Resources */,
1162+ 12BD95B6237ED8AC009614D8 /* TimePointView.xib in Resources */,
1163+ 12A075F9237353F500D749BB /* MapView1.xib in Resources */,
1164+ 12A075FB2373561A00D749BB /* Reservation.storyboard in Resources */,
1165+ 12BD95B0237EBEDF009614D8 /* FirstStepView.xib in Resources */,
635 F30369B5233679EA00A8EA84 /* LaunchScreen.storyboard in Resources */,
1166 F30369B5233679EA00A8EA84 /* LaunchScreen.storyboard in Resources */,
636 1246B717235CD186004CBFA1 /* NoReservationsView.xib in Resources */,
1167 1246B717235CD186004CBFA1 /* NoReservationsView.xib in Resources */,
1168+ 12B3A7B123AC1885009E2FA6 /* AdminNoReservationsView.xib in Resources */,
1169+ 12B3A79823ABADB9009E2FA6 /* AdminReservationView.xib in Resources */,
637 F3FC7DD42315BE630080D851 /* Assets.xcassets in Resources */,
1170 F3FC7DD42315BE630080D851 /* Assets.xcassets in Resources */,
638 12EA994C2362F8EF005D9FE9 /* AddressView.xib in Resources */,
1171 12EA994C2362F8EF005D9FE9 /* AddressView.xib in Resources */,
639 F3EBE2452340FDA7000FEE1A /* NameTextView.xib in Resources */,
1172 F3EBE2452340FDA7000FEE1A /* NameTextView.xib in Resources */,
@@ -663,53 +1196,120 @@
663 buildActionMask = 2147483647;
1196 buildActionMask = 2147483647;
664 files = (
1197 files = (
665 12EA99482362B684005D9FE9 /* AddresViewController.swift in Sources */,
1198 12EA99482362B684005D9FE9 /* AddresViewController.swift in Sources */,
1199+ 124D007F237222B200AE76B9 /* ReservationsResolver.swift in Sources */,
1200+ 129F163023CC8EF600EE13FC /* PhoneTextField.swift in Sources */,
1201+ 129F162C23CC8D2E00EE13FC /* AdminSecondStepReservationViewCoordinator.swift in Sources */,
1202+ 12230AF723900D44004A01E5 /* EstablishmentResolver.swift in Sources */,
666 1246B719235CD3AD004CBFA1 /* NoReservationsView.swift in Sources */,
1203 1246B719235CD3AD004CBFA1 /* NoReservationsView.swift in Sources */,
667 F3895F4A233D323700FA6A37 /* CodeCoordinator.swift in Sources */,
1204 F3895F4A233D323700FA6A37 /* CodeCoordinator.swift in Sources */,
1205+ 12B3A7B923ACCD83009E2FA6 /* AddOtherAdminResolver.swift in Sources */,
1206+ 12E3507F239FDFC2004BB188 /* ClientProfileCoordinator.swift in Sources */,
1207+ 12D928B323C7464B0051724D /* BlocklistPutResolver.swift in Sources */,
668 12838EB4236779C700CEF108 /* ApiResolver.swift in Sources */,
1208 12838EB4236779C700CEF108 /* ApiResolver.swift in Sources */,
669 12838EA9236764A600CEF108 /* APIClient.swift in Sources */,
1209 12838EA9236764A600CEF108 /* APIClient.swift in Sources */,
670 123615D12355141600D1122A /* ProfileView.swift in Sources */,
1210 123615D12355141600D1122A /* ProfileView.swift in Sources */,
671 12F73B022357AD7B007D4476 /* ProfileChangeViewController.swift in Sources */,
1211 12F73B022357AD7B007D4476 /* ProfileChangeViewController.swift in Sources */,
672 F3851B2E2340A444009EE0B6 /* TitleTextView.swift in Sources */,
1212 F3851B2E2340A444009EE0B6 /* TitleTextView.swift in Sources */,
1213+ 125AE51F2395585300F847BD /* ChangeReservationViewController.swift in Sources */,
673 12EA99462362B674005D9FE9 /* AddressCoordinator.swift in Sources */,
1214 12EA99462362B674005D9FE9 /* AddressCoordinator.swift in Sources */,
674 12B6A613235B9B4300898B31 /* BaseViewController.swift in Sources */,
1215 12B6A613235B9B4300898B31 /* BaseViewController.swift in Sources */,
1216+ 12BE4B332399A52E00FC974B /* AdminTabbarCoordinator.swift in Sources */,
1217+ 12230AFA23901029004A01E5 /* DataStorage.swift in Sources */,
1218+ 12B3A7A823ABDDB1009E2FA6 /* AdminReservationCellView.swift in Sources */,
675 F3FC7DFF2315C8D70080D851 /* NameCoordinator.swift in Sources */,
1219 F3FC7DFF2315C8D70080D851 /* NameCoordinator.swift in Sources */,
1220+ 123D25F7239E5839002FA32B /* ClientTableViewCell.swift in Sources */,
1221+ 120BFC7C23891965001EBE89 /* ThirdStepView.swift in Sources */,
1222+ 123D25F0239E3E11002FA32B /* AdminAddressCoordinator.swift in Sources */,
676 F3FC7DCF2315BE610080D851 /* NameViewController.swift in Sources */,
1223 F3FC7DCF2315BE610080D851 /* NameViewController.swift in Sources */,
1224+ 12B3A79F23ABCA21009E2FA6 /* AdminReservationView.swift in Sources */,
677 12838EB82367834900CEF108 /* MyCodable.swift in Sources */,
1225 12838EB82367834900CEF108 /* MyCodable.swift in Sources */,
678 F3DC5A05233E89D500B5E751 /* Button.swift in Sources */,
1226 F3DC5A05233E89D500B5E751 /* Button.swift in Sources */,
679 F3FC7DFB2315C25D0080D851 /* ApplicationCoordinator.swift in Sources */,
1227 F3FC7DFB2315C25D0080D851 /* ApplicationCoordinator.swift in Sources */,
1228+ 12B3A7BE23AD0049009E2FA6 /* AdminReservationEditingViewController.swift in Sources */,
1229+ 12BE4B352399A60A00FC974B /* AdminTabbarController.swift in Sources */,
1230+ 12FC403A2382CB6F00AB593B /* TableButton.swift in Sources */,
1231+ 12D928AF23C47DCF0051724D /* ADeleteReservationResolver.swift in Sources */,
680 12EB698123661CB300E4D1CE /* AddressCell.swift in Sources */,
1232 12EB698123661CB300E4D1CE /* AddressCell.swift in Sources */,
681 F3895F48233D2EEA00FA6A37 /* CodeSmsViewController.swift in Sources */,
1233 F3895F48233D2EEA00FA6A37 /* CodeSmsViewController.swift in Sources */,
682 12219CEF2353A63F0057DDEE /* ReservationsCoordinator.swift in Sources */,
1234 12219CEF2353A63F0057DDEE /* ReservationsCoordinator.swift in Sources */,
1235+ 12230AF423900BCB004A01E5 /* EstablishmentService.swift in Sources */,
1236+ 129F162723CC838200EE13FC /* AdminFirstStepReservationViewCoordinator.swift in Sources */,
1237+ 12A07601237359BB00D749BB /* MapView1.swift in Sources */,
1238+ 125AE5212395753400F847BD /* HFunc.swift in Sources */,
1239+ 12BD95B8237EDB01009614D8 /* TimePointView.swift in Sources */,
683 12838EB2236779A000CEF108 /* AuthResolver.swift in Sources */,
1240 12838EB2236779A000CEF108 /* AuthResolver.swift in Sources */,
1241+ 12A075F72373535C00D749BB /* FirstStepReservationViewController.swift in Sources */,
1242+ 12E35078239FAFA4004BB188 /* ClientsListResolver.swift in Sources */,
684 1246B71C235CDF3A004CBFA1 /* ReservationCell.swift in Sources */,
1243 1246B71C235CDF3A004CBFA1 /* ReservationCell.swift in Sources */,
1244+ 12E3507B239FCFB2004BB188 /* ProfileTableViewController.swift in Sources */,
1245+ 1212048C23914CFD00CAF162 /* DeleteReservationResolver.swift in Sources */,
1246+ 12A075FD237357E900D749BB /* FirstStepReservationCoordinator.swift in Sources */,
1247+ 124D0081237222C900AE76B9 /* ReservationsService.swift in Sources */,
685 1246B720235CDF9E004CBFA1 /* ReservationView.swift in Sources */,
1248 1246B720235CDF9E004CBFA1 /* ReservationView.swift in Sources */,
1249+ 12B3A7A523ABDA25009E2FA6 /* AdminReservationsCoordinator.swift in Sources */,
1250+ 123D25F4239E3FF5002FA32B /* ClientsViewController.swift in Sources */,
1251+ 12050569238048CE00801618 /* Extensions+Date.swift in Sources */,
1252+ 124D007C23721FD400AE76B9 /* Reservation.swift in Sources */,
1253+ 12D8FA2F237F00E200305F57 /* DateView.swift in Sources */,
1254+ 123D25F2239E3E44002FA32B /* ClientsCoordinator.swift in Sources */,
1255+ 12F9652E23869F3100FCA696 /* SecondStepReservationCoordinator.swift in Sources */,
686 F3895F4E233D348B00FA6A37 /* PhoneCoordinator.swift in Sources */,
1256 F3895F4E233D348B00FA6A37 /* PhoneCoordinator.swift in Sources */,
687 12B6A617235BB37D00898B31 /* ChangeView.swift in Sources */,
1257 12B6A617235BB37D00898B31 /* ChangeView.swift in Sources */,
1258+ 12230AEC238FD8C5004A01E5 /* MapView2.swift in Sources */,
1259+ 12B3A7A323ABD594009E2FA6 /* AdminReservationsController.swift in Sources */,
688 F3FC7DCD2315BE610080D851 /* AppDelegate.swift in Sources */,
1260 F3FC7DCD2315BE610080D851 /* AppDelegate.swift in Sources */,
689 12838EB623677FB900CEF108 /* ApiRequest.swift in Sources */,
1261 12838EB623677FB900CEF108 /* ApiRequest.swift in Sources */,
1262+ 1220A6FA23CDBE7E00365E36 /* AdminFinalStepReservationCoordinator.swift in Sources */,
690 12118B9E23682049006BAEE3 /* PhoneCodeResolver.swift in Sources */,
1263 12118B9E23682049006BAEE3 /* PhoneCodeResolver.swift in Sources */,
691 F30369BE2336924D00A8EA84 /* ClientTabbarCoordinator.swift in Sources */,
1264 F30369BE2336924D00A8EA84 /* ClientTabbarCoordinator.swift in Sources */,
692 F3FC7DFD2315C4710080D851 /* BaseCoordinator.swift in Sources */,
1265 F3FC7DFD2315C4710080D851 /* BaseCoordinator.swift in Sources */,
1266+ 1220A6F723CDB64A00365E36 /* AdminSecondStepReservationViewController.swift in Sources */,
1267+ 12E35071239FA8CA004BB188 /* Extensions+String.swift in Sources */,
1268+ 12FC07B923C8D17600EDC55B /* DeleteOtherAdminResolver.swift in Sources */,
693 F3DC59FF233E893400B5E751 /* Extensions+UIFont.swift in Sources */,
1269 F3DC59FF233E893400B5E751 /* Extensions+UIFont.swift in Sources */,
694 F3895F53233D5EEB00FA6A37 /* BlackButtonStyle.swift in Sources */,
1270 F3895F53233D5EEB00FA6A37 /* BlackButtonStyle.swift in Sources */,
1271+ 12F9652C23868E7200FCA696 /* SecondStepView.swift in Sources */,
1272+ 120BFC7923891942001EBE89 /* ThirdStepReservationCoordinator.swift in Sources */,
1273+ 12B3A7BC23ACFD75009E2FA6 /* ReservationsAroundResolver.swift in Sources */,
695 12118B9C23682022006BAEE3 /* RegistrationResolver.swift in Sources */,
1274 12118B9C23682022006BAEE3 /* RegistrationResolver.swift in Sources */,
1275+ 12B3A7C423AD2FCD009E2FA6 /* AChangeReservationResolver.swift in Sources */,
696 F30369C02336925800A8EA84 /* ClientTabbarController.swift in Sources */,
1276 F30369C02336925800A8EA84 /* ClientTabbarController.swift in Sources */,
1277+ 12B3A7C023AD0749009E2FA6 /* AdminReservationEditingCoordinator.swift in Sources */,
697 F31E20C72348DDB900BEA215 /* Extensions+UIView.swift in Sources */,
1278 F31E20C72348DDB900BEA215 /* Extensions+UIView.swift in Sources */,
698 F3851B302340A513009EE0B6 /* RegistationViewModel.swift in Sources */,
1279 F3851B302340A513009EE0B6 /* RegistationViewModel.swift in Sources */,
1280+ 12B3A7AF23AC1872009E2FA6 /* AdminNoReservationsView.swift in Sources */,
699 12838EAE2367701300CEF108 /* AuthorizationService.swift in Sources */,
1281 12838EAE2367701300CEF108 /* AuthorizationService.swift in Sources */,
700 12C72EDF23537D1B00AD7004 /* ProfileCoordinator.swift in Sources */,
1282 12C72EDF23537D1B00AD7004 /* ProfileCoordinator.swift in Sources */,
1283+ 12E3507D239FD1F3004BB188 /* ClientReservationsResolver.swift in Sources */,
701 12EA994E2362FCE8005D9FE9 /* AddressView.swift in Sources */,
1284 12EA994E2362FCE8005D9FE9 /* AddressView.swift in Sources */,
1285+ 1220A6F423CDB1AD00365E36 /* GetClientByPhoneNumberResolver.swift in Sources */,
702 F3895F4C233D347E00FA6A37 /* PhoneNumberViewController.swift in Sources */,
1286 F3895F4C233D347E00FA6A37 /* PhoneNumberViewController.swift in Sources */,
703 12EE4A76234BB8FE005B4D1A /* CodeView.swift in Sources */,
1287 12EE4A76234BB8FE005B4D1A /* CodeView.swift in Sources */,
1288+ 12230AF123900974004A01E5 /* TotalStorage.swift in Sources */,
1289+ 1252D9E523CDCDB300446B6F /* ACreateReservationResolver.swift in Sources */,
1290+ 12BD95B2237EC22B009614D8 /* FirstStepView.swift in Sources */,
704 F3EBE2472340FE87000FEE1A /* NameTextView.swift in Sources */,
1291 F3EBE2472340FE87000FEE1A /* NameTextView.swift in Sources */,
1292+ 120BFC7723891925001EBE89 /* ThirdStepReservationViewController.swift in Sources */,
705 F3DC5A01233E896300B5E751 /* ButtonStyle.swift in Sources */,
1293 F3DC5A01233E896300B5E751 /* ButtonStyle.swift in Sources */,
1294+ 12A075FF237358F200D749BB /* MapView.swift in Sources */,
1295+ 1205056B2380588C00801618 /* Extenstions+IBDesignable.swift in Sources */,
1296+ 12E35074239FAF52004BB188 /* ClientsService.swift in Sources */,
706 12C72EE1235380D400AD7004 /* ProfileClientViewController.swift in Sources */,
1297 12C72EE1235380D400AD7004 /* ProfileClientViewController.swift in Sources */,
707 F31E20C92348E0F300BEA215 /* AuthorizationViewController.swift in Sources */,
1298 F31E20C92348E0F300BEA215 /* AuthorizationViewController.swift in Sources */,
1299+ 1212048E23915A0A00CAF162 /* ChangeReservationResolver.swift in Sources */,
1300+ 12230AFC239034D3004A01E5 /* SecondStepReservationViewController.swift in Sources */,
708 F3851B2923409F8A009EE0B6 /* PhoneView.swift in Sources */,
1301 F3851B2923409F8A009EE0B6 /* PhoneView.swift in Sources */,
1302+ 12D928B123C486380051724D /* BaseTableViewController.swift in Sources */,
709 12C72EE3235380EB00AD7004 /* ReservationsViewController.swift in Sources */,
1303 12C72EE3235380EB00AD7004 /* ReservationsViewController.swift in Sources */,
710 12B6A611235B8EC500898B31 /* User.swift in Sources */,
1304 12B6A611235B8EC500898B31 /* User.swift in Sources */,
1305+ 12B3A7AD23ABEABF009E2FA6 /* AReservationsResolver.swift in Sources */,
1306+ 12B4447C238EF25C000B6B16 /* CreateReservationResolver.swift in Sources */,
711 12B6A60F235B857A00898B31 /* ProfileChangeCoordinator.swift in Sources */,
1307 12B6A60F235B857A00898B31 /* ProfileChangeCoordinator.swift in Sources */,
1308+ 12B3A79C23ABB8ED009E2FA6 /* IntervalPickerView.swift in Sources */,
1309+ 12FC40382382C93D00AB593B /* TableButtonStyle.swift in Sources */,
1310+ 12D8FA31237F08C700305F57 /* AllReservations.swift in Sources */,
712 12838EB0236777D400CEF108 /* ServerError.swift in Sources */,
1311 12838EB0236777D400CEF108 /* ServerError.swift in Sources */,
1312+ 125AE51D2395583E00F847BD /* ChangeReservationCoordinator.swift in Sources */,
713 );
1313 );
714 runOnlyForDeploymentPostprocessing = 0;
1314 runOnlyForDeploymentPostprocessing = 0;
715 };
1315 };
@@ -866,14 +1466,15 @@
866 buildSettings = {
1466 buildSettings = {
867 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
1467 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
868 CODE_SIGN_STYLE = Automatic;
1468 CODE_SIGN_STYLE = Automatic;
1469+ CURRENT_PROJECT_VERSION = 5;
869− DEVELOPMENT_TEAM = 9Z6LFGKA9G;
1470+ DEVELOPMENT_TEAM = 23S94LRHD8;
870 INFOPLIST_FILE = "Hookah Tau/Info.plist";
1471 INFOPLIST_FILE = "Hookah Tau/Info.plist";
871 IPHONEOS_DEPLOYMENT_TARGET = 11.0;
1472 IPHONEOS_DEPLOYMENT_TARGET = 11.0;
872 LD_RUNPATH_SEARCH_PATHS = (
1473 LD_RUNPATH_SEARCH_PATHS = (
873 "$(inherited)",
1474 "$(inherited)",
874 "@executable_path/Frameworks",
1475 "@executable_path/Frameworks",
875 );
1476 );
876− PRODUCT_BUNDLE_IDENTIFIER = "com.hookahtau.Hookah-Tau";
1477+ PRODUCT_BUNDLE_IDENTIFIER = "com.hookahtau.Hookah-Tau1";
877 PRODUCT_NAME = "$(TARGET_NAME)";
1478 PRODUCT_NAME = "$(TARGET_NAME)";
878 SWIFT_VERSION = 5.0;
1479 SWIFT_VERSION = 5.0;
879 TARGETED_DEVICE_FAMILY = "1,2";
1480 TARGETED_DEVICE_FAMILY = "1,2";
@@ -885,14 +1486,15 @@
885 buildSettings = {
1486 buildSettings = {
886 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
1487 ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
887 CODE_SIGN_STYLE = Automatic;
1488 CODE_SIGN_STYLE = Automatic;
1489+ CURRENT_PROJECT_VERSION = 5;
888− DEVELOPMENT_TEAM = 9Z6LFGKA9G;
1490+ DEVELOPMENT_TEAM = 23S94LRHD8;
889 INFOPLIST_FILE = "Hookah Tau/Info.plist";
1491 INFOPLIST_FILE = "Hookah Tau/Info.plist";
890 IPHONEOS_DEPLOYMENT_TARGET = 11.0;
1492 IPHONEOS_DEPLOYMENT_TARGET = 11.0;
891 LD_RUNPATH_SEARCH_PATHS = (
1493 LD_RUNPATH_SEARCH_PATHS = (
892 "$(inherited)",
1494 "$(inherited)",
893 "@executable_path/Frameworks",
1495 "@executable_path/Frameworks",
894 );
1496 );
895− PRODUCT_BUNDLE_IDENTIFIER = "com.hookahtau.Hookah-Tau";
1497+ PRODUCT_BUNDLE_IDENTIFIER = "com.hookahtau.Hookah-Tau1";
896 PRODUCT_NAME = "$(TARGET_NAME)";
1498 PRODUCT_NAME = "$(TARGET_NAME)";
897 SWIFT_VERSION = 5.0;
1499 SWIFT_VERSION = 5.0;
898 TARGETED_DEVICE_FAMILY = "1,2";
1500 TARGETED_DEVICE_FAMILY = "1,2";
Hookah Tau/API/APIClient.swift+43−4
@@ -11,8 +11,13 @@ import Alamofire
11
11
12/// http://iko.soy/hookah-tau-docs/latest/index.html
12/// http://iko.soy/hookah-tau-docs/latest/index.html
13class APIClient {
13class APIClient {
14− typealias CompletionBlock = (Result<Codable, Error>) -> Void
14+ typealias CompletionBlock = (Result<AFDataResponse<Data?>, GeneralError>) -> Void
15
15
16+ private init() {}
17+
18+ /// Shared instance of the client
19+ static let shared = APIClient()
20+
16 enum BaseUrls {
21 enum BaseUrls {
17 static let staging = "https://hookah-tau-staging.herokuapp.com"
22 static let staging = "https://hookah-tau-staging.herokuapp.com"
18 // static let production = ""
23 // static let production = ""
@@ -22,11 +27,45 @@ class APIClient {
22 func load(request: URLRequest, completion: @escaping CompletionBlock) {
27 func load(request: URLRequest, completion: @escaping CompletionBlock) {
23 AF.request(request).response { response in
28 AF.request(request).response { response in
24
29
25− if let error = response.error, let _ = error.responseCode {
30+ if let error = self.getErrorFromResponce(response) {
26− completion(.failure(ServerError.internalServerError))
31+ completion(.failure(error))
32+ return
27 }
33 }
28
34
29− completion(.success(response.data))
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 404:
52+ return GeneralError.notFound
53+ case 422:
54+ if let data = response.data, let error = SE.fromJSONToSelf(data: data) {
55+ print(error)
56+ return GeneralError.serverError(error)
57+ }
58+
59+ fallthrough // прекрасно, я знаю
60+ default:
61+ return GeneralError.somethingWentCompletelyWrong
62+ }
63+ }
64+
65+ if let _ = response.error {
66+ return GeneralError.alamofireError
30 }
67 }
68+
69+ return GeneralError.somethingWentCompletelyWrong
31 }
70 }
32}
71}
Hookah Tau/API/ApiRequest.swift+1−1
@@ -34,7 +34,7 @@ class ApiRequest {
34 let url = URL(string: APIClient.BaseUrls.staging + (resolver.groupName ?? "") + resolver.name)!
34 let url = URL(string: APIClient.BaseUrls.staging + (resolver.groupName ?? "") + resolver.name)!
35 self.request = URLRequest(url: url)
35 self.request = URLRequest(url: url)
36
36
37− self.request.httpBody = resolver.parameters().toJSONData()
37+ self.request.httpBody = resolver.parameters()?.toJSONData()
38 self.request.setValue("application/json;charset=utf-8",
38 self.request.setValue("application/json;charset=utf-8",
39 forHTTPHeaderField: "Content-Type")
39 forHTTPHeaderField: "Content-Type")
40 self.request.httpMethod = httpMethod.rawValue
40 self.request.httpMethod = httpMethod.rawValue
Hookah Tau/API/ApiResolver.swift+9−3
@@ -8,12 +8,18 @@
8
8
9import Foundation
9import Foundation
10
10
11−protocol ApiResolver {
11+protocol ApiResolver: class {
12+
12 var name: String { get }
13 var name: String { get }
13
14
14 var groupName: String? { get }
15 var groupName: String? { get }
15
16
17+ /// Parameter for request
16− func parameters() -> MyCodable
18+ func parameters() -> MyCodable?
19+}
20+
21+extension ApiResolver {
22+ var groupName: String? { return nil }
17
23
18− func targetClass() -> MyCodable.Type
24+ func parameters() -> MyCodable? { return nil }
19}
25}
Hookah Tau/API/Authorization api/AuthorizationService.swift+40−20
@@ -7,6 +7,7 @@
7//
7//
8
8
9import Foundation
9import Foundation
10+import Alamofire
10
11
11/// Слой методов для авторизации пользователя в приложении
12/// Слой методов для авторизации пользователя в приложении
12class AuthorizationService {
13class AuthorizationService {
@@ -17,27 +18,28 @@ class AuthorizationService {
17 }
18 }
18
19
19 /// Check if phone number is registered and send verification code to that number.
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
20 func authenticate(withPhone phoneNumber: String,
23 func authenticate(withPhone phoneNumber: String,
21− completion: @escaping (Result<Bool, Error>) -> Void) {
24+ completion: @escaping (Result<Bool, GeneralError>) -> Void) {
22− let resolver = AuthResolver(phoneNumber: phoneNumber)
25+ let resolver = AuthResolver<AuthResponse>(phoneNumber: phoneNumber)
23 let request = ApiRequest(resolver: resolver, httpMethod: .post)
26 let request = ApiRequest(resolver: resolver, httpMethod: .post)
24
27
25 apiClient.load(request: request.request) { (result) in
28 apiClient.load(request: request.request) { (result) in
26 switch result {
29 switch result {
27 case .failure(let err):
30 case .failure(let err):
28 completion(.failure(err))
31 completion(.failure(err))
29− case .success(let data):
32+ case .success(let response):
30− print(data) // TODO remove
31−
32 guard
33 guard
33− let d = data as? Data,
34+ let d = response.data,
34− let decodedData = resolver.targetClass().fromJSONToSelf(data: d) as? AuthResolver.Response
35+ let decodedData = resolver.targetClass().fromJSONToSelf(data: d)
35 else {
36 else {
36− completion(.failure(ServerError.internalServerError))
37+ completion(.failure(GeneralError.decodeError))
37 return
38 return
38 }
39 }
39
40
40 completion(.success(decodedData.isUserRegistered))
41 completion(.success(decodedData.isUserRegistered))
42+ return
41 }
43 }
42 }
44 }
43 }
45 }
@@ -45,24 +47,26 @@ class AuthorizationService {
45 /// Verify login of **already existing user** with phone number and verification code.
47 /// Verify login of **already existing user** with phone number and verification code.
46 func phonecode(phoneNumber: String,
48 func phonecode(phoneNumber: String,
47 code: String,
49 code: String,
48− completion: @escaping (Result<User, Error>) -> Void) {
50+ completion: @escaping (Result<User, GeneralError>) -> Void) {
49− let resolver = PhoneCodeResolver(phoneNumber: phoneNumber, code: code)
51+ let resolver = PhoneCodeResolver<User>(phoneNumber: phoneNumber, code: code)
50 let request = ApiRequest(resolver: resolver, httpMethod: .post)
52 let request = ApiRequest(resolver: resolver, httpMethod: .post)
51
53
52 apiClient.load(request: request.request) { result in
54 apiClient.load(request: request.request) { result in
53 switch result {
55 switch result {
54 case .failure(let err):
56 case .failure(let err):
55 completion(.failure(err))
57 completion(.failure(err))
56− case .success(let data):
58+ case .success(let response):
57 guard
59 guard
58− let unwrappedData = data as? Data,
60+ let unwrappedData = response.data,
59− let decodedData = resolver.targetClass().fromJSONToSelf(data: unwrappedData) as? User
61+ let decodedData = resolver.targetClass().fromJSONToSelf(data: unwrappedData)
60 else {
62 else {
61− completion(.failure(ServerError.decodeError))
63+ completion(.failure(GeneralError.decodeError))
62 return
64 return
63 }
65 }
64
66
67+ self.setCookies(response: response)
65 completion(.success(decodedData))
68 completion(.success(decodedData))
69+ return
66 }
70 }
67 }
71 }
68 }
72 }
@@ -71,25 +75,41 @@ class AuthorizationService {
71 /// Register new user with a given name, phone number and verification code.
75 /// Register new user with a given name, phone number and verification code.
72 func register(name: String,
76 func register(name: String,
73 code: String,
77 code: String,
74− phoneNumber: String, completion: @escaping (Result<User, Error>) -> Void) {
78+ phoneNumber: String, completion: @escaping (Result<User, GeneralError>) -> Void) {
75− let resolver = RegistrationResolver(name: name, code: code, phoneNumber: phoneNumber)
79+ let resolver = RegistrationResolver<User>(name: name, code: code, phoneNumber: phoneNumber)
76 let request = ApiRequest(resolver: resolver, httpMethod: .post)
80 let request = ApiRequest(resolver: resolver, httpMethod: .post)
77
81
78 apiClient.load(request: request.request) { result in
82 apiClient.load(request: request.request) { result in
79 switch result {
83 switch result {
80 case .failure(let err):
84 case .failure(let err):
85+ print(err)
81 completion(.failure(err))
86 completion(.failure(err))
87+ return
82− case .success(let data):
88+ case .success(let response):
83 guard
89 guard
84− let unwrappedData = data as? Data,
90+ let unwrappedData = response.data,
85− let decodedData = resolver.targetClass().fromJSONToSelf(data: unwrappedData) as? User
91+ let decodedData = resolver.targetClass().fromJSONToSelf(data: unwrappedData)
86 else {
92 else {
87− completion(.failure(ServerError.decodeError))
93+ completion(.failure(GeneralError.decodeError))
88 return
94 return
89 }
95 }
90
96
97+ self.setCookies(response: response)
91 completion(.success(decodedData))
98 completion(.success(decodedData))
99+ return
92 }
100 }
93 }
101 }
94 }
102 }
103+
104+ // MARK: - Helpers
105+
106+ private func setCookies(response: AFDataResponse<Data?>) {
107+ let fields = response.response?.allHeaderFields as? [String :String]
108+ guard let fieldss = fields, let url = URL(string: APIClient.BaseUrls.staging) else {
109+ return
110+ }
111+ let cookies = HTTPCookie.cookies(withResponseHeaderFields: fieldss, for: url)
112+ HTTPCookieStorage.shared.setCookies(cookies, for: url, mainDocumentURL: nil)
113+ DataStorage.standard.setCookies(cookies, forURL: url)
114+ }
95}
115}
Hookah Tau/API/Authorization api/Resolvers/AuthResolver.swift+7−7
@@ -8,18 +8,18 @@
8
8
9import Foundation
9import Foundation
10
10
11−class AuthResolver: ApiResolver {
11+struct AuthResponse: MyCodable {
12+ var isUserRegistered: Bool
13+}
12
14
15+class AuthResolver<Response: MyCodable>: ApiResolver {
16+
13 // MARK: - Request/Response types
17 // MARK: - Request/Response types
14
18
15 struct Request: MyCodable {
19 struct Request: MyCodable {
16 var phoneNumber: String
20 var phoneNumber: String
17 }
21 }
18
22
19− struct Response: MyCodable {
20− var isUserRegistered: Bool
21− }
22−
23 // MARK: - Properties
23 // MARK: - Properties
24
24
25 private var phoneNumber: String
25 private var phoneNumber: String
@@ -38,11 +38,11 @@ class AuthResolver: ApiResolver {
38 self.phoneNumber = phoneNumber
38 self.phoneNumber = phoneNumber
39 }
39 }
40
40
41− func parameters() -> MyCodable {
41+ func parameters() -> MyCodable? {
42 return Request(phoneNumber: phoneNumber)
42 return Request(phoneNumber: phoneNumber)
43 }
43 }
44
44
45− func targetClass() -> MyCodable.Type {
45+ func targetClass() -> Response.Type {
46 return Response.self
46 return Response.self
47 }
47 }
48}
48}
Hookah Tau/API/Authorization api/Resolvers/PhoneCodeResolver.swift+5−5
@@ -8,8 +8,8 @@
8
8
9import Foundation
9import Foundation
10
10
11−class PhoneCodeResolver: ApiResolver {
11+class PhoneCodeResolver<Response: MyCodable>: ApiResolver {
12−
12+
13 // MARK: - types
13 // MARK: - types
14
14
15 struct Request: MyCodable {
15 struct Request: MyCodable {
@@ -37,12 +37,12 @@ class PhoneCodeResolver: ApiResolver {
37 self.code = code
37 self.code = code
38 }
38 }
39
39
40− func parameters() -> MyCodable {
40+ func parameters() -> MyCodable? {
41 return Request(phoneNumber: phoneNumber,
41 return Request(phoneNumber: phoneNumber,
42 code: code)
42 code: code)
43 }
43 }
44
44
45− func targetClass() -> MyCodable.Type {
45+ func targetClass() -> Response.Type {
46− return User.self
46+ return Response.self
47 }
47 }
48}
48}
Hookah Tau/API/Authorization api/Resolvers/RegistrationResolver.swift+5−4
@@ -8,7 +8,8 @@
8
8
9import Foundation
9import Foundation
10
10
11−class RegistrationResolver: ApiResolver {
11+class RegistrationResolver<Response: MyCodable>: ApiResolver {
12+
12 // MARK: - types
13 // MARK: - types
13
14
14 struct Request: MyCodable {
15 struct Request: MyCodable {
@@ -39,13 +40,13 @@ class RegistrationResolver: ApiResolver {
39 self.phoneNumber = phoneNumber
40 self.phoneNumber = phoneNumber
40 }
41 }
41
42
42− func parameters() -> MyCodable {
43+ func parameters() -> MyCodable? {
43 return Request(name: uname,
44 return Request(name: uname,
44 phoneNumber: phoneNumber,
45 phoneNumber: phoneNumber,
45 code: code)
46 code: code)
46 }
47 }
47
48
48− func targetClass() -> MyCodable.Type {
49+ func targetClass() -> Response.Type {
49− return User.self
50+ return Response.self
50 }
51 }
51}
52}
Hookah Tau/API/ClientsService/ClientsService.swiftnew file+129
@@ -0,0 +1,129 @@
1+//
2+// ClientsService.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 10/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+class ClientsService {
12+ private var apiClient: APIClient
13+
14+ init(apiClient: APIClient) {
15+ self.apiClient = apiClient
16+ }
17+
18+ func loadClientsList(completion: @escaping ((Result<[Client], GeneralError>) -> Void)) {
19+ let resolver = ClientsListResolver<ClientsResponce>()
20+ let request = ApiRequest(resolver: resolver, httpMethod: .get)
21+
22+ apiClient.load(request: request.request) { (res) in
23+ switch res {
24+ case .success(let responce):
25+ guard
26+ let d = responce.data,
27+ let decodedData = resolver.targetClass().fromJSONToSelf(data: d)
28+ else {
29+ completion(.failure(GeneralError.decodeError))
30+ return }
31+
32+ completion(.success(decodedData.clients))
33+ return
34+ case .failure(let err):
35+ completion(.failure(err))
36+ return
37+ }
38+ }
39+ }
40+
41+ func loadReservationsForUser(withUUID uuid: String,
42+ completion: @escaping ((Result<[Reservation], GeneralError>) -> Void)) {
43+ let resolver = ClientReservationsResolver<UserReservationsResponce>(userId: uuid)
44+ let request = ApiRequest(resolver: resolver, httpMethod: .get)
45+
46+ apiClient.load(request: request.request) { (res) in
47+ switch res {
48+ case .failure(let err):
49+ completion(.failure(err))
50+ return
51+ case .success(let responce):
52+ guard
53+ let d = responce.data,
54+ let decData = resolver.targetClass().fromJSONToSelf(data: d)
55+ else {
56+ completion(.failure(GeneralError.decodeError))
57+ return
58+ }
59+
60+ completion(.success(decData.reservations))
61+ return
62+ }
63+ }
64+
65+ }
66+
67+ /// Метод для удаления или добавления пользователя в чс
68+ func changeBlockList(whatToDo: CrudMethod, phone: String, completion: @escaping ((Bool) -> Void)) {
69+ let resolver = BlocklistPutResolver(blockList: [phone])
70+ let request = ApiRequest(resolver: resolver, httpMethod: whatToDo)
71+
72+ apiClient.load(request: request.request) { (res) in
73+ switch res {
74+ case .failure:
75+ completion(false)
76+ return
77+ case .success:
78+ completion(true)
79+ return
80+ }
81+ }
82+ }
83+
84+ /// Метод для изменения статуса админки
85+ func changeAdmin(crud: CrudMethod, data: String, completion: @escaping ((Bool) -> Void)) {
86+ var resolver: ApiResolver!
87+ if crud == .put {
88+ resolver = AddOtherAdminResolver(phone: data)
89+ } else {
90+ resolver = DeleteOtherAdminResolver(uuid: data)
91+ }
92+
93+ let request = ApiRequest(resolver: resolver, httpMethod: crud)
94+
95+ apiClient.load(request: request.request) { result in
96+ switch result {
97+ case .failure:
98+ completion(false)
99+ return
100+ case .success:
101+ completion(true)
102+ return
103+ }
104+ }
105+ }
106+
107+ func getClientByPhoneNumber(userPhone: String, completion: @escaping ((Result<FullUser, GeneralError>) -> Void)) {
108+ let resolver = GetClientByPhoneNumberResolver<ClientInResponce>(userPhone: userPhone)
109+ let request = ApiRequest(resolver: resolver, httpMethod: .get)
110+
111+ apiClient.load(request: request.request) { (result) in
112+ switch result {
113+ case .failure(let err):
114+ completion(.failure(err))
115+ return
116+ case .success(let responce):
117+ guard
118+ let d = responce.data,
119+ let decData = resolver.targetClass().fromJSONToSelf(data: d)
120+ else {
121+ completion(.failure(GeneralError.decodeError))
122+ return
123+ }
124+
125+ completion(.success(decData.client))
126+ }
127+ }
128+ }
129+}
Hookah Tau/API/ClientsService/Resolvers/AddOtherAdminResolver.swiftnew file+35
@@ -0,0 +1,35 @@
1+//
2+// AddOtherAdminResolver.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 20/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+/// `PUT /admin/other`
12+class AddOtherAdminResolver: ApiResolver {
13+ /// Request struct
14+ struct Request: MyCodable {
15+ var newAdmin: String
16+ }
17+
18+ var name: String {
19+ return "/other"
20+ }
21+
22+ var groupName: String? {
23+ return "/admin"
24+ }
25+
26+ var phone: String
27+
28+ init(phone: String) {
29+ self.phone = phone
30+ }
31+
32+ func parameters() -> MyCodable? {
33+ return Request(newAdmin: phone)
34+ }
35+}
Hookah Tau/API/ClientsService/Resolvers/BlocklistPutResolver.swiftnew file+33
@@ -0,0 +1,33 @@
1+//
2+// BlocklistPutResolver.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 09/01/2020.
6+// Copyright © 2020 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+struct RequestBlocklist: MyCodable {
12+ var blockList: [String]
13+}
14+
15+class BlocklistPutResolver: ApiResolver {
16+ var name: String {
17+ return "/blocklist"
18+ }
19+
20+ var groupName: String? {
21+ return "/admin"
22+ }
23+
24+ var blockList: [String]
25+
26+ init(blockList: [String]) {
27+ self.blockList = blockList
28+ }
29+
30+ func parameters() -> MyCodable? {
31+ return RequestBlocklist(blockList: blockList)
32+ }
33+}
Hookah Tau/API/ClientsService/Resolvers/ClientReservationsResolver.swiftnew file+34
@@ -0,0 +1,34 @@
1+//
2+// ClientReservations.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 10/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+struct UserReservationsResponce: MyCodable {
12+ var reservations: [Reservation]
13+}
14+
15+class ClientReservationsResolver<Responce: MyCodable>: ApiResolver {
16+
17+ var name: String {
18+ return "/clients/\(userId)/reservations"
19+ }
20+
21+ var groupName: String? {
22+ return "/admin"
23+ }
24+
25+ var userId: String
26+
27+ init(userId: String) {
28+ self.userId = userId
29+ }
30+
31+ func targetClass() -> Responce.Type {
32+ return Responce.self
33+ }
34+}
Hookah Tau/API/ClientsService/Resolvers/ClientsListResolver.swiftnew file+28
@@ -0,0 +1,28 @@
1+//
2+// ClientsListResolver.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 10/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+struct ClientsResponce: MyCodable {
12+ var clients: [Client]
13+}
14+
15+class ClientsListResolver<Responce: MyCodable>: ApiResolver {
16+
17+ var name: String {
18+ return "/clients"
19+ }
20+
21+ var groupName: String? {
22+ return "/admin"
23+ }
24+
25+ func targetClass() -> Responce.Type {
26+ return Responce.self
27+ }
28+}
Hookah Tau/API/ClientsService/Resolvers/DeleteOtherAdminResolver.swiftnew file+35
@@ -0,0 +1,35 @@
1+//
2+// DeleteOtherAdminResolver.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 10/01/2020.
6+// Copyright © 2020 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+
12+class DeleteOtherAdminResolver: ApiResolver {
13+
14+ var name: String {
15+ return "/other"
16+ }
17+
18+ var groupName: String? {
19+ return "/admin"
20+ }
21+
22+ struct Request: MyCodable {
23+ let badAdmin: String
24+ }
25+
26+ let uuid: String
27+
28+ init(uuid: String) {
29+ self.uuid = uuid
30+ }
31+
32+ func parameters() -> MyCodable? {
33+ return Request(badAdmin: self.uuid)
34+ }
35+}
Hookah Tau/API/ClientsService/Resolvers/GetClientByPhoneNumberResolver.swiftnew file+35
@@ -0,0 +1,35 @@
1+//
2+// GetClientByPhoneNumberResolver.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 14/01/2020.
6+// Copyright © 2020 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+struct ClientInResponce: MyCodable {
12+ var client: FullUser
13+}
14+
15+/// `GET /admin/clients/by-phone/{user id}`
16+class GetClientByPhoneNumberResolver<Responce: MyCodable>: ApiResolver {
17+
18+ var name: String {
19+ return "/clients/by-phone/\(userPhone)"
20+ }
21+
22+ var groupName: String? {
23+ return "/admin"
24+ }
25+
26+ var userPhone: String
27+
28+ init(userPhone: String) {
29+ self.userPhone = userPhone
30+ }
31+
32+ func targetClass() -> Responce.Type {
33+ return Responce.self
34+ }
35+}
Hookah Tau/API/Establishments api/EstablishmentService.swiftnew file+39
@@ -0,0 +1,39 @@
1+//
2+// EstablishmentService.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 28/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+class EstablishmentSevice {
12+ private var apiClient: APIClient
13+
14+ init(apiClient: APIClient) {
15+ self.apiClient = apiClient
16+ }
17+
18+ func loadAdmins(completion: @escaping ((Result<[Int: String], GeneralError>) -> Void)) {
19+ let resolver = EstablishmentResolver<[Int: String]>()
20+ let request = ApiRequest(resolver: resolver, httpMethod: .get)
21+
22+ apiClient.load(request: request.request) { result in
23+ switch result {
24+ case .failure(let err):
25+ completion(.failure(err))
26+ case .success(let response):
27+ guard
28+ let data = response.data,
29+ let decondedData = try? JSONDecoder().decode([Int: String].self, from: data)
30+ else {
31+ completion(.failure(.decodeError))
32+ return
33+ }
34+
35+ completion(.success(decondedData))
36+ }
37+ }
38+ }
39+}
Hookah Tau/API/Establishments api/Resolvers/EstablishmentResolver.swiftnew file+24
@@ -0,0 +1,24 @@
1+//
2+// EstablishmentReslver.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 28/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+
12+class EstablishmentResolver<Response>: ApiResolver {
13+ var name: String {
14+ return "/admins"
15+ }
16+
17+ var groupName: String? {
18+ return "/establishments"
19+ }
20+
21+ func targetClass() -> Response.Type {
22+ return Response.self
23+ }
24+}
Hookah Tau/API/MyCodable.swift+12−2
@@ -16,10 +16,20 @@ protocol MyCodable: Codable {
16
16
17extension MyCodable {
17extension MyCodable {
18 func toJSONData() -> Data? {
18 func toJSONData() -> Data? {
19+ let encoder = JSONEncoder()
20+ encoder.dateEncodingStrategy = .formatted(DateFormatter.iso8601Full)
19− return try? JSONEncoder().encode(self)
21+ return try? encoder.encode(self)
20 }
22 }
21
23
22 static func fromJSONToSelf(data: Data) -> Self? {
24 static func fromJSONToSelf(data: Data) -> Self? {
25+ let decoder = JSONDecoder()
26+ decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601Full)
27+ return try? decoder.decode(self, from: data)
28+ }
29+
30+ static func fromJSONToSelfArray<T: MyCodable>(data : Data) -> [T]? {
31+ let decoder = JSONDecoder()
32+ decoder.dateDecodingStrategy = .formatted(DateFormatter.iso8601Full)
23− return try? JSONDecoder().decode(self, from: data)
33+ return try? decoder.decode([T].self, from: data)
24 }
34 }
25}
35}
Hookah Tau/API/Reservations api/ReservationsService.swiftnew file+231
@@ -0,0 +1,231 @@
1+//
2+// ReservationsService.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 06/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+import Alamofire
11+
12+/// Слой методов для авторизации пользователя в приложении
13+class ReservationsService {
14+ private var apiClient: APIClient
15+
16+ init(apiClient: APIClient) {
17+ self.apiClient = apiClient
18+ }
19+
20+ /// Gets all reservations made by the current user.
21+ func loadUsersReservations(completion: @escaping (Result<[Reservation], GeneralError>) -> Void) {
22+ let resolver = ReservationsResolver<ReservationsResponce>()
23+
24+ let request = ApiRequest(resolver: resolver, httpMethod: .get)
25+ apiClient.load(request: request.request) { (result) in
26+ switch result {
27+ case .failure(let err):
28+ completion(.failure(err))
29+ case .success(let response):
30+ guard
31+ let d = response.data,
32+ let decodedData = resolver.targetClass().fromJSONToSelf(data: d)
33+ else {
34+ completion(.failure(GeneralError.decodeError))
35+ return
36+ }
37+
38+ completion(.success(decodedData.reservations))
39+ }
40+ }
41+ }
42+
43+ func deleteReservation(isAdmin: Bool, uuid: String, completion: @escaping ((Bool) -> Void)) {
44+ var resolver: ApiResolver!
45+ if isAdmin {
46+ resolver = ADeleteReservationResolver(uuid: uuid)
47+ } else {
48+ resolver = DeleteReservationResolver(uuid: uuid)
49+ }
50+
51+ let request = ApiRequest(resolver: resolver, httpMethod: .delete)
52+
53+ apiClient.load(request: request.request) { result in
54+ switch result {
55+ case .failure:
56+ completion(false)
57+ return
58+ case .success:
59+ completion(true)
60+ return
61+ }
62+ }
63+ }
64+
65+ func updateReservation(startTime: Date,
66+ numberOfGuests: Int,
67+ endTime: Date,
68+ uuid: String,
69+ isAdmin: Bool,
70+ completion: @escaping ((Bool) -> Void)) {
71+
72+ let reservation = ChangeReservationResolver.Request(startTime: startTime,
73+ uuid: uuid,
74+ numberOfGuests: numberOfGuests,
75+ endTime: endTime)
76+ var resolver: ApiResolver!
77+ if isAdmin {
78+ resolver = AChangeReservationResolver(data: reservation)
79+ } else {
80+ resolver = ChangeReservationResolver(data: reservation)
81+ }
82+
83+ let request = ApiRequest(resolver: resolver, httpMethod: .put)
84+
85+ apiClient.load(request: request.request) { result in
86+ switch result {
87+ case .failure:
88+ completion(false)
89+ return
90+ case .success:
91+ completion(true)
92+ return
93+ }
94+ }
95+ }
96+
97+
98+ func getAllReservations(establishmentID: Int,
99+ completion: @escaping ((Result<AllReservations, GeneralError>) -> Void)) {
100+ let resolver = AllReservationsResolver<AllReservations>(establishmentID: establishmentID)
101+
102+ let request = ApiRequest(resolver: resolver, httpMethod: .get)
103+
104+ apiClient.load(request: request.request) { result in
105+ switch result {
106+ case .failure(let err):
107+ completion(.failure(err))
108+ case .success(let response):
109+ guard
110+ let d = response.data,
111+ let decodedData = resolver.targetClass().fromJSONToSelf(data: d)
112+ else {
113+ completion(.failure(GeneralError.decodeError))
114+ return
115+ }
116+
117+ completion(.success(decodedData))
118+ }
119+ }
120+ }
121+
122+ func createReservation(data: ReservationData,
123+ completion: @escaping ((Result<Reservation, GeneralError>) -> Void)) {
124+ // for 1 time use
125+ struct Response: MyCodable {
126+ var reservationUUID: String
127+ }
128+
129+ // local map for output
130+ func map(data: ReservationData, id: String) -> Reservation {
131+
132+ return Reservation(uuid: id,
133+ establishment: data.establishment,
134+ startTime: data.startTime,
135+ endTime: data.endTime,
136+ numberOfGuests: data.numberOfGuests,
137+ reservedTable: data.reservedTable)
138+ }
139+
140+ let resolver = CreateReservationResolver<Response>(data: data)
141+ let request = ApiRequest(resolver: resolver, httpMethod: .post)
142+
143+ apiClient.load(request: request.request) { result in
144+ switch result {
145+ case .failure(let err):
146+ completion(.failure(err))
147+ case .success(let response):
148+ guard
149+ let d = response.data,
150+ let id = resolver.targetClass().fromJSONToSelf(data: d)?.reservationUUID
151+ else {
152+ completion(.failure(GeneralError.decodeError))
153+ return
154+ }
155+
156+ completion(.success(map(data: data, id: id)))
157+ }
158+ }
159+ }
160+
161+ func getAdminReservations(establishmentId: Int, completion: @escaping ((Result<[ReservationWithUser], GeneralError>)-> Void)) {
162+ let resolver = AReservationsResolver<AReservationResponse>(establishmentId: establishmentId)
163+ let request = ApiRequest(resolver: resolver, httpMethod: .get)
164+
165+ apiClient.load(request: request.request) { (res) in
166+ switch res {
167+ case .failure(let err):
168+ completion(.failure(err))
169+ return
170+ case .success(let response):
171+ guard
172+ let d = response.data,
173+ let decodedData = resolver.targetClass().fromJSONToSelf(data: d)
174+ else {
175+ completion(.failure(GeneralError.decodeError))
176+ return
177+ }
178+
179+ completion(.success(decodedData.reservations))
180+ return
181+ }
182+ }
183+ }
184+
185+ func getReservationsAround(uuid: String, completion: @escaping((Result<[ReservationPeriod], GeneralError>) -> Void)) {
186+ let resolver = ReservationsAroundResolver<ReservationsAroundResponse>(reservationId: uuid)
187+ let request = ApiRequest(resolver: resolver, httpMethod: .get)
188+
189+ apiClient.load(request: request.request) { (res) in
190+ switch res {
191+ case .failure(let err):
192+ completion(.failure(err))
193+ return
194+ case .success(let response):
195+ guard
196+ let d = response.data,
197+ let decodedData = resolver.targetClass().fromJSONToSelf(data: d)
198+ else {
199+ completion(.failure(GeneralError.decodeError))
200+ return
201+ }
202+
203+ completion(.success(decodedData.reservations))
204+ return
205+ }
206+ }
207+ }
208+
209+ func createReservation(reservation: AdminCreateReservation, completion: @escaping ((Result<String, GeneralError>) -> Void)) {
210+ let resolver = ACreateReservationResolver<ACreateAdminResonce>(data: reservation)
211+ let request = ApiRequest(resolver: resolver, httpMethod: .post)
212+
213+ apiClient.load(request: request.request) { (res) in
214+ switch res {
215+ case .failure(let err):
216+ completion(.failure(err))
217+ return
218+ case .success(let responce):
219+ guard
220+ let d = responce.data,
221+ let decData = resolver.targetClass().fromJSONToSelf(data: d)
222+ else {
223+ completion(.failure(GeneralError.decodeError))
224+ return
225+ }
226+
227+ completion(.success(decData.reservationUUID))
228+ }
229+ }
230+ }
231+}
Hookah Tau/API/Reservations api/Resolvers/Admin/AChangeReservationResolver.swiftnew file+34
@@ -0,0 +1,34 @@
1+//
2+// AChangeReservationResolver.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 20/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+
12+/// `PUT /admin/reservations`
13+
14+class AChangeReservationResolver: ApiResolver {
15+
16+ var name: String {
17+ return "/reservations"
18+ }
19+
20+ var groupName: String? {
21+ return "/admin"
22+ }
23+
24+ var data: ChangeReservationResolver.Request
25+
26+ init(data: ChangeReservationResolver.Request) {
27+ self.data = data
28+ }
29+
30+ func parameters() -> MyCodable? {
31+ return data
32+ }
33+}
34+
Hookah Tau/API/Reservations api/Resolvers/Admin/ACreateReservationResolver.swiftnew file+37
@@ -0,0 +1,37 @@
1+//
2+// ACreateReservationResolver.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 14/01/2020.
6+// Copyright © 2020 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+struct ACreateAdminResonce: MyCodable {
12+ var reservationUUID: String
13+}
14+
15+class ACreateReservationResolver<Responce: MyCodable>: ApiResolver {
16+ var name: String {
17+ return "/reservations"
18+ }
19+
20+ var groupName: String? {
21+ return "/admin"
22+ }
23+
24+ var data: AdminCreateReservation
25+
26+ init(data: AdminCreateReservation) {
27+ self.data = data
28+ }
29+
30+ func parameters() -> MyCodable? {
31+ return data
32+ }
33+
34+ func targetClass() -> Responce.Type {
35+ return Responce.self
36+ }
37+}
Hookah Tau/API/Reservations api/Resolvers/Admin/ADeleteReservationResolver.swiftnew file+34
@@ -0,0 +1,34 @@
1+//
2+// ADeleteReservationResolver.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 07/01/2020.
6+// Copyright © 2020 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+class ADeleteReservationResolver: ApiResolver {
12+
13+ struct Request: MyCodable {
14+ var reservation: String
15+ }
16+
17+ var name: String {
18+ return "/reservations"
19+ }
20+
21+ var groupName: String? {
22+ return "/admin"
23+ }
24+
25+ var uuid: String
26+
27+ init(uuid: String) {
28+ self.uuid = uuid
29+ }
30+
31+ func parameters() -> MyCodable? {
32+ return Request(reservation: uuid)
33+ }
34+}
Hookah Tau/API/Reservations api/Resolvers/Admin/AReservationsResolver.swiftnew file+35
@@ -0,0 +1,35 @@
1+//
2+// AReservationsResolver.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 19/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+struct AReservationResponse: MyCodable {
12+ var reservations: [ReservationWithUser]
13+}
14+
15+/// `/admin/reservations/{establishment id}`
16+class AReservationsResolver<Response: MyCodable>: ApiResolver {
17+
18+ var name: String {
19+ return "/reservations/\(establishmentId)"
20+ }
21+
22+ var groupName: String? {
23+ return "/admin"
24+ }
25+
26+ var establishmentId: Int
27+
28+ init(establishmentId: Int) {
29+ self.establishmentId = establishmentId
30+ }
31+
32+ func targetClass() -> Response.Type {
33+ return Response.self
34+ }
35+}
Hookah Tau/API/Reservations api/Resolvers/ChangeReservationResolver.swiftnew file+35
@@ -0,0 +1,35 @@
1+//
2+// ChangeReservationResolver.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 29/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+class ChangeReservationResolver: ApiResolver {
12+
13+ /// Структура запроса с возможными данными для
14+ /// изменением бронирования
15+ struct Request: MyCodable {
16+ var startTime: Date
17+ var uuid: String
18+ var numberOfGuests: Int
19+ var endTime: Date
20+ }
21+
22+ var name: String {
23+ return "/reservations"
24+ }
25+
26+ var data: Request
27+
28+ init(data: Request) {
29+ self.data = data
30+ }
31+
32+ func parameters() -> MyCodable? {
33+ return data
34+ }
35+}
Hookah Tau/API/Reservations api/Resolvers/CreateReservationResolver.swiftnew file+30
@@ -0,0 +1,30 @@
1+//
2+// CreateReservationResolver.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 27/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+class CreateReservationResolver<Response: Codable>: ApiResolver {
12+
13+ var name: String {
14+ return "/reservations"
15+ }
16+
17+ var data: ReservationData
18+
19+ init(data: ReservationData) {
20+ self.data = data
21+ }
22+
23+ func parameters() -> MyCodable? {
24+ return data
25+ }
26+
27+ func targetClass() -> Response.Type {
28+ return Response.self
29+ }
30+}
Hookah Tau/API/Reservations api/Resolvers/DeleteReservationResolver.swiftnew file+31
@@ -0,0 +1,31 @@
1+//
2+// DeleteReservationResolver.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 29/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+
12+class DeleteReservationResolver: ApiResolver {
13+
14+ struct Request: MyCodable {
15+ var reservationUUID: String
16+ }
17+
18+ var name: String {
19+ return "/reservations"
20+ }
21+
22+ var uuid: String
23+
24+ init(uuid: String) {
25+ self.uuid = uuid
26+ }
27+
28+ func parameters() -> MyCodable? {
29+ return Request(reservationUUID: uuid)
30+ }
31+}
Hookah Tau/API/Reservations api/Resolvers/ReservationsAroundResolver.swiftnew file+35
@@ -0,0 +1,35 @@
1+//
2+// ReservationsAroundResolver.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 20/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+struct ReservationsAroundResponse: MyCodable {
12+ var reservations: [ReservationPeriod]
13+}
14+
15+/// ` GET /reservations/around/{reservation id}`
16+class ReservationsAroundResolver<Response: MyCodable>: ApiResolver {
17+
18+ var groupName: String? {
19+ return "/reservations"
20+ }
21+
22+ var name: String {
23+ return "/around/\(reservationId)"
24+ }
25+
26+ var reservationId: String
27+
28+ init(reservationId: String) {
29+ self.reservationId = reservationId
30+ }
31+
32+ func targetClass() -> Response.Type {
33+ return Response.self
34+ }
35+}
Hookah Tau/API/Reservations api/Resolvers/ReservationsResolver.swiftnew file+46
@@ -0,0 +1,46 @@
1+//
2+// ReservationsResolver.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 06/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+struct ReservationsResponce: MyCodable {
10+ var reservations: [Reservation]
11+}
12+
13+class ReservationsResolver<Response: MyCodable>: ApiResolver {
14+ var name: String {
15+ return "/reservations"
16+ }
17+
18+ func targetClass() -> Response.Type {
19+ return Response.self
20+ }
21+}
22+
23+class AllReservationsResolver<Response: Codable>: ApiResolver {
24+
25+ struct Request: MyCodable {
26+ var establishmentUUID: Int
27+ }
28+
29+ var name: String {
30+ return "/all/\(self.establishmentID)"
31+ }
32+
33+ var groupName: String? {
34+ return "/reservations"
35+ }
36+
37+ var establishmentID: Int
38+
39+ init(establishmentID: Int) {
40+ self.establishmentID = establishmentID
41+ }
42+
43+ func targetClass() -> Response.Type {
44+ return Response.self
45+ }
46+}
Hookah Tau/API/ServerError.swift+10−2
@@ -8,8 +8,16 @@
8
8
9import Foundation
9import Foundation
10
10
11+struct SE: MyCodable {
12+ var error: String?
13+}
11
14
12−enum ServerError: Error {
15+enum GeneralError: Error {
13− case internalServerError
16+ case serverError(SE)
14 case decodeError
17 case decodeError
18+ case noData
19+ case notFound
20+ case Error400
21+ case somethingWentCompletelyWrong
22+ case alamofireError
15}
23}
Hookah Tau/AppDelegate.swift+1−2
@@ -21,14 +21,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
21 window?.makeKeyAndVisible()
21 window?.makeKeyAndVisible()
22
22
23 let navController = UINavigationController()
23 let navController = UINavigationController()
24− window?.rootViewController = navController
25
24
26 applicationCoordinator = ApplicationCoordinator(navigationController: navController)
25 applicationCoordinator = ApplicationCoordinator(navigationController: navController)
27 applicationCoordinator?.start()
26 applicationCoordinator?.start()
28
27
29 return true
28 return true
30 }
29 }
31−
30+
32 func applicationWillResignActive(_ application: UIApplication) {
31 func applicationWillResignActive(_ application: UIApplication) {
33 // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
32 // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
34 // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
33 // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
Hookah Tau/Components/BlackButtonStyle.swift+8
@@ -39,6 +39,14 @@ class BlackButtonStyle: NSObject {
39 button.addSubviewThatFills(view)
39 button.addSubviewThatFills(view)
40 }
40 }
41
41
42+ func changeToBookedState(button: Button) {
43+ TableStyle.setStripes(for: button)
44+ }
45+
46+ func changeToAvailiableState(button: Button) {
47+ button.layer.sublayers?.removeAll()
48+ }
49+
42 func backToNormalStyle(button: Button) {
50 func backToNormalStyle(button: Button) {
43 subviewLayer?.removeFromSuperview()
51 subviewLayer?.removeFromSuperview()
44 }
52 }
Hookah Tau/Components/Button.swift+18−1
@@ -22,7 +22,14 @@ class Button: UIButton {
22
22
23 override var isEnabled: Bool {
23 override var isEnabled: Bool {
24 didSet {
24 didSet {
25− //alpha = isEnabled ? 1 : 0.8
25+ alpha = isEnabled ? 1 : 0.8
26+ }
27+ }
28+
29+ /// State when chosen table is Booked
30+ public var availiable = true {
31+ willSet {
32+ willUpdateBookingState(newValue: availiable)
26 }
33 }
27 }
34 }
28
35
@@ -43,4 +50,14 @@ class Button: UIButton {
43 style?.backToNormalStyle(button: self)
50 style?.backToNormalStyle(button: self)
44 }
51 }
45 }
52 }
53+
54+ private func willUpdateBookingState(newValue avaliable: Bool) {
55+ isEnabled = avaliable
56+
57+// if availiable {
58+// style?.changeToAvailiableState(button: self)
59+// } else {
60+// style?.changeToBookedState(button: self)
61+// }
62+ }
46}
63}
Hookah Tau/Components/PhoneTextField.swiftnew file+63
@@ -0,0 +1,63 @@
1+//
2+// PhoneTextField.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 13/01/2020.
6+// Copyright © 2020 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class PhoneTextField: UITextField, UITextFieldDelegate {
12+
13+ override func awakeFromNib() {
14+ super.awakeFromNib()
15+
16+ self.delegate = self
17+ self.keyboardType = .numberPad
18+ }
19+
20+ func bind(withModel model: String) {
21+ self.text = model.formattedNumber()
22+ }
23+
24+ func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
25+ let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
26+ textField.text = formattedNumber(newString)
27+ return false
28+ }
29+
30+ private func formattedNumber(_ number: String) -> String {
31+ let cleanPhoneNumber = number.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
32+
33+ var result = ""
34+ let phoneMask = "+X (XXX) XXX XX XX"
35+ var index = cleanPhoneNumber.startIndex
36+ for ch in phoneMask where index < cleanPhoneNumber.endIndex {
37+ if ch == "X" {
38+ result.append(cleanPhoneNumber[index])
39+ index = cleanPhoneNumber.index(after: index)
40+ } else {
41+ result.append(ch)
42+ }
43+ }
44+ return result
45+ }
46+
47+ /// +o (oxx) xxx xx xx
48+ ///
49+ /// need tor return only `x`, length is 9. And it has to have prefix +7 (9...) or +8 (9..)
50+ func getFormattedNumber() -> String? {
51+ guard let number = self.text else { return nil }
52+
53+ let cleanPhoneNumber = String(number.components(separatedBy: CharacterSet.decimalDigits.inverted).joined())
54+ let hasPrefix = cleanPhoneNumber.hasPrefix("79") || cleanPhoneNumber.hasPrefix("89")
55+
56+ let result = String(cleanPhoneNumber.dropFirst(2))
57+ if result.count == 9 && hasPrefix {
58+ return result
59+ }
60+
61+ return nil
62+ }
63+}
Hookah Tau/Components/TableButton.swiftnew file+54
@@ -0,0 +1,54 @@
1+//
2+// TableButton.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 18/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class TableButton: UIButton {
12+
13+ var style: TableButtonStyle?
14+
15+ var isChosen: Bool = false {
16+ didSet {
17+ if isChosen {
18+ style?.setSelected(button: self)
19+ } else {
20+ style?.setUnselected(button: self)
21+ }
22+ }
23+ }
24+
25+ var isBooked: Bool = false {
26+ didSet {
27+ if !isBooked {
28+ style?.setAvaliable(button: self)
29+ } else {
30+ style?.setBooked(button: self)
31+ }
32+ }
33+ }
34+
35+ override func awakeFromNib() {
36+ super.awakeFromNib()
37+ }
38+
39+ func select() {
40+ isChosen = true
41+ }
42+
43+ func active() {
44+ isBooked = false
45+ }
46+
47+ func inactive() {
48+ isBooked = true
49+ }
50+
51+ func unselect() {
52+ isChosen = false
53+ }
54+}
Hookah Tau/Components/TableButtonStyle.swiftnew file+87
@@ -0,0 +1,87 @@
1+//
2+// MapButtonStyle.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 18/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class TableStyle {
12+
13+ static func setStripes(for view: UIView) {
14+ let replicationLayer = createReplicatorLayer()
15+ replicationLayer.frame = view.bounds
16+ replicationLayer.frame.origin.x = -100
17+
18+ view.layer.masksToBounds = true
19+ view.layer.addSublayer(replicationLayer)
20+ }
21+
22+ private static func createReplicatorLayer() -> CAReplicatorLayer {
23+ let replicatorLayer = CAReplicatorLayer()
24+
25+ let stripe = CALayer()
26+
27+ stripe.frame = CGRect(x: 0, y: -10, width: 2, height: 500)
28+ stripe.backgroundColor = UIColor.white.cgColor
29+ stripe.transform = CATransform3DMakeRotation(0.52, 0, 0, 1.0)
30+
31+ replicatorLayer.instanceCount = 60
32+ replicatorLayer.instanceTransform = CATransform3DMakeTranslation(20, 0, 0)
33+ replicatorLayer.addSublayer(stripe)
34+
35+ return replicatorLayer
36+ }
37+
38+}
39+
40+
41+class TableButtonStyle {
42+ enum Constants {
43+ static let cornerRadius: CGFloat = 8
44+ static let clearColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 0)
45+ }
46+
47+ enum Selection {
48+ static let borderColor: CGColor = #colorLiteral(red: 0.2901960784, green: 0.5647058824, blue: 0.8862745098, alpha: 1)
49+ static let borderWidth: CGFloat = 3
50+ static let fillColor = #colorLiteral(red: 0.2078431373, green: 0.5215686275, blue: 0.8862745098, alpha: 0.5)
51+ }
52+
53+ enum Booked {
54+ static let fillColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 0.5)
55+ }
56+
57+ func setSelected(button: TableButton) {
58+ button.layer.borderWidth = Selection.borderWidth
59+ button.layer.borderColor = Selection.borderColor
60+ button.backgroundColor = Selection.fillColor
61+ }
62+
63+ func setBooked(button: TableButton) {
64+ button.backgroundColor = Booked.fillColor
65+ }
66+
67+ func setAvaliable(button: TableButton) {
68+ button.layer.sublayers?.removeAll()
69+ button.backgroundColor = button.isChosen ? Selection.fillColor : Constants.clearColor
70+ }
71+
72+ func setUnselected(button: TableButton) {
73+ button.layer.borderWidth = 0
74+ button.backgroundColor = button.isBooked ? Booked.fillColor : Constants.clearColor
75+ }
76+}
77+
78+// MARK: - ButtonStyle
79+
80+extension TableButtonStyle {
81+
82+ func apply(to button: TableButton?) {
83+ button?.layer.cornerRadius = Constants.cornerRadius
84+ button?.style = self
85+ }
86+
87+}
Hookah Tau/Controllers/Admin/Clients/Cells/ClientTableViewCell.swiftnew file+53
@@ -0,0 +1,53 @@
1+//
2+// ClientTableViewCell.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 09/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+let clientTableViewCellId = "ClientTableViewCell"
12+
13+class ClientTableViewCell: UITableViewCell {
14+
15+ // MARK: - IBAOutlets
16+
17+ @IBOutlet weak var nameLabel: UILabel!
18+
19+ @IBOutlet weak var phoneNumberLabel: UILabel!
20+
21+ @IBOutlet weak var numberOfReservationsLabel: UILabel!
22+
23+
24+ @IBOutlet weak var blockListLabel: UILabel!
25+
26+ @IBOutlet weak var isAdminLabel: UILabel!
27+
28+
29+
30+ // MARK: - Lifecycle
31+
32+ override func awakeFromNib() {
33+ super.awakeFromNib()
34+ // Initialization code
35+ }
36+
37+ override func setSelected(_ selected: Bool, animated: Bool) {
38+ super.setSelected(selected, animated: animated)
39+
40+ // Configure the view for the selected state
41+ }
42+
43+ // MARK: - Bind
44+
45+ func bind(withModel model: Client) {
46+ blockListLabel.isHidden = !model.isBlocked
47+ isAdminLabel.isHidden = !model.isAdmin
48+ nameLabel.text = model.name ?? "❔"
49+ phoneNumberLabel.text = model.phoneNumber.formattedNumber()
50+ numberOfReservationsLabel.text = model.reservationCount > 99 ? "99+" : "\(model.reservationCount)"
51+ }
52+
53+}
Hookah Tau/Controllers/Admin/Clients/ClientList.storyboardnew file+196
@@ -0,0 +1,196 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3+ <device id="retina4_7" orientation="portrait" appearance="light"/>
4+ <dependencies>
5+ <deployment identifier="iOS"/>
6+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
7+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
8+ <capability name="Stack View standard spacing" minToolsVersion="9.0"/>
9+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
10+ </dependencies>
11+ <scenes>
12+ <!--Clients View Controller-->
13+ <scene sceneID="U7X-Jx-osU">
14+ <objects>
15+ <tableViewController storyboardIdentifier="ClientsViewController" id="Wfj-Ux-iLR" customClass="ClientsViewController" customModule="Hookah_Tau" customModuleProvider="target" sceneMemberID="viewController">
16+ <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="qFn-ia-vVI">
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" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
20+ <prototypes>
21+ <tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="ClientTableViewCell" rowHeight="69" id="QPb-0v-yOO" customClass="ClientTableViewCell" customModule="Hookah_Tau" customModuleProvider="target">
22+ <rect key="frame" x="0.0" y="28" width="375" height="69"/>
23+ <autoresizingMask key="autoresizingMask"/>
24+ <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="QPb-0v-yOO" id="48D-Q7-dBg">
25+ <rect key="frame" x="0.0" y="0.0" width="375" height="69"/>
26+ <autoresizingMask key="autoresizingMask"/>
27+ <subviews>
28+ <stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="fhd-NM-rld">
29+ <rect key="frame" x="16" y="8" width="343" height="53"/>
30+ <subviews>
31+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" alignment="top" translatesAutoresizingMaskIntoConstraints="NO" id="vNV-bN-3No">
32+ <rect key="frame" x="0.0" y="0.0" width="179" height="53"/>
33+ <subviews>
34+ <stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" alignment="center" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="uoO-w2-5yG">
35+ <rect key="frame" x="0.0" y="0.0" width="78.5" height="26.5"/>
36+ <subviews>
37+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Стасик" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="4Cp-jh-0Gq">
38+ <rect key="frame" x="0.0" y="0.0" width="78.5" height="26.5"/>
39+ <fontDescription key="fontDescription" type="boldSystem" pointSize="22"/>
40+ <color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
41+ <nil key="highlightedColor"/>
42+ </label>
43+ </subviews>
44+ </stackView>
45+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="+7 (901) 733-01-79" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0gY-h5-AV2">
46+ <rect key="frame" x="0.0" y="29" width="179" height="24"/>
47+ <fontDescription key="fontDescription" type="system" pointSize="20"/>
48+ <nil key="textColor"/>
49+ <nil key="highlightedColor"/>
50+ </label>
51+ </subviews>
52+ </stackView>
53+ <stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="tJg-ju-bFY">
54+ <rect key="frame" x="219.5" y="0.0" width="123.5" height="53"/>
55+ <subviews>
56+ <stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="4et-BC-7iY">
57+ <rect key="frame" x="0.0" y="14.5" width="103.5" height="24"/>
58+ <subviews>
59+ <stackView opaque="NO" contentMode="scaleToFill" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="BHD-cD-L1c">
60+ <rect key="frame" x="0.0" y="0.0" width="51" height="24"/>
61+ <subviews>
62+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="❌" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="MaQ-TW-666">
63+ <rect key="frame" x="0.0" y="0.0" width="23" height="24"/>
64+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
65+ <nil key="textColor"/>
66+ <nil key="highlightedColor"/>
67+ </label>
68+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="👑" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="0Xa-FL-GVr">
69+ <rect key="frame" x="28" y="0.0" width="23" height="24"/>
70+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
71+ <nil key="textColor"/>
72+ <nil key="highlightedColor"/>
73+ </label>
74+ </subviews>
75+ </stackView>
76+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="99+" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Gzo-kW-60Z">
77+ <rect key="frame" x="66" y="0.0" width="37.5" height="24"/>
78+ <fontDescription key="fontDescription" type="system" pointSize="20"/>
79+ <nil key="textColor"/>
80+ <nil key="highlightedColor"/>
81+ </label>
82+ </subviews>
83+ </stackView>
84+ <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="arrow" translatesAutoresizingMaskIntoConstraints="NO" id="DrQ-Ss-gIT">
85+ <rect key="frame" x="111.5" y="16.5" width="12" height="20"/>
86+ </imageView>
87+ </subviews>
88+ <constraints>
89+ <constraint firstAttribute="width" constant="123.5" id="eeb-nt-pTB"/>
90+ </constraints>
91+ </stackView>
92+ </subviews>
93+ </stackView>
94+ </subviews>
95+ <constraints>
96+ <constraint firstItem="fhd-NM-rld" firstAttribute="leading" secondItem="48D-Q7-dBg" secondAttribute="leading" constant="16" id="EDr-e5-ffF"/>
97+ <constraint firstAttribute="trailing" secondItem="fhd-NM-rld" secondAttribute="trailing" constant="16" id="jMD-pP-Fcj"/>
98+ <constraint firstAttribute="bottom" secondItem="fhd-NM-rld" secondAttribute="bottom" constant="8" id="lGU-2k-95p"/>
99+ <constraint firstItem="fhd-NM-rld" firstAttribute="top" secondItem="48D-Q7-dBg" secondAttribute="top" constant="8" id="yPf-vR-dW7"/>
100+ </constraints>
101+ </tableViewCellContentView>
102+ <connections>
103+ <outlet property="blockListLabel" destination="MaQ-TW-666" id="Us0-tw-9tj"/>
104+ <outlet property="isAdminLabel" destination="0Xa-FL-GVr" id="fzz-2t-Lda"/>
105+ <outlet property="nameLabel" destination="4Cp-jh-0Gq" id="g6e-c0-x0u"/>
106+ <outlet property="numberOfReservationsLabel" destination="Gzo-kW-60Z" id="9J2-S4-fKq"/>
107+ <outlet property="phoneNumberLabel" destination="0gY-h5-AV2" id="rdb-GS-ceF"/>
108+ </connections>
109+ </tableViewCell>
110+ </prototypes>
111+ <connections>
112+ <outlet property="dataSource" destination="Wfj-Ux-iLR" id="uby-48-VjA"/>
113+ <outlet property="delegate" destination="Wfj-Ux-iLR" id="oeU-I8-pfI"/>
114+ </connections>
115+ </tableView>
116+ </tableViewController>
117+ <placeholder placeholderIdentifier="IBFirstResponder" id="epE-4n-L3K" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
118+ </objects>
119+ <point key="canvasLocation" x="0.80000000000000004" y="-112.89355322338831"/>
120+ </scene>
121+ <!--Profile Table View Controller-->
122+ <scene sceneID="APa-VZ-6hd">
123+ <objects>
124+ <viewController storyboardIdentifier="ProfileTableViewController" id="ZVH-jf-rtc" customClass="ProfileTableViewController" customModule="Hookah_Tau" customModuleProvider="target" sceneMemberID="viewController">
125+ <view key="view" contentMode="scaleToFill" id="rcq-M9-CvR">
126+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
127+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
128+ <subviews>
129+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ciO-ia-Oob">
130+ <rect key="frame" x="0.0" y="0.0" width="375" height="65"/>
131+ <subviews>
132+ <stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" alignment="center" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="IBA-bj-CWK">
133+ <rect key="frame" x="8" y="16" width="359" height="37"/>
134+ <subviews>
135+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="4WC-gi-jbD" customClass="Button" customModule="Hookah_Tau" customModuleProvider="target">
136+ <rect key="frame" x="0.0" y="0.0" width="180" height="37"/>
137+ <color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
138+ <constraints>
139+ <constraint firstAttribute="width" constant="180" id="bJA-V9-gw9"/>
140+ <constraint firstAttribute="height" constant="37" id="pdo-AO-o8a"/>
141+ </constraints>
142+ <state key="normal" title="Button"/>
143+ </button>
144+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="gfq-ql-lka" customClass="Button" customModule="Hookah_Tau" customModuleProvider="target">
145+ <rect key="frame" x="224" y="0.0" width="135" height="37"/>
146+ <color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
147+ <constraints>
148+ <constraint firstAttribute="width" constant="135" id="Xn3-v6-SLp"/>
149+ <constraint firstAttribute="height" constant="37" id="z68-vW-NjK"/>
150+ </constraints>
151+ <state key="normal" title="Button"/>
152+ </button>
153+ </subviews>
154+ </stackView>
155+ </subviews>
156+ <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
157+ <constraints>
158+ <constraint firstItem="IBA-bj-CWK" firstAttribute="leading" secondItem="ciO-ia-Oob" secondAttribute="leading" constant="8" id="WZS-Vq-tuP"/>
159+ <constraint firstAttribute="trailing" secondItem="IBA-bj-CWK" secondAttribute="trailing" constant="8" id="Yk3-D1-1kg"/>
160+ <constraint firstItem="IBA-bj-CWK" firstAttribute="top" secondItem="ciO-ia-Oob" secondAttribute="top" constant="16" id="fWO-oj-NuN"/>
161+ <constraint firstItem="IBA-bj-CWK" firstAttribute="leading" secondItem="ciO-ia-Oob" secondAttribute="leading" constant="8" id="gP2-jh-5El"/>
162+ <constraint firstAttribute="height" constant="65" id="pzZ-zw-kbc"/>
163+ </constraints>
164+ </view>
165+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="T0s-lX-Z5m">
166+ <rect key="frame" x="0.0" y="65" width="375" height="602"/>
167+ <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
168+ </view>
169+ </subviews>
170+ <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
171+ <constraints>
172+ <constraint firstItem="ciO-ia-Oob" firstAttribute="trailing" secondItem="tIS-3m-xwG" secondAttribute="trailing" id="3An-sM-OCN"/>
173+ <constraint firstItem="ciO-ia-Oob" firstAttribute="top" secondItem="tIS-3m-xwG" secondAttribute="top" id="5zl-uZ-sTi"/>
174+ <constraint firstItem="T0s-lX-Z5m" firstAttribute="leading" secondItem="tIS-3m-xwG" secondAttribute="leading" id="7qA-Hh-3Us"/>
175+ <constraint firstItem="T0s-lX-Z5m" firstAttribute="trailing" secondItem="tIS-3m-xwG" secondAttribute="trailing" id="9VD-9Q-wGX"/>
176+ <constraint firstItem="T0s-lX-Z5m" firstAttribute="top" secondItem="ciO-ia-Oob" secondAttribute="bottom" id="RSL-fo-wIb"/>
177+ <constraint firstItem="ciO-ia-Oob" firstAttribute="leading" secondItem="tIS-3m-xwG" secondAttribute="leading" id="Tuq-su-gbn"/>
178+ <constraint firstItem="T0s-lX-Z5m" firstAttribute="bottom" secondItem="tIS-3m-xwG" secondAttribute="bottom" id="iQj-LM-Im9"/>
179+ </constraints>
180+ <viewLayoutGuide key="safeArea" id="tIS-3m-xwG"/>
181+ </view>
182+ <connections>
183+ <outlet property="contentView" destination="T0s-lX-Z5m" id="9Z0-5T-f7Z"/>
184+ <outlet property="copyButton" destination="gfq-ql-lka" id="BNi-Ip-AyF"/>
185+ <outlet property="phoneButton" destination="4WC-gi-jbD" id="xlf-g7-CzU"/>
186+ </connections>
187+ </viewController>
188+ <placeholder placeholderIdentifier="IBFirstResponder" id="g4Y-1k-Lxr" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
189+ </objects>
190+ <point key="canvasLocation" x="1188" y="-113"/>
191+ </scene>
192+ </scenes>
193+ <resources>
194+ <image name="arrow" width="12" height="20"/>
195+ </resources>
196+</document>
Hookah Tau/Controllers/Admin/Clients/ClientsCoordinator.swiftnew file+36
@@ -0,0 +1,36 @@
1+//
2+// ClientsCoordinator.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 09/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+
12+class ClientsCoordinator: BaseCoordinator {
13+
14+ override func start() {
15+ let storyboard = UIStoryboard(name: "ClientList", bundle: nil)
16+ let viewController = storyboard.instantiateViewController(withIdentifier: "ClientsViewController") as? ClientsViewController
17+ viewController?.coordinator = self
18+ guard let vc = viewController else { return }
19+ navigationController?.navigationBar.prefersLargeTitles = true
20+ navigationController?.viewControllers = [vc]
21+ }
22+
23+ /// TODO
24+ func openUserProfile(_ profile: Client) {
25+ let profileCoordinator = ClientProfileCoordinator(navigationController: navigationController)
26+ profileCoordinator.user = profile
27+
28+ self.addDependency(profileCoordinator)
29+ profileCoordinator.didEndFlow = {
30+ self.removeDependency(profileCoordinator)
31+ self.navigationController?.popViewController(animated: true)
32+ }
33+
34+ profileCoordinator.start()
35+ }
36+}
Hookah Tau/Controllers/Admin/Clients/ClientsViewController.swiftnew file+239
@@ -0,0 +1,239 @@
1+//
2+// ClientsViewController.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 09/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class ClientsViewController: BaseTableViewController {
12+
13+ // MARK: - Properties
14+
15+ var dataSource: [Client] = [] {
16+ didSet {
17+ tableView.reloadData()
18+ }
19+ }
20+
21+ var filteredDataSource: [Client] = []
22+
23+ var clientsService: ClientsService?
24+
25+ weak var coordinator: ClientsCoordinator?
26+
27+ var searchControl: UISearchController?
28+
29+ var isFiltering: Bool {
30+ return searchControl?.isActive ?? false && !searchBarIsEmpty()
31+ }
32+
33+ func searchBarIsEmpty() -> Bool {
34+ return searchControl?.searchBar.text?.isEmpty ?? true
35+ }
36+
37+ // MARK: - Lifecycle
38+
39+ override func viewDidLoad() {
40+ super.viewDidLoad()
41+
42+ setupNavigationBar()
43+ setupSearchController()
44+ clientsService = ClientsService(apiClient: APIClient.shared)
45+ }
46+
47+ override func viewWillAppear(_ animated: Bool) {
48+ super.viewWillAppear(animated)
49+ tabBarController?.tabBar.isHidden = false
50+ }
51+
52+ override func viewDidAppear(_ animated: Bool) {
53+ super.viewDidAppear(animated)
54+ loadClients()
55+ }
56+
57+ // MARK: - Setup
58+
59+ func setupNavigationBar() {
60+
61+ navigationItem.title = "Клиенты"
62+ navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .action, target: self, action: #selector(shareCSVFile))
63+ }
64+
65+ func setupSearchController() {
66+ searchControl = UISearchController(searchResultsController: nil)
67+ navigationItem.searchController = searchControl
68+ navigationItem.hidesSearchBarWhenScrolling = false
69+ searchControl?.obscuresBackgroundDuringPresentation = false
70+ definesPresentationContext = true
71+ searchControl?.searchResultsUpdater = self
72+ }
73+
74+ // MARK: - Api calls
75+
76+ /// Загружаем клиентов кальянки
77+ func loadClients() {
78+ clientsService?.loadClientsList(completion: { [ weak self ] (res) in
79+ switch res {
80+ case .failure(let err):
81+ print(err)
82+ case .success(let arr):
83+ self?.dataSource = arr
84+ }
85+ })
86+ }
87+
88+ // MARK: - Button handlers
89+
90+ /// https://developer.apple.com/design/human-interface-guidelines/ios/views/activity-views/
91+ @objc func shareCSVFile() {
92+ //Your CSV text
93+ let str = HFunc.main.exportClientsToCSV(dataSource)
94+ let filename = getDocumentsDirectory().appendingPathComponent("clients.csv")
95+
96+ do {
97+ try str.write(toFile: filename, atomically: true, encoding: String.Encoding.utf8)
98+
99+ let fileData = NSURL(fileURLWithPath: filename)
100+
101+ let objectsToShare = [fileData]
102+ let activityVC = UIActivityViewController(activityItems: objectsToShare, applicationActivities: nil)
103+
104+ self.present(activityVC, animated: true, completion: nil)
105+
106+ } catch {
107+ self.displayAlert(with: "По какой-то причине не могу сохранить файл!")
108+ }
109+ }
110+
111+ func getDocumentsDirectory() -> NSString {
112+ let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
113+ let documentsDirectory = paths[0]
114+ return documentsDirectory as NSString
115+ }
116+
117+ // MARK: - Change blocklist status
118+
119+ func updateUserBlockStatus(wasBlocked: Bool, phone: String, indexPath: IndexPath) {
120+ let crudMethod = wasBlocked ? CrudMethod.delete : CrudMethod.put
121+ clientsService?.changeBlockList(whatToDo: crudMethod, phone: phone, completion: { (res) in
122+ if res {
123+ self.tableView?.beginUpdates()
124+ self.tableView?.reloadRows(at: [indexPath], with: .automatic)
125+ if self.isFiltering {
126+ self.filteredDataSource[indexPath.row].isBlocked = !wasBlocked
127+ let changed = self.filteredDataSource[indexPath.row]
128+ let filtered = self.dataSource.map { (one: Client) -> Client in
129+ if one.uuid == changed.uuid {
130+ return changed
131+ } else { return one } }
132+ self.dataSource = filtered
133+ } else {
134+ self.dataSource[indexPath.row].isBlocked = !wasBlocked
135+ }
136+ self.tableView?.endUpdates()
137+ } else {
138+ self.displayAlert(with: "Не удалось \(wasBlocked ? "удалить" : "добавить") пользователя \(wasBlocked ? "из" : "в") чс! Попробуйте еще раз!")
139+ return
140+ }
141+ })
142+ }
143+
144+ func updateAdminStatus(user: Client, indexPath: IndexPath) {
145+
146+ let wasAdmin = user.isAdmin
147+ let crudMethod = wasAdmin ? CrudMethod.delete : CrudMethod.put
148+ let data = user.isAdmin ? user.uuid : user.phoneNumber
149+
150+ clientsService?.changeAdmin(crud: crudMethod, data: data, completion: { (res) in
151+ if res {
152+ self.tableView?.beginUpdates()
153+ self.tableView?.reloadRows(at: [indexPath], with: .automatic)
154+ if self.isFiltering {
155+ self.filteredDataSource[indexPath.row].isAdmin = !wasAdmin
156+ let changed = self.filteredDataSource[indexPath.row]
157+ let filtered = self.dataSource.map { (one: Client) -> Client in
158+ if one.uuid == changed.uuid {
159+ return changed
160+ } else { return one } }
161+ self.dataSource = filtered
162+ } else {
163+ self.dataSource[indexPath.row].isAdmin = !wasAdmin
164+ }
165+ self.tableView?.endUpdates()
166+ } else {
167+ self.displayAlert(with: "Не удалось \(wasAdmin ? "распромоутить" : "запромоутить") пользователя! Попробуйте еще раз!")
168+ return
169+ }
170+ })
171+ }
172+}
173+
174+// MARK: - Tableview
175+
176+extension ClientsViewController {
177+
178+ override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
179+ let cell = tableView.dequeueReusableCell(withIdentifier: clientTableViewCellId, for: indexPath) as! ClientTableViewCell
180+
181+ let userModel = isFiltering ? filteredDataSource[indexPath.row] : dataSource[indexPath.row]
182+ cell.bind(withModel: userModel)
183+
184+ return cell
185+ }
186+
187+ override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
188+ return isFiltering ? filteredDataSource.count : dataSource.count
189+ }
190+
191+ override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
192+ let user = isFiltering ? filteredDataSource[indexPath.row] : dataSource[indexPath.row]
193+ coordinator?.openUserProfile(user)
194+ searchControl?.dismiss(animated: true, completion: nil)
195+ }
196+
197+ // Update
198+
199+ override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
200+ return true
201+ }
202+
203+ override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
204+
205+ let user = isFiltering ? filteredDataSource[indexPath.row] : dataSource[indexPath.row]
206+ let blockButton = UITableViewRowAction(style: .normal, title: "\(!user.isBlocked ? "в чс" : "убрать из чс")") { _,_ in
207+
208+ self.updateUserBlockStatus(wasBlocked: user.isBlocked, phone: user.phoneNumber, indexPath: indexPath)
209+ }
210+
211+ blockButton.backgroundColor = .black
212+
213+ return [blockButton]
214+ }
215+
216+ override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
217+
218+ let user = isFiltering ? filteredDataSource[indexPath.row] : dataSource[indexPath.row]
219+
220+ let promote = UIContextualAction(style: .normal, title: "\(!user.isAdmin ? "дать админку" : "забрать админку")") { (_, _, _) in
221+ self.updateAdminStatus(user: user, indexPath: indexPath)
222+ }
223+ promote.backgroundColor = .black
224+
225+ return UISwipeActionsConfiguration(actions: [promote])
226+ }
227+}
228+
229+extension ClientsViewController: UISearchResultsUpdating {
230+ func filterContentForSearchText(_ searchText: String, scope: String = "All")
231+ {
232+ filteredDataSource = dataSource.filter { ($0.name?.lowercased().contains(searchText.lowercased()) ?? false) || ($0.phoneNumber.lowercased().contains(searchText.lowercased())) }
233+ tableView.reloadData()
234+ }
235+
236+ func updateSearchResults(for searchController: UISearchController) {
237+ filterContentForSearchText(searchController.searchBar.text ?? "")
238+ }
239+}
Hookah Tau/Controllers/Admin/Clients/Profile/ClientProfileCoordinator.swiftnew file+52
@@ -0,0 +1,52 @@
1+//
2+// ClientProfileCoordinator.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 10/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+
12+class ClientProfileCoordinator: BaseCoordinator {
13+
14+ var user: Client?
15+
16+ override func start() {
17+ let storyboard = UIStoryboard(name: "ClientList", bundle: nil)
18+ let viewController = storyboard.instantiateViewController(withIdentifier: "ProfileTableViewController") as? ProfileTableViewController
19+ viewController?.user = user
20+ viewController?.coodinator = self
21+ guard let vc = viewController else { return }
22+ navigationController?.pushViewController(vc, animated: true)
23+ }
24+
25+
26+ // Создание бронирования из профиля клиента
27+ func createReservation(forUserWithPhone phoneNumber: String, user fullUser: FullUser) {
28+ let coordinator = AdminFirstStepReservationViewCoordinator(navigationController: navigationController)
29+ coordinator.phone = phoneNumber
30+ coordinator.fullUser = fullUser
31+
32+ addDependency(coordinator)
33+ coordinator.didFinish = { [weak self] in
34+ self?.removeDependency(coordinator)
35+ self?.navigationController?.popViewController(animated: true)
36+ }
37+
38+ coordinator.start()
39+ }
40+
41+ // Просмотр (и редактирование) брони
42+ func seeExistingReservation(data: ReservationWithUser) {
43+ let reservationCoordinator = AdminReservationEditingCoordinator(navigationController: navigationController)
44+ addDependency(reservationCoordinator)
45+ reservationCoordinator.reservationData = data
46+ reservationCoordinator.didEndFlow = { [weak self] in
47+ self?.removeDependency(reservationCoordinator)
48+ self?.navigationController?.popViewController(animated: true)
49+ }
50+ reservationCoordinator.start()
51+ }
52+}
Hookah Tau/Controllers/Admin/Clients/Profile/ProfileTableViewController.swiftnew file+257
@@ -0,0 +1,257 @@
1+//
2+// ProfileTableViewController.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 10/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class ProfileTableViewController: BaseViewController {
12+
13+ // MARK: - IBAOutlets
14+
15+ @IBOutlet weak var phoneButton: Button! {
16+ didSet {
17+ guard let u = user else { return }
18+
19+ let style = BlackButtonStyle()
20+ style.apply(to: phoneButton, withTitle: u.phoneNumber.formattedNumber())
21+ phoneButton.addTarget(self, action: #selector(callUser), for: .touchUpInside)
22+ }
23+ }
24+
25+ @IBOutlet weak var copyButton: Button! {
26+ didSet {
27+ let style = BlackButtonStyle()
28+ style.apply(to: copyButton, withTitle: "СКОПИРОВАТЬ")
29+ copyButton.addTarget(self, action: #selector(copyNumber(_:)), for: .touchUpInside)
30+ }
31+ }
32+
33+
34+ @IBOutlet weak var contentView: UIView!
35+
36+ // MARK: - properties
37+
38+ var tableView: UITableView?
39+
40+ var clientsService: ClientsService?
41+
42+ var reservationsService: ReservationsService?
43+
44+ var user: Client?
45+
46+ weak var coodinator: ClientProfileCoordinator?
47+
48+ var noReserationsYetView: AdminNoReservationsView?
49+
50+ var activityIndicator: UIActivityIndicatorView?
51+
52+ var refreshControl: UIRefreshControl?
53+
54+ var dataSource: [Reservation] = [] {
55+ didSet {
56+ guard let empty = noReserationsYetView else { return }
57+ if dataSource.isEmpty {
58+ empty.alpha = 1
59+ tableView?.alpha = 0
60+ } else {
61+ empty.alpha = 0
62+ tableView?.alpha = 1
63+ }
64+
65+ refreshControl?.endRefreshing()
66+ activityIndicator?.stopAnimating()
67+ tableView?.reloadData()
68+ }
69+ }
70+
71+ // MARK: - Lifecycle
72+
73+ override func viewDidLoad() {
74+ super.viewDidLoad()
75+
76+ clientsService = ClientsService(apiClient: APIClient.shared)
77+ reservationsService = ReservationsService(apiClient: APIClient.shared)
78+
79+ setupNavBar()
80+ setupTable()
81+ setupActivityIndicator()
82+ //loadReservations()
83+ }
84+
85+ override func viewWillAppear(_ animated: Bool) {
86+ super.viewWillAppear(animated)
87+ tabBarController?.tabBar.isHidden = true
88+ }
89+
90+ override func viewDidAppear(_ animated: Bool) {
91+ super.viewDidAppear(animated)
92+ loadReservations()
93+ }
94+
95+ // MARK: - Setup
96+
97+ func setupNavBar() {
98+ guard let u = user else { return }
99+
100+ navigationItem.title = "\(u.name ?? "❔")\(u.isAdmin ? "👑" : "")\(u.isBlocked ? "❌" : "")"
101+ navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(book))
102+ }
103+
104+ func setupTable() {
105+ tableView = UITableView()
106+
107+ // refresh control
108+ refreshControl = UIRefreshControl()
109+ refreshControl?.addTarget(self, action: #selector(refresh), for: .valueChanged)
110+ tableView?.refreshControl = refreshControl
111+
112+ tableView?.dataSource = self
113+ tableView?.delegate = self
114+
115+ noReserationsYetView = AdminNoReservationsView.loadFromNib()
116+ noReserationsYetView?.setup(withTitle: "У этого пользователя пока нет ни одной брони :(")
117+ noReserationsYetView?.bookButton?.addTarget(self, action: #selector(book), for: .touchUpInside)
118+ contentView.addSubviewThatFills(noReserationsYetView)
119+ noReserationsYetView?.alpha = 0
120+
121+ tableView?.register(ReservationCell.self, forCellReuseIdentifier: reservationCell)
122+
123+ contentView.addSubviewThatFills(tableView)
124+
125+ tableView?.alpha = 0
126+ }
127+
128+ func setupActivityIndicator() {
129+
130+ activityIndicator = UIActivityIndicatorView()
131+ view.addSubviewThatFills(activityIndicator)
132+ activityIndicator?.startAnimating()
133+
134+ }
135+
136+ // MARK: - Api calls
137+
138+ func loadReservations() {
139+ guard let uuid = user?.uuid else { return }
140+ clientsService?.loadReservationsForUser(withUUID: uuid,
141+ completion: { [weak self] (res) in
142+ switch res {
143+ case .failure(let err):
144+ self?.displayAlert(forError: err)
145+ case .success(let data):
146+ self?.dataSource = data
147+ }
148+ })
149+ }
150+
151+
152+ func deleteReservation(uuid: String, completion: @escaping (([Reservation]?) -> Void)) {
153+ reservationsService?.deleteReservation(isAdmin: true, uuid: uuid) { result in
154+ if result {
155+ let newReservations = self.dataSource.filter { $0.uuid != uuid }
156+ completion(newReservations)
157+ return
158+ }
159+
160+ completion(nil)
161+ }
162+ }
163+
164+ // MARK: - Button handlers
165+
166+ @objc func callUser() {
167+ guard
168+ let u = user,
169+ let urlPhone = URL(string: "tel://79\(u.phoneNumber)")
170+ else { return }
171+
172+ UIApplication.shared.open(urlPhone)
173+ }
174+
175+ @objc func copyNumber(_ sender: UIButton) {
176+ guard
177+ let u = user
178+ else { return }
179+ //Copy a string to the pasteboard.
180+ UIPasteboard.general.string = "79\(u.phoneNumber)"
181+
182+ //Alert
183+ self.displayAlert(with: "Номер скопирован!")
184+ }
185+
186+ @objc func refresh() {
187+ loadReservations()
188+ }
189+
190+ @objc func book() {
191+ guard
192+ let phone = user?.phoneNumber,
193+ let user = user
194+ else { return }
195+
196+ let fullUser = FullUser(uuid: user.uuid,
197+ name: user.name,
198+ phoneNumber: user.phoneNumber,
199+ isAdmin: user.isAdmin)
200+ coodinator?.createReservation(forUserWithPhone: phone, user: fullUser)
201+ }
202+}
203+
204+// MARK: - Table view data source
205+
206+extension ProfileTableViewController: UITableViewDelegate, UITableViewDataSource {
207+
208+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
209+ return dataSource.count
210+ }
211+
212+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
213+ let cell = tableView.dequeueReusableCell(withIdentifier: reservationCell, for: indexPath) as! ReservationCell
214+
215+ cell.bind(withModel: dataSource[indexPath.row])
216+
217+ return cell
218+ }
219+
220+ // tap
221+
222+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
223+ guard let u = user else { return }
224+ let r = dataSource[indexPath.row]
225+ let user = FullUser(uuid: u.uuid, name: u.name, phoneNumber: u.phoneNumber, isAdmin: u.isAdmin)
226+ let reservation = ReservationWithUser(reservation: r, user: user)
227+ coodinator?.seeExistingReservation(data: reservation)
228+ }
229+
230+ // Update
231+
232+ func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
233+ return true
234+ }
235+
236+ func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
237+
238+ let uuid = dataSource[indexPath.row].uuid
239+ let cancelButton = UITableViewRowAction(style: .normal, title: "отменить бронь") { _,_ in
240+
241+ self.deleteReservation(uuid: uuid) { optionalNewVal in
242+ if let newval = optionalNewVal {
243+ self.tableView?.beginUpdates()
244+ self.tableView?.deleteRows(at: [indexPath], with: .automatic)
245+ self.dataSource = newval
246+ self.tableView?.endUpdates()
247+ } else {
248+ self.displayAlert(with: "Не удалось удалить вашу бронь! Попробуйте еще раз!")
249+ }
250+ }
251+ }
252+
253+ cancelButton.backgroundColor = .black
254+
255+ return [cancelButton]
256+ }
257+}
Hookah Tau/Controllers/Admin/Reservations/AdminAddressCoordinator.swiftnew file+35
@@ -0,0 +1,35 @@
1+//
2+// AdminAddressCoordinator.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 09/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+class AdminAddressCoordinator: BaseCoordinator {
12+
13+ override func start() {
14+ let addressViewController = AddresViewController()
15+ addressViewController.coordinator = self
16+ navigationController?.viewControllers = [addressViewController]
17+
18+ guard let id = DataStorage.standard.getEstablishmentChoice() else { return }
19+ openReservations(forEstablishment: id)
20+ }
21+
22+ private func openReservations(forEstablishment id: Int) {
23+ let reservationsCoordinator = AdminReservationsCoordinator(navigationController: navigationController)
24+ addDependency(reservationsCoordinator)
25+ reservationsCoordinator.establishmentId = id
26+ reservationsCoordinator.start()
27+ }
28+}
29+
30+extension AdminAddressCoordinator: AddressMapper {
31+ func chooseAddress(establishmentId id: Int) {
32+ DataStorage.standard.setupEstablishmentChoice(id: id)
33+ openReservations(forEstablishment: id)
34+ }
35+}
Hookah Tau/Controllers/Admin/Reservations/AdminReservationEditingCoordinator.swiftnew file+31
@@ -0,0 +1,31 @@
1+//
2+// AdminReservationEditingCoordinator.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 20/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+
12+class AdminReservationEditingCoordinator: BaseCoordinator, ShowReservationCoordinator {
13+
14+ // MARK: - Peoperties
15+
16+ var didFinish: (() -> Void)?
17+
18+ var reservationData: ReservationWithUser?
19+
20+ // MARK: - Lifecycle
21+
22+ override func start() {
23+ let storyboard = UIStoryboard(name: "AdminReservations", bundle: nil)
24+ let vc = storyboard.instantiateViewController(withIdentifier: "AdminReservationEditingViewController") as! AdminReservationEditingViewController
25+ vc.reservationData = reservationData
26+ vc.coordinator = self
27+
28+ navigationController?.pushViewController(vc, animated: true)
29+ }
30+
31+}
Hookah Tau/Controllers/Admin/Reservations/AdminReservationEditingViewController.swiftnew file+187
@@ -0,0 +1,187 @@
1+//
2+// AdminReservationEditingViewController.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 20/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class AdminReservationEditingViewController: BaseViewController {
12+
13+ // MARK:- IBAOutlets
14+
15+ @IBOutlet weak var tableContentView: UIView!
16+
17+ @IBOutlet weak var mainContentView: UIView!
18+
19+ // MARK:- Properties
20+
21+ var mapView: MapImageScroll?
22+
23+ var reservationView: AdminReservationView?
24+
25+ var reservationData: ReservationWithUser?
26+
27+ weak var coordinator: ShowReservationCoordinator?
28+
29+ var reservationService: ReservationsService?
30+
31+ var reservedPeriods: [ReservationPeriod] = [] {
32+ didSet {
33+ if let _ = reservationView {
34+ setupReservationViewWithIntervals()
35+ }
36+ }
37+ }
38+
39+ // MARK:- Lifecycle
40+
41+ override func viewDidLoad() {
42+ super.viewDidLoad()
43+
44+ setupContentViews()
45+
46+ reservationService = ReservationsService(apiClient: APIClient.shared)
47+
48+ loadReservedPeriods()
49+
50+ setupButtons()
51+ }
52+
53+ override func viewWillAppear(_ animated: Bool) {
54+ super.viewWillAppear(animated)
55+
56+ tabBarController?.tabBar.isHidden = true
57+ }
58+
59+ // MARK: - Setup
60+
61+ func setupButtons() {
62+
63+ reservationView?.saveButton?.addTarget(self, action: #selector(saveReservation), for: .touchUpInside)
64+ reservationView?.cancelButton?.addTarget(self, action: #selector(cancelReservation), for: .touchUpInside)
65+ }
66+
67+ func setupContentViews() {
68+ guard
69+ let model = reservationData
70+ else { return }
71+
72+ if model.establishment == 1 {
73+ mapView = MapView1.loadFromNib()
74+ } else {
75+ mapView = MapView2.loadFromNib()
76+ }
77+
78+ navigationItem.title = TotalStorage.standard.getEstablishment(model.establishment)?.address
79+
80+ mapView?.isUserInteractionEnabled = false
81+
82+ tableContentView.addSubviewThatFills(mapView)
83+ mapView?.scrollToTable(table: model.reservedTable)
84+
85+ reservationView = AdminReservationView.loadFromNib()
86+ reservationView?.bind(withModel: model)
87+
88+ mainContentView.addSubviewThatFills(reservationView)
89+ }
90+
91+ func setupReservationViewWithIntervals() {
92+ reservationView?.bind(withIntervals: reservedPeriods)
93+ }
94+
95+ func loadReservedPeriods() {
96+ guard
97+ let model = reservationData,
98+ let uuid = model.uuid
99+ else {
100+ setupReservationViewWithIntervals()
101+ return }
102+
103+ reservationService?.getReservationsAround(uuid: uuid, completion: { [weak self] (res) in
104+ switch res {
105+ case .failure(let err):
106+ self?.displayAlert(forError: err)
107+ return
108+ case .success(let reserv):
109+ self?.reservedPeriods = reserv
110+ return
111+ }
112+ })
113+ }
114+
115+ // MARK:- Handlers
116+
117+ @objc func saveReservation() {
118+ guard
119+ let r = reservationView?.data,
120+ let uuid = reservationData?.uuid
121+ else {
122+ createReservation()
123+ return
124+ }
125+
126+ reservationView?.saveButton?.loading = true
127+
128+ reservationService?.updateReservation(startTime: r.startTime,
129+ numberOfGuests: r.numberOfGuests,
130+ endTime: r.endTime,
131+ uuid: uuid,
132+ isAdmin: true,
133+ completion: { [weak self] (res) in
134+ if res {
135+ self?.coordinator?.didFinish?()
136+ } else {
137+ self?.displayAlert(with: "Что-то пошло не так! Попробуешь еще раз?")
138+ }
139+
140+ self?.reservationView?.saveButton?.loading = false
141+ })
142+ }
143+
144+ func createReservation() {
145+ guard
146+ let r = reservationData,
147+ let d = reservationData?.owner.phoneNumber else { return }
148+
149+ let data = ReservationData(establishment: r.establishment,
150+ startTime: r.startTime,
151+ endTime: r.endTime,
152+ numberOfGuests: r.numberOfGuests,
153+ reservedTable: r.reservedTable)
154+ print(data)
155+ reservationService?.createReservation(reservation: AdminCreateReservation(reservation: data, user: d), completion: { (res) in
156+ switch res {
157+ case .failure(_):
158+ self.displayAlert(with: "Что-то пошло не так!\nПопробуешь еще раз?")
159+ return
160+ case .success(let uuid):
161+ self.reservationData?.uuid = uuid
162+ self.coordinator?.didFinish?()
163+ return
164+ }
165+ })
166+ }
167+
168+ @objc func cancelReservation() {
169+ guard
170+ let uuid = reservationData?.uuid
171+ else {
172+ cancelPrereservation()
173+ return }
174+
175+ reservationService?.deleteReservation(isAdmin: true, uuid: uuid, completion: { [weak self] (res) in
176+ if res {
177+ self?.coordinator?.didFinish?()
178+ } else {
179+ self?.displayAlert(with: "Что-то пошло не так!\nПопробуешь еще раз?")
180+ }
181+ })
182+ }
183+
184+ func cancelPrereservation() {
185+ coordinator?.didFinish?()
186+ }
187+}
Hookah Tau/Controllers/Admin/Reservations/AdminReservations.storyboardnew file+132
@@ -0,0 +1,132 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3+ <device id="retina4_7" orientation="portrait" appearance="light"/>
4+ <dependencies>
5+ <deployment identifier="iOS"/>
6+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
7+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
8+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
9+ </dependencies>
10+ <scenes>
11+ <!--Admin Reservations Controller-->
12+ <scene sceneID="3G1-PQ-PoW">
13+ <objects>
14+ <viewController storyboardIdentifier="AdminReservationsController" id="Imn-L1-YIQ" customClass="AdminReservationsController" customModule="Hookah_Tau" customModuleProvider="target" sceneMemberID="viewController">
15+ <view key="view" contentMode="scaleToFill" id="DoI-oD-1Zy">
16+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
17+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
18+ <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
19+ <viewLayoutGuide key="safeArea" id="QBQ-KH-Pid"/>
20+ </view>
21+ </viewController>
22+ <placeholder placeholderIdentifier="IBFirstResponder" id="Rnh-3m-3dv" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
23+ </objects>
24+ <point key="canvasLocation" x="111" y="159"/>
25+ </scene>
26+ <!--Admin Reservation Editing View Controller-->
27+ <scene sceneID="hWe-Nm-jBL">
28+ <objects>
29+ <viewController storyboardIdentifier="AdminReservationEditingViewController" id="BgQ-ca-cuZ" customClass="AdminReservationEditingViewController" customModule="Hookah_Tau" customModuleProvider="target" sceneMemberID="viewController">
30+ <view key="view" contentMode="scaleToFill" id="tjV-EU-XvW">
31+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
32+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
33+ <subviews>
34+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uNU-TW-vfq">
35+ <rect key="frame" x="0.0" y="262" width="375" height="385"/>
36+ <color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
37+ <constraints>
38+ <constraint firstAttribute="height" constant="385" id="wfZ-i9-ruU"/>
39+ </constraints>
40+ </view>
41+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ghQ-Jv-1Uo">
42+ <rect key="frame" x="0.0" y="0.0" width="375" height="254"/>
43+ <color key="backgroundColor" systemColor="systemGreenColor" red="0.20392156859999999" green="0.78039215689999997" blue="0.34901960780000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
44+ </view>
45+ </subviews>
46+ <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
47+ <constraints>
48+ <constraint firstItem="ghQ-Jv-1Uo" firstAttribute="leading" secondItem="qeh-vb-wIH" secondAttribute="leading" id="6ue-fN-8T9"/>
49+ <constraint firstAttribute="bottom" secondItem="uNU-TW-vfq" secondAttribute="bottom" constant="20" symbolic="YES" id="CEP-Ac-DSe"/>
50+ <constraint firstItem="uNU-TW-vfq" firstAttribute="leading" secondItem="qeh-vb-wIH" secondAttribute="leading" id="Ff2-mz-GAH"/>
51+ <constraint firstItem="ghQ-Jv-1Uo" firstAttribute="top" secondItem="qeh-vb-wIH" secondAttribute="top" id="MMC-Og-ggL"/>
52+ <constraint firstItem="uNU-TW-vfq" firstAttribute="trailing" secondItem="qeh-vb-wIH" secondAttribute="trailing" id="fRe-ZZ-rez"/>
53+ <constraint firstItem="ghQ-Jv-1Uo" firstAttribute="trailing" secondItem="qeh-vb-wIH" secondAttribute="trailing" id="hPh-8O-if8"/>
54+ <constraint firstItem="uNU-TW-vfq" firstAttribute="top" secondItem="ghQ-Jv-1Uo" secondAttribute="bottom" constant="8" symbolic="YES" id="rDO-oM-rwy"/>
55+ </constraints>
56+ <viewLayoutGuide key="safeArea" id="qeh-vb-wIH"/>
57+ </view>
58+ <connections>
59+ <outlet property="mainContentView" destination="uNU-TW-vfq" id="Fy6-pC-ach"/>
60+ <outlet property="tableContentView" destination="ghQ-Jv-1Uo" id="GN2-MW-a2J"/>
61+ </connections>
62+ </viewController>
63+ <placeholder placeholderIdentifier="IBFirstResponder" id="LNT-df-eny" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
64+ </objects>
65+ <point key="canvasLocation" x="959.20000000000005" y="158.77061469265368"/>
66+ </scene>
67+ <!--Admin Second Step Reservation View Controller-->
68+ <scene sceneID="VhI-Z9-iqB">
69+ <objects>
70+ <viewController storyboardIdentifier="AdminSecondStepReservationViewController" id="eNJ-Ja-201" customClass="AdminSecondStepReservationViewController" customModule="Hookah_Tau" customModuleProvider="target" sceneMemberID="viewController">
71+ <view key="view" contentMode="scaleToFill" id="7mk-5I-JJs">
72+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
73+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
74+ <subviews>
75+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="oAV-GW-iea">
76+ <rect key="frame" x="0.0" y="0.0" width="375" height="293"/>
77+ <color key="backgroundColor" systemColor="systemIndigoColor" red="0.34509803919999998" green="0.33725490200000002" blue="0.83921568629999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
78+ </view>
79+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="a4v-ng-lm7">
80+ <rect key="frame" x="0.0" y="293" width="375" height="374"/>
81+ <subviews>
82+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="13" translatesAutoresizingMaskIntoConstraints="NO" id="1Bb-cO-4qW">
83+ <rect key="frame" x="20" y="22" width="335" height="88.5"/>
84+ <subviews>
85+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Номер клиента" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="bME-Wr-gh7">
86+ <rect key="frame" x="62.5" y="0.0" width="210.5" height="33.5"/>
87+ <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
88+ <nil key="textColor"/>
89+ <nil key="highlightedColor"/>
90+ </label>
91+ <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="+7 (988) 888-88-88" textAlignment="natural" minimumFontSize="28" translatesAutoresizingMaskIntoConstraints="NO" id="wBC-oa-ekQ" customClass="PhoneTextField" customModule="Hookah_Tau" customModuleProvider="target">
92+ <rect key="frame" x="13.5" y="46.5" width="308.5" height="42"/>
93+ <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
94+ <textInputTraits key="textInputTraits"/>
95+ </textField>
96+ </subviews>
97+ </stackView>
98+ </subviews>
99+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
100+ <constraints>
101+ <constraint firstItem="1Bb-cO-4qW" firstAttribute="top" secondItem="a4v-ng-lm7" secondAttribute="top" constant="22" id="7fb-Up-Wmb"/>
102+ <constraint firstAttribute="trailing" secondItem="1Bb-cO-4qW" secondAttribute="trailing" constant="20" id="934-Ef-0xf"/>
103+ <constraint firstItem="1Bb-cO-4qW" firstAttribute="leading" secondItem="a4v-ng-lm7" secondAttribute="leading" constant="20" id="sr5-aO-q1p"/>
104+ <constraint firstAttribute="height" constant="374" id="uRW-2Q-OyM"/>
105+ </constraints>
106+ </view>
107+ </subviews>
108+ <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
109+ <constraints>
110+ <constraint firstItem="a4v-ng-lm7" firstAttribute="leading" secondItem="8aV-w1-NXE" secondAttribute="leading" id="OjU-Pl-gu1"/>
111+ <constraint firstItem="oAV-GW-iea" firstAttribute="trailing" secondItem="8aV-w1-NXE" secondAttribute="trailing" id="bPj-q1-pY6"/>
112+ <constraint firstItem="oAV-GW-iea" firstAttribute="top" secondItem="8aV-w1-NXE" secondAttribute="top" id="gUc-es-YHL"/>
113+ <constraint firstItem="a4v-ng-lm7" firstAttribute="bottom" secondItem="8aV-w1-NXE" secondAttribute="bottom" id="hZH-fU-zM5"/>
114+ <constraint firstItem="oAV-GW-iea" firstAttribute="leading" secondItem="8aV-w1-NXE" secondAttribute="leading" id="mos-8B-wjS"/>
115+ <constraint firstItem="a4v-ng-lm7" firstAttribute="trailing" secondItem="8aV-w1-NXE" secondAttribute="trailing" id="ntK-1r-AUW"/>
116+ <constraint firstItem="a4v-ng-lm7" firstAttribute="top" secondItem="oAV-GW-iea" secondAttribute="bottom" id="siv-Xa-Nl5"/>
117+ </constraints>
118+ <viewLayoutGuide key="safeArea" id="8aV-w1-NXE"/>
119+ </view>
120+ <connections>
121+ <outlet property="heightConstraint" destination="uRW-2Q-OyM" id="fmn-uI-ooi"/>
122+ <outlet property="mapContainerView" destination="oAV-GW-iea" id="jmP-q0-WbS"/>
123+ <outlet property="phoneContentView" destination="a4v-ng-lm7" id="W1O-A7-6EP"/>
124+ <outlet property="phoneTextField" destination="wBC-oa-ekQ" id="Btg-A3-tpm"/>
125+ </connections>
126+ </viewController>
127+ <placeholder placeholderIdentifier="IBFirstResponder" id="hNg-aQ-v7C" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
128+ </objects>
129+ <point key="canvasLocation" x="1664.8" y="158.77061469265368"/>
130+ </scene>
131+ </scenes>
132+</document>
Hookah Tau/Controllers/Admin/Reservations/List of reservations/AdminReservationsController.swiftnew file+219
@@ -0,0 +1,219 @@
1+//
2+// AdminReservationsController.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 19/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class AdminReservationsController: BaseViewController {
12+
13+ // MARK:- Properties
14+
15+ /// Establishment id
16+ var id: Int?
17+
18+ var tableView: UITableView? = {
19+ let tableView = UITableView()
20+ tableView.backgroundColor = .white
21+ return tableView
22+ }()
23+
24+ var contentView: UIView = {
25+ let view = UIView()
26+ view.translatesAutoresizingMaskIntoConstraints = false
27+ return view
28+ }()
29+
30+ var noReservationsView: AdminNoReservationsView?
31+
32+ var reservationsService: ReservationsService?
33+
34+ weak var coordinator: AdminReservationsCoordinator?
35+
36+ var refrControl: UIRefreshControl?
37+
38+ var activityIndicator: UIActivityIndicatorView?
39+
40+ var activeReservations: [ReservationWithUser] = [] {
41+ didSet {
42+ guard let empty = noReservationsView else { return }
43+ if activeReservations.isEmpty {
44+ contentView.bringSubviewToFront(empty)
45+ empty.alpha = 1
46+ } else {
47+ contentView.sendSubviewToBack(empty)
48+ tableView?.alpha = 1
49+ }
50+ activityIndicator?.stopAnimating()
51+ tableView?.reloadData()
52+ }
53+ }
54+
55+ override func viewDidLoad() {
56+ super.viewDidLoad()
57+
58+ reservationsService = ReservationsService(apiClient: APIClient.shared)
59+ setupRefreshControl()
60+ setUpContentView()
61+ setupNavBar()
62+ }
63+
64+ override func viewDidAppear(_ animated: Bool) {
65+ super.viewDidAppear(animated)
66+
67+ performUpdate()
68+ tabBarController?.tabBar.isHidden = false
69+ }
70+
71+ // MARK:- Setup
72+
73+ func setupNavBar() {
74+ self.navigationItem.title = "Брони"
75+ navigationController?.navigationBar.prefersLargeTitles = true
76+ navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(book))
77+ }
78+
79+ func setUpContentView() {
80+ self.view.addSubviewThatFills(contentView)
81+
82+ activityIndicator = UIActivityIndicatorView()
83+ contentView.addSubviewThatFills(activityIndicator)
84+ activityIndicator?.startAnimating()
85+
86+ // empty
87+ noReservationsView = AdminNoReservationsView.loadFromNib()
88+ contentView.addSubviewThatFills(noReservationsView)
89+ noReservationsView?.bookButton?.addTarget(self, action: #selector(book), for: .touchUpInside)
90+
91+ noReservationsView?.alpha = 0
92+
93+ // table
94+ contentView.addSubviewThatFills(tableView)
95+
96+ setUpTableView()
97+ }
98+
99+ func setUpTableView() {
100+ tableView?.delegate = self
101+ tableView?.dataSource = self
102+
103+ tableView?.alpha = 0
104+
105+ tableView?.register(AdminReservationCell.self, forCellReuseIdentifier: adminReservationCellId)
106+ }
107+
108+ func performUpdate() {
109+
110+ guard let id = id else { return }
111+
112+ reservationsService?.getAdminReservations(establishmentId: id, completion: { [weak self] (result) in
113+ switch result {
114+ case .success(let reservations):
115+ self?.activeReservations = reservations
116+ case .failure(let err):
117+ self?.displayAlert(forError: err)
118+ }
119+ self?.refrControl?.endRefreshing()
120+ })
121+ }
122+
123+ // MARK:- Button handler
124+
125+ @objc func book() {
126+ guard let establishmentId = id else { return }
127+ coordinator?.book(inEstablishment: establishmentId)
128+ }
129+
130+ func deleteReservation(uuid: String, completion: @escaping (([ReservationWithUser]?) -> Void)) {
131+ reservationsService?.deleteReservation(isAdmin: true, uuid: uuid) { result in
132+ if result {
133+ let newReservations = self.activeReservations.filter { $0.uuid != uuid }
134+ completion(newReservations)
135+ return
136+ }
137+
138+ completion(nil)
139+ }
140+ }
141+
142+ // MARK: - Refresh control
143+
144+ @objc func refreshData( _ ff: UIRefreshControl) {
145+ performUpdate()
146+ }
147+
148+ func setupRefreshControl() {
149+ refrControl = UIRefreshControl()
150+ tableView?.refreshControl = refrControl
151+ refrControl?.addTarget(self, action: #selector(refreshData(_:)), for: .valueChanged)
152+ }
153+
154+}
155+
156+
157+// MARK: - Table view data source
158+
159+extension AdminReservationsController: UITableViewDelegate, UITableViewDataSource {
160+
161+ func numberOfSections(in tableView: UITableView) -> Int {
162+ return 1
163+ }
164+
165+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
166+ return activeReservations.count
167+ }
168+
169+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
170+ let cell = tableView.dequeueReusableCell(withIdentifier: adminReservationCellId, for: indexPath) as! AdminReservationCell
171+
172+ cell.bind(withModel: activeReservations[indexPath.row])
173+ return cell
174+ }
175+
176+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
177+ let reservation = activeReservations[indexPath.row]
178+ coordinator?.seeExistingReservation(data: reservation)
179+ }
180+
181+ // Dimesions
182+
183+ func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
184+ return UITableView.automaticDimension
185+ }
186+
187+ func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
188+ return 180.0
189+ }
190+
191+ // Update
192+
193+ func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
194+ return true
195+ }
196+
197+ func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
198+
199+ guard let uuid = activeReservations[indexPath.row].uuid else { return [] }
200+ let cancelButton = UITableViewRowAction(style: .normal, title: "отменить бронь") { _,_ in
201+
202+ self.deleteReservation(uuid: uuid) { optionalNewVal in
203+ if let newval = optionalNewVal {
204+ self.tableView?.beginUpdates()
205+ self.tableView?.deleteRows(at: [indexPath], with: .automatic)
206+ self.activeReservations = newval
207+ self.tableView?.endUpdates()
208+ } else {
209+ self.displayAlert(with: "Не удалось удалить вашу бронь! Попробуйте еще раз!")
210+ }
211+ }
212+ }
213+
214+ cancelButton.backgroundColor = .black
215+
216+ return [cancelButton]
217+ }
218+
219+}
Hookah Tau/Controllers/Admin/Reservations/List of reservations/AdminReservationsCoordinator.swiftnew file+54
@@ -0,0 +1,54 @@
1+//
2+// AdminReservationsCoordinator.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 19/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class AdminReservationsCoordinator: BaseCoordinator {
12+
13+ // MARK: - Properties
14+
15+ var didFinish: (() -> Void)?
16+
17+ var establishmentId: Int?
18+
19+ // MARK: - Lifecycle
20+
21+ override func start() {
22+ let storyboard = UIStoryboard(name: "AdminReservations", bundle: nil)
23+ let vc = storyboard.instantiateViewController(withIdentifier: "AdminReservationsController") as! AdminReservationsController
24+ vc.id = establishmentId
25+ vc.coordinator = self
26+
27+ navigationController?.pushViewController(vc, animated: true)
28+ }
29+
30+ func seeExistingReservation(data: ReservationWithUser) {
31+ let reservationCoordinator = AdminReservationEditingCoordinator(navigationController: navigationController)
32+ addDependency(reservationCoordinator)
33+ reservationCoordinator.reservationData = data
34+ reservationCoordinator.didEndFlow = { [weak self] in
35+ self?.removeDependency(reservationCoordinator)
36+ self?.navigationController?.popViewController(animated: true)
37+ }
38+ reservationCoordinator.start()
39+ }
40+
41+ /// Сюда мы идем после нажатия кнопочки "Забронировать" или плюсика справа сверху
42+ func book(inEstablishment establishmentId: Int) {
43+ let reservationCoordinator = AdminFirstStepReservationViewCoordinator(navigationController: navigationController)
44+ reservationCoordinator.establishment = establishmentId
45+ addDependency(reservationCoordinator)
46+ reservationCoordinator.didFinish = { [weak self] in
47+ self?.navigationController?.popViewController(animated: false)
48+ self?.removeDependency(reservationCoordinator)
49+ self?.didFinish?()
50+ }
51+
52+ reservationCoordinator.start()
53+ }
54+}
Hookah Tau/Controllers/Admin/Reservations/List of reservations/Cell/AdminReservationCellView.swiftnew file+84
@@ -0,0 +1,84 @@
1+//
2+// AdminReservationCell.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 19/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+let adminReservationCellId = "AdminReservationCell"
12+
13+class AdminReservationCell: UITableViewCell {
14+ // MARK: - Properties
15+
16+ var reservationCellView: AdminReservationCellView?
17+
18+ // MARK: - Init
19+
20+ override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?)
21+ {
22+ super.init(style: style, reuseIdentifier: reuseIdentifier)
23+
24+ reservationCellView = AdminReservationCellView.loadFromNib()
25+ contentView.addSubviewThatFills(reservationCellView)
26+
27+ self.selectionStyle = .none
28+ }
29+
30+ required init?(coder aDecoder: NSCoder)
31+ {
32+ fatalError("init(coder:) has not been implemented")
33+ }
34+
35+ // MARK: - Data
36+
37+ func bind(withModel model: ReservationWithUser) {
38+ reservationCellView?.bind(withModel: model)
39+ }
40+}
41+
42+class AdminReservationCellView: UIView {
43+
44+ // MARK:- IBAOutlets
45+
46+ @IBOutlet weak var tableImage: UIImageView?
47+
48+ @IBOutlet weak var guestNumber: UILabel?
49+
50+ @IBOutlet weak var dateLabel: UILabel?
51+
52+ @IBOutlet weak var timeLabel: UILabel?
53+
54+ @IBOutlet weak var personLabel: UILabel?
55+
56+ // MARK: - Lifecycle
57+
58+ override func awakeFromNib() {
59+ super.awakeFromNib()
60+ // Initialization code
61+ }
62+
63+ // MARK: - bind
64+
65+ func bind(withModel model: ReservationWithUser) {
66+ let (date, time) = Date.format(model.startTime, model.endTime)
67+
68+ dateLabel?.text = date
69+ timeLabel?.text = time
70+
71+ guestNumber?.text = setPeople(number: model.numberOfGuests)
72+ personLabel?.text = model.owner.name ?? "❔❔❔"
73+ tableImage?.image = TotalStorage.standard.getTable(establishment: model.establishment, table: model.reservedTable)?.image
74+ }
75+
76+ func setPeople(number: Int) -> String {
77+ switch number {
78+ case 2...4:
79+ return "\(number) человека"
80+ default:
81+ return "\(number) человек"
82+ }
83+ }
84+}
Hookah Tau/Controllers/Admin/Reservations/List of reservations/Cell/AdminReservationCellView.xibnew file+83
@@ -0,0 +1,83 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3+ <device id="retina4_7" orientation="portrait" appearance="light"/>
4+ <dependencies>
5+ <deployment identifier="iOS"/>
6+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
7+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
8+ <capability name="Stack View standard spacing" minToolsVersion="9.0"/>
9+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
10+ </dependencies>
11+ <objects>
12+ <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
13+ <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
14+ <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="AdminReservationCellView" customModule="Hookah_Tau" customModuleProvider="target">
15+ <rect key="frame" x="0.0" y="0.0" width="414" height="180"/>
16+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
17+ <subviews>
18+ <stackView opaque="NO" contentMode="scaleToFill" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="au9-nK-XbR">
19+ <rect key="frame" x="20" y="20" width="374" height="140"/>
20+ <subviews>
21+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" alignment="top" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="rCf-m8-E3w">
22+ <rect key="frame" x="0.0" y="0.0" width="248" height="140"/>
23+ <subviews>
24+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Стасик" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="KV8-UG-2ws">
25+ <rect key="frame" x="0.0" y="0.0" width="54" height="19.5"/>
26+ <fontDescription key="fontDescription" type="system" pointSize="16"/>
27+ <color key="textColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="calibratedRGB"/>
28+ <nil key="highlightedColor"/>
29+ </label>
30+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="nsQ-SE-3vW">
31+ <rect key="frame" x="0.0" y="47" width="102" height="45"/>
32+ <subviews>
33+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="27 марта" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9k3-E5-XKR">
34+ <rect key="frame" x="0.0" y="0.0" width="76.5" height="20.5"/>
35+ <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
36+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
37+ <nil key="highlightedColor"/>
38+ </label>
39+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3:00 — 5:30" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="IdW-5d-DS0">
40+ <rect key="frame" x="0.0" y="24.5" width="102" height="20.5"/>
41+ <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
42+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
43+ <nil key="highlightedColor"/>
44+ </label>
45+ </subviews>
46+ </stackView>
47+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3 человека" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VhL-qH-zM9">
48+ <rect key="frame" x="0.0" y="119.5" width="89" height="20.5"/>
49+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
50+ <nil key="textColor"/>
51+ <nil key="highlightedColor"/>
52+ </label>
53+ </subviews>
54+ </stackView>
55+ <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="1_7" translatesAutoresizingMaskIntoConstraints="NO" id="Oa6-n7-joV">
56+ <rect key="frame" x="256" y="0.0" width="118" height="140"/>
57+ </imageView>
58+ </subviews>
59+ </stackView>
60+ </subviews>
61+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
62+ <constraints>
63+ <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="au9-nK-XbR" secondAttribute="trailing" constant="20" id="Afx-VC-Cb6"/>
64+ <constraint firstItem="au9-nK-XbR" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="20" id="P5E-WG-AYe"/>
65+ <constraint firstItem="au9-nK-XbR" firstAttribute="bottom" secondItem="vUN-kp-3ea" secondAttribute="bottom" constant="-20" id="Z6h-RB-tvj"/>
66+ <constraint firstItem="au9-nK-XbR" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="20" id="fJE-La-qC6"/>
67+ </constraints>
68+ <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
69+ <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
70+ <connections>
71+ <outlet property="dateLabel" destination="9k3-E5-XKR" id="wBe-yq-PN9"/>
72+ <outlet property="guestNumber" destination="VhL-qH-zM9" id="Ent-pM-uER"/>
73+ <outlet property="personLabel" destination="KV8-UG-2ws" id="eYB-qn-fJL"/>
74+ <outlet property="tableImage" destination="Oa6-n7-joV" id="cyD-bY-OpP"/>
75+ <outlet property="timeLabel" destination="IdW-5d-DS0" id="ddO-Dx-LDK"/>
76+ </connections>
77+ <point key="canvasLocation" x="137.59999999999999" y="90.854572713643179"/>
78+ </view>
79+ </objects>
80+ <resources>
81+ <image name="1_7" width="118" height="133"/>
82+ </resources>
83+</document>
Hookah Tau/Controllers/Admin/Reservations/Making a reeservation/1/AdminFirstStepReservationViewCoordinator.swiftnew file+108
@@ -0,0 +1,108 @@
1+//
2+// AdminFirstStepReservationViewCoordinator.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 13/01/2020.
6+// Copyright © 2020 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+enum UserType {
12+ case admin
13+ case client
14+}
15+
16+protocol FirstStepReservationProtocol: BaseCoordinator {
17+
18+ var user: UserType { get }
19+
20+ func makeReservation(model: SecondStepModel, mapView: MapImageScroll?)
21+}
22+
23+class AdminFirstStepReservationViewCoordinator: BaseCoordinator{
24+
25+ // MARK: - Properties
26+
27+ var didFinish: (() -> Void)?
28+
29+ var establishment: Int = 1
30+
31+ var phone: String?
32+
33+ var fullUser: FullUser?
34+
35+ var model: SecondStepModel?
36+
37+ // MARK: - Lifecycle
38+
39+ override func start() {
40+ let storyboard = UIStoryboard(name: "Reservation", bundle: nil)
41+ let reservationVC = storyboard.instantiateViewController(withIdentifier: "ReservationViewController") as! FirstStepReservationViewController
42+
43+ reservationVC.coordinator = self
44+ reservationVC.establishmentId = establishment
45+
46+ navigationController?.pushViewController(reservationVC, animated: true)
47+ }
48+
49+
50+}
51+
52+extension AdminFirstStepReservationViewCoordinator: FirstStepReservationProtocol {
53+
54+ var user: UserType {
55+ get {
56+ return .admin
57+ }
58+ }
59+
60+ func makeReservation(model: SecondStepModel, mapView: MapImageScroll?) {
61+ self.model = model
62+
63+ if let p = phone {
64+ goToFinalStep(with: fullUser, phone: p)
65+ } else {
66+ goToSecondStep(model: model, mapView: mapView)
67+ }
68+ }
69+
70+ func goToFinalStep(with user: FullUser?, phone phoneNumber: String) {
71+ let finalStepCoordinator = AdminFinalStepReservationCoordinator(navigationController: navigationController)
72+ finalStepCoordinator.model = self.model
73+
74+ addDependency(finalStepCoordinator)
75+ finalStepCoordinator.didFinish = { [weak self] in
76+ self?.navigationController?.popViewController(animated: false)
77+ self?.removeDependency(finalStepCoordinator)
78+ self?.didFinish?()
79+ }
80+
81+ if let u = user {
82+ finalStepCoordinator.user = u
83+ } else {
84+ finalStepCoordinator.user = FullUser(uuid: nil,
85+ name: nil,
86+ phoneNumber: phoneNumber,
87+ isAdmin: true)
88+ }
89+
90+ finalStepCoordinator.start()
91+ }
92+
93+ func goToSecondStep(model: SecondStepModel, mapView: MapImageScroll?) {
94+ let reservationCoordinaotor = AdminSecondStepReservationViewCoordinator(navigationController: navigationController)
95+ reservationCoordinaotor.model = model
96+ reservationCoordinaotor.map = mapView
97+
98+ addDependency(reservationCoordinaotor)
99+ reservationCoordinaotor.didFinish = { [weak self] in
100+ self?.navigationController?.popViewController(animated: false)
101+ self?.removeDependency(reservationCoordinaotor)
102+ self?.didFinish?()
103+ }
104+
105+ reservationCoordinaotor.start()
106+ }
107+}
108+
Hookah Tau/Controllers/Admin/Reservations/Making a reeservation/2/AdminSecondStepReservationViewController.swiftnew file+124
@@ -0,0 +1,124 @@
1+//
2+// AdminSecondStepReservationViewController.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 13/01/2020.
6+// Copyright © 2020 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class AdminSecondStepReservationViewController: BaseViewController {
12+
13+ // MARK: - IBAOutlets
14+
15+ @IBOutlet weak var mapContainerView: UIView!
16+
17+ @IBOutlet weak var heightConstraint: NSLayoutConstraint!
18+
19+ @IBOutlet weak var phoneTextField: PhoneTextField!
20+
21+ @IBOutlet weak var phoneContentView: UIView!
22+
23+ // MARK: - Properties
24+
25+ var table: Int?
26+
27+ var mapScrollView: MapImageScroll?
28+
29+ var clientsService: ClientsService?
30+
31+ weak var coordinator: AdminSecondStepReservationViewCoordinator?
32+
33+ let continueButton: Button = {
34+ let button = Button()
35+ let style = BlackButtonStyle()
36+ style.apply(to: button, withTitle: "ДАЛЕЕ")
37+ return button
38+ }()
39+
40+ let cancelButton: Button = {
41+ let button = Button()
42+ let style = BlackButtonStyle()
43+ style.apply(to: button, withTitle: "ОТМЕНИТЬ")
44+ return button
45+ }()
46+
47+ // MARK: - Lifecycle
48+
49+ override func viewDidLoad() {
50+ super.viewDidLoad()
51+
52+ setupMap()
53+ setupButtons()
54+ setUpKeyboard()
55+
56+ clientsService = ClientsService(apiClient: APIClient.shared)
57+ phoneTextField.becomeFirstResponder()
58+ }
59+
60+ // MARK: - Set up
61+
62+ func setupMap() {
63+ guard let t = table else { return }
64+ mapScrollView?.scrollToTable(table: t)
65+ mapContainerView.addSubviewThatFills(mapScrollView)
66+ }
67+
68+ func setupButtons() {
69+ addStackViewWithButtons(leftBtn: cancelButton,
70+ rightBtn: continueButton,
71+ constant: -phoneContentView.frame.height - 20)
72+
73+ cancelButton.addTarget(self, action: #selector(cancel), for: .touchUpInside)
74+ continueButton.addTarget(self, action: #selector(continueBooking), for: .touchUpInside)
75+ }
76+
77+ // MARK: - Keyboad
78+
79+ @objc
80+ override func handleKeyboardNotifications(notification: NSNotification) {
81+ if let userInfo = notification.userInfo {
82+ let keyBoardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
83+ heightConstraint?.constant = notification.name == UIResponder.keyboardWillShowNotification ? keyBoardFrame.height + view.safeAreaInsets.bottom + 120.5 : 120.5
84+ bottomConstraint?.constant = notification.name == UIResponder.keyboardWillShowNotification ? -keyBoardFrame.height + view.safeAreaInsets.bottom - 140.5 : -140.5
85+
86+ UIView.animate(withDuration: 0, delay: 0, options: .curveEaseOut, animations: {
87+ self.view.layoutIfNeeded()
88+ })
89+ }
90+ }
91+
92+ // MARK: - Button handlers
93+
94+ @objc func cancel() {
95+ coordinator?.didEndFlow?()
96+ }
97+
98+ @objc func continueBooking() {
99+ continueButton.loading = true
100+
101+ guard let phone = phoneTextField.text?.toApiPhoneNumberFormat(), phone.count == 9 else {
102+ displayAlert(with: "Сначала укажи номер!")
103+ return
104+ }
105+
106+ clientsService?.getClientByPhoneNumber(userPhone: phone, completion: { [unowned self] (res) in
107+ switch res {
108+ case .failure(let err):
109+ switch err {
110+ case .notFound:
111+ self.coordinator?.continueReservation(with: nil, phone: phone)
112+ default:
113+ self.displayAlert(with: "Что-то пошло не так! Попробуй еще раз :)")
114+ }
115+ case .success(let user):
116+ self.coordinator?.continueReservation(with: user, phone: phone)
117+ }
118+
119+ self.continueButton.loading = false
120+ })
121+ }
122+
123+
124+}
Hookah Tau/Controllers/Admin/Reservations/Making a reeservation/2/AdminSecondStepReservationViewCoordinator.swiftnew file+54
@@ -0,0 +1,54 @@
1+//
2+// AdminSecondStepReservationViewCoordinator.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 13/01/2020.
6+// Copyright © 2020 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class AdminSecondStepReservationViewCoordinator: BaseCoordinator {
12+
13+ // MARK: - Properties
14+
15+ var didFinish: (() -> Void)?
16+
17+ var map: MapImageScroll?
18+
19+ var model: SecondStepModel?
20+
21+ // MARK: - Lifecycle
22+
23+ override func start() {
24+ let storyboard = UIStoryboard(name: "AdminReservations", bundle: nil)
25+ let reservationVC = storyboard.instantiateViewController(withIdentifier: "AdminSecondStepReservationViewController") as! AdminSecondStepReservationViewController
26+ reservationVC.mapScrollView = map
27+ reservationVC.table = model?.table
28+ reservationVC.coordinator = self
29+ navigationController?.pushViewController(reservationVC, animated: true)
30+ }
31+
32+ func continueReservation(with user: FullUser?, phone phoneNumber: String) {
33+ let finalStepCoordinator = AdminFinalStepReservationCoordinator(navigationController: navigationController)
34+ finalStepCoordinator.model = self.model
35+
36+ addDependency(finalStepCoordinator)
37+ finalStepCoordinator.didFinish = { [weak self] in
38+ self?.navigationController?.popViewController(animated: false)
39+ self?.removeDependency(finalStepCoordinator)
40+ self?.didFinish?()
41+ }
42+
43+ if let u = user {
44+ finalStepCoordinator.user = u
45+ } else {
46+ finalStepCoordinator.user = FullUser(uuid: nil,
47+ name: nil,
48+ phoneNumber: phoneNumber,
49+ isAdmin: true)
50+ }
51+
52+ finalStepCoordinator.start()
53+ }
54+}
Hookah Tau/Controllers/Admin/Reservations/Making a reeservation/3/AdminFinalStepReservationCoordinator.swiftnew file+47
@@ -0,0 +1,47 @@
1+//
2+// AdminFinalStepReservationCoordinator.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 14/01/2020.
6+// Copyright © 2020 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+protocol ShowReservationCoordinator: BaseCoordinator {
12+ var didFinish: (() -> Void)? { get }
13+}
14+
15+class AdminFinalStepReservationCoordinator: BaseCoordinator, ShowReservationCoordinator {
16+
17+ // MARK: - Properties
18+
19+ var didFinish: (() -> Void)?
20+
21+ var user: FullUser?
22+
23+ var model: SecondStepModel?
24+
25+ // MARK: - Lifecycle
26+
27+ override func start() {
28+ let storyboard = UIStoryboard(name: "AdminReservations", bundle: nil)
29+ let vc = storyboard.instantiateViewController(withIdentifier: "AdminReservationEditingViewController") as! AdminReservationEditingViewController
30+
31+ guard
32+ let model = model,
33+ let u = user else { return }
34+ vc.reservationData = ReservationWithUser(uuid: nil,
35+ establishment: model.establishment,
36+ startTime: model.startTime,
37+ endTime: model.startTime.addHours(2),
38+ numberOfGuests: 2,
39+ reservedTable: model.table,
40+ owner: u)
41+
42+ vc.reservedPeriods = model.reservedintervals
43+ vc.coordinator = self
44+
45+ navigationController?.pushViewController(vc, animated: true)
46+ }
47+}
Hookah Tau/Controllers/Admin/Reservations/Views/AdminReservationView.swiftnew file+371
@@ -0,0 +1,371 @@
1+//
2+// AdminReservationView.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 19/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class AdminReservationView: UIView {
12+
13+ // MARK:- IBAOutlets
14+
15+ @IBOutlet weak var tableLabel: UILabel?
16+
17+ @IBOutlet weak var dateLabel: UILabel?
18+
19+ @IBOutlet weak var timeLabel: UILabel?
20+
21+ @IBOutlet weak var cancelButton: Button? {
22+ didSet {
23+ let style = BlackButtonStyle()
24+ style.apply(to: cancelButton, withTitle: "ОТМЕНИТЬ")
25+ }
26+ }
27+
28+ @IBOutlet weak var nameLabel: UILabel?
29+
30+ @IBOutlet weak var callButton: Button?
31+
32+ @IBOutlet weak var numberOfGuestLabel: UILabel?
33+
34+ @IBOutlet weak var guestSlider: UISlider?
35+
36+ @IBOutlet weak var intervalsContainerView: UIView?
37+
38+ @IBOutlet weak var scrollView: UIScrollView!
39+
40+ @IBOutlet weak var intervalsStackView: UIStackView!
41+
42+ @IBOutlet weak var saveButton: Button? {
43+ didSet {
44+ let style = BlackButtonStyle()
45+ style.apply(to: saveButton, withTitle: "СОХРАНИТЬ")
46+ }
47+ }
48+
49+ // MARK: - Properties
50+
51+ // констрейнты начала и конца интервала
52+ var startLeadingConstraint: NSLayoutConstraint!
53+ var endLeadingConstraint: NSLayoutConstraint!
54+
55+ var startPoint: UIImageView = {
56+ var view = UIImageView()
57+ view.isUserInteractionEnabled = true
58+ view.image = #imageLiteral(resourceName: "handle_left")
59+ view.translatesAutoresizingMaskIntoConstraints = false
60+ return view
61+ }()
62+
63+ var endPoint: UIImageView = {
64+ var view = UIImageView()
65+ view.isUserInteractionEnabled = true
66+ view.image = #imageLiteral(resourceName: "Group")
67+ view.translatesAutoresizingMaskIntoConstraints = false
68+ return view
69+ }()
70+
71+ /// Интервал
72+ var fillInterval: UIView = {
73+ var view = UIView()
74+ view.backgroundColor = #colorLiteral(red: 0, green: 0.5921568627, blue: 1, alpha: 0.3426262843)
75+ view.translatesAutoresizingMaskIntoConstraints = false
76+ return view
77+ }()
78+
79+ /// Время начала вертелки.
80+ /// Для клиента это 12:00 смены (надо вызвать метод для получения текущей смены).
81+ /// Для администратора это `t` - 24h
82+ var startDate: Date?
83+
84+ // MARK:- Properties
85+
86+ var intervalPickerView: IntervalPickerView?
87+
88+ /// Модель вьюшки.
89+ /// Обновляем каждый раз когда меняется время и дата
90+ var data: ReservationData?
91+
92+ var model: ReservationWithUser?
93+
94+ // MARK: - Setup
95+
96+ func bind(withModel model: ReservationWithUser) {
97+ let style = BlackButtonStyle()
98+ style.apply(to: callButton, withTitle: model.owner.phoneNumber.formattedNumber())
99+
100+
101+ let (date, time) = Date.format(model.startTime, model.endTime)
102+ dateLabel?.text = date
103+ timeLabel?.text = time
104+
105+ tableLabel?.text = "\(model.reservedTable) столик"
106+ nameLabel?.text = "\(model.owner.name ?? "❔")"
107+
108+ data = ReservationData(establishment: model.establishment,
109+ startTime: model.startTime,
110+ endTime: model.endTime,
111+ numberOfGuests: model.numberOfGuests,
112+ reservedTable: model.reservedTable)
113+ self.model = model
114+
115+ let (start, _) = model.owner.isAdmin ? model.startTime.getAdminStartingAndEndPoint() : model.startTime.getClientStartingAndEndPoint()
116+
117+ startDate = start
118+
119+ setUpIntervals()
120+
121+ guestSlider?.maximumValue = Float((TotalStorage.standard.getTable(establishment: model.establishment, table: model.reservedTable)?.maxGuestNumber ?? 2) + 1)
122+ }
123+
124+ func bind(withIntervals intervals: [ReservationPeriod]) {
125+ guard
126+ let data = data,
127+ let isAdmin = model?.owner.isAdmin
128+ else { return }
129+
130+ setUpReservedIntervals(intervals, isAdmin)
131+ setUpBookingPeriod(data.startTime, data.endTime)
132+ }
133+
134+ func updateDateAndTime(withData d: ReservationData?) {
135+ guard let d = d else { return }
136+ let (date, time) = Date.format(d.startTime, d.endTime)
137+
138+ dateLabel?.text = date
139+ timeLabel?.text = time
140+ }
141+
142+ @IBAction func valueChanged(_ sender: UISlider) {
143+ let value = Int(sender.value)
144+
145+ if sender.value == sender.maximumValue {
146+ numberOfGuestLabel?.text = "Количество гостей: \(Int(value) - 1)+"
147+ } else {
148+ numberOfGuestLabel?.text = "Количество гостей: \(value)"
149+ }
150+
151+ guard let d = data else { return }
152+
153+ data = ReservationData(establishment: d.establishment,
154+ startTime: d.startTime,
155+ endTime: d.endTime,
156+ numberOfGuests: value,
157+ reservedTable: d.reservedTable)
158+ }
159+
160+
161+ func getNumberOfCalians(_ sliderValue: Int) -> String {
162+ return "Минимальное количество кальянов: \(sliderValue > 6 ? 2 : 1)"
163+ }
164+
165+ // MARK: - Setup
166+
167+ /// Тут происходит установка интервалов. Разное для клиента и админа
168+ func setUpIntervals() {
169+ guard let startPoint = startDate else { return }
170+
171+ let startingPoint = startPoint.getAdminsStartingPoint()
172+ let endingPoint = Constants.adminDuration
173+
174+ for period in startingPoint...endingPoint {
175+ let timePointView = TimePointView.loadFromNib()
176+ timePointView?.setUp(withTimePoint: period)
177+ let view = UIView(frame: CGRect(x: 0, y: 0, width: 35, height: 106))
178+ view.addSubviewThatFills(timePointView)
179+ intervalsStackView.addArrangedSubview(view)
180+ }
181+ }
182+
183+
184+ private func setUpBookingPeriod(_ startTime: Date, _ endTime: Date? = nil) {
185+ self.scrollView.addSubview(startPoint)
186+ self.scrollView.addSubview(endPoint)
187+ self.scrollView.addSubview(fillInterval)
188+
189+ guard let startDate = startDate else { return }
190+
191+ let startConstr = CGFloat(startTime.getMinutesPeriods(fromStart: startDate) * Constants.widthTimePoint)
192+
193+ scrollView.setContentOffset(CGPoint(x: startConstr - 50, y: 0), animated: true)
194+
195+ startLeadingConstraint = startPoint.leftAnchor.constraint(equalTo: scrollView.leftAnchor,
196+ constant: startConstr)
197+
198+ if let end = endTime {
199+ let endConstr = CGFloat(end.getMinutesPeriods(fromStart: startDate) * Constants.widthTimePoint)
200+ endLeadingConstraint = endPoint.leftAnchor.constraint(equalTo: scrollView.leftAnchor,
201+ constant: endConstr)
202+ } else {
203+ endLeadingConstraint = endPoint.leftAnchor.constraint(equalTo: scrollView.leftAnchor,
204+ constant:startConstr + Constants.minBookingLength)
205+ }
206+
207+ NSLayoutConstraint.activate([
208+
209+ startPoint.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0),
210+ endPoint.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0),
211+
212+ startPoint.widthAnchor.constraint(equalToConstant: Constants.pointWidth),
213+ endPoint.widthAnchor.constraint(equalToConstant: Constants.pointWidth),
214+
215+ startPoint.heightAnchor.constraint(equalToConstant: Constants.pointHeight),
216+ endPoint.heightAnchor.constraint(equalToConstant: Constants.pointHeight),
217+
218+ startLeadingConstraint,
219+ endLeadingConstraint,
220+
221+ fillInterval.leftAnchor.constraint(equalTo: startPoint.rightAnchor, constant: 0),
222+ fillInterval.rightAnchor.constraint(equalTo: endPoint.leftAnchor, constant: 0),
223+
224+ fillInterval.heightAnchor.constraint(equalToConstant: Constants.pointHeight),
225+ fillInterval.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0)
226+ ])
227+
228+ let scrollViewPanStartGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanStart(recognizer:)))
229+ startPoint.addGestureRecognizer(scrollViewPanStartGesture)
230+
231+ let scrollViewPanEndGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanEnd(recognizer:)))
232+ endPoint.addGestureRecognizer(scrollViewPanEndGesture)
233+ }
234+
235+ func setUpReservedIntervals(_ reservations: [ReservationPeriod], _ isAdmin: Bool) {
236+
237+ for reservation in reservations {
238+ let uiview = UIView(frame: CGRect(x: 0, y: 0, width: Constants.widthTimePoint * reservation.duration, height: Constants.heightIntervals))
239+ uiview.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 0.5)
240+
241+
242+ uiview.translatesAutoresizingMaskIntoConstraints = false
243+ scrollView.addSubview(uiview)
244+
245+ guard let startDate = startDate else { return }
246+
247+ let s = reservation.startTime.getMinutesPeriods(fromStart: startDate) * Constants.widthTimePoint
248+ let d = reservation.duration * Constants.widthTimePoint
249+
250+ let startTime = s < 0 ? 0 : s
251+
252+ let total = (isAdmin ? Constants.adminDuration : Constants.clientDuration) * Constants.widthTimePoint
253+ var duration = 0
254+ if s < 0 {
255+ duration = reservation.endTime.getMinutesPeriods(fromStart: startDate) * Constants.widthTimePoint
256+ } else {
257+ duration = (s + d) > total ? total - s : d
258+ }
259+
260+ NSLayoutConstraint.activate([
261+ uiview.bottomAnchor.constraint(equalToSystemSpacingBelow: scrollView.bottomAnchor, multiplier: 0),
262+ uiview.leftAnchor.constraint(equalTo: scrollView.leftAnchor,
263+ constant: CGFloat(Constants.widthTimePoint / 2 + startTime)),
264+ uiview.widthAnchor.constraint(equalToConstant: CGFloat(duration)),
265+ uiview.heightAnchor.constraint(equalToConstant: CGFloat(Constants.heightIntervals))
266+ ])
267+ }
268+ }
269+
270+ // MARK: - Pan
271+
272+ @objc func handlePanStart(recognizer:UIPanGestureRecognizer) {
273+
274+ let translation = recognizer.translation(in: self.intervalsStackView)
275+ let duration = abs(startLeadingConstraint.constant + translation.x - endLeadingConstraint.constant)
276+
277+ if let view = recognizer.view, duration >= Constants.minBookingLength {
278+ startLeadingConstraint.constant += translation.x
279+
280+ changeTime(startLeadingConstraint, .start)
281+
282+ view.setNeedsUpdateConstraints()
283+ }
284+
285+ recognizer.setTranslation(CGPoint.zero, in: self.intervalsStackView)
286+ }
287+
288+ @objc func handlePanEnd(recognizer:UIPanGestureRecognizer) {
289+
290+ let translation = recognizer.translation(in: self.intervalsStackView)
291+ let duration = abs(startLeadingConstraint.constant - (endLeadingConstraint.constant + translation.x))
292+
293+ if let view = recognizer.view, duration >= Constants.minBookingLength {
294+ endLeadingConstraint.constant += translation.x
295+
296+ changeTime(endLeadingConstraint, .end)
297+ view.setNeedsUpdateConstraints()
298+ }
299+
300+ recognizer.setTranslation(CGPoint.zero, in: self.intervalsStackView)
301+ }
302+
303+ func changeTime(_ constraint: NSLayoutConstraint, _ period: Period) {
304+ guard let startDate = startDate, let d = data else { return }
305+
306+ let offset = Int(constraint.constant) % Constants.widthTimePoint
307+
308+ if offset == 0 {
309+ let p = Int(constraint.constant) / Constants.widthTimePoint
310+ let time = startDate.addPeriods(p)
311+
312+ switch period {
313+ case .start:
314+ data = ReservationData(establishment: d.establishment,
315+ startTime: time,
316+ endTime: d.endTime,
317+ numberOfGuests: d.numberOfGuests,
318+ reservedTable: d.reservedTable)
319+ case .end:
320+ data = ReservationData(establishment: d.establishment,
321+ startTime: d.startTime,
322+ endTime: time,
323+ numberOfGuests: d.numberOfGuests,
324+ reservedTable: d.reservedTable)
325+ }
326+
327+ updateDateAndTime(withData: data)
328+ }
329+ }
330+}
331+
332+// MARK: - Nested types
333+
334+extension AdminReservationView {
335+ enum Constants {
336+ static let widthTimePoint = 11
337+
338+ static let adminDuration = 288
339+ static let clientDuration = 90
340+ // 12:00 = 12 * 6
341+ static let clientStartingPoint = 72
342+
343+ static let pointWidth: CGFloat = 19
344+ static let pointHeight: CGFloat = 85
345+
346+ static let minBookingLength: CGFloat = 132
347+
348+ static let heightIntervals = 85
349+ }
350+
351+ enum Period {
352+ case start
353+ case end
354+ }
355+}
356+
357+
358+extension AdminReservationView: PickerViewUpdater {
359+
360+ func update(withModel model: IntervalPickerModel) {
361+ guard let d = data else { return }
362+
363+ data = ReservationData(establishment: d.establishment,
364+ startTime: model.startTime,
365+ endTime: model.endTime,
366+ numberOfGuests: d.numberOfGuests,
367+ reservedTable: d.reservedTable)
368+
369+ updateDateAndTime(withData: data)
370+ }
371+}
Hookah Tau/Controllers/Admin/Reservations/Views/AdminReservationView.xibnew file+223
@@ -0,0 +1,223 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3+ <device id="retina4_7" orientation="portrait" appearance="light"/>
4+ <dependencies>
5+ <deployment identifier="iOS"/>
6+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
7+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
8+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
9+ </dependencies>
10+ <objects>
11+ <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
12+ <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
13+ <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="AdminReservationView" customModule="Hookah_Tau" customModuleProvider="target">
14+ <rect key="frame" x="0.0" y="0.0" width="414" height="385"/>
15+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
16+ <subviews>
17+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="24" translatesAutoresizingMaskIntoConstraints="NO" id="AEf-R4-K1F">
18+ <rect key="frame" x="0.0" y="16" width="414" height="349"/>
19+ <subviews>
20+ <stackView opaque="NO" contentMode="scaleToFill" alignment="top" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="oh7-ka-ctS">
21+ <rect key="frame" x="21" y="0.0" width="372" height="136.5"/>
22+ <subviews>
23+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="22" translatesAutoresizingMaskIntoConstraints="NO" id="IY6-F4-aln">
24+ <rect key="frame" x="0.0" y="0.0" width="129" height="136.5"/>
25+ <subviews>
26+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="5" translatesAutoresizingMaskIntoConstraints="NO" id="hEV-kW-bBH">
27+ <rect key="frame" x="0.0" y="0.0" width="129" height="77.5"/>
28+ <subviews>
29+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="7 столик" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qNR-EO-oMK">
30+ <rect key="frame" x="0.0" y="0.0" width="93" height="26.5"/>
31+ <fontDescription key="fontDescription" type="boldSystem" pointSize="22"/>
32+ <nil key="textColor"/>
33+ <nil key="highlightedColor"/>
34+ </label>
35+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="29 марта" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="KHM-Yj-bBa">
36+ <rect key="frame" x="0.0" y="31.5" width="78" height="20.5"/>
37+ <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
38+ <nil key="textColor"/>
39+ <nil key="highlightedColor"/>
40+ </label>
41+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3:00 — 5:00ffff" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gau-aS-vtT">
42+ <rect key="frame" x="0.0" y="57" width="129" height="20.5"/>
43+ <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
44+ <nil key="textColor"/>
45+ <nil key="highlightedColor"/>
46+ </label>
47+ </subviews>
48+ <constraints>
49+ <constraint firstAttribute="trailing" secondItem="gau-aS-vtT" secondAttribute="trailing" id="OYu-7e-8pX"/>
50+ <constraint firstItem="gau-aS-vtT" firstAttribute="leading" secondItem="hEV-kW-bBH" secondAttribute="leading" id="egK-i8-r2N"/>
51+ </constraints>
52+ </stackView>
53+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="iiD-uf-IDp" customClass="Button" customModule="Hookah_Tau" customModuleProvider="target">
54+ <rect key="frame" x="0.0" y="99.5" width="109" height="37"/>
55+ <color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
56+ <constraints>
57+ <constraint firstAttribute="width" constant="109" id="11r-IB-3r0"/>
58+ <constraint firstAttribute="height" constant="37" id="aej-Xt-jCJ"/>
59+ </constraints>
60+ <state key="normal" title="Button"/>
61+ </button>
62+ </subviews>
63+ <constraints>
64+ <constraint firstAttribute="trailing" secondItem="hEV-kW-bBH" secondAttribute="trailing" id="7MI-YT-uHi"/>
65+ <constraint firstAttribute="width" constant="129" id="BQh-sd-jbL"/>
66+ <constraint firstItem="hEV-kW-bBH" firstAttribute="leading" secondItem="IY6-F4-aln" secondAttribute="leading" id="tsb-PN-YGB"/>
67+ </constraints>
68+ </stackView>
69+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" alignment="bottom" spacing="28" translatesAutoresizingMaskIntoConstraints="NO" id="Kjb-P4-v2B">
70+ <rect key="frame" x="133" y="0.0" width="239" height="136.5"/>
71+ <subviews>
72+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="bottom" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="59c-nG-5Fh">
73+ <rect key="frame" x="65" y="0.0" width="174" height="71.5"/>
74+ <subviews>
75+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Стасик" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OoG-F4-OVu">
76+ <rect key="frame" x="95.5" y="0.0" width="78.5" height="26.5"/>
77+ <fontDescription key="fontDescription" type="boldSystem" pointSize="22"/>
78+ <nil key="textColor"/>
79+ <nil key="highlightedColor"/>
80+ </label>
81+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Xkd-xQ-cFd" customClass="Button" customModule="Hookah_Tau" customModuleProvider="target">
82+ <rect key="frame" x="0.0" y="34.5" width="174" height="37"/>
83+ <color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
84+ <constraints>
85+ <constraint firstAttribute="height" constant="37" id="7Wj-WF-xzD"/>
86+ <constraint firstAttribute="width" constant="174" id="ZYo-ko-rwn"/>
87+ </constraints>
88+ <state key="normal" title="Button"/>
89+ </button>
90+ </subviews>
91+ </stackView>
92+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="npC-AF-3pj" customClass="Button" customModule="Hookah_Tau" customModuleProvider="target">
93+ <rect key="frame" x="103" y="99.5" width="136" height="37"/>
94+ <color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
95+ <constraints>
96+ <constraint firstAttribute="width" constant="136" id="jg5-lP-Ekd"/>
97+ <constraint firstAttribute="height" constant="37" id="w98-8a-pmH"/>
98+ </constraints>
99+ <state key="normal" title="Button"/>
100+ </button>
101+ </subviews>
102+ </stackView>
103+ </subviews>
104+ </stackView>
105+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="9RO-aK-yNh">
106+ <rect key="frame" x="21" y="160.5" width="372" height="58.5"/>
107+ <subviews>
108+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Количество гостей: 2" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="yTM-U5-XjI">
109+ <rect key="frame" x="0.0" y="0.0" width="372" height="20.5"/>
110+ <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
111+ <nil key="textColor"/>
112+ <nil key="highlightedColor"/>
113+ </label>
114+ <slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="2" minValue="1" maxValue="8" translatesAutoresizingMaskIntoConstraints="NO" id="Lqw-9Z-34d">
115+ <rect key="frame" x="-2" y="28.5" width="376" height="31"/>
116+ <connections>
117+ <action selector="valueChanged:" destination="iN0-l3-epB" eventType="valueChanged" id="OwE-Nq-PHY"/>
118+ </connections>
119+ </slider>
120+ </subviews>
121+ </stackView>
122+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="vKS-ia-RxW">
123+ <rect key="frame" x="0.0" y="243" width="414" height="106"/>
124+ <subviews>
125+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="EiP-QO-PbU">
126+ <rect key="frame" x="0.0" y="20" width="414" height="85"/>
127+ <subviews>
128+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="OpX-io-LgH">
129+ <rect key="frame" x="0.0" y="0.0" width="414" height="1"/>
130+ <color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
131+ <constraints>
132+ <constraint firstAttribute="height" constant="1" id="k2D-mj-31b"/>
133+ </constraints>
134+ </view>
135+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fOe-cM-riJ">
136+ <rect key="frame" x="0.0" y="84" width="414" height="1"/>
137+ <color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
138+ <constraints>
139+ <constraint firstAttribute="height" constant="1" id="A2B-su-3Hi"/>
140+ </constraints>
141+ </view>
142+ </subviews>
143+ <color key="backgroundColor" red="0.8901960784313725" green="0.8901960784313725" blue="0.8901960784313725" alpha="0.6951519692" colorSpace="calibratedRGB"/>
144+ <constraints>
145+ <constraint firstItem="OpX-io-LgH" firstAttribute="top" secondItem="EiP-QO-PbU" secondAttribute="top" id="73d-6U-4Jc"/>
146+ <constraint firstAttribute="height" constant="85" id="JMc-tA-wHe"/>
147+ <constraint firstAttribute="trailing" secondItem="fOe-cM-riJ" secondAttribute="trailing" id="JVQ-ub-pEy"/>
148+ <constraint firstItem="OpX-io-LgH" firstAttribute="leading" secondItem="EiP-QO-PbU" secondAttribute="leading" id="hBd-hJ-yz7"/>
149+ <constraint firstAttribute="bottom" secondItem="fOe-cM-riJ" secondAttribute="bottom" id="im0-y1-nC8"/>
150+ <constraint firstItem="fOe-cM-riJ" firstAttribute="leading" secondItem="EiP-QO-PbU" secondAttribute="leading" id="qH9-Ww-nd5"/>
151+ <constraint firstAttribute="trailing" secondItem="OpX-io-LgH" secondAttribute="trailing" id="ueR-ZM-ELi"/>
152+ </constraints>
153+ </view>
154+ <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="trF-VM-0qV">
155+ <rect key="frame" x="0.0" y="0.0" width="414" height="106"/>
156+ <subviews>
157+ <stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="bpJ-pe-tQf">
158+ <rect key="frame" x="0.0" y="0.0" width="414" height="106"/>
159+ </stackView>
160+ </subviews>
161+ <constraints>
162+ <constraint firstAttribute="bottom" secondItem="bpJ-pe-tQf" secondAttribute="bottom" id="BxY-h0-LHi"/>
163+ <constraint firstItem="bpJ-pe-tQf" firstAttribute="leading" secondItem="trF-VM-0qV" secondAttribute="leading" id="Ec5-Lb-NsX"/>
164+ <constraint firstAttribute="trailing" secondItem="bpJ-pe-tQf" secondAttribute="trailing" id="d0a-8z-CE6"/>
165+ <constraint firstItem="bpJ-pe-tQf" firstAttribute="width" secondItem="trF-VM-0qV" secondAttribute="width" priority="250" id="mYw-sK-G8B"/>
166+ <constraint firstItem="bpJ-pe-tQf" firstAttribute="height" secondItem="trF-VM-0qV" secondAttribute="height" id="wdB-oa-hvt"/>
167+ <constraint firstItem="bpJ-pe-tQf" firstAttribute="top" secondItem="trF-VM-0qV" secondAttribute="top" id="x6e-gw-nXA"/>
168+ </constraints>
169+ <viewLayoutGuide key="contentLayoutGuide" id="QAB-NG-eb4"/>
170+ <viewLayoutGuide key="frameLayoutGuide" id="xUW-w2-Jj1"/>
171+ </scrollView>
172+ </subviews>
173+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
174+ <constraints>
175+ <constraint firstAttribute="bottom" secondItem="trF-VM-0qV" secondAttribute="bottom" id="4kT-64-W5t"/>
176+ <constraint firstItem="trF-VM-0qV" firstAttribute="top" secondItem="vKS-ia-RxW" secondAttribute="top" id="M4X-6f-0WX"/>
177+ <constraint firstItem="trF-VM-0qV" firstAttribute="leading" secondItem="vKS-ia-RxW" secondAttribute="leading" id="ZXK-Vm-o6S"/>
178+ <constraint firstAttribute="trailing" secondItem="trF-VM-0qV" secondAttribute="trailing" id="a2R-kF-NcS"/>
179+ <constraint firstAttribute="height" constant="106" id="ebp-l7-k9Z"/>
180+ <constraint firstItem="EiP-QO-PbU" firstAttribute="leading" secondItem="vKS-ia-RxW" secondAttribute="leading" id="fHc-26-5lu"/>
181+ <constraint firstAttribute="bottom" secondItem="EiP-QO-PbU" secondAttribute="bottom" constant="1" id="kxj-VY-srA"/>
182+ <constraint firstAttribute="trailing" secondItem="EiP-QO-PbU" secondAttribute="trailing" id="sbQ-sE-GAh"/>
183+ </constraints>
184+ </view>
185+ </subviews>
186+ <constraints>
187+ <constraint firstItem="vKS-ia-RxW" firstAttribute="leading" secondItem="AEf-R4-K1F" secondAttribute="leading" id="0cO-AV-PJL"/>
188+ <constraint firstAttribute="trailing" secondItem="oh7-ka-ctS" secondAttribute="trailing" constant="21" id="KeE-6F-h5x"/>
189+ <constraint firstAttribute="trailing" secondItem="9RO-aK-yNh" secondAttribute="trailing" constant="21" id="Kh6-4l-QJO"/>
190+ <constraint firstAttribute="bottom" secondItem="vKS-ia-RxW" secondAttribute="bottom" id="TKW-xo-b61"/>
191+ <constraint firstItem="9RO-aK-yNh" firstAttribute="leading" secondItem="AEf-R4-K1F" secondAttribute="leading" constant="21" id="Teg-kp-nZt"/>
192+ <constraint firstItem="oh7-ka-ctS" firstAttribute="leading" secondItem="AEf-R4-K1F" secondAttribute="leading" constant="21" id="mJ2-gh-vCg"/>
193+ <constraint firstAttribute="trailing" secondItem="vKS-ia-RxW" secondAttribute="trailing" id="uDV-uM-jby"/>
194+ </constraints>
195+ </stackView>
196+ </subviews>
197+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
198+ <constraints>
199+ <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="AEf-R4-K1F" secondAttribute="trailing" id="C1h-bA-S6p"/>
200+ <constraint firstItem="AEf-R4-K1F" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" id="H1o-ua-Tnn"/>
201+ <constraint firstAttribute="top" secondItem="AEf-R4-K1F" secondAttribute="top" constant="-16" id="eYI-is-YLt"/>
202+ <constraint firstAttribute="bottom" secondItem="AEf-R4-K1F" secondAttribute="bottom" constant="20" id="fdG-zZ-LeV"/>
203+ </constraints>
204+ <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
205+ <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
206+ <connections>
207+ <outlet property="callButton" destination="Xkd-xQ-cFd" id="B3w-gf-Yqg"/>
208+ <outlet property="cancelButton" destination="iiD-uf-IDp" id="zwd-eJ-GsP"/>
209+ <outlet property="dateLabel" destination="KHM-Yj-bBa" id="iS8-eN-OFW"/>
210+ <outlet property="guestSlider" destination="Lqw-9Z-34d" id="cEj-JF-J4D"/>
211+ <outlet property="intervalsContainerView" destination="vKS-ia-RxW" id="0Ig-rh-hXD"/>
212+ <outlet property="intervalsStackView" destination="bpJ-pe-tQf" id="aF0-Qm-kuh"/>
213+ <outlet property="nameLabel" destination="OoG-F4-OVu" id="rnW-fG-jFs"/>
214+ <outlet property="numberOfGuestLabel" destination="yTM-U5-XjI" id="z0O-94-Mp7"/>
215+ <outlet property="saveButton" destination="npC-AF-3pj" id="Rfm-Zl-zES"/>
216+ <outlet property="scrollView" destination="trF-VM-0qV" id="P5v-fg-hUW"/>
217+ <outlet property="tableLabel" destination="qNR-EO-oMK" id="63P-AS-fgf"/>
218+ <outlet property="timeLabel" destination="gau-aS-vtT" id="7Iy-Cz-zdY"/>
219+ </connections>
220+ <point key="canvasLocation" x="137.59999999999999" y="95.802098950524751"/>
221+ </view>
222+ </objects>
223+</document>
Hookah Tau/Controllers/Admin/Reservations/Views/No reserv/AdminNoReservationsView.swiftnew file+27
@@ -0,0 +1,27 @@
1+//
2+// AdminNoReservationsView.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 19/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class AdminNoReservationsView: UIView {
12+
13+ // MARK: - IBAOutlets
14+
15+ @IBOutlet weak var titleLabel: UILabel?
16+
17+ @IBOutlet weak var bookButton: Button? {
18+ didSet {
19+ let style = BlackButtonStyle()
20+ style.apply(to: bookButton, withTitle: "ЗАБРОНИРОВАТЬ")
21+ }
22+ }
23+
24+ func setup(withTitle title: String) {
25+ titleLabel?.text = title
26+ }
27+}
Hookah Tau/Controllers/Admin/Reservations/Views/No reserv/AdminNoReservationsView.xibnew file+54
@@ -0,0 +1,54 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3+ <device id="retina6_1" orientation="portrait" appearance="light"/>
4+ <dependencies>
5+ <deployment identifier="iOS"/>
6+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
7+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
8+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
9+ </dependencies>
10+ <objects>
11+ <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
12+ <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
13+ <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="AdminNoReservationsView" customModule="Hookah_Tau" customModuleProvider="target">
14+ <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
15+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
16+ <subviews>
17+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="46" translatesAutoresizingMaskIntoConstraints="NO" id="Ytd-08-vB9">
18+ <rect key="frame" x="107" y="382" width="200" height="142"/>
19+ <subviews>
20+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="В вашем заведении пока не забронирован ни один столик :(" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Svd-DB-3Zu">
21+ <rect key="frame" x="4" y="0.0" width="192" height="61"/>
22+ <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
23+ <nil key="textColor"/>
24+ <nil key="highlightedColor"/>
25+ </label>
26+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="y3F-ua-mLb" customClass="Button" customModule="Hookah_Tau" customModuleProvider="target">
27+ <rect key="frame" x="12.5" y="107" width="175" height="35"/>
28+ <color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
29+ <constraints>
30+ <constraint firstAttribute="width" constant="175" id="LnY-Iu-4e6"/>
31+ <constraint firstAttribute="height" constant="35" id="oN3-oc-fHx"/>
32+ </constraints>
33+ <state key="normal" title="Button"/>
34+ </button>
35+ </subviews>
36+ <constraints>
37+ <constraint firstAttribute="width" constant="200" id="pgG-jg-faG"/>
38+ </constraints>
39+ </stackView>
40+ </subviews>
41+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
42+ <constraints>
43+ <constraint firstItem="Ytd-08-vB9" firstAttribute="centerY" secondItem="vUN-kp-3ea" secondAttribute="centerY" id="ewL-z8-YVD"/>
44+ <constraint firstItem="Ytd-08-vB9" firstAttribute="centerX" secondItem="vUN-kp-3ea" secondAttribute="centerX" id="txy-j9-mF1"/>
45+ </constraints>
46+ <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
47+ <connections>
48+ <outlet property="bookButton" destination="y3F-ua-mLb" id="aC4-gz-Gqd"/>
49+ <outlet property="titleLabel" destination="Svd-DB-3Zu" id="n8Z-Wq-vzC"/>
50+ </connections>
51+ <point key="canvasLocation" x="137.68115942028987" y="91.741071428571431"/>
52+ </view>
53+ </objects>
54+</document>
Hookah Tau/Controllers/Admin/Tab bar/Admin.storyboardnew file+84
@@ -0,0 +1,84 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3+ <device id="retina6_1" orientation="portrait" appearance="light"/>
4+ <dependencies>
5+ <deployment identifier="iOS"/>
6+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
7+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
8+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
9+ </dependencies>
10+ <scenes>
11+ <!--Клиенты-->
12+ <scene sceneID="Q3i-Bo-7ns">
13+ <objects>
14+ <viewController id="Ifn-72-mgx" customClass="UINavigationController" sceneMemberID="viewController">
15+ <view key="view" contentMode="scaleToFill" id="R2t-qa-IDx">
16+ <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
17+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
18+ <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
19+ <viewLayoutGuide key="safeArea" id="yHG-bf-ndj"/>
20+ </view>
21+ <tabBarItem key="tabBarItem" title="Клиенты" image="peoplee" id="9K1-uc-72H"/>
22+ </viewController>
23+ <placeholder placeholderIdentifier="IBFirstResponder" id="8X9-ca-bCi" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
24+ </objects>
25+ <point key="canvasLocation" x="1061" y="-327"/>
26+ </scene>
27+ <!--Профиль-->
28+ <scene sceneID="cAb-sw-24k">
29+ <objects>
30+ <viewController id="Jsm-cH-XbJ" customClass="UINavigationController" sceneMemberID="viewController">
31+ <view key="view" contentMode="scaleToFill" id="s24-jW-vhb">
32+ <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
33+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
34+ <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
35+ <viewLayoutGuide key="safeArea" id="f96-UV-eoi"/>
36+ </view>
37+ <tabBarItem key="tabBarItem" title="Профиль" image="person" id="ziF-T0-lg1"/>
38+ </viewController>
39+ <placeholder placeholderIdentifier="IBFirstResponder" id="qqi-vY-Mog" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
40+ </objects>
41+ <point key="canvasLocation" x="1061" y="433"/>
42+ </scene>
43+ <!--Брони-->
44+ <scene sceneID="p6D-Gs-DUK">
45+ <objects>
46+ <viewController id="Tc3-UW-Fwi" customClass="UINavigationController" sceneMemberID="viewController">
47+ <view key="view" contentMode="scaleToFill" id="dmc-GR-czf">
48+ <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
49+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
50+ <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
51+ <viewLayoutGuide key="safeArea" id="z8c-Ub-USh"/>
52+ </view>
53+ <tabBarItem key="tabBarItem" title="Брони" image="stack" id="szW-oN-ixR"/>
54+ </viewController>
55+ <placeholder placeholderIdentifier="IBFirstResponder" id="1ST-zr-4Zs" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
56+ </objects>
57+ <point key="canvasLocation" x="1062" y="-1047"/>
58+ </scene>
59+ <!--Admin Tabbar Controller-->
60+ <scene sceneID="cWI-V1-4ZB">
61+ <objects>
62+ <tabBarController storyboardIdentifier="AdminTabbarController" id="mgG-ir-mFF" customClass="AdminTabbarController" customModule="Hookah_Tau" customModuleProvider="target" sceneMemberID="viewController">
63+ <tabBar key="tabBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="src-m7-O6h">
64+ <rect key="frame" x="0.0" y="0.0" width="414" height="49"/>
65+ <autoresizingMask key="autoresizingMask"/>
66+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
67+ </tabBar>
68+ <connections>
69+ <segue destination="Tc3-UW-Fwi" kind="relationship" relationship="viewControllers" id="K4a-6A-WdW"/>
70+ <segue destination="Ifn-72-mgx" kind="relationship" relationship="viewControllers" id="NdV-rq-bli"/>
71+ <segue destination="Jsm-cH-XbJ" kind="relationship" relationship="viewControllers" id="2PQ-04-zRb"/>
72+ </connections>
73+ </tabBarController>
74+ <placeholder placeholderIdentifier="IBFirstResponder" id="oiC-tc-RNa" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
75+ </objects>
76+ <point key="canvasLocation" x="-817" y="-251"/>
77+ </scene>
78+ </scenes>
79+ <resources>
80+ <image name="peoplee" width="51" height="25"/>
81+ <image name="person" width="25" height="24"/>
82+ <image name="stack" width="25" height="24"/>
83+ </resources>
84+</document>
Hookah Tau/Controllers/Admin/Tab bar/AdminTabbarController.swiftnew file+48
@@ -0,0 +1,48 @@
1+//
2+// AdminTabbarController.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 05/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class AdminTabbarController: UITabBarController {
12+
13+ // MARK: - Properties
14+
15+ var onViewDidLoad: TabbarClosure?
16+ var onReservationsFlow: TabbarClosure?
17+ var onClientsFlow: TabbarClosure?
18+ var onProfileFlow: TabbarClosure?
19+
20+ // MARK: - Lifecycle
21+
22+ override func viewDidLoad() {
23+ super.viewDidLoad()
24+
25+ delegate = self
26+ if let controller = customizableViewControllers?.first as? UINavigationController {
27+ onViewDidLoad?(controller)
28+ }
29+ }
30+
31+ func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController)
32+ {
33+
34+ guard let controller = viewControllers?[selectedIndex] as? UINavigationController else { return }
35+
36+ switch selectedIndex {
37+ case 0:
38+ onReservationsFlow?(controller)
39+ case 1:
40+ onClientsFlow?(controller)
41+ default:
42+ onProfileFlow?(controller)
43+ }
44+ }
45+
46+}
47+
48+extension AdminTabbarController: UITabBarControllerDelegate {}
Hookah Tau/Controllers/Admin/Tab bar/AdminTabbarCoordinator.swiftnew file+73
@@ -0,0 +1,73 @@
1+//
2+// AdminTabbarCoordinator.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 05/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+
12+class AdminTabbarCoordinator: BaseCoordinator {
13+
14+ /// Мы тут определяем что происходит с таббарами
15+ override func start() {
16+ let storyboard = UIStoryboard(name: "Admin", bundle: nil)
17+ let tabbarView = storyboard.instantiateViewController(withIdentifier: "AdminTabbarController") as? AdminTabbarController
18+
19+ tabbarView?.onViewDidLoad = runReservationsFlow()
20+ tabbarView?.onClientsFlow = runClientsFlow()
21+ tabbarView?.onProfileFlow = runProfileFlow()
22+ tabbarView?.onReservationsFlow = runReservationsFlow()
23+
24+ let appDelegate = UIApplication.shared.delegate as? AppDelegate
25+ guard let appD = appDelegate else { return }
26+ appD.window?.rootViewController = tabbarView
27+ }
28+
29+ private func runReservationsFlow() -> TabbarClosure {
30+ return { [unowned self] navigationController in
31+ if navigationController.viewControllers.isEmpty {
32+ let addressCoordinator = AdminAddressCoordinator(navigationController: navigationController)
33+ self.addDependency(addressCoordinator)
34+
35+ addressCoordinator.didEndFlow = {
36+ self.removeDependency(addressCoordinator)
37+ }
38+
39+ addressCoordinator.start()
40+ }
41+ }
42+ }
43+
44+ private func runClientsFlow() -> TabbarClosure {
45+ return { [unowned self] navigationController in
46+ if navigationController.viewControllers.isEmpty {
47+ let clientsCoordinator = ClientsCoordinator(navigationController: navigationController)
48+ self.addDependency(clientsCoordinator)
49+
50+ clientsCoordinator.didEndFlow = {
51+ self.removeDependency(clientsCoordinator)
52+ }
53+
54+ clientsCoordinator.start()
55+ }
56+ }
57+ }
58+
59+ private func runProfileFlow() -> TabbarClosure {
60+ return { [unowned self] navigationController in
61+ if navigationController.viewControllers.isEmpty {
62+ let profileCoordinator = ProfileCoordinator(navigationController: navigationController)
63+ profileCoordinator.didEndFlow = { [weak self, weak profileCoordinator] in
64+ self?.removeDependency(profileCoordinator)
65+ self?.didEndFlow?()
66+ }
67+ self.addDependency(profileCoordinator)
68+ profileCoordinator.start()
69+ }
70+ }
71+ }
72+
73+}
Hookah Tau/Controllers/ApplicationCoordinator.swift+23−6
@@ -10,7 +10,9 @@ import UIKit
10
10
11class ApplicationCoordinator: BaseCoordinator {
11class ApplicationCoordinator: BaseCoordinator {
12
12
13− var launch: LaunchOptions?
13+ var launch: LaunchOptions? {
14+ return LaunchOptions.configure()
15+ }
14
16
15 override func start() {
17 override func start() {
16 guard let launch = launch else { return }
18 guard let launch = launch else { return }
@@ -27,13 +29,11 @@ class ApplicationCoordinator: BaseCoordinator {
27
29
28 required init(navigationController: UINavigationController?) {
30 required init(navigationController: UINavigationController?) {
29 super.init(navigationController: navigationController)
31 super.init(navigationController: navigationController)
30− self.launch = LaunchOptions.configure()
31 }
32 }
32
33
33 private func runAuthFlow() {
34 private func runAuthFlow() {
34 let phoneCoordinator = PhoneCoordinator(navigationController: navigationController)
35 let phoneCoordinator = PhoneCoordinator(navigationController: navigationController)
35 phoneCoordinator.didEndFlow = { [weak self] in
36 phoneCoordinator.didEndFlow = { [weak self] in
36− self?.launch = LaunchOptions.main // TESTING
37 self?.start()
37 self?.start()
38 self?.removeDependency(phoneCoordinator)
38 self?.removeDependency(phoneCoordinator)
39 }
39 }
@@ -42,7 +42,13 @@ class ApplicationCoordinator: BaseCoordinator {
42 }
42 }
43
43
44 private func runAdminFlow() {
44 private func runAdminFlow() {
45−
45+ let tabbarCoordinator = AdminTabbarCoordinator(navigationController: navigationController)
46+ tabbarCoordinator.didEndFlow = { [weak self, weak tabbarCoordinator] in
47+ self?.start()
48+ self?.removeDependency(tabbarCoordinator)
49+ }
50+ addDependency(tabbarCoordinator)
51+ tabbarCoordinator.start()
46 }
52 }
47
53
48 private func runClientFlow() {
54 private func runClientFlow() {
@@ -62,8 +68,19 @@ extension ApplicationCoordinator {
62 case auth, admin, main
68 case auth, admin, main
63
69
64 static func configure() -> LaunchOptions {
70 static func configure() -> LaunchOptions {
65− print("configuring...")
71+ let isAuthorized = DataStorage.standard.isLoggedIn()
72+ let isAdmin = DataStorage.standard.getUserModel()?.isAdmin ?? false
73+
74+ if !isAuthorized {
75+ DataStorage.standard.setLoggedInState(false)
66− return .auth
76+ return .auth
77+ }
78+
79+ if isAdmin {
80+ return .admin
81+ }
82+
83+ return .main
67 }
84 }
68 }
85 }
69}
86}
Hookah Tau/Controllers/Authentication/Code from sms/CodeCoordinator.swift+3−10
@@ -12,10 +12,6 @@ import UIKit
12class CodeCoordinator: BaseCoordinator {
12class CodeCoordinator: BaseCoordinator {
13
13
14 // MARK: - Properties
14 // MARK: - Properties
15−
16− var didEndFlow: (() -> Void)?
17−
18− weak var parentCoordinator: PhoneCoordinator?
19
15
20 override func start() {
16 override func start() {
21 let smsInput = CodeSmsViewController()
17 let smsInput = CodeSmsViewController()
@@ -28,12 +24,9 @@ class CodeCoordinator: BaseCoordinator {
28 navigationController?.popViewController(animated: true)
24 navigationController?.popViewController(animated: true)
29 }
25 }
30
26
31− /// - warning: Depends on weather is authorized or not, mb need parameter
27+ /// Last step of authorization process
32 func goToNextStep() {
28 func goToNextStep() {
33− let nameCoordinator = NameCoordinator(navigationController: navigationController)
29+ print("Did end auth flow...")
34− nameCoordinator.didEndFlow = { [weak self] in self?.didEndFlow?() }
30+ didEndFlow?()
35− nameCoordinator.parentCoordinator = self
36− addDependency(nameCoordinator)
37− nameCoordinator.start()
38 }
31 }
39}
32}
Hookah Tau/Controllers/Authentication/Code from sms/CodeSmsViewController.swift+39−3
@@ -15,6 +15,8 @@ final class CodeSmsViewController: AuthorizationViewController {
15 weak var coordinator: CodeCoordinator?
15 weak var coordinator: CodeCoordinator?
16
16
17 weak var codeView: CodeView?
17 weak var codeView: CodeView?
18+
19+ var authService: AuthorizationService?
18
20
19 let nextButton: Button = {
21 let nextButton: Button = {
20 let button = Button(frame: CGRect.zero)
22 let button = Button(frame: CGRect.zero)
@@ -33,18 +35,22 @@ final class CodeSmsViewController: AuthorizationViewController {
33
35
34 setUpButtons()
36 setUpButtons()
35 setUpContentView()
37 setUpContentView()
38+
39+ authService = AuthorizationService(apiClient: APIClient.shared)
40+
41+ codeView?.onComplete = tapHandlerNextButton
42+ codeView?.firstTextField?.becomeFirstResponder()
36 }
43 }
37
44
38 override func viewWillAppear(_ animated: Bool) {
45 override func viewWillAppear(_ animated: Bool) {
39 super.viewWillAppear(animated)
46 super.viewWillAppear(animated)
40
47
41− codeView?.fst?.becomeFirstResponder()
42 }
48 }
43
49
44 override func viewWillDisappear(_ animated: Bool) {
50 override func viewWillDisappear(_ animated: Bool) {
45 super.viewWillDisappear(animated)
51 super.viewWillDisappear(animated)
46
52
47− codeView?.fst?.resignFirstResponder()
53+ codeView?.lastTextField?.resignFirstResponder()
48 }
54 }
49
55
50 // MARK: - Setup
56 // MARK: - Setup
@@ -71,7 +77,37 @@ final class CodeSmsViewController: AuthorizationViewController {
71
77
72 @objc
78 @objc
73 func tapHandlerNextButton() {
79 func tapHandlerNextButton() {
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+ DataStorage.standard.setLoggedInState(true)
74− coordinator?.goToNextStep()
95+ self?.coordinator?.goToNextStep()
96+ }
97+ }
98+
99+ nextButton.loading = true
100+ if let name = DataStorage.standard.name {
101+ authService?.register(name: name,
102+ code: code,
103+ phoneNumber: phone,
104+ completion: handleCompletion)
105+ return
106+ }
107+
108+ authService?.phonecode(phoneNumber: phone,
109+ code: code,
110+ completion: handleCompletion)
75 }
111 }
76
112
77 @objc
113 @objc
Hookah Tau/Controllers/Authentication/Name/NameCoordinator.swift+6−7
@@ -10,11 +10,7 @@ import UIKit
10
10
11class NameCoordinator: BaseCoordinator {
11class NameCoordinator: BaseCoordinator {
12
12
13− // MARK: - Properties
13+ // MARK: - Lifecycle
14−
15− var didEndFlow: (() -> Void)?
16−
17− var parentCoordinator: CodeCoordinator?
18
14
19 override func start() {
15 override func start() {
20 let nameViewController = NameViewController()
16 let nameViewController = NameViewController()
@@ -23,8 +19,11 @@ class NameCoordinator: BaseCoordinator {
23 }
19 }
24
20
25 func goToNextStep() {
21 func goToNextStep() {
26− print("Did end auth flow...")
22+ let codeCoordinator = CodeCoordinator(navigationController: navigationController)
27− didEndFlow?()
23+ codeCoordinator.didEndFlow = { [weak self] in self?.didEndFlow?() }
24+ codeCoordinator.parentCoordinator = self
25+ addDependency(codeCoordinator)
26+ codeCoordinator.start()
28 }
27 }
29
28
30 func goBack() {
29 func goBack() {
Hookah Tau/Controllers/Authentication/Name/NameViewController.swift+9−1
@@ -68,8 +68,16 @@ class NameViewController: AuthorizationViewController {
68 // MARK: - Handlers
68 // MARK: - Handlers
69
69
70 @objc func tapHandlerNextButton() {
70 @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
71 coordinator?.goToNextStep()
80 coordinator?.goToNextStep()
72− //nextButton.loading = !nextButton.loading
73 }
81 }
74
82
75 @objc func tapHandlerReturnButton() {
83 @objc func tapHandlerReturnButton() {
Hookah Tau/Controllers/Authentication/Phone/PhoneCoordinator.swift+23−11
@@ -6,25 +6,37 @@
6// Copyright © 2019 Daria Rednikina. All rights reserved.
6// Copyright © 2019 Daria Rednikina. All rights reserved.
7//
7//
8
8
9−import Foundation
9+import UIKit
10
10
11class PhoneCoordinator: BaseCoordinator {
11class PhoneCoordinator: BaseCoordinator {
12
12
13− // MARK: - Properties
13+ // MARK: - Lifecycle
14−
15− var didEndFlow: (() -> Void)?
16
14
17 override func start() {
15 override func start() {
18 let phoneVC = PhoneNumberViewController()
16 let phoneVC = PhoneNumberViewController()
19 phoneVC.coordinator = self
17 phoneVC.coordinator = self
20− navigationController?.viewControllers = [ phoneVC ]
18+
19+ let appDelegate = UIApplication.shared.delegate as? AppDelegate
20+ guard let appD = appDelegate else { return }
21+ appD.window?.rootViewController = navigationController
22+
23+ navigationController?.pushViewController(phoneVC, animated: false)
21 }
24 }
22
25
26+ /// Method chooses next step. Depends on weather user was previously registered
27+ /// - Parameter isUserRegistered: was user registered
23− func goToNextStep() {
28+ func goToNextStep(isUserRegistered: Bool) {
29+ let coordinator: BaseCoordinator!
30+
31+ if isUserRegistered {
24− let codeCoordinator = CodeCoordinator(navigationController: navigationController)
32+ coordinator = CodeCoordinator(navigationController: navigationController)
33+ } else {
34+ coordinator = NameCoordinator(navigationController: navigationController)
35+ }
36+
25− codeCoordinator.didEndFlow = { [weak self] in self?.didEndFlow?() }
37+ coordinator.didEndFlow = { [weak self] in self?.didEndFlow?() }
26− codeCoordinator.parentCoordinator = self
38+ coordinator.parentCoordinator = self
27− addDependency(codeCoordinator)
39+ addDependency(coordinator)
28− codeCoordinator.start()
40+ coordinator.start()
29 }
41 }
30}
42}
Hookah Tau/Controllers/Authentication/Phone/PhoneNumberViewController.swift+19−4
@@ -32,7 +32,7 @@ final class PhoneNumberViewController: AuthorizationViewController {
32 setUpButtons()
32 setUpButtons()
33 setUpContentView()
33 setUpContentView()
34
34
35− authService = AuthorizationService(apiClient: APIClient())
35+ authService = AuthorizationService(apiClient: APIClient.shared)
36 }
36 }
37
37
38 override func viewWillAppear(_ animated: Bool) {
38 override func viewWillAppear(_ animated: Bool) {
@@ -67,9 +67,24 @@ final class PhoneNumberViewController: AuthorizationViewController {
67
67
68 @objc
68 @objc
69 func tapHandlerNextButton() {
69 func tapHandlerNextButton() {
70− 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+
71− authService?.authenticate(withPhone: "8888888888", completion: { (result) in
78+ authService?.authenticate(withPhone: phoneText, completion: { [weak self] (result) in
72− print(result)
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+ }
73 })
88 })
74 }
89 }
75}
90}
Hookah Tau/Controllers/BaseCoordinator.swift+4
@@ -31,6 +31,10 @@ class BaseCoordinator: Coordinator {
31 var childCoordinators = [Coordinator]()
31 var childCoordinators = [Coordinator]()
32
32
33 var navigationController: UINavigationController?
33 var navigationController: UINavigationController?
34+
35+ var didEndFlow: (() -> Void)?
36+
37+ weak var parentCoordinator: BaseCoordinator?
34
38
35 func start() { } // should be overriden
39 func start() { } // should be overriden
36
40
Hookah Tau/Controllers/BaseTableViewController.swiftnew file+28
@@ -0,0 +1,28 @@
1+//
2+// BaseTableViewController.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 07/01/2020.
6+// Copyright © 2020 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class BaseTableViewController: UITableViewController {
12+
13+ // MARK: - Alert
14+
15+ func displayAlert(forError error: GeneralError = GeneralError.noData, with message: String? = nil) {
16+ var title: String? = message == nil ? error.localizedDescription : message
17+ switch error {
18+ case .serverError(let se):
19+ title = se.error
20+ fallthrough // да тоже произведение искусства
21+ default:
22+ let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
23+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
24+ self.present(alert, animated: true, completion: nil)
25+ }
26+ }
27+
28+}
Hookah Tau/Controllers/BaseViewController.swift+29−4
@@ -16,7 +16,10 @@ class BaseViewController: UIViewController {
16
16
17 // MARK: - Buttons
17 // MARK: - Buttons
18
18
19− func addStackViewWithButtons(leftBtn: Button? = nil, rightBtn: Button? = nil) {
19+ func addStackViewWithButtons(leftBtn: Button? = nil,
20+ rightBtn: Button? = nil,
21+ constant: CGFloat = -20) {
22+
20 let stackView = UIStackView()
23 let stackView = UIStackView()
21 stackView.translatesAutoresizingMaskIntoConstraints = false
24 stackView.translatesAutoresizingMaskIntoConstraints = false
22 stackView.backgroundColor = .red
25 stackView.backgroundColor = .red
@@ -25,7 +28,14 @@ class BaseViewController: UIViewController {
25 leftBtn?.translatesAutoresizingMaskIntoConstraints = false
28 leftBtn?.translatesAutoresizingMaskIntoConstraints = false
26 rightBtn?.translatesAutoresizingMaskIntoConstraints = false
29 rightBtn?.translatesAutoresizingMaskIntoConstraints = false
27
30
28− bottomConstraint = NSLayoutConstraint(item: stackView, attribute: .bottom, relatedBy: .equal, toItem: view.safeAreaLayoutGuide, attribute: .bottom, multiplier: 1, constant: -20)
31+ bottomConstraint = NSLayoutConstraint(item: stackView,
32+ attribute: .bottom,
33+ relatedBy: .equal,
34+ toItem: view.safeAreaLayoutGuide,
35+ attribute: .bottom,
36+ multiplier: 1,
37+ constant: constant)
38+
29 guard let bottomContraint = bottomConstraint else { return }
39 guard let bottomContraint = bottomConstraint else { return }
30 view.addConstraint(bottomContraint)
40 view.addConstraint(bottomContraint)
31
41
@@ -87,7 +97,7 @@ class BaseViewController: UIViewController {
87 }
97 }
88
98
89 @objc
99 @objc
90− private func handleKeyboardNotifications(notification: NSNotification){
100+ func handleKeyboardNotifications(notification: NSNotification){
91 if let userInfo = notification.userInfo {
101 if let userInfo = notification.userInfo {
92 let keyBoardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
102 let keyBoardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
93 bottomConstraint?.constant = notification.name == UIResponder.keyboardWillShowNotification ? -keyBoardFrame.height + view.safeAreaInsets.bottom - 20 : 0
103 bottomConstraint?.constant = notification.name == UIResponder.keyboardWillShowNotification ? -keyBoardFrame.height + view.safeAreaInsets.bottom - 20 : 0
@@ -97,5 +107,20 @@ class BaseViewController: UIViewController {
97 })
107 })
98 }
108 }
99 }
109 }
100−
110+
111+ // MARK: - Alert
112+
113+ func displayAlert(forError error: GeneralError = GeneralError.noData, with message: String? = nil) {
114+ var title: String? = message == nil ? error.localizedDescription : message
115+ switch error {
116+ case .serverError(let se):
117+ title = se.error
118+ fallthrough // да тоже произведение искусства
119+ default:
120+ let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
121+ alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
122+ self.present(alert, animated: true, completion: nil)
123+ }
124+ }
125+
101}
126}
Hookah Tau/Controllers/Client/Profile/ProfileChangeCoordinator.swift+6−6
@@ -16,8 +16,6 @@ class ProfileChangeCoordinator: BaseCoordinator {
16
16
17 // MARK: - Properties
17 // MARK: - Properties
18
18
19− var didEndFlow: (() -> Void)?
20−
21 var changeModel: ChangeModel?
19 var changeModel: ChangeModel?
22
20
23 // MARK: - Lifecycle
21 // MARK: - Lifecycle
@@ -31,10 +29,12 @@ class ProfileChangeCoordinator: BaseCoordinator {
31 }
29 }
32
30
33 /// Done
31 /// Done
34− func update(withModel model: User) {
32+ func update(withModel model: User?) {
33+ if let model = model {
35− for controller in navigationController?.viewControllers ?? [] {
34+ for controller in navigationController?.viewControllers ?? [] {
36− guard let updatableController = controller as? UserUpdate else { continue }
35+ guard let updatableController = controller as? UserUpdate else { continue }
37− updatableController.updateUser(withModel: model)
36+ updatableController.updateUser(withModel: model)
37+ }
38 }
38 }
39
39
40 didEndFlow?()
40 didEndFlow?()
Hookah Tau/Controllers/Client/Profile/ProfileChangeViewController.swift+22−3
@@ -23,6 +23,8 @@ class ProfileChangeViewController: BaseViewController {
23
23
24 var changeView: ChangeView?
24 var changeView: ChangeView?
25
25
26+ var settingsService: ClientsService?
27+
26 var contentView: UIView = {
28 var contentView: UIView = {
27 let view = UIView()
29 let view = UIView()
28 view.translatesAutoresizingMaskIntoConstraints = false
30 view.translatesAutoresizingMaskIntoConstraints = false
@@ -57,6 +59,8 @@ class ProfileChangeViewController: BaseViewController {
57
59
58 setUpView()
60 setUpView()
59 setUpKeyboard()
61 setUpKeyboard()
62+
63+ settingsService = ClientsService(apiClient: APIClient.shared)
60 }
64 }
61
65
62 func setUpView() {
66 func setUpView() {
@@ -86,15 +90,30 @@ class ProfileChangeViewController: BaseViewController {
86 var userModel = model.userModel
90 var userModel = model.userModel
87 switch model.editingOption {
91 switch model.editingOption {
88 case .invite:
92 case .invite:
89− print("New person is added")
93+ addNewAdmin(phone: text.toApiPhoneNumberFormat())
90− return
91 case .name:
94 case .name:
95+ // TODO:-f
92 userModel.name = text
96 userModel.name = text
97+ coordinator?.update(withModel: userModel)
93 case .phone:
98 case .phone:
99+ // TODO:-
94 userModel.phoneNumber = text
100 userModel.phoneNumber = text
101+ coordinator?.update(withModel: userModel)
95 }
102 }
96
103
97− coordinator?.update(withModel: userModel)
98 }
104 }
99
105
106+ func addNewAdmin(phone: String) {
107+ doneButton.loading = true
108+
109+ settingsService?.changeAdmin(crud: CrudMethod.put, data: phone) { [weak self] res in
110+ if res {
111+ self?.coordinator?.update(withModel: nil)
112+ } else {
113+ self?.displayAlert(with: "Что-то пошло не так!\nПопробуй еще раз")
114+ }
115+
116+ self?.doneButton.loading = false
117+ }
118+ }
100}
119}
Hookah Tau/Controllers/Client/Profile/ProfileClientViewController.swift+12−2
@@ -14,8 +14,9 @@ class ProfileClientViewController: UIViewController {
14
14
15 weak var coordinator: ProfileCoordinator?
15 weak var coordinator: ProfileCoordinator?
16
16
17− var model: User = User(name: "Стасик", phoneNumber: "+ 8 901 733 01 79", isAdmin: false) {
17+ var model: User? {
18 didSet {
18 didSet {
19+ guard let model = model else { return }
19 profileContentView?.bind(withModel: model)
20 profileContentView?.bind(withModel: model)
20 }
21 }
21 }
22 }
@@ -33,7 +34,9 @@ class ProfileClientViewController: UIViewController {
33 navigationController?.navigationBar.prefersLargeTitles = true
34 navigationController?.navigationBar.prefersLargeTitles = true
34
35
35 profileContentView = ProfileView.loadFromNib()
36 profileContentView = ProfileView.loadFromNib()
36− profileContentView?.bind(withModel: model)
37+ profileContentView?.logOutButton?.addTarget(self, action: #selector(logout), for: .touchUpInside)
38+
39+ model = DataStorage.standard.getUserModel()
37
40
38 view.addSubviewThatFills(profileContentView)
41 view.addSubviewThatFills(profileContentView)
39
42
@@ -41,19 +44,26 @@ class ProfileClientViewController: UIViewController {
41 }
44 }
42
45
43 @objc func presentChangeViewPhone() {
46 @objc func presentChangeViewPhone() {
47+ guard let model = model else { return }
44 let changeModel = ChangeModel(userModel: model, editingOption: .phone)
48 let changeModel = ChangeModel(userModel: model, editingOption: .phone)
45 coordinator?.presentEditMode(withModel: changeModel)
49 coordinator?.presentEditMode(withModel: changeModel)
46 }
50 }
47
51
48 @objc func presentChangeViewInvite() {
52 @objc func presentChangeViewInvite() {
53+ guard let model = model else { return }
49 let changeModel = ChangeModel(userModel: model, editingOption: .invite)
54 let changeModel = ChangeModel(userModel: model, editingOption: .invite)
50 coordinator?.presentEditMode(withModel: changeModel)
55 coordinator?.presentEditMode(withModel: changeModel)
51 }
56 }
52
57
53 @objc func presentChangeViewName() {
58 @objc func presentChangeViewName() {
59+ guard let model = model else { return }
54 let changeModel = ChangeModel(userModel: model, editingOption: .name)
60 let changeModel = ChangeModel(userModel: model, editingOption: .name)
55 coordinator?.presentEditMode(withModel: changeModel)
61 coordinator?.presentEditMode(withModel: changeModel)
56 }
62 }
63+
64+ @objc func logout() {
65+ coordinator?.logout()
66+ }
57}
67}
58
68
59// MARK: - UserUpdate ext
69// MARK: - UserUpdate ext
Hookah Tau/Controllers/Client/Profile/ProfileCoordinator.swift+2−1
@@ -33,6 +33,7 @@ class ProfileCoordinator: BaseCoordinator {
33 }
33 }
34
34
35 func logout() {
35 func logout() {
36−
36+ DataStorage.standard.setLoggedInState(false)
37+ didEndFlow?()
37 }
38 }
38}
39}
Hookah Tau/Controllers/Client/Profile/Views/ChangeView.swift+32−1
@@ -11,15 +11,20 @@ import UIKit
11
11
12class ChangeView: UIView {
12class ChangeView: UIView {
13
13
14+ var type: EditingOption = .invite
15+
14 @IBOutlet weak var titleLabel: UILabel?
16 @IBOutlet weak var titleLabel: UILabel?
15
17
16 @IBOutlet weak var textField: UITextField?
18 @IBOutlet weak var textField: UITextField?
17
19
18 func bind(withModel model: ChangeModel) {
20 func bind(withModel model: ChangeModel) {
21+ self.textField?.delegate = self
22+ self.type = model.editingOption
23+
19 switch model.editingOption {
24 switch model.editingOption {
20 case .invite:
25 case .invite:
21 titleLabel?.text = "Добавить администратора"
26 titleLabel?.text = "Добавить администратора"
22− textField?.placeholder = "+ 7 888 888 88 88"
27+ textField?.placeholder = "+ 7 (888) 888-88-88"
23 textField?.keyboardType = .numberPad
28 textField?.keyboardType = .numberPad
24 case .name:
29 case .name:
25 titleLabel?.text = "Имя"
30 titleLabel?.text = "Имя"
@@ -32,3 +37,29 @@ class ChangeView: UIView {
32 }
37 }
33 }
38 }
34}
39}
40+
41+extension ChangeView: UITextFieldDelegate {
42+
43+ func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
44+ let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
45+ textField.text = formattedNumber(newString)
46+ return false
47+ }
48+
49+ private func formattedNumber(_ number: String) -> String {
50+ let cleanPhoneNumber = number.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
51+
52+ var result = ""
53+ let phoneMask = "+X (XXX) XXX XX XX"
54+ var index = cleanPhoneNumber.startIndex
55+ for ch in phoneMask where index < cleanPhoneNumber.endIndex {
56+ if ch == "X" {
57+ result.append(cleanPhoneNumber[index])
58+ index = cleanPhoneNumber.index(after: index)
59+ } else {
60+ result.append(ch)
61+ }
62+ }
63+ return result
64+ }
65+}
Hookah Tau/Controllers/Client/Profile/Views/ChangeView.xib+1−1
@@ -17,7 +17,7 @@
17 <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="19Q-ZO-gm4">
17 <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="19Q-ZO-gm4">
18 <rect key="frame" x="20" y="11" width="335" height="83.5"/>
18 <rect key="frame" x="20" y="11" width="335" height="83.5"/>
19 <subviews>
19 <subviews>
20− <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Имя" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oB7-uQ-xmw">
20+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Имя" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="oB7-uQ-xmw">
21 <rect key="frame" x="0.0" y="0.0" width="335" height="33.5"/>
21 <rect key="frame" x="0.0" y="0.0" width="335" height="33.5"/>
22 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
22 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
23 <color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
23 <color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
Hookah Tau/Controllers/Client/Profile/Views/ProfileView.xib+5−5
@@ -28,7 +28,7 @@
28 </label>
28 </label>
29 <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jpS-Vl-9C3">
29 <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jpS-Vl-9C3">
30 <rect key="frame" x="0.0" y="44" width="374" height="50"/>
30 <rect key="frame" x="0.0" y="44" width="374" height="50"/>
31− <color key="backgroundColor" systemColor="systemBlueColor" red="0.0" green="0.47843137250000001" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
31+ <color key="backgroundColor" systemColor="systemBlueColor" red="0.0" green="0.47843137254901963" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
32 <constraints>
32 <constraints>
33 <constraint firstAttribute="width" constant="374" id="9z3-UR-zHl"/>
33 <constraint firstAttribute="width" constant="374" id="9z3-UR-zHl"/>
34 <constraint firstAttribute="height" constant="50" id="Hr9-hL-I1c"/>
34 <constraint firstAttribute="height" constant="50" id="Hr9-hL-I1c"/>
@@ -47,7 +47,7 @@
47 </label>
47 </label>
48 <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="SVP-b8-AJi">
48 <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="SVP-b8-AJi">
49 <rect key="frame" x="0.0" y="41.5" width="374" height="50"/>
49 <rect key="frame" x="0.0" y="41.5" width="374" height="50"/>
50− <color key="backgroundColor" systemColor="systemRedColor" red="1" green="0.23137254900000001" blue="0.18823529410000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
50+ <color key="backgroundColor" systemColor="systemRedColor" red="1" green="0.23137254901960785" blue="0.18823529411764706" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
51 <constraints>
51 <constraints>
52 <constraint firstAttribute="width" constant="374" id="2Ra-kt-T1F"/>
52 <constraint firstAttribute="width" constant="374" id="2Ra-kt-T1F"/>
53 <constraint firstAttribute="height" constant="50" id="iGE-m0-EQ2"/>
53 <constraint firstAttribute="height" constant="50" id="iGE-m0-EQ2"/>
@@ -58,7 +58,7 @@
58 <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="wcw-Ss-ld4">
58 <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="wcw-Ss-ld4">
59 <rect key="frame" x="0.0" y="245.5" width="374" height="91.5"/>
59 <rect key="frame" x="0.0" y="245.5" width="374" height="91.5"/>
60 <subviews>
60 <subviews>
61− <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="NQB-i0-WVL">
61+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="NQB-i0-WVL">
62 <rect key="frame" x="0.0" y="0.0" width="71.5" height="33.5"/>
62 <rect key="frame" x="0.0" y="0.0" width="71.5" height="33.5"/>
63 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
63 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
64 <color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
64 <color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -66,7 +66,7 @@
66 </label>
66 </label>
67 <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="dyQ-12-OMI">
67 <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="dyQ-12-OMI">
68 <rect key="frame" x="0.0" y="41.5" width="374" height="50"/>
68 <rect key="frame" x="0.0" y="41.5" width="374" height="50"/>
69− <color key="backgroundColor" systemColor="systemIndigoColor" red="0.34509803919999998" green="0.33725490200000002" blue="0.83921568629999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
69+ <color key="backgroundColor" systemColor="systemIndigoColor" red="0.34509803921568627" green="0.33725490196078434" blue="0.83921568627450982" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
70 <constraints>
70 <constraints>
71 <constraint firstAttribute="height" constant="50" id="SYN-47-LUA"/>
71 <constraint firstAttribute="height" constant="50" id="SYN-47-LUA"/>
72 <constraint firstAttribute="width" constant="374" id="hQD-KD-teb"/>
72 <constraint firstAttribute="width" constant="374" id="hQD-KD-teb"/>
@@ -78,7 +78,7 @@
78 </stackView>
78 </stackView>
79 <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="oKK-WM-xZ3" customClass="Button" customModule="Hookah_Tau" customModuleProvider="target">
79 <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="oKK-WM-xZ3" customClass="Button" customModule="Hookah_Tau" customModuleProvider="target">
80 <rect key="frame" x="20" y="446" width="374" height="37"/>
80 <rect key="frame" x="20" y="446" width="374" height="37"/>
81− <color key="backgroundColor" systemColor="opaqueSeparatorColor" red="0.77647058820000003" green="0.77647058820000003" blue="0.7843137255" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
81+ <color key="backgroundColor" systemColor="opaqueSeparatorColor" red="0.77647058823529413" green="0.77647058823529413" blue="0.78431372549019607" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
82 <constraints>
82 <constraints>
83 <constraint firstAttribute="height" constant="37" id="I8c-3h-mMu"/>
83 <constraint firstAttribute="height" constant="37" id="I8c-3h-mMu"/>
84 </constraints>
84 </constraints>
Hookah Tau/Controllers/Client/Reservations/Active Reservations/ReservationsCoordinator.swift+14
@@ -29,4 +29,18 @@ class ReservationsCoordinator: BaseCoordinator {
29 }
29 }
30 addressCoordinator.start()
30 addressCoordinator.start()
31 }
31 }
32+
33+ func viewReservation(_ reservation: Reservation) {
34+ let thirdStepRCoordinator = ThirdStepReservationCoordinator(navigationController: navigationController)
35+ addDependency(thirdStepRCoordinator)
36+
37+ thirdStepRCoordinator.resrvation = reservation
38+
39+ thirdStepRCoordinator.didFinish = { [weak self] in
40+ self?.removeDependency(thirdStepRCoordinator)
41+ self?.navigationController?.popViewController(animated: true)
42+ }
43+
44+ thirdStepRCoordinator.start()
45+ }
32}
46}
Hookah Tau/Controllers/Client/Reservations/Active Reservations/ReservationsViewController.swift+141−20
@@ -8,13 +8,27 @@
8
8
9import UIKit
9import UIKit
10
10
11−let reservationCell = "ReservationCell"
12−
13−class ReservationsViewController: UIViewController {
11+class ReservationsViewController: BaseViewController {
14
12
15 // MARK: - Properties
13 // MARK: - Properties
16
14
17− var activeReservations = [1]
15+ var activeReservations: [Reservation] = [] {
16+ didSet {
17+ guard let empty = noReservationsView else { return }
18+ if activeReservations.isEmpty {
19+ contentView.bringSubviewToFront(empty)
20+ empty.alpha = 1
21+ } else {
22+ contentView.sendSubviewToBack(empty)
23+ tableView?.alpha = 1
24+ }
25+
26+ activityIndicator.stopAnimating()
27+ tableView?.reloadData()
28+ }
29+ }
30+
31+ var reservationsService: ReservationsService?
18
32
19 weak var coordinator: ReservationsCoordinator?
33 weak var coordinator: ReservationsCoordinator?
20
34
@@ -26,20 +40,70 @@ class ReservationsViewController: UIViewController {
26
40
27 var noReservationsView: NoReservationsView?
41 var noReservationsView: NoReservationsView?
28
42
29− var tableView: UITableView?
43+ var tableView: UITableView? = {
44+ let tableView = UITableView()
45+ tableView.backgroundColor = .white
46+ return tableView
47+ }()
48+
49+ var activityIndicator = UIActivityIndicatorView()
30
50
31 // MARK: - Lifecycle
51 // MARK: - Lifecycle
32
52
33 override func viewDidLoad() {
53 override func viewDidLoad() {
34 super.viewDidLoad()
54 super.viewDidLoad()
35
55
56+ setUpNavigationBar()
57+
58+ reservationsService = ReservationsService(apiClient: APIClient.shared)
59+ navigationController?.navigationBar.prefersLargeTitles = true
36 setUpContentView()
60 setUpContentView()
61+ }
62+
63+ override func viewWillAppear(_ animated: Bool) {
64+ super.viewWillAppear(animated)
65+
66+ tabBarController?.tabBar.isHidden = false
67+ }
68+
69+ override func viewDidAppear(_ animated: Bool) {
70+ super.viewDidAppear(animated)
37
71
72+ performUpdate()
73+ }
74+
75+ // MARK: - Setup
76+
77+ func setUpContentView() {
78+ self.view.addSubviewThatFills(contentView)
79+
80+ contentView.addSubviewThatFills(activityIndicator)
81+ activityIndicator.startAnimating()
82+
83+ // empty
84+ noReservationsView = NoReservationsView.loadFromNib()
85+ contentView.addSubviewThatFills(noReservationsView)
86+ noReservationsView?.makeReservationButton?.addTarget(self, action: #selector(tapToMakeReservation), for: .touchUpInside)
87+
88+ noReservationsView?.alpha = 0
89+
90+ // table
91+ contentView.addSubviewThatFills(tableView)
92+
93+ setUpTableView()
94+ setupRefreshControl()
95+ }
96+
97+ func setUpTableView() {
38 tableView?.delegate = self
98 tableView?.delegate = self
39 tableView?.dataSource = self
99 tableView?.dataSource = self
40
100
101+ tableView?.alpha = 0
102+
41 tableView?.register(ReservationCell.self, forCellReuseIdentifier: reservationCell)
103 tableView?.register(ReservationCell.self, forCellReuseIdentifier: reservationCell)
42−
104+ }
105+
106+ func setUpNavigationBar() {
43 view.backgroundColor = .white
107 view.backgroundColor = .white
44 navigationItem.title = "Брони"
108 navigationItem.title = "Брони"
45
109
@@ -47,27 +111,51 @@ class ReservationsViewController: UIViewController {
47 UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(tapToMakeReservation))
111 UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(tapToMakeReservation))
48 }
112 }
49
113
50− // MARK: - Setup
114+ // MARK: - handlers
51
115
52− func setUpContentView() {
116+ @objc func tapToMakeReservation() {
53− self.view.addSubviewThatFills(contentView)
117+ coordinator?.makeReservation()
54−
118+ }
55− if activeReservations.isEmpty {
119+
56− noReservationsView = NoReservationsView.loadFromNib()
120+ // MARK: - Update
57− contentView.addSubviewThatFills(noReservationsView)
121+
58− noReservationsView?.makeReservationButton?.addTarget(self, action: #selector(tapToMakeReservation), for: .touchUpInside)
122+ func performUpdate() {
59− } else {
123+ reservationsService?.loadUsersReservations(completion: { [weak self] (result) in
60− tableView = UITableView()
124+ switch result {
61− contentView.addSubviewThatFills(tableView)
125+ case .success(let reservations):
126+ self?.activeReservations = reservations
127+ case .failure(let err):
128+ self?.displayAlert(forError: err)
129+ }
130+ self?.refrControl?.endRefreshing()
131+ })
132+ }
133+
134+ func deleteReservation(uuid: String, completion: @escaping (([Reservation]?) -> Void)) {
135+ reservationsService?.deleteReservation(isAdmin: false, uuid: uuid) { result in
136+ if result {
137+ let newReservations = self.activeReservations.filter { $0.uuid != uuid }
138+ completion(newReservations)
139+ return
140+ }
141+
142+ completion(nil)
62 }
143 }
63 }
144 }
64
145
65− // MARK: - handlers
146+ // MARK: - Refresh control
66
147
67− @objc func tapToMakeReservation() {
148+ var refrControl: UIRefreshControl?
68− coordinator?.makeReservation()
149+
150+ func setupRefreshControl() {
151+ refrControl = UIRefreshControl()
152+ tableView?.refreshControl = refrControl
153+ refrControl?.addTarget(self, action: #selector(refreshData(_:)), for: .valueChanged)
69 }
154 }
70
155
156+ @objc func refreshData( _ ff: UIRefreshControl) {
157+ performUpdate()
158+ }
71}
159}
72
160
73extension ReservationsViewController: UITableViewDelegate, UITableViewDataSource {
161extension ReservationsViewController: UITableViewDelegate, UITableViewDataSource {
@@ -82,6 +170,11 @@ extension ReservationsViewController: UITableViewDelegate, UITableViewDataSource
82 return cell
170 return cell
83 }
171 }
84
172
173+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
174+ let reservation = activeReservations[indexPath.row]
175+ coordinator?.viewReservation(reservation)
176+ }
177+
85 func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
178 func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
86 return UITableView.automaticDimension
179 return UITableView.automaticDimension
87 }
180 }
@@ -89,4 +182,32 @@ extension ReservationsViewController: UITableViewDelegate, UITableViewDataSource
89 func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
182 func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
90 return 180.0
183 return 180.0
91 }
184 }
185+
186+ // Update
187+
188+ func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
189+ return true
190+ }
191+
192+ func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
193+
194+ let uuid = activeReservations[indexPath.row].uuid
195+ let cancelButton = UITableViewRowAction(style: .normal, title: "отменить бронь") { _,_ in
196+
197+ self.deleteReservation(uuid: uuid) { optionalNewVal in
198+ if let newval = optionalNewVal {
199+ self.tableView?.beginUpdates()
200+ self.tableView?.deleteRows(at: [indexPath], with: .automatic)
201+ self.activeReservations = newval
202+ self.tableView?.endUpdates()
203+ } else {
204+ self.displayAlert(with: "Не удалось удалить вашу бронь! Попробуйте еще раз!")
205+ }
206+ }
207+ }
208+
209+ cancelButton.backgroundColor = .black
210+
211+ return [cancelButton]
212+ }
92}
213}
Hookah Tau/Controllers/Client/Reservations/Adress of reservation/AddresViewController.swift+43−2
@@ -12,11 +12,11 @@ class AddresViewController: BaseViewController {
12
12
13 // MARK: - Properties
13 // MARK: - Properties
14
14
15− weak var coordinator: AddressCoordinator?
15+ weak var coordinator: AddressMapper?
16
16
17 var tableView: UITableView?
17 var tableView: UITableView?
18
18
19− var dataSource: [Int]? = [1]
19+ var dataSource: [Int]? = [1, 2]
20
20
21 // MARK: - Lifecycle
21 // MARK: - Lifecycle
22
22
@@ -24,9 +24,16 @@ class AddresViewController: BaseViewController {
24 view.backgroundColor = .white
24 view.backgroundColor = .white
25 navigationItem.title = "Заведения"
25 navigationItem.title = "Заведения"
26
26
27+ navigationController?.navigationBar.prefersLargeTitles = true
27 setUpTableView()
28 setUpTableView()
28 }
29 }
29
30
31+ override func viewWillAppear(_ animated: Bool) {
32+ super.viewWillAppear(animated)
33+
34+ tabBarController?.tabBar.isHidden = false
35+ }
36+
30 // MARK: - Setup
37 // MARK: - Setup
31
38
32 func setUpTableView() {
39 func setUpTableView() {
@@ -53,6 +60,13 @@ extension AddresViewController: UITableViewDelegate, UITableViewDataSource {
53 let cell = tableView.dequeueReusableCell(withIdentifier: addressCellIdentifier,
60 let cell = tableView.dequeueReusableCell(withIdentifier: addressCellIdentifier,
54 for: indexPath) as! AddressCell
61 for: indexPath) as! AddressCell
55
62
63+ guard
64+ let val = dataSource?[indexPath.row],
65+ let model = TotalStorage.standard.getEstablishment(val)
66+ else { return cell }
67+
68+ cell.bind(withModel: model)
69+
56 return cell
70 return cell
57 }
71 }
58
72
@@ -60,4 +74,31 @@ extension AddresViewController: UITableViewDelegate, UITableViewDataSource {
60 return 168.0
74 return 168.0
61 }
75 }
62
76
77+ func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
78+ let establishment = dataSource?[indexPath.row]
79+ guard let id = establishment else { return }
80+
81+ coordinator?.chooseAddress(establishmentId: id)
82+ }
83+
84+ func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
85+ return true
86+ }
87+
88+ func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
89+
90+ guard
91+ let val = dataSource?[indexPath.row],
92+ let phone = TotalStorage.standard.getEstablishment(val)?.admin,
93+ let urlPhone = URL(string: "tel://79\(phone)")
94+ else { return nil }
95+
96+ let editButton = UITableViewRowAction(style: .normal, title: "🤙🏻") { _,_ in
97+ UIApplication.shared.open(urlPhone)
98+ }
99+
100+ editButton.backgroundColor = .black
101+
102+ return [editButton]
103+ }
63}
104}
Hookah Tau/Controllers/Client/Reservations/Adress of reservation/AddressCoordinator.swift+21−1
@@ -8,6 +8,10 @@
8
8
9import UIKit
9import UIKit
10
10
11+protocol AddressMapper: class {
12+ func chooseAddress(establishmentId id: Int)
13+}
14+
11class AddressCoordinator: BaseCoordinator {
15class AddressCoordinator: BaseCoordinator {
12
16
13 // MARK: - Properties
17 // MARK: - Properties
@@ -29,7 +33,23 @@ class AddressCoordinator: BaseCoordinator {
29
33
30 /// continue with reservation
34 /// continue with reservation
31 /// TODO: pass some data
35 /// TODO: pass some data
32− func chooseTableAndTime() {
36+ private func chooseTableAndTime(inEstablishment id: Int) {
37+ let reservationCoordinator = FirstStepReservationCoordinator(navigationController: navigationController)
38+ reservationCoordinator.establishment = id
39+
40+ addDependency(reservationCoordinator)
41+ reservationCoordinator.didFinish = { [weak self] in
42+ self?.removeDependency(reservationCoordinator)
43+ self?.navigationController?.popViewController(animated: false)
44+ self?.didFinish?()
45+ }
33
46
47+ reservationCoordinator.start()
48+ }
49+}
50+
51+extension AddressCoordinator: AddressMapper {
52+ func chooseAddress(establishmentId id: Int) {
53+ chooseTableAndTime(inEstablishment: id)
34 }
54 }
35}
55}
Hookah Tau/Controllers/Client/Reservations/Cells/Address/AddressCell.swift+4
@@ -27,6 +27,10 @@ class AddressCell: UITableViewCell {
27
27
28 selectionStyle = .none
28 selectionStyle = .none
29 }
29 }
30+
31+ func bind(withModel model: TotalStorage.EstablishmentData) {
32+ addressView?.bind(withModel: model)
33+ }
30
34
31 required init?(coder aDecoder: NSCoder)
35 required init?(coder aDecoder: NSCoder)
32 {
36 {
Hookah Tau/Controllers/Client/Reservations/Cells/Address/AddressView.swift+6−2
@@ -18,8 +18,12 @@ class AddressView: UIView {
18
18
19 @IBOutlet weak var tableCountLabel: UILabel?
19 @IBOutlet weak var tableCountLabel: UILabel?
20
20
21− @IBOutlet weak var callAdminButton: UIButton?
22−
23 @IBOutlet weak var mapImageView: UIImageView?
21 @IBOutlet weak var mapImageView: UIImageView?
24
22
23+ func bind(withModel model: TotalStorage.EstablishmentData) {
24+ addressLabel?.text = model.address
25+ tableCountLabel?.text = "\(model.maxTableNumber) столов"
26+ mapImageView?.image = model.image
27+ }
28+
25}
29}
Hookah Tau/Controllers/Client/Reservations/Cells/Address/AddressView.xib+28−26
@@ -15,42 +15,45 @@
15 <rect key="frame" x="0.0" y="0.0" width="375" height="168"/>
15 <rect key="frame" x="0.0" y="0.0" width="375" height="168"/>
16 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
16 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
17 <subviews>
17 <subviews>
18− <stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" alignment="center" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="CqA-Dd-eAx">
18+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" alignment="top" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="CqA-Dd-eAx">
19 <rect key="frame" x="16" y="16" width="343" height="136"/>
19 <rect key="frame" x="16" y="16" width="343" height="136"/>
20 <subviews>
20 <subviews>
21− <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="bottom" spacing="30" translatesAutoresizingMaskIntoConstraints="NO" id="wiw-lV-dNd">
21+ <stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" translatesAutoresizingMaskIntoConstraints="NO" id="VqZ-I8-mm0">
22− <rect key="frame" x="0.0" y="16" width="164.5" height="104.5"/>
22+ <rect key="frame" x="0.0" y="0.0" width="200" height="136"/>
23 <subviews>
23 <subviews>
24− <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Покровский, 11" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9py-8w-XFJ">
25− <rect key="frame" x="0.0" y="0.0" width="164.5" height="26.5"/>
26− <fontDescription key="fontDescription" type="boldSystem" pointSize="22"/>
27− <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
28− <nil key="highlightedColor"/>
29− </label>
30− <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="bottom" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="lyA-et-Gg5">
24+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" alignment="bottom" spacing="30" translatesAutoresizingMaskIntoConstraints="NO" id="wiw-lV-dNd">
31− <rect key="frame" x="54" y="56.5" width="110.5" height="48"/>
25+ <rect key="frame" x="0.0" y="0.0" width="200" height="136"/>
32 <subviews>
26 <subviews>
33− <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3:00 — 12:00" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SrE-GD-vqp">
27+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Гороховский пер. 12c5, апт. 205" textAlignment="right" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9py-8w-XFJ">
34− <rect key="frame" x="0.0" y="0.0" width="110.5" height="20.5"/>
28+ <rect key="frame" x="7" y="0.0" width="193" height="53"/>
35− <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
29+ <fontDescription key="fontDescription" type="boldSystem" pointSize="22"/>
36 <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
30 <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
37 <nil key="highlightedColor"/>
31 <nil key="highlightedColor"/>
38 </label>
32 </label>
33+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="bottom" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="lyA-et-Gg5">
34+ <rect key="frame" x="89.5" y="88" width="110.5" height="48"/>
35+ <subviews>
36+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="3:00 — 12:00" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="SrE-GD-vqp">
37+ <rect key="frame" x="0.0" y="0.0" width="110.5" height="20.5"/>
38+ <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
39+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
40+ <nil key="highlightedColor"/>
41+ </label>
39− <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="8 столов" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9nc-mT-R6o">
42+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="8 столов" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="9nc-mT-R6o">
40− <rect key="frame" x="44.5" y="28.5" width="66" height="19.5"/>
43+ <rect key="frame" x="44.5" y="28.5" width="66" height="19.5"/>
41− <fontDescription key="fontDescription" type="system" pointSize="16"/>
44+ <fontDescription key="fontDescription" type="system" pointSize="16"/>
42− <color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
45+ <color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
43− <nil key="highlightedColor"/>
46+ <nil key="highlightedColor"/>
44− </label>
47+ </label>
48+ </subviews>
49+ </stackView>
45 </subviews>
50 </subviews>
51+ <constraints>
52+ <constraint firstAttribute="width" constant="200" id="Lgy-TT-MRN"/>
53+ </constraints>
46 </stackView>
54 </stackView>
47 </subviews>
55 </subviews>
48 </stackView>
56 </stackView>
49− <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="GTd-he-e50">
50− <rect key="frame" x="227" y="53" width="116" height="30"/>
51− <color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
52− <state key="normal" title="Большая кнопка"/>
53− </button>
54 </subviews>
57 </subviews>
55 </stackView>
58 </stackView>
56 <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Map" translatesAutoresizingMaskIntoConstraints="NO" id="2Oj-R4-qoR">
59 <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Map" translatesAutoresizingMaskIntoConstraints="NO" id="2Oj-R4-qoR">
@@ -75,7 +78,6 @@
75 <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
78 <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
76 <connections>
79 <connections>
77 <outlet property="addressLabel" destination="9py-8w-XFJ" id="RzN-vT-9co"/>
80 <outlet property="addressLabel" destination="9py-8w-XFJ" id="RzN-vT-9co"/>
78− <outlet property="callAdminButton" destination="GTd-he-e50" id="bdq-pP-al5"/>
79 <outlet property="mapImageView" destination="2Oj-R4-qoR" id="3ao-l1-GTd"/>
81 <outlet property="mapImageView" destination="2Oj-R4-qoR" id="3ao-l1-GTd"/>
80 <outlet property="openHoursLabel" destination="SrE-GD-vqp" id="gAb-rI-1wP"/>
82 <outlet property="openHoursLabel" destination="SrE-GD-vqp" id="gAb-rI-1wP"/>
81 <outlet property="tableCountLabel" destination="9nc-mT-R6o" id="tlM-MK-qTx"/>
83 <outlet property="tableCountLabel" destination="9nc-mT-R6o" id="tlM-MK-qTx"/>
Hookah Tau/Controllers/Client/Reservations/Cells/Reservation/ReservationCell.swift+4−3
@@ -8,6 +8,8 @@
8
8
9import UIKit
9import UIKit
10
10
11+let reservationCell = "ReservationCell"
12+
11class ReservationCell: UITableViewCell {
13class ReservationCell: UITableViewCell {
12
14
13 // MARK: - Properties
15 // MARK: - Properties
@@ -33,8 +35,7 @@ class ReservationCell: UITableViewCell {
33
35
34 // MARK: - Data
36 // MARK: - Data
35
37
36− func bind(withModel model: Int) {
38+ func bind(withModel model: Reservation) {
37−
39+ reservationCellView?.bind(withModel: model)
38 }
40 }
39−
40}
41}
Hookah Tau/Controllers/Client/Reservations/Cells/Reservation/ReservationView.swift+11−61
@@ -21,68 +21,18 @@ class ReservationView: UIView {
21 @IBOutlet weak var maxPeopleLabel: UILabel?
21 @IBOutlet weak var maxPeopleLabel: UILabel?
22
22
23 @IBOutlet weak var tableImageView: UIImageView?
23 @IBOutlet weak var tableImageView: UIImageView?
24−
25− @IBOutlet weak var cancelButton: Button? {
26− didSet {
27− let style = BlackButtonStyle()
28− style.apply(to: cancelButton, withTitle: "ОТМЕНИТЬ")
29− }
30− }
31−
32− @IBOutlet weak var changeButton: Button? {
33− didSet {
34− let style = BlackButtonStyle()
35− style.apply(to: changeButton, withTitle: "ИЗМЕНИТЬ")
36− }
37− }
38−
39− @IBOutlet weak var swipableView: UIView?
40−
41−
42− override func awakeFromNib() {
43− super.awakeFromNib()
44−
45− }
46−
47− // https://developer.apple.com/videos/play/wwdc2015/229/
48− @IBAction func swipeImageView(_ recognizer: UIPanGestureRecognizer) {
49− let translation = recognizer.translation(in: self.swipableView)
50− guard let view = recognizer.view else { return }
51−
52− let point = CGPoint(x: view.center.x + translation.x, y: view.center.y)
53− if self.swipableView?.bounds.contains(point) == true {
54− view.center = point
55− }
56−
57− recognizer.setTranslation(CGPoint.zero, in: self.swipableView)
58−
59− if recognizer.state == .ended {
60− // 1
61− let velocity = recognizer.velocity(in: self.swipableView)
62− let magnitude = sqrt((velocity.x * velocity.x) + (velocity.y * velocity.y))
63− let slideMultiplier = magnitude / 300
64− print("magnitude: \(magnitude), slideMultiplier: \(slideMultiplier)")
65−
66− // 2
67− let slideFactor = 0.1 * slideMultiplier //Increase for more of a slide
68− // 3
69− var finalPoint = CGPoint(x:view.center.x + (velocity.x * slideFactor),
70− y:view.center.y)
71− // 4
72− finalPoint.x = min(max(finalPoint.x, 0), self.swipableView?.bounds.size.width ?? 0.0 - view.bounds.width / 2 )
73
24
74− // 5
25+ func bind(withModel model: Reservation) {
75− UIView.animate(withDuration: Double(slideFactor * 2),
26+ guard let table = TotalStorage.standard.getTable(establishment: model.establishment, table: model.reservedTable),
76− delay: 0,
27+ let establishment = TotalStorage.standard.getEstablishment(model.establishment)
77− // 6
28+ else { return }
78− options: UIView.AnimationOptions.curveEaseOut,
79− animations: { view.center = finalPoint },
80− completion: nil)
81− }
82− }
83−
84− private func checkArea(_ coord: CGPoint) {
85
29
30+ tableImageView?.image = table.image
31+ adressLabel?.text = establishment.address
32+ maxPeopleLabel?.text = "\(model.numberOfGuests) " + (2...4 ~= model.numberOfGuests ? "человека" : "человек")
33+
34+ let (date, time) = Date.format(model.startTime, model.endTime)
35+ dateLabel?.text = date
36+ timeLabel?.text = time
86 }
37 }
87−
88}
38}
Hookah Tau/Controllers/Client/Reservations/Cells/Reservation/ReservationView.xib+7−55
@@ -16,12 +16,12 @@
16 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
16 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
17 <subviews>
17 <subviews>
18 <stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="gQe-LE-1cq">
18 <stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="gQe-LE-1cq">
19− <rect key="frame" x="20" y="22" width="374" height="136"/>
19+ <rect key="frame" x="20" y="20" width="374" height="140"/>
20 <subviews>
20 <subviews>
21 <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="25" translatesAutoresizingMaskIntoConstraints="NO" id="Ujo-xw-Sa3">
21 <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="25" translatesAutoresizingMaskIntoConstraints="NO" id="Ujo-xw-Sa3">
22− <rect key="frame" x="0.0" y="0.0" width="256" height="136"/>
22+ <rect key="frame" x="0.0" y="2" width="243" height="136"/>
23 <subviews>
23 <subviews>
24− <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Покровский, 11" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="QIM-G6-tcZ">
24+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Покровский, 11" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="QIM-G6-tcZ">
25 <rect key="frame" x="0.0" y="0.0" width="122" height="20.5"/>
25 <rect key="frame" x="0.0" y="0.0" width="122" height="20.5"/>
26 <fontDescription key="fontDescription" type="system" pointSize="17"/>
26 <fontDescription key="fontDescription" type="system" pointSize="17"/>
27 <color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
27 <color key="textColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
@@ -52,36 +52,8 @@
52 </label>
52 </label>
53 </subviews>
53 </subviews>
54 </stackView>
54 </stackView>
55− <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="18" translatesAutoresizingMaskIntoConstraints="NO" id="KIf-G6-yu0">
56− <rect key="frame" x="264" y="24" width="110" height="88"/>
57− <subviews>
58− <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="7Kd-Rw-JdQ" customClass="Button" customModule="Hookah_Tau" customModuleProvider="target">
59− <rect key="frame" x="0.0" y="0.0" width="110" height="35"/>
60− <color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
61− <constraints>
62− <constraint firstAttribute="width" constant="110" id="db3-Z9-qoz"/>
63− <constraint firstAttribute="height" constant="35" id="fvx-DS-94n"/>
64− </constraints>
65− <state key="normal" title="Отменить"/>
66− </button>
67− <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="L1q-Xa-K3y" customClass="Button" customModule="Hookah_Tau" customModuleProvider="target">
68− <rect key="frame" x="0.0" y="53" width="110" height="35"/>
69− <color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
70− <constraints>
71− <constraint firstAttribute="height" constant="35" id="29E-JH-Mv8"/>
72− <constraint firstAttribute="width" constant="110" id="UCh-du-cnE"/>
73− </constraints>
74− <state key="normal" title="Изменить"/>
75− </button>
76− </subviews>
77− </stackView>
78− </subviews>
79− </stackView>
80− <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="m4Q-p5-icZ">
81− <rect key="frame" x="144" y="0.0" width="250" height="180"/>
82− <subviews>
83 <imageView clipsSubviews="YES" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Table" translatesAutoresizingMaskIntoConstraints="NO" id="4u1-cy-ujz">
55 <imageView clipsSubviews="YES" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Table" translatesAutoresizingMaskIntoConstraints="NO" id="4u1-cy-ujz">
84− <rect key="frame" x="127" y="20" width="123" height="140"/>
56+ <rect key="frame" x="251" y="0.0" width="123" height="140"/>
85 <gestureRecognizers/>
57 <gestureRecognizers/>
86 <constraints>
58 <constraints>
87 <constraint firstAttribute="width" constant="123" id="3ZG-yo-kHP"/>
59 <constraint firstAttribute="width" constant="123" id="3ZG-yo-kHP"/>
@@ -89,50 +61,30 @@
89 <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="123" id="weI-IM-eIN"/>
61 <constraint firstAttribute="width" relation="greaterThanOrEqual" constant="123" id="weI-IM-eIN"/>
90 <constraint firstAttribute="height" constant="140" id="xVK-aV-obb"/>
62 <constraint firstAttribute="height" constant="140" id="xVK-aV-obb"/>
91 </constraints>
63 </constraints>
92− <connections>
93− <outletCollection property="gestureRecognizers" destination="PRF-YF-b6m" appends="YES" id="DjN-h4-Tdp"/>
94− </connections>
95 </imageView>
64 </imageView>
96 </subviews>
65 </subviews>
97− <color key="backgroundColor" red="0.012039811643835616" green="0.56175085616438358" blue="0.76225385273972601" alpha="0.2090378852739726" colorSpace="custom" customColorSpace="sRGB"/>
66+ </stackView>
98− <constraints>
99− <constraint firstItem="4u1-cy-ujz" firstAttribute="top" secondItem="m4Q-p5-icZ" secondAttribute="top" constant="20" id="E3a-gf-bth"/>
100− <constraint firstAttribute="width" constant="250" id="JQc-Wc-jc0"/>
101− <constraint firstAttribute="trailing" secondItem="4u1-cy-ujz" secondAttribute="trailing" id="JfQ-6n-41A"/>
102− <constraint firstAttribute="bottom" secondItem="4u1-cy-ujz" secondAttribute="bottom" constant="20" id="gNA-cS-SKe"/>
103− <constraint firstAttribute="height" constant="180" id="uWH-MW-jqV"/>
104− </constraints>
105− </view>
106 </subviews>
67 </subviews>
107 <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
68 <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
108 <constraints>
69 <constraints>
109 <constraint firstItem="gQe-LE-1cq" firstAttribute="centerY" secondItem="vUN-kp-3ea" secondAttribute="centerY" id="0yM-pg-c5m"/>
70 <constraint firstItem="gQe-LE-1cq" firstAttribute="centerY" secondItem="vUN-kp-3ea" secondAttribute="centerY" id="0yM-pg-c5m"/>
110− <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="m4Q-p5-icZ" secondAttribute="trailing" constant="20" id="8aC-G7-xGp"/>
71+ <constraint firstItem="gQe-LE-1cq" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="20" id="TQU-8e-jES"/>
111− <constraint firstItem="m4Q-p5-icZ" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" id="D3I-d9-a60"/>
112 <constraint firstItem="gQe-LE-1cq" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="20" id="guw-kE-R18"/>
72 <constraint firstItem="gQe-LE-1cq" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="20" id="guw-kE-R18"/>
73+ <constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="gQe-LE-1cq" secondAttribute="bottom" constant="20" id="pgS-KL-ECQ"/>
113 <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="gQe-LE-1cq" secondAttribute="trailing" constant="20" id="rw9-VV-8S5"/>
74 <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="gQe-LE-1cq" secondAttribute="trailing" constant="20" id="rw9-VV-8S5"/>
114 <constraint firstItem="gQe-LE-1cq" firstAttribute="centerX" secondItem="vUN-kp-3ea" secondAttribute="centerX" id="vQj-YK-exg"/>
75 <constraint firstItem="gQe-LE-1cq" firstAttribute="centerX" secondItem="vUN-kp-3ea" secondAttribute="centerX" id="vQj-YK-exg"/>
115− <constraint firstItem="m4Q-p5-icZ" firstAttribute="bottom" secondItem="vUN-kp-3ea" secondAttribute="bottom" id="yMN-Gy-814"/>
116 </constraints>
76 </constraints>
117 <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
77 <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
118 <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
78 <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
119 <connections>
79 <connections>
120 <outlet property="adressLabel" destination="QIM-G6-tcZ" id="bRM-hB-jwH"/>
80 <outlet property="adressLabel" destination="QIM-G6-tcZ" id="bRM-hB-jwH"/>
121− <outlet property="cancelButton" destination="7Kd-Rw-JdQ" id="3wP-jU-Vlz"/>
122− <outlet property="changeButton" destination="L1q-Xa-K3y" id="Uot-1g-x9O"/>
123 <outlet property="dateLabel" destination="lUJ-we-26h" id="cMr-gz-ofj"/>
81 <outlet property="dateLabel" destination="lUJ-we-26h" id="cMr-gz-ofj"/>
124 <outlet property="maxPeopleLabel" destination="fYQ-YQ-Bw4" id="Utn-Bd-dVK"/>
82 <outlet property="maxPeopleLabel" destination="fYQ-YQ-Bw4" id="Utn-Bd-dVK"/>
125− <outlet property="swipableView" destination="m4Q-p5-icZ" id="1gn-M1-Lfg"/>
126 <outlet property="tableImageView" destination="4u1-cy-ujz" id="d1Z-TE-u1y"/>
83 <outlet property="tableImageView" destination="4u1-cy-ujz" id="d1Z-TE-u1y"/>
127 <outlet property="timeLabel" destination="EOi-j1-jQM" id="CV4-G4-wUw"/>
84 <outlet property="timeLabel" destination="EOi-j1-jQM" id="CV4-G4-wUw"/>
128 </connections>
85 </connections>
129 <point key="canvasLocation" x="165" y="350"/>
86 <point key="canvasLocation" x="165" y="350"/>
130 </view>
87 </view>
131− <panGestureRecognizer minimumNumberOfTouches="1" id="PRF-YF-b6m">
132− <connections>
133− <action selector="swipeImageView:" destination="iN0-l3-epB" id="zIb-fN-SWJ"/>
134− </connections>
135− </panGestureRecognizer>
136 </objects>
88 </objects>
137 <resources>
89 <resources>
138 <image name="Table" width="102" height="117"/>
90 <image name="Table" width="102" height="117"/>
Hookah Tau/Controllers/Client/Reservations/Maps/1/MapView1.swiftnew file+170
@@ -0,0 +1,170 @@
1+//
2+// MapView1.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 06/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+protocol MapHandler: class {
12+ func handleTap(withId id: Int)
13+}
14+
15+class MapView1: MapImageScroll, UIScrollViewDelegate {
16+
17+ // MARK: - Properties
18+
19+ @IBOutlet weak var shadow: UIView! {
20+ didSet {
21+ super.shadowView = shadow
22+ shadow.alpha = 0
23+ }
24+ }
25+
26+ override var handler: MapHandler? {
27+ didSet {
28+ handleTap(button: firstTable, id: 1)
29+ }
30+ }
31+
32+
33+ @IBOutlet weak var scrollView: UIScrollView! {
34+ didSet {
35+ super.scrollViewParent = scrollView
36+ }
37+ }
38+
39+ @IBOutlet weak var image: UIImageView! {
40+ didSet {
41+ super.img = image
42+ }
43+ }
44+
45+ @IBOutlet weak var firstTable: TableButton! {
46+ didSet {
47+ let style = TableButtonStyle()
48+ style.apply(to: firstTable)
49+ firstTable.transform = CGAffineTransform(rotationAngle: CGFloat(-Double.pi / 4));
50+ }
51+ }
52+
53+
54+ @IBOutlet weak var secondTable: TableButton! {
55+ didSet {
56+ let style = TableButtonStyle()
57+ style.apply(to: secondTable)
58+ }
59+ }
60+
61+ @IBOutlet weak var trirdTable: TableButton! {
62+ didSet {
63+ let style = TableButtonStyle()
64+ style.apply(to: trirdTable)
65+ }
66+ }
67+
68+ @IBOutlet weak var forthTable: TableButton! {
69+ didSet {
70+ let style = TableButtonStyle()
71+ style.apply(to: forthTable)
72+ }
73+ }
74+
75+ @IBOutlet weak var fifthTable: TableButton! {
76+ didSet {
77+ let style = TableButtonStyle()
78+ style.apply(to: fifthTable)
79+ }
80+ }
81+
82+ @IBOutlet weak var sixthTable: TableButton! {
83+ didSet {
84+ let style = TableButtonStyle()
85+ style.apply(to: sixthTable)
86+ }
87+ }
88+
89+ @IBOutlet weak var seventhTable: TableButton! {
90+ didSet {
91+ let style = TableButtonStyle()
92+ style.apply(to: seventhTable)
93+ }
94+ }
95+
96+ @IBOutlet weak var eighthTable: TableButton! {
97+ didSet {
98+ let style = TableButtonStyle()
99+ style.apply(to: eighthTable)
100+ }
101+ }
102+
103+ // MARK: - Lifecycle
104+
105+ override func awakeFromNib() {
106+ super.awakeFromNib()
107+
108+ self.scrollView.delegate = self
109+ self.scrollView.delaysContentTouches = false
110+
111+ image.addSubview(shadow)
112+ image.addSubview(firstTable)
113+ image.addSubview(secondTable)
114+ image.addSubview(trirdTable)
115+ image.addSubview(forthTable)
116+ image.addSubview(fifthTable)
117+ image.addSubview(sixthTable)
118+ image.addSubview(seventhTable)
119+ image.addSubview(eighthTable)
120+
121+ tables[1] = firstTable
122+ tables[2] = secondTable
123+ tables[3] = trirdTable
124+ tables[4] = forthTable
125+ tables[5] = fifthTable
126+ tables[6] = sixthTable
127+ tables[7] = seventhTable
128+ tables[8] = eighthTable
129+
130+ configureFor(image.frame.size)
131+ }
132+
133+ @IBAction func chooseSecondTable(_ sender: TableButton) {
134+ handleTap(button: sender, id: 2)
135+ }
136+
137+ @IBAction func chooseFifthTable(_ sender: TableButton) {
138+ handleTap(button: sender, id: 5)
139+ }
140+
141+ @IBAction func choseSeventhTable(_ sender: TableButton) {
142+ handleTap(button: sender, id: 7)
143+ }
144+
145+ @IBAction func chooseFirstTable(_ sender: TableButton) {
146+ handleTap(button: sender, id: 1)
147+ }
148+
149+ @IBAction func chooseThirdTable(_ sender: TableButton) {
150+ handleTap(button: sender, id: 3)
151+ }
152+
153+ @IBAction func choose4thTable(_ sender: TableButton) {
154+ handleTap(button: sender, id: 4)
155+ }
156+
157+ @IBAction func choose6thTable(_ sender: TableButton) {
158+ handleTap(button: sender, id: 6)
159+ }
160+
161+ @IBAction func choose8thTable(_ sender: TableButton) {
162+ handleTap(button: sender, id: 8)
163+ }
164+
165+ // MARK: - Scroll view
166+
167+ func viewForZooming(in scrollView: UIScrollView) -> UIView? {
168+ self.image
169+ }
170+}
Hookah Tau/Controllers/Client/Reservations/Maps/1/MapView1.xibnew file+175
@@ -0,0 +1,175 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3+ <device id="retina4_7" orientation="portrait" appearance="light"/>
4+ <dependencies>
5+ <deployment identifier="iOS"/>
6+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
7+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
8+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
9+ </dependencies>
10+ <objects>
11+ <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
12+ <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
13+ <view clipsSubviews="YES" contentMode="scaleToFill" id="iN0-l3-epB" customClass="MapView1" customModule="Hookah_Tau" customModuleProvider="target">
14+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
15+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
16+ <subviews>
17+ <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="FuE-LV-NBR">
18+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
19+ <subviews>
20+ <imageView clipsSubviews="YES" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="map_1" translatesAutoresizingMaskIntoConstraints="NO" id="btD-Wl-3P6">
21+ <rect key="frame" x="0.0" y="0.0" width="536" height="636"/>
22+ </imageView>
23+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Y23-Wu-4BW" customClass="TableButton" customModule="Hookah_Tau" customModuleProvider="target">
24+ <rect key="frame" x="44" y="37" width="102" height="119"/>
25+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
26+ <constraints>
27+ <constraint firstAttribute="width" constant="102" id="EKZ-Ny-4RQ"/>
28+ <constraint firstAttribute="height" constant="119" id="u1N-5d-cFR"/>
29+ </constraints>
30+ <connections>
31+ <action selector="chooseFirstTable:" destination="iN0-l3-epB" eventType="touchUpInside" id="1O9-7Q-a8g"/>
32+ </connections>
33+ </button>
34+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="xsw-fJ-8cN" customClass="TableButton" customModule="Hookah_Tau" customModuleProvider="target">
35+ <rect key="frame" x="209" y="116" width="131" height="117"/>
36+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
37+ <constraints>
38+ <constraint firstAttribute="width" constant="131" id="2xi-Oz-ceC"/>
39+ <constraint firstAttribute="height" constant="117" id="xPD-fh-9Zg"/>
40+ </constraints>
41+ <connections>
42+ <action selector="chooseSecondTable:" destination="iN0-l3-epB" eventType="touchUpInside" id="5XE-gZ-DyJ"/>
43+ </connections>
44+ </button>
45+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="onR-hl-Q2x" customClass="TableButton" customModule="Hookah_Tau" customModuleProvider="target">
46+ <rect key="frame" x="60" y="213" width="102" height="119"/>
47+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
48+ <constraints>
49+ <constraint firstAttribute="height" constant="119" id="JWf-x8-7CB"/>
50+ <constraint firstAttribute="width" constant="102" id="LZN-1O-mIP"/>
51+ </constraints>
52+ <connections>
53+ <action selector="choose6thTable:" destination="iN0-l3-epB" eventType="touchUpInside" id="gA4-cd-CZo"/>
54+ </connections>
55+ </button>
56+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Fw9-l6-N8a" customClass="TableButton" customModule="Hookah_Tau" customModuleProvider="target">
57+ <rect key="frame" x="60" y="350" width="102" height="120"/>
58+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
59+ <constraints>
60+ <constraint firstAttribute="height" constant="120" id="2Vp-Ip-7cO"/>
61+ <constraint firstAttribute="width" constant="102" id="2hI-75-UCM"/>
62+ </constraints>
63+ <connections>
64+ <action selector="choseSeventhTable:" destination="iN0-l3-epB" eventType="touchUpInside" id="94e-DE-SAJ"/>
65+ </connections>
66+ </button>
67+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="KJZ-Nl-7hH" customClass="TableButton" customModule="Hookah_Tau" customModuleProvider="target">
68+ <rect key="frame" x="222" y="273" width="104" height="120"/>
69+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
70+ <constraints>
71+ <constraint firstAttribute="height" constant="120" id="Ifu-kd-YHH"/>
72+ <constraint firstAttribute="width" constant="104" id="phm-aP-6dx"/>
73+ </constraints>
74+ <connections>
75+ <action selector="chooseFifthTable:" destination="iN0-l3-epB" eventType="touchUpInside" id="Tc2-ir-tPU"/>
76+ </connections>
77+ </button>
78+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="mUw-im-1BC" customClass="TableButton" customModule="Hookah_Tau" customModuleProvider="target">
79+ <rect key="frame" x="222" y="433" width="92" height="67"/>
80+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
81+ <constraints>
82+ <constraint firstAttribute="width" constant="92" id="AJ6-ku-4R3"/>
83+ <constraint firstAttribute="height" constant="67" id="jVI-uI-YDT"/>
84+ </constraints>
85+ <connections>
86+ <action selector="choose8thTable:" destination="iN0-l3-epB" eventType="touchUpInside" id="C8U-7T-Bsc"/>
87+ </connections>
88+ </button>
89+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="aIM-Qn-Uhl" customClass="TableButton" customModule="Hookah_Tau" customModuleProvider="target">
90+ <rect key="frame" x="386" y="116" width="131" height="119"/>
91+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
92+ <constraints>
93+ <constraint firstAttribute="width" constant="131" id="Lhq-jM-PQ0"/>
94+ <constraint firstAttribute="height" constant="119" id="vsx-u8-L6o"/>
95+ </constraints>
96+ <connections>
97+ <action selector="chooseThirdTable:" destination="iN0-l3-epB" eventType="touchUpInside" id="eQS-Wo-hTf"/>
98+ </connections>
99+ </button>
100+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wpi-6E-c5p" customClass="TableButton" customModule="Hookah_Tau" customModuleProvider="target">
101+ <rect key="frame" x="399" y="275" width="104" height="119"/>
102+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
103+ <constraints>
104+ <constraint firstAttribute="height" constant="119" id="Lib-3b-h8b"/>
105+ <constraint firstAttribute="width" constant="104" id="nch-5L-cRo"/>
106+ </constraints>
107+ <connections>
108+ <action selector="choose4thTable:" destination="iN0-l3-epB" eventType="touchUpInside" id="U5p-In-FEa"/>
109+ </connections>
110+ </button>
111+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="zbE-1g-Qz4">
112+ <rect key="frame" x="0.0" y="0.0" width="536" height="636"/>
113+ <color key="backgroundColor" white="1" alpha="0.69999999999999996" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
114+ </view>
115+ </subviews>
116+ <constraints>
117+ <constraint firstItem="btD-Wl-3P6" firstAttribute="leading" secondItem="Fw9-l6-N8a" secondAttribute="trailing" constant="-162" id="1lP-Gg-qgW"/>
118+ <constraint firstItem="btD-Wl-3P6" firstAttribute="leading" secondItem="onR-hl-Q2x" secondAttribute="trailing" constant="-162" id="1vg-zK-zAf"/>
119+ <constraint firstItem="Y23-Wu-4BW" firstAttribute="leading" secondItem="BDw-f7-kaJ" secondAttribute="leading" constant="44" id="913-M2-zhQ"/>
120+ <constraint firstItem="btD-Wl-3P6" firstAttribute="leading" secondItem="FuE-LV-NBR" secondAttribute="leading" id="ALg-fi-eby"/>
121+ <constraint firstItem="Fw9-l6-N8a" firstAttribute="top" secondItem="btD-Wl-3P6" secondAttribute="bottom" constant="-286" id="DyF-KK-1VM"/>
122+ <constraint firstItem="btD-Wl-3P6" firstAttribute="top" secondItem="FuE-LV-NBR" secondAttribute="top" id="EKh-3k-EL9"/>
123+ <constraint firstItem="mUw-im-1BC" firstAttribute="top" secondItem="KJZ-Nl-7hH" secondAttribute="bottom" constant="40" id="GaI-BE-NZy"/>
124+ <constraint firstItem="btD-Wl-3P6" firstAttribute="width" secondItem="FuE-LV-NBR" secondAttribute="width" priority="250" id="MDs-kk-aza"/>
125+ <constraint firstItem="btD-Wl-3P6" firstAttribute="top" secondItem="aIM-Qn-Uhl" secondAttribute="bottom" constant="-235" id="NwR-U0-V6F"/>
126+ <constraint firstItem="mUw-im-1BC" firstAttribute="top" secondItem="btD-Wl-3P6" secondAttribute="bottom" constant="-203" id="UXz-DD-dPH"/>
127+ <constraint firstItem="mUw-im-1BC" firstAttribute="leading" secondItem="Fw9-l6-N8a" secondAttribute="trailing" constant="60" id="V4u-ko-iFV"/>
128+ <constraint firstItem="KJZ-Nl-7hH" firstAttribute="top" secondItem="xsw-fJ-8cN" secondAttribute="bottom" constant="40" id="XFV-l2-chC"/>
129+ <constraint firstItem="btD-Wl-3P6" firstAttribute="top" secondItem="onR-hl-Q2x" secondAttribute="bottom" constant="-332" id="XNT-i4-1l8"/>
130+ <constraint firstItem="aIM-Qn-Uhl" firstAttribute="leading" secondItem="xsw-fJ-8cN" secondAttribute="trailing" constant="46" id="XVo-Ke-EGt"/>
131+ <constraint firstAttribute="trailing" secondItem="btD-Wl-3P6" secondAttribute="trailing" id="Ybd-7M-6Ft"/>
132+ <constraint firstItem="wpi-6E-c5p" firstAttribute="leading" secondItem="KJZ-Nl-7hH" secondAttribute="trailing" constant="73" id="cy7-0f-qGh"/>
133+ <constraint firstItem="wpi-6E-c5p" firstAttribute="top" secondItem="aIM-Qn-Uhl" secondAttribute="bottom" constant="40" id="dek-gp-H4X"/>
134+ <constraint firstItem="btD-Wl-3P6" firstAttribute="height" secondItem="FuE-LV-NBR" secondAttribute="height" priority="250" id="fSd-Le-0Bc"/>
135+ <constraint firstAttribute="bottom" secondItem="btD-Wl-3P6" secondAttribute="bottom" id="neR-bl-aSU"/>
136+ <constraint firstItem="KJZ-Nl-7hH" firstAttribute="leading" secondItem="onR-hl-Q2x" secondAttribute="trailing" constant="60" id="pka-Xh-e1c"/>
137+ <constraint firstItem="xsw-fJ-8cN" firstAttribute="leading" secondItem="onR-hl-Q2x" secondAttribute="trailing" constant="47" id="r9E-ps-zBG"/>
138+ <constraint firstItem="Y23-Wu-4BW" firstAttribute="top" secondItem="BDw-f7-kaJ" secondAttribute="top" constant="37" id="rgb-Sa-T0E"/>
139+ </constraints>
140+ <viewLayoutGuide key="contentLayoutGuide" id="BDw-f7-kaJ"/>
141+ <viewLayoutGuide key="frameLayoutGuide" id="KRK-7M-mdV"/>
142+ </scrollView>
143+ </subviews>
144+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
145+ <constraints>
146+ <constraint firstItem="FuE-LV-NBR" firstAttribute="trailing" secondItem="vUN-kp-3ea" secondAttribute="trailing" id="0zj-Rn-Ogr"/>
147+ <constraint firstItem="FuE-LV-NBR" firstAttribute="bottom" secondItem="vUN-kp-3ea" secondAttribute="bottom" id="87c-vf-vMB"/>
148+ <constraint firstItem="btD-Wl-3P6" firstAttribute="trailing" secondItem="zbE-1g-Qz4" secondAttribute="trailing" id="ASF-YJ-jez"/>
149+ <constraint firstItem="zbE-1g-Qz4" firstAttribute="leading" secondItem="btD-Wl-3P6" secondAttribute="leading" id="Ec8-Qz-Dn3"/>
150+ <constraint firstItem="btD-Wl-3P6" firstAttribute="bottom" secondItem="zbE-1g-Qz4" secondAttribute="bottom" id="UFk-wW-dCx"/>
151+ <constraint firstItem="FuE-LV-NBR" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" id="bhi-Ox-g2b"/>
152+ <constraint firstItem="zbE-1g-Qz4" firstAttribute="top" secondItem="btD-Wl-3P6" secondAttribute="top" id="j32-id-sD4"/>
153+ <constraint firstItem="FuE-LV-NBR" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="jLf-Pz-gy1"/>
154+ </constraints>
155+ <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
156+ <connections>
157+ <outlet property="eighthTable" destination="mUw-im-1BC" id="x44-wS-xJg"/>
158+ <outlet property="fifthTable" destination="KJZ-Nl-7hH" id="KXs-8a-Fo8"/>
159+ <outlet property="firstTable" destination="Y23-Wu-4BW" id="9m3-Oy-Gwc"/>
160+ <outlet property="forthTable" destination="wpi-6E-c5p" id="26b-bH-kEA"/>
161+ <outlet property="image" destination="btD-Wl-3P6" id="Ou2-hT-jap"/>
162+ <outlet property="scrollView" destination="FuE-LV-NBR" id="pWc-cI-eXE"/>
163+ <outlet property="secondTable" destination="xsw-fJ-8cN" id="2vH-LX-qF0"/>
164+ <outlet property="seventhTable" destination="Fw9-l6-N8a" id="Jwb-yY-Opx"/>
165+ <outlet property="shadow" destination="zbE-1g-Qz4" id="tHA-7d-6Aa"/>
166+ <outlet property="sixthTable" destination="onR-hl-Q2x" id="Zi7-hJ-KLq"/>
167+ <outlet property="trirdTable" destination="aIM-Qn-Uhl" id="aFd-cB-neE"/>
168+ </connections>
169+ <point key="canvasLocation" x="-194.40000000000001" y="97.601199400299862"/>
170+ </view>
171+ </objects>
172+ <resources>
173+ <image name="map_1" width="536" height="636"/>
174+ </resources>
175+</document>
Hookah Tau/Controllers/Client/Reservations/Maps/2/MapView2.swiftnew file+152
@@ -0,0 +1,152 @@
1+//
2+// MapView2.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 28/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class MapView2: MapImageScroll {
12+
13+ // MARK: - Tables
14+
15+ @IBOutlet weak var table2: TableButton! {
16+ didSet {
17+ let style = TableButtonStyle()
18+ style.apply(to: table2)
19+ }
20+ }
21+
22+ @IBOutlet weak var table3: TableButton!{
23+ didSet {
24+ let style = TableButtonStyle()
25+ style.apply(to: table3)
26+ }
27+ }
28+
29+ @IBOutlet weak var table4: TableButton! {
30+ didSet {
31+ let style = TableButtonStyle()
32+ style.apply(to: table4)
33+ }
34+ }
35+
36+ @IBOutlet weak var table5: TableButton! {
37+ didSet {
38+ let style = TableButtonStyle()
39+ style.apply(to: table5)
40+ }
41+ }
42+
43+ @IBOutlet weak var table6: TableButton! {
44+ didSet {
45+ let style = TableButtonStyle()
46+ style.apply(to: table6)
47+ }
48+ }
49+
50+ @IBOutlet weak var table7: TableButton! {
51+ didSet {
52+ let style = TableButtonStyle()
53+ style.apply(to: table7)
54+ }
55+ }
56+
57+ @IBOutlet weak var table8: TableButton! {
58+ didSet {
59+ let style = TableButtonStyle()
60+ style.apply(to: table8)
61+ }
62+ }
63+
64+ override var handler: MapHandler? {
65+ didSet {
66+ handleTap(button: table8, id: 8)
67+ }
68+ }
69+
70+ // MARK: - Super props
71+
72+ @IBOutlet weak var scrollView: UIScrollView! {
73+ didSet {
74+ super.scrollViewParent = scrollView
75+ }
76+ }
77+
78+ @IBOutlet weak var image: UIImageView! {
79+ didSet {
80+ super.img = image
81+ }
82+ }
83+
84+ @IBOutlet weak var shadow: UIView! {
85+ didSet {
86+ super.shadowView = shadow
87+ shadow.alpha = 0
88+ }
89+ }
90+
91+ // MARK: - Lifecycle
92+
93+ override func awakeFromNib() {
94+ super.awakeFromNib()
95+
96+ self.scrollView.delegate = self
97+ self.scrollView.delaysContentTouches = false
98+
99+ tables[2] = table2
100+ tables[3] = table3
101+ tables[4] = table4
102+ tables[5] = table5
103+ tables[6] = table6
104+ tables[7] = table7
105+ tables[8] = table8
106+
107+ tables.values.forEach { image.addSubview($0) }
108+ image.addSubview(shadowView)
109+
110+ configureFor(image.frame.size)
111+ }
112+
113+ // MARK: - Taps
114+
115+ @IBAction func tap2(_ sender: TableButton) {
116+ handleTap(button: sender, id: 2)
117+ }
118+
119+ @IBAction func tap3(_ sender: TableButton) {
120+ handleTap(button: sender, id: 3)
121+ }
122+
123+ @IBAction func tap4(_ sender: TableButton) {
124+ handleTap(button: sender, id: 4)
125+ }
126+
127+ @IBAction func tap5(_ sender: TableButton) {
128+ handleTap(button: sender, id: 5)
129+ }
130+
131+ @IBAction func tap6(_ sender: TableButton) {
132+ handleTap(button: sender, id: 6)
133+ }
134+
135+ @IBAction func tap7(_ sender: TableButton) {
136+ handleTap(button: sender, id: 7)
137+ }
138+
139+ @IBAction func tap8(_ sender: TableButton) {
140+ handleTap(button: sender, id: 8)
141+ }
142+
143+}
144+
145+// MARK: - Scroll view
146+
147+extension MapView2: UIScrollViewDelegate {
148+
149+ func viewForZooming(in scrollView: UIScrollView) -> UIView? {
150+ self.image
151+ }
152+}
Hookah Tau/Controllers/Client/Reservations/Maps/2/MapView2.xibnew file+155
@@ -0,0 +1,155 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14868" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3+ <device id="retina4_7" orientation="portrait" appearance="light"/>
4+ <dependencies>
5+ <deployment identifier="iOS"/>
6+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14824"/>
7+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
8+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
9+ </dependencies>
10+ <objects>
11+ <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
12+ <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
13+ <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="MapView2" customModule="Hookah_Tau" customModuleProvider="target">
14+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
15+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
16+ <subviews>
17+ <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="emi-wK-dKY">
18+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
19+ <subviews>
20+ <imageView clipsSubviews="YES" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="map_2" translatesAutoresizingMaskIntoConstraints="NO" id="Obf-Cs-bNe">
21+ <rect key="frame" x="0.0" y="0.0" width="683" height="329"/>
22+ </imageView>
23+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="N1V-4M-SuG" customClass="TableButton" customModule="Hookah_Tau" customModuleProvider="target">
24+ <rect key="frame" x="70" y="18" width="92" height="66"/>
25+ <constraints>
26+ <constraint firstAttribute="width" constant="92" id="Cd4-io-y5G"/>
27+ <constraint firstAttribute="height" constant="66" id="wSz-LG-S6h"/>
28+ </constraints>
29+ <connections>
30+ <action selector="tap8:" destination="iN0-l3-epB" eventType="touchUpInside" id="23l-0c-N7c"/>
31+ </connections>
32+ </button>
33+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="2KA-Fl-otO" customClass="TableButton" customModule="Hookah_Tau" customModuleProvider="target">
34+ <rect key="frame" x="190" y="17" width="104" height="95"/>
35+ <constraints>
36+ <constraint firstAttribute="height" constant="95" id="aIs-ch-V6c"/>
37+ <constraint firstAttribute="width" constant="104" id="vje-NJ-ttB"/>
38+ </constraints>
39+ <connections>
40+ <action selector="tap7:" destination="iN0-l3-epB" eventType="touchUpInside" id="GuY-TU-Mbv"/>
41+ </connections>
42+ </button>
43+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Uyw-7g-fgk" customClass="TableButton" customModule="Hookah_Tau" customModuleProvider="target">
44+ <rect key="frame" x="190" y="190" width="68" height="94"/>
45+ <constraints>
46+ <constraint firstAttribute="width" constant="68" id="IyU-Gp-dLh"/>
47+ <constraint firstAttribute="height" constant="94" id="N7G-Ua-jrx"/>
48+ </constraints>
49+ <connections>
50+ <action selector="tap4:" destination="iN0-l3-epB" eventType="touchUpInside" id="mUZ-2d-VkM"/>
51+ </connections>
52+ </button>
53+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Sko-xU-trE" customClass="TableButton" customModule="Hookah_Tau" customModuleProvider="target">
54+ <rect key="frame" x="286" y="177" width="103" height="118"/>
55+ <constraints>
56+ <constraint firstAttribute="width" constant="103" id="7QV-8e-19v"/>
57+ <constraint firstAttribute="height" constant="118" id="ApA-IR-eB1"/>
58+ </constraints>
59+ <connections>
60+ <action selector="tap3:" destination="iN0-l3-epB" eventType="touchUpInside" id="Vx6-bj-c44"/>
61+ </connections>
62+ </button>
63+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="pMi-ey-nl3" customClass="TableButton" customModule="Hookah_Tau" customModuleProvider="target">
64+ <rect key="frame" x="323" y="18" width="102" height="95"/>
65+ <constraints>
66+ <constraint firstAttribute="width" constant="102" id="9Pn-tv-fnZ"/>
67+ <constraint firstAttribute="height" constant="95" id="PXg-Gy-7TO"/>
68+ </constraints>
69+ <connections>
70+ <action selector="tap6:" destination="iN0-l3-epB" eventType="touchUpInside" id="ge0-j5-gLW"/>
71+ </connections>
72+ </button>
73+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="S2Z-ye-LWO" customClass="TableButton" customModule="Hookah_Tau" customModuleProvider="target">
74+ <rect key="frame" x="455" y="18" width="102" height="94"/>
75+ <constraints>
76+ <constraint firstAttribute="width" constant="102" id="E4e-Gs-QYc"/>
77+ <constraint firstAttribute="height" constant="94" id="NLu-i5-Mr6"/>
78+ </constraints>
79+ <connections>
80+ <action selector="tap5:" destination="iN0-l3-epB" eventType="touchUpInside" id="LkA-Xo-cwd"/>
81+ </connections>
82+ </button>
83+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="QLb-ch-cuY" customClass="TableButton" customModule="Hookah_Tau" customModuleProvider="target">
84+ <rect key="frame" x="426" y="164" width="131" height="146"/>
85+ <constraints>
86+ <constraint firstAttribute="width" constant="131" id="I3b-2n-Bgm"/>
87+ <constraint firstAttribute="height" constant="146" id="Tlu-Sy-7GC"/>
88+ </constraints>
89+ <connections>
90+ <action selector="tap2:" destination="iN0-l3-epB" eventType="touchUpInside" id="7N9-GF-PKo"/>
91+ </connections>
92+ </button>
93+ <view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Kvb-P4-fah">
94+ <rect key="frame" x="0.0" y="0.0" width="683" height="329"/>
95+ <color key="backgroundColor" white="1" alpha="0.6951519691780822" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
96+ </view>
97+ </subviews>
98+ <constraints>
99+ <constraint firstItem="Sko-xU-trE" firstAttribute="top" secondItem="pMi-ey-nl3" secondAttribute="bottom" constant="64" id="6aW-Tf-64S"/>
100+ <constraint firstItem="2KA-Fl-otO" firstAttribute="leading" secondItem="N1V-4M-SuG" secondAttribute="trailing" constant="28" id="7Ei-ax-wx6"/>
101+ <constraint firstItem="N1V-4M-SuG" firstAttribute="leading" secondItem="Obf-Cs-bNe" secondAttribute="leading" constant="70" id="7Rw-z2-eKD"/>
102+ <constraint firstItem="2KA-Fl-otO" firstAttribute="top" secondItem="Obf-Cs-bNe" secondAttribute="top" constant="17" id="Cos-le-6UH"/>
103+ <constraint firstItem="Obf-Cs-bNe" firstAttribute="trailing" secondItem="QLb-ch-cuY" secondAttribute="trailing" constant="126" id="FhV-De-jji"/>
104+ <constraint firstItem="Obf-Cs-bNe" firstAttribute="height" secondItem="emi-wK-dKY" secondAttribute="height" priority="250" id="H4t-Mm-gPX"/>
105+ <constraint firstItem="QLb-ch-cuY" firstAttribute="top" secondItem="S2Z-ye-LWO" secondAttribute="bottom" constant="52" id="HJJ-ia-caS"/>
106+ <constraint firstItem="Uyw-7g-fgk" firstAttribute="top" secondItem="2KA-Fl-otO" secondAttribute="bottom" constant="78" id="MOn-dn-UV6"/>
107+ <constraint firstItem="Kvb-P4-fah" firstAttribute="trailing" secondItem="Obf-Cs-bNe" secondAttribute="trailing" id="N0a-np-nCu"/>
108+ <constraint firstItem="Sko-xU-trE" firstAttribute="leading" secondItem="Uyw-7g-fgk" secondAttribute="trailing" constant="28" id="SRb-lA-XTw"/>
109+ <constraint firstAttribute="trailing" secondItem="Obf-Cs-bNe" secondAttribute="trailing" id="Saf-Tc-0J5"/>
110+ <constraint firstItem="pMi-ey-nl3" firstAttribute="top" secondItem="Obf-Cs-bNe" secondAttribute="top" constant="18" id="Vov-VH-rha"/>
111+ <constraint firstAttribute="bottom" secondItem="Obf-Cs-bNe" secondAttribute="bottom" id="WPI-RB-B4A"/>
112+ <constraint firstItem="Kvb-P4-fah" firstAttribute="leading" secondItem="Obf-Cs-bNe" secondAttribute="leading" id="YgQ-Rl-iQD"/>
113+ <constraint firstItem="Kvb-P4-fah" firstAttribute="top" secondItem="Obf-Cs-bNe" secondAttribute="top" id="aPY-ua-gcI"/>
114+ <constraint firstItem="pMi-ey-nl3" firstAttribute="leading" secondItem="2KA-Fl-otO" secondAttribute="trailing" constant="29" id="aR4-Q4-cQ3"/>
115+ <constraint firstItem="Obf-Cs-bNe" firstAttribute="width" secondItem="emi-wK-dKY" secondAttribute="width" priority="250" id="dkc-x7-EPa"/>
116+ <constraint firstItem="S2Z-ye-LWO" firstAttribute="top" secondItem="Obf-Cs-bNe" secondAttribute="top" constant="18" id="e7Q-BO-paM"/>
117+ <constraint firstItem="S2Z-ye-LWO" firstAttribute="leading" secondItem="pMi-ey-nl3" secondAttribute="trailing" constant="30" id="kwk-Ex-x1d"/>
118+ <constraint firstItem="Kvb-P4-fah" firstAttribute="bottom" secondItem="Obf-Cs-bNe" secondAttribute="bottom" id="nF9-OF-52s"/>
119+ <constraint firstItem="QLb-ch-cuY" firstAttribute="leading" secondItem="Sko-xU-trE" secondAttribute="trailing" constant="37" id="pYH-nL-sHE"/>
120+ <constraint firstItem="N1V-4M-SuG" firstAttribute="top" secondItem="Obf-Cs-bNe" secondAttribute="top" constant="18" id="uVj-fC-gUh"/>
121+ <constraint firstItem="Obf-Cs-bNe" firstAttribute="top" secondItem="emi-wK-dKY" secondAttribute="top" id="uXg-WE-LVS"/>
122+ <constraint firstItem="Obf-Cs-bNe" firstAttribute="leading" secondItem="emi-wK-dKY" secondAttribute="leading" id="zgm-8D-6cN"/>
123+ </constraints>
124+ <viewLayoutGuide key="contentLayoutGuide" id="heY-yN-cxj"/>
125+ <viewLayoutGuide key="frameLayoutGuide" id="Si0-40-mPV"/>
126+ </scrollView>
127+ </subviews>
128+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
129+ <constraints>
130+ <constraint firstItem="emi-wK-dKY" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" id="0jl-ST-MHN"/>
131+ <constraint firstItem="emi-wK-dKY" firstAttribute="trailing" secondItem="vUN-kp-3ea" secondAttribute="trailing" id="AsB-3G-0nu"/>
132+ <constraint firstItem="emi-wK-dKY" firstAttribute="bottom" secondItem="vUN-kp-3ea" secondAttribute="bottom" id="g8Y-gE-OhP"/>
133+ <constraint firstItem="emi-wK-dKY" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="rLX-cv-To7"/>
134+ </constraints>
135+ <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
136+ <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
137+ <connections>
138+ <outlet property="image" destination="Obf-Cs-bNe" id="X9U-1H-cye"/>
139+ <outlet property="scrollView" destination="emi-wK-dKY" id="9ZQ-1W-Ig5"/>
140+ <outlet property="shadow" destination="Kvb-P4-fah" id="Qaa-1I-iuh"/>
141+ <outlet property="table2" destination="QLb-ch-cuY" id="13h-54-jLj"/>
142+ <outlet property="table3" destination="Sko-xU-trE" id="6rG-Ov-XI4"/>
143+ <outlet property="table4" destination="Uyw-7g-fgk" id="je7-hC-uEg"/>
144+ <outlet property="table5" destination="S2Z-ye-LWO" id="WjB-f2-q3D"/>
145+ <outlet property="table6" destination="pMi-ey-nl3" id="XsT-cC-4o9"/>
146+ <outlet property="table7" destination="2KA-Fl-otO" id="baj-Ek-51Q"/>
147+ <outlet property="table8" destination="N1V-4M-SuG" id="8a7-Kv-JqN"/>
148+ </connections>
149+ <point key="canvasLocation" x="138.40000000000001" y="120.98950524737631"/>
150+ </view>
151+ </objects>
152+ <resources>
153+ <image name="map_2" width="683" height="329"/>
154+ </resources>
155+</document>
Hookah Tau/Controllers/Client/Reservations/Maps/MapView.swiftnew file+103
@@ -0,0 +1,103 @@
1+//
2+// MapView.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 06/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+
12+class MapImageScroll: UIView {
13+
14+ weak var handler: MapHandler?
15+
16+ var scrollViewParent: UIScrollView!
17+
18+ var shadowView: UIView!
19+
20+ var img: UIImageView!
21+
22+ var tables: [Int: TableButton] = [:]
23+
24+ var zoomScale: CGFloat = 1.2
25+
26+ func scrollToTable(table: Int) {
27+ guard let tableButton = tables[table] else { return }
28+
29+ tableButton.select()
30+ img.bringSubviewToFront(shadowView)
31+ img.bringSubviewToFront(tableButton)
32+
33+ shadowView.alpha = 1
34+
35+ scrollViewParent.zoom(to: tableButton.frame, animated: true)
36+ scrollViewParent.contentOffset = CGPoint(x: scrollViewParent.contentOffset.x, y: scrollViewParent.contentOffset.y + 100)
37+ scrollViewParent.setZoomScale(zoomScale, animated: true)
38+ }
39+
40+ func scrollBack() {
41+ img.sendSubviewToBack(shadowView)
42+ shadowView.alpha = 0
43+ }
44+
45+ func configureFor(_ imageSize: CGSize) {
46+ self.scrollViewParent.contentSize = imageSize
47+ setMaxMinZoomScaleForCurrentBounds()
48+ }
49+
50+ func setMaxMinZoomScaleForCurrentBounds() {
51+ let boundsSize = self.scrollViewParent.bounds.size
52+ let imageSize = img.bounds.size
53+
54+ //1. calculate minimumZoomscale
55+ let xScale = boundsSize.width / imageSize.width
56+ let yScale = boundsSize.height / imageSize.height
57+ var minScale = min(xScale, yScale)
58+
59+ //2. calculate maximumZoomscale
60+ var maxScale: CGFloat = 1.0
61+ if minScale < 0.1 {
62+ maxScale = 0.3
63+ }
64+ if minScale >= 0.1 && minScale < 0.5 {
65+ maxScale = 0.7
66+ }
67+ if minScale >= 0.5 {
68+ maxScale = max(1.0, minScale)
69+ }
70+
71+ if img.frame.width < frame.width {
72+ let scaleW = frame.width / img.frame.width
73+ minScale = scaleW
74+ }
75+
76+ if img.frame.height < frame.height {
77+ let scaleH = frame.height / img.frame.height
78+ maxScale = scaleH
79+ }
80+
81+ img.transform = CGAffineTransform(scaleX: minScale, y: minScale)
82+
83+ self.scrollViewParent.maximumZoomScale = maxScale
84+ self.scrollViewParent.minimumZoomScale = minScale
85+ }
86+
87+ // Method for handling button taps.
88+ func handleTap(button: TableButton, id: Int) {
89+ tables.values.forEach { $0.unselect() }
90+ button.select()
91+ handler?.handleTap(withId: id)
92+ }
93+}
94+
95+extension MapImageScroll {
96+
97+ func performUpdate(inactive: [Int]) {
98+
99+ let active = Set(tables.keys).subtracting(inactive)
100+ active.forEach { ind in tables[ind]?.active() }
101+ inactive.forEach { ind in tables[ind]?.inactive() }
102+ }
103+}
Hookah Tau/Controllers/Client/Reservations/Reservation.storyboardnew file+158
@@ -0,0 +1,158 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3+ <device id="retina6_1" orientation="portrait" appearance="light"/>
4+ <dependencies>
5+ <deployment identifier="iOS"/>
6+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
7+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
8+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
9+ </dependencies>
10+ <scenes>
11+ <!--First Step Reservation View Controller-->
12+ <scene sceneID="EKz-nU-dQ1">
13+ <objects>
14+ <viewController storyboardIdentifier="ReservationViewController" id="Fha-pE-okd" customClass="FirstStepReservationViewController" customModule="Hookah_Tau" customModuleProvider="target" sceneMemberID="viewController">
15+ <view key="view" contentMode="scaleToFill" id="cwG-Fa-gXD">
16+ <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
17+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
18+ <subviews>
19+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="RRF-jh-Xme">
20+ <rect key="frame" x="0.0" y="671" width="414" height="225"/>
21+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
22+ <constraints>
23+ <constraint firstAttribute="height" constant="225" id="43Q-xl-4hv"/>
24+ </constraints>
25+ </view>
26+ </subviews>
27+ <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
28+ <constraints>
29+ <constraint firstItem="RRF-jh-Xme" firstAttribute="leading" secondItem="MXi-ad-tf6" secondAttribute="leading" id="Bs8-yy-Zht"/>
30+ <constraint firstAttribute="bottom" secondItem="RRF-jh-Xme" secondAttribute="bottom" id="KZv-en-hrr"/>
31+ <constraint firstItem="RRF-jh-Xme" firstAttribute="trailing" secondItem="MXi-ad-tf6" secondAttribute="trailing" id="ehP-xe-Opz"/>
32+ </constraints>
33+ <viewLayoutGuide key="safeArea" id="MXi-ad-tf6"/>
34+ </view>
35+ <connections>
36+ <outlet property="childContentView" destination="RRF-jh-Xme" id="blV-8a-srQ"/>
37+ </connections>
38+ </viewController>
39+ <placeholder placeholderIdentifier="IBFirstResponder" id="TB1-Hv-wQ6" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
40+ </objects>
41+ <point key="canvasLocation" x="-140.57971014492756" y="-38.169642857142854"/>
42+ </scene>
43+ <!--Second Step Reservation View Controller-->
44+ <scene sceneID="NON-tn-ls5">
45+ <objects>
46+ <viewController storyboardIdentifier="SecondStepReservationViewController" id="OAC-Ui-1o4" customClass="SecondStepReservationViewController" customModule="Hookah_Tau" customModuleProvider="target" sceneMemberID="viewController">
47+ <view key="view" contentMode="scaleToFill" id="dZe-8U-Rs6">
48+ <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
49+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
50+ <subviews>
51+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="NBJ-7w-vpV">
52+ <rect key="frame" x="0.0" y="544" width="414" height="352"/>
53+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
54+ <constraints>
55+ <constraint firstAttribute="height" constant="352" id="KKm-d5-Srx"/>
56+ </constraints>
57+ </view>
58+ </subviews>
59+ <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
60+ <constraints>
61+ <constraint firstItem="NBJ-7w-vpV" firstAttribute="leading" secondItem="YKO-1X-SYz" secondAttribute="leading" id="ZCK-Y0-Dnb"/>
62+ <constraint firstItem="NBJ-7w-vpV" firstAttribute="trailing" secondItem="YKO-1X-SYz" secondAttribute="trailing" id="ulb-DD-Mmh"/>
63+ <constraint firstAttribute="bottom" secondItem="NBJ-7w-vpV" secondAttribute="bottom" id="zEI-rl-4r7"/>
64+ </constraints>
65+ <viewLayoutGuide key="safeArea" id="YKO-1X-SYz"/>
66+ </view>
67+ <connections>
68+ <outlet property="childContentView" destination="NBJ-7w-vpV" id="aa6-nY-PUA"/>
69+ </connections>
70+ </viewController>
71+ <placeholder placeholderIdentifier="IBFirstResponder" id="rVf-H4-jP0" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
72+ </objects>
73+ <point key="canvasLocation" x="706" y="-38"/>
74+ </scene>
75+ <!--Third Step Reservation View Controller-->
76+ <scene sceneID="zRo-J6-3SG">
77+ <objects>
78+ <viewController storyboardIdentifier="ThirdStepReservationViewController" id="BZu-JB-b3i" customClass="ThirdStepReservationViewController" customModule="Hookah_Tau" customModuleProvider="target" sceneMemberID="viewController">
79+ <view key="view" contentMode="scaleToFill" id="3b8-mw-Gl1">
80+ <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
81+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
82+ <subviews>
83+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="e2X-iN-FVj">
84+ <rect key="frame" x="0.0" y="556" width="414" height="340"/>
85+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
86+ <constraints>
87+ <constraint firstAttribute="height" constant="340" id="5U9-DA-Asv"/>
88+ </constraints>
89+ </view>
90+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="lUx-gC-C8T">
91+ <rect key="frame" x="0.0" y="44" width="414" height="512"/>
92+ <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
93+ </view>
94+ </subviews>
95+ <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
96+ <constraints>
97+ <constraint firstItem="e2X-iN-FVj" firstAttribute="top" secondItem="lUx-gC-C8T" secondAttribute="bottom" id="3gm-hP-Bdu"/>
98+ <constraint firstItem="e2X-iN-FVj" firstAttribute="trailing" secondItem="gXx-re-YYI" secondAttribute="trailing" id="3mf-Ee-0xt"/>
99+ <constraint firstItem="e2X-iN-FVj" firstAttribute="leading" secondItem="gXx-re-YYI" secondAttribute="leading" id="H2H-uv-zpb"/>
100+ <constraint firstItem="gXx-re-YYI" firstAttribute="trailing" secondItem="lUx-gC-C8T" secondAttribute="trailing" id="N8u-eH-14m"/>
101+ <constraint firstAttribute="bottom" secondItem="e2X-iN-FVj" secondAttribute="bottom" id="nQM-9e-gfE"/>
102+ <constraint firstItem="lUx-gC-C8T" firstAttribute="leading" secondItem="gXx-re-YYI" secondAttribute="leading" id="oir-mE-qlS"/>
103+ <constraint firstItem="gXx-re-YYI" firstAttribute="top" secondItem="lUx-gC-C8T" secondAttribute="top" id="zDp-B5-m8M"/>
104+ </constraints>
105+ <viewLayoutGuide key="safeArea" id="gXx-re-YYI"/>
106+ </view>
107+ <connections>
108+ <outlet property="containerView" destination="e2X-iN-FVj" id="6Zy-M4-sSA"/>
109+ <outlet property="contentForMap" destination="lUx-gC-C8T" id="LBl-cs-Yav"/>
110+ </connections>
111+ </viewController>
112+ <placeholder placeholderIdentifier="IBFirstResponder" id="En2-ct-Lfj" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
113+ </objects>
114+ <point key="canvasLocation" x="1457.9710144927537" y="-38.169642857142854"/>
115+ </scene>
116+ <!--Change Reservation View Controller-->
117+ <scene sceneID="zZt-Ob-8J3">
118+ <objects>
119+ <viewController storyboardIdentifier="ChangeReservationViewController" id="qmR-St-vh2" customClass="ChangeReservationViewController" customModule="Hookah_Tau" customModuleProvider="target" sceneMemberID="viewController">
120+ <view key="view" contentMode="scaleToFill" id="mnl-c9-WGz">
121+ <rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
122+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
123+ <subviews>
124+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kon-kQ-rMb">
125+ <rect key="frame" x="0.0" y="502" width="414" height="394"/>
126+ <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
127+ <constraints>
128+ <constraint firstAttribute="height" constant="394" id="Ppm-PI-ASV"/>
129+ </constraints>
130+ </view>
131+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6Dy-xy-vQH">
132+ <rect key="frame" x="0.0" y="0.0" width="414" height="502"/>
133+ <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
134+ </view>
135+ </subviews>
136+ <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
137+ <constraints>
138+ <constraint firstItem="kon-kQ-rMb" firstAttribute="leading" secondItem="Oj9-9F-isg" secondAttribute="leading" id="5wF-Rc-6Na"/>
139+ <constraint firstAttribute="bottom" secondItem="kon-kQ-rMb" secondAttribute="bottom" id="IAO-aj-hyT"/>
140+ <constraint firstItem="kon-kQ-rMb" firstAttribute="top" secondItem="6Dy-xy-vQH" secondAttribute="bottom" id="W6B-dA-vrO"/>
141+ <constraint firstItem="6Dy-xy-vQH" firstAttribute="leading" secondItem="Oj9-9F-isg" secondAttribute="leading" id="WKh-xt-L3z"/>
142+ <constraint firstItem="kon-kQ-rMb" firstAttribute="trailing" secondItem="Oj9-9F-isg" secondAttribute="trailing" id="cZG-IB-ohm"/>
143+ <constraint firstItem="6Dy-xy-vQH" firstAttribute="top" secondItem="mnl-c9-WGz" secondAttribute="top" id="iK5-6K-mEo"/>
144+ <constraint firstItem="6Dy-xy-vQH" firstAttribute="trailing" secondItem="Oj9-9F-isg" secondAttribute="trailing" id="rrY-dK-bTz"/>
145+ </constraints>
146+ <viewLayoutGuide key="safeArea" id="Oj9-9F-isg"/>
147+ </view>
148+ <connections>
149+ <outlet property="childContentView" destination="kon-kQ-rMb" id="5DH-RF-lEQ"/>
150+ <outlet property="mapContentView" destination="6Dy-xy-vQH" id="5Cu-uI-WT3"/>
151+ </connections>
152+ </viewController>
153+ <placeholder placeholderIdentifier="IBFirstResponder" id="H8g-xl-PmS" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
154+ </objects>
155+ <point key="canvasLocation" x="2350.7246376811595" y="-38.169642857142854"/>
156+ </scene>
157+ </scenes>
158+</document>
Hookah Tau/Controllers/Client/Reservations/Steps/1/FirstStepReservationCoordinator.swiftnew file+66
@@ -0,0 +1,66 @@
1+//
2+// ReservationCoordinator.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 06/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class FirstStepReservationCoordinator: BaseCoordinator, FirstStepReservationProtocol {
12+
13+ // MARK: - Properties
14+
15+ var didFinish: (() -> Void)?
16+
17+ var establishment: Int = 1
18+
19+ // MARK: - Lifecycle
20+
21+ override func start() {
22+ let storyboard = UIStoryboard(name: "Reservation", bundle: nil)
23+ let reservationVC = storyboard.instantiateViewController(withIdentifier: "ReservationViewController") as! FirstStepReservationViewController
24+
25+ reservationVC.coordinator = self
26+ reservationVC.establishmentId = establishment
27+
28+ navigationController?.pushViewController(reservationVC, animated: true)
29+ }
30+
31+ var user: UserType {
32+ get {
33+ return .client
34+ }
35+ }
36+
37+
38+ func makeReservation(model: SecondStepModel,
39+ mapView: MapImageScroll?) {
40+
41+ let reservationCoordinator =
42+ SecondStepReservationCoordinator(navigationController: navigationController)
43+
44+ reservationCoordinator.model = model
45+ reservationCoordinator.mapView = mapView
46+
47+ addDependency(reservationCoordinator)
48+
49+ reservationCoordinator.didFinish = {
50+ self.navigationController?.popViewController(animated: false)
51+ self.removeDependency(reservationCoordinator)
52+ self.didFinish?()
53+ }
54+
55+ reservationCoordinator.goBack = { [weak self] mapView in
56+
57+ self?.navigationController?.popViewController(animated: false)
58+ self?.removeDependency(reservationCoordinator)
59+ if let vv = self?.navigationController?.topViewController as? FirstStepReservationViewController {
60+ vv.mapView = mapView
61+ }
62+ }
63+
64+ reservationCoordinator.start()
65+ }
66+}
Hookah Tau/Controllers/Client/Reservations/Steps/1/FirstStepReservationViewController.swiftnew file+200
@@ -0,0 +1,200 @@
1+//
2+// ReservationViewController.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 06/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+
12+class FirstStepReservationViewController: BaseViewController {
13+
14+ // MARK: - Properties
15+
16+ weak var coordinator: FirstStepReservationProtocol?
17+
18+ @IBOutlet weak var childContentView: UIView!
19+
20+ var firstStepView: FirstStepView?
21+
22+ var tableId: Int = 2
23+
24+ var reservationsService: ReservationsService?
25+
26+ var mapView: MapImageScroll? {
27+ didSet {
28+ view.addSubviewThatFills(mapView)
29+ guard let map = mapView else { return }
30+ map.scrollViewParent.contentInset = UIEdgeInsets(top: 0,
31+ left: 0,
32+ bottom: childContentView.frame.height,
33+ right: 0)
34+ map.isUserInteractionEnabled = true
35+ map.scrollViewParent.setZoomScale(0.5, animated: true)
36+ map.scrollBack()
37+ view.sendSubviewToBack(map)
38+ }
39+ }
40+
41+ var allReservations: AllReservations? {
42+ didSet {
43+ guard let _ = allReservations else {
44+ return
45+ }
46+
47+ updateChildViewControllerWithChosenId(tableId)
48+ }
49+ }
50+
51+ let continueButton: Button = {
52+ let button = Button()
53+ let style = BlackButtonStyle()
54+ style.apply(to: button, withTitle: "ЗАБРОНИРОВАТЬ")
55+ return button
56+ }()
57+
58+ let callButton: Button = {
59+ let button = Button()
60+ let style = BlackButtonStyle()
61+ style.apply(to: button, withTitle: "ПОЗВОНИТЬ")
62+ return button
63+ }()
64+
65+ var establishmentId: Int = 1
66+
67+ // MARK: - Lifecycle
68+
69+ override func viewDidLoad() {
70+ super.viewDidLoad()
71+
72+ configureMap()
73+ setupChildContainer()
74+
75+ setupButtons()
76+
77+ reservationsService = ReservationsService(apiClient: APIClient.shared)
78+
79+ performUpdate()
80+
81+ navigationItem.title = TotalStorage.standard.getEstablishment(establishmentId)?.address
82+ }
83+
84+ func setupButtons() {
85+
86+ guard let coord = coordinator else { return }
87+
88+ if coord.user == .admin {
89+ addStackViewWithButtons(rightBtn: continueButton,
90+ constant: -childContentView.frame.height - 20)
91+ } else {
92+ addStackViewWithButtons(leftBtn: callButton,
93+ rightBtn: continueButton,
94+ constant: -childContentView.frame.height - 20)
95+
96+ callButton.addTarget(self, action: #selector(call), for: .touchUpInside)
97+ }
98+
99+ continueButton.addTarget(self, action: #selector(continueBooking), for: .touchUpInside)
100+ }
101+
102+ func performUpdate() {
103+ reservationsService?.getAllReservations(establishmentID: establishmentId, completion: { [weak self] result in
104+ switch result {
105+ case .failure(let err):
106+ self?.displayAlert(forError: err)
107+ case .success(let allReservations):
108+ self?.allReservations = allReservations
109+ }
110+ })
111+ }
112+
113+ override func viewDidAppear(_ animated: Bool) {
114+ super.viewDidAppear(animated)
115+
116+ firstStepView?.setContent()
117+ }
118+
119+ override func viewWillAppear(_ animated: Bool) {
120+ super.viewWillAppear(animated)
121+
122+ tabBarController?.tabBar.isHidden = true
123+ }
124+
125+ func configureMap() {
126+ if establishmentId == 1 {
127+ mapView = MapView1.loadFromNib()
128+ } else if establishmentId == 2 {
129+ mapView = MapView2.loadFromNib()
130+ }
131+ mapView?.handler = self
132+ }
133+
134+ func setupChildContainer() {
135+ firstStepView = FirstStepView.loadFromNib()
136+ firstStepView?.mapUpdater = self
137+ childContentView.addSubviewThatFills(firstStepView)
138+ }
139+
140+ func updateChildViewControllerWithChosenId(_ id: Int) {
141+ let reservationsForTable = allReservations?.reservations[id] ?? []
142+ firstStepView?.setUp(reservationsForTable)
143+ }
144+
145+ // MARK: - button handlers
146+
147+ @objc func continueBooking() {
148+ let map = mapView
149+
150+ guard let startTime = firstStepView?.fullDate else { return }
151+ let isAdmin = DataStorage.standard.getUserModel()?.isAdmin ?? false
152+
153+ let (start, end) = isAdmin ? startTime.getAdminStartingAndEndPoint() : startTime.getClientStartingAndEndPoint()
154+
155+ let reservPeriods = HFunc.main.filterPeriodsStartEndDates(allReservations?.reservations[tableId] ?? [],
156+ start,
157+ end)
158+
159+ let model = SecondStepModel(establishment: establishmentId,
160+ table: tableId,
161+ startTime: startTime,
162+ reservedintervals: reservPeriods)
163+
164+ coordinator?.makeReservation(model: model,
165+ mapView: map)
166+ }
167+
168+ @objc func call() {
169+ guard
170+ let phone = TotalStorage.standard.getEstablishment(establishmentId)?.admin,
171+ let urlPhone = URL(string: "tel://79\(phone)") else { return }
172+
173+ UIApplication.shared.open(urlPhone)
174+ }
175+}
176+
177+extension FirstStepReservationViewController: MapHandler {
178+ func handleTap(withId id: Int) {
179+ tableId = id
180+ updateChildViewControllerWithChosenId(id)
181+ }
182+}
183+
184+extension FirstStepReservationViewController: MapUpdater {
185+ ///
186+ func update(_ date: Date) {
187+ let areBooked = allReservations?.intervals[date] ?? []
188+
189+ let isAdmin = DataStorage.standard.getUserModel()?.isAdmin ?? false
190+ let closed = !isAdmin && date.isInClosingHours()
191+
192+ if Set(areBooked).contains(tableId) || closed {
193+ continueButton.availiable = false
194+ } else {
195+ continueButton.availiable = true
196+ }
197+
198+ mapView?.performUpdate(inactive: areBooked)
199+ }
200+}
Hookah Tau/Controllers/Client/Reservations/Steps/1/FirstStepView.swiftnew file+291
@@ -0,0 +1,291 @@
1+//
2+// FirstStepView.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 15/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+protocol MapUpdater: class {
12+ func update(_ date: Date)
13+}
14+
15+class FirstStepView: UIView {
16+
17+ weak var mapUpdater: MapUpdater?
18+
19+ var chosenPoint: Int? {
20+ didSet {
21+ guard
22+ let periods = chosenPoint,
23+ let dateInt = date,
24+ let newDate = Date.getDateFromCurrent(days: dateInt, periods: periods)
25+ else { return }
26+ fullDate = newDate
27+ }
28+ }
29+
30+ var fullDate: Date? {
31+ didSet {
32+ guard let date = fullDate, let _ = chosenPoint else { return }
33+
34+ let (prev, curr, next) = date.getPrevCurrentNextMonth()
35+ setupMonth(prev, curr, next)
36+
37+ mapUpdater?.update(date)
38+ }
39+ }
40+
41+ var date: Int? {
42+ didSet {
43+ guard
44+ let dateInt = date,
45+ let periods = chosenPoint,
46+ let newDate = Date.getDateFromCurrent(days: dateInt, periods: periods)
47+ else { return }
48+ fullDate = newDate
49+
50+ guard
51+ let date = fullDate?.getDMY()
52+ else { return }
53+
54+ let bookings = HFunc.main.filterPeriodsInDate(bookingsForDates, date)
55+ setUpBookingsForIntervals(reservations: bookings)
56+ }
57+ }
58+
59+ var bookingsForDates: [ReservationPeriod] = [] {
60+ didSet {
61+ guard
62+ let date = fullDate?.getDMY()
63+ else { return }
64+
65+ let bookings = HFunc.main.filterPeriodsInDate(bookingsForDates, date)
66+
67+ setUpBookingsForIntervals(reservations: bookings)
68+ }
69+ }
70+
71+ // intervals
72+
73+ @IBOutlet weak var scrollViewIntervals: UIScrollView!
74+
75+ @IBOutlet weak var stackViewIntervals: UIStackView!
76+
77+ // dates
78+
79+ @IBOutlet weak var datesStackView: UIStackView!
80+
81+ @IBOutlet weak var datesScrollView: UIScrollView!
82+
83+ // note labels
84+
85+ @IBOutlet weak var earlierThan12Label: UILabel!
86+
87+ @IBOutlet weak var laterThan3Label: UILabel!
88+
89+ // months
90+
91+ @IBOutlet weak var previousMonthLabel: UILabel!
92+
93+ @IBOutlet weak var currentMonthLabel: UILabel!
94+
95+ @IBOutlet weak var nextMonthLabel: UILabel!
96+
97+
98+ var bookingsViewsForDate: [UIView] = []
99+
100+
101+ override func awakeFromNib() {
102+ super.awakeFromNib()
103+
104+ scrollViewIntervals.delegate = self
105+ datesScrollView.delegate = self
106+
107+ scrollViewIntervals.showsHorizontalScrollIndicator = false
108+ datesScrollView.showsHorizontalScrollIndicator = false
109+
110+ setUpDateStackView()
111+ setUpIntervalStackView()
112+ }
113+
114+ func setUpIntervalStackView() {
115+
116+ for period in 0...Constants.day10MinPeriods {
117+
118+ let timePointView = TimePointView.loadFromNib()
119+ timePointView?.setUp(withTimePoint: period)
120+ let view = UIView(frame: CGRect(x: 0, y: 0, width: 35, height: 92))
121+ view.addSubviewThatFills(timePointView)
122+ stackViewIntervals.addArrangedSubview(view)
123+ }
124+
125+ setupClosingInterval()
126+ }
127+
128+ /// Вьюшка, обозначающая часы закрытия кальянки
129+ func setupClosingInterval() {
130+ let offset = CGFloat(Constants.startClosingTime * Constants.widthTimePoint + Constants.widthTimePoint / 2)
131+ let width = CGFloat(Constants.closingDuration * Constants.widthTimePoint)
132+ let height = CGFloat(Constants.heightIntervals)
133+ let closingTimeView = UIView(frame: CGRect(x: 0, y: 0, width: width, height: height))
134+ closingTimeView.translatesAutoresizingMaskIntoConstraints = false
135+ scrollViewIntervals.addSubview(closingTimeView)
136+ closingTimeView.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 0.5)
137+
138+ NSLayoutConstraint.activate([
139+ closingTimeView.leftAnchor.constraint(equalTo: scrollViewIntervals.leftAnchor, constant: offset),
140+ closingTimeView.bottomAnchor.constraint(equalToSystemSpacingBelow: scrollViewIntervals.bottomAnchor, multiplier: 0),
141+ closingTimeView.heightAnchor.constraint(equalToConstant: height),
142+ closingTimeView.widthAnchor.constraint(equalToConstant: width)
143+ ])
144+ }
145+
146+ func setUpBookingsForIntervals(reservations: [ReservationPeriod]) {
147+ bookingsViewsForDate.forEach { $0.removeFromSuperview() }
148+ bookingsViewsForDate = []
149+
150+ for reservation in reservations {
151+ let uiview = UIView(frame: CGRect(x: 0, y: 0, width: Constants.widthTimePoint * reservation.duration, height: Constants.heightIntervals))
152+ uiview.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 0.5)
153+
154+
155+ uiview.translatesAutoresizingMaskIntoConstraints = false
156+ scrollViewIntervals.addSubview(uiview)
157+
158+ guard let date = date else { return }
159+ let startDate = Date.getDateFromCurrent(days: date)!.set(hours: 0, minutes: 0, seconds: 0)!
160+
161+ let s = reservation.startTime.getMinutesPeriods(fromStart: startDate) * Constants.widthTimePoint
162+ let d = reservation.duration * Constants.widthTimePoint
163+
164+ let startTime = s < 0 ? 0 : s
165+ var duration = 0
166+ if s < 0 {
167+ duration = reservation.endTime.getMinutesPeriods(fromStart: startDate) * Constants.widthTimePoint
168+ } else {
169+ duration = (s + d) > Constants.widthOfIntervals ? Constants.widthOfIntervals - s : d
170+ }
171+
172+ NSLayoutConstraint.activate([
173+ uiview.bottomAnchor.constraint(equalToSystemSpacingBelow: scrollViewIntervals.bottomAnchor, multiplier: 0),
174+ uiview.leftAnchor.constraint(equalTo: scrollViewIntervals.leftAnchor,
175+ constant: CGFloat(Constants.widthTimePoint / 2 + startTime)),
176+ uiview.widthAnchor.constraint(equalToConstant: CGFloat(duration)),
177+ uiview.heightAnchor.constraint(equalToConstant: CGFloat(Constants.heightIntervals))
178+ ])
179+
180+ bookingsViewsForDate.append(uiview)
181+ }
182+ }
183+
184+ func setContent() {
185+ scrollViewIntervals.contentSize = stackViewIntervals.frame.size
186+ datesScrollView.contentSize = datesStackView.frame.size
187+
188+ let halfWidth = frame.size.width / 2
189+ let edges = UIEdgeInsets(top: 0, left: halfWidth, bottom: 0, right: halfWidth)
190+
191+ scrollViewIntervals.contentInset = edges
192+ datesScrollView.contentInset = edges
193+
194+ datesScrollView.setContentOffset(CGPoint(x: abs(Constants.widthDate * Constants.daysCount / 2 - Int(edges.left)) + 8, y: 0),
195+ animated: true)
196+ scrollViewIntervals.setContentOffset(CGPoint(x: abs(Constants.widthTimePoint * Constants.day10MinPeriods / 2 - Int(edges.left)) + 7, y: 0),
197+ animated: true)
198+ }
199+
200+
201+ /// Method for setting up `stackViewIntervals` with appropriate busy intervals of chosen table
202+ /// - Parameter data: Map of booked periods by key - date - is provided
203+ func setUp(_ data: [ReservationPeriod]) {
204+ bookingsForDates = data
205+ }
206+
207+ func setUpDateStackView() {
208+ let dates = Date.getNext(days: Constants.daysCount)
209+
210+ for date in dates {
211+ let dateView = DateView.loadFromNib()
212+ dateView?.setUp(with: date)
213+ let view = UIView(frame: CGRect(x: 0, y: 0, width: 21, height: 59))
214+ view.addSubviewThatFills(dateView)
215+ datesStackView.addArrangedSubview(view)
216+ }
217+ }
218+
219+ func setupMonth(_ prev: String, _ curr: String, _ next: String) {
220+ currentMonthLabel.text = curr
221+ previousMonthLabel.text = prev
222+ nextMonthLabel.text = next
223+ }
224+}
225+
226+extension FirstStepView: UIScrollViewDelegate {
227+
228+
229+ func scrollViewDidScroll(_ scrollView: UIScrollView) {
230+
231+ if scrollView == scrollViewIntervals {
232+ intervalsDidScroll()
233+ } else {
234+ datesDidScroll()
235+ }
236+ }
237+
238+ func intervalsDidScroll() {
239+ let inset = scrollViewIntervals.contentInset.left
240+ let alpha12 = 1 / (abs(inset + scrollViewIntervals.contentOffset.x) / inset)
241+ let alpha3 = 1 / (abs(inset + scrollViewIntervals.contentOffset.x - stackViewIntervals.frame.width) / inset)
242+
243+ earlierThan12Label.textColor = earlierThan12Label.textColor.withAlphaComponent(alpha12)
244+ laterThan3Label.textColor = laterThan3Label.textColor.withAlphaComponent(alpha3)
245+
246+ for (index, stack) in stackViewIntervals.subviews.enumerated() {
247+ if let intervalStack = stack.subviews[0] as? TimePointView {
248+ let positionToSelf = intervalStack.convert(intervalStack.frame, to: self)
249+
250+ if abs((positionToSelf.minX + positionToSelf.maxX) / 2 - frame.size.width / 2) < 5.5
251+ {
252+ intervalStack.timePoint.backgroundColor = .darkGray
253+ chosenPoint = index
254+ } else {
255+ intervalStack.timePoint.backgroundColor = .lightGray
256+ }
257+ }
258+ }
259+ }
260+
261+ func datesDidScroll() {
262+ for (_, stack) in datesStackView.subviews.enumerated() {
263+ if let dateView = stack.subviews[0] as? DateView {
264+ let positionToSelf = dateView.convert(dateView.frame, to: self)
265+
266+ if abs((positionToSelf.minX + positionToSelf.maxX) / 2 - frame.size.width / 2) < 15.5 {
267+ dateView.dateLabel.textColor = .black
268+ date = Int(datesScrollView.contentInset.left + datesScrollView.contentOffset.x) / 31
269+ } else {
270+ dateView.dateLabel.textColor = .lightGray
271+ }
272+ }
273+ }
274+ }
275+}
276+
277+// MARK: - Nested types
278+
279+extension FirstStepView {
280+ enum Constants {
281+ /// 24 ч * 60 минут / 10 минут в периоде; [00:00 ... 23:50]
282+ static let day10MinPeriods = 143
283+ static let daysCount = 14
284+ static let widthTimePoint = 11
285+ static let widthDate = 31
286+ static let heightIntervals = 70
287+ static let closingDuration = 54
288+ static let startClosingTime = 18
289+ static let widthOfIntervals = 1573
290+ }
291+}
Hookah Tau/Controllers/Client/Reservations/Steps/1/FirstStepView.xibnew file+250
@@ -0,0 +1,250 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3+ <device id="retina5_5" orientation="portrait" appearance="light"/>
4+ <dependencies>
5+ <deployment identifier="iOS"/>
6+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
7+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
8+ <capability name="Stack View standard spacing" minToolsVersion="9.0"/>
9+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
10+ </dependencies>
11+ <objects>
12+ <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
13+ <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
14+ <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="FirstStepView" customModule="Hookah_Tau" customModuleProvider="target">
15+ <rect key="frame" x="0.0" y="0.0" width="414" height="225"/>
16+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
17+ <subviews>
18+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pgz-8G-Kx4" userLabel="Under scroll dates">
19+ <rect key="frame" x="0.0" y="29" width="414" height="59"/>
20+ <subviews>
21+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Ocm-Ba-WCp">
22+ <rect key="frame" x="0.0" y="0.0" width="414" height="1"/>
23+ <color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
24+ <constraints>
25+ <constraint firstAttribute="height" constant="1" id="Gst-4x-W6O"/>
26+ </constraints>
27+ </view>
28+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Eb3-64-oGl">
29+ <rect key="frame" x="0.0" y="58" width="414" height="1"/>
30+ <color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
31+ <constraints>
32+ <constraint firstAttribute="height" constant="1" id="Llo-E4-www"/>
33+ </constraints>
34+ </view>
35+ </subviews>
36+ <color key="backgroundColor" red="0.8901960784313725" green="0.8901960784313725" blue="0.8901960784313725" alpha="0.14999999999999999" colorSpace="custom" customColorSpace="calibratedRGB"/>
37+ <constraints>
38+ <constraint firstItem="Ocm-Ba-WCp" firstAttribute="leading" secondItem="pgz-8G-Kx4" secondAttribute="leading" id="AyK-U9-Htr"/>
39+ <constraint firstAttribute="trailing" secondItem="Eb3-64-oGl" secondAttribute="trailing" id="UWJ-H4-iVD"/>
40+ <constraint firstItem="Eb3-64-oGl" firstAttribute="leading" secondItem="pgz-8G-Kx4" secondAttribute="leading" id="UqX-3l-90O"/>
41+ <constraint firstAttribute="trailing" secondItem="Ocm-Ba-WCp" secondAttribute="trailing" id="drj-rX-UvL"/>
42+ <constraint firstAttribute="bottom" secondItem="Eb3-64-oGl" secondAttribute="bottom" id="ebx-vE-0aF"/>
43+ <constraint firstItem="Ocm-Ba-WCp" firstAttribute="top" secondItem="pgz-8G-Kx4" secondAttribute="top" id="nEC-cm-sIz"/>
44+ </constraints>
45+ </view>
46+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="954-g3-FZD" userLabel="Under scroll view">
47+ <rect key="frame" x="0.0" y="97" width="414" height="91"/>
48+ <subviews>
49+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="dh5-kt-LFH">
50+ <rect key="frame" x="0.0" y="21" width="414" height="70"/>
51+ <subviews>
52+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="nqq-Xp-Lig">
53+ <rect key="frame" x="0.0" y="0.0" width="414" height="1"/>
54+ <color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
55+ <constraints>
56+ <constraint firstAttribute="height" constant="1" id="me1-hs-aWn"/>
57+ </constraints>
58+ </view>
59+ <stackView opaque="NO" contentMode="scaleToFill" distribution="fillEqually" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="kS7-N4-kdg">
60+ <rect key="frame" x="16" y="9" width="382" height="52"/>
61+ <subviews>
62+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Для брони времени раньше 12:00 свяжитесь с администратором" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6DK-77-Npa">
63+ <rect key="frame" x="0.0" y="0.0" width="187" height="52"/>
64+ <fontDescription key="fontDescription" type="system" pointSize="13"/>
65+ <color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
66+ <nil key="highlightedColor"/>
67+ </label>
68+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Для брони времени позже 3:00 свяжитесь с администратором" textAlignment="right" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="qz5-ce-S3I">
69+ <rect key="frame" x="195" y="0.0" width="187" height="52"/>
70+ <fontDescription key="fontDescription" type="system" pointSize="13"/>
71+ <color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
72+ <nil key="highlightedColor"/>
73+ </label>
74+ </subviews>
75+ </stackView>
76+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="zqK-OT-P26">
77+ <rect key="frame" x="0.0" y="69" width="414" height="1"/>
78+ <color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
79+ <constraints>
80+ <constraint firstAttribute="height" constant="1" id="iUR-sJ-1M1"/>
81+ <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="1" id="xeo-B1-TLL"/>
82+ </constraints>
83+ </view>
84+ </subviews>
85+ <color key="backgroundColor" red="0.8901960784313725" green="0.8901960784313725" blue="0.8901960784313725" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
86+ <constraints>
87+ <constraint firstItem="nqq-Xp-Lig" firstAttribute="top" secondItem="dh5-kt-LFH" secondAttribute="top" id="6xZ-TR-tvC"/>
88+ <constraint firstAttribute="trailing" secondItem="zqK-OT-P26" secondAttribute="trailing" id="SeY-dj-8cT"/>
89+ <constraint firstAttribute="trailing" secondItem="nqq-Xp-Lig" secondAttribute="trailing" id="XtL-jd-AZH"/>
90+ <constraint firstItem="kS7-N4-kdg" firstAttribute="leading" secondItem="dh5-kt-LFH" secondAttribute="leading" constant="16" id="d0b-bx-btq"/>
91+ <constraint firstItem="zqK-OT-P26" firstAttribute="top" secondItem="kS7-N4-kdg" secondAttribute="bottom" constant="8" id="eNz-f4-nlO"/>
92+ <constraint firstAttribute="bottom" secondItem="zqK-OT-P26" secondAttribute="bottom" id="m0O-4r-wfZ"/>
93+ <constraint firstItem="kS7-N4-kdg" firstAttribute="top" secondItem="nqq-Xp-Lig" secondAttribute="bottom" constant="8" id="o6k-wD-gGp"/>
94+ <constraint firstAttribute="trailing" secondItem="kS7-N4-kdg" secondAttribute="trailing" constant="16" id="qfN-XY-Uqq"/>
95+ <constraint firstItem="nqq-Xp-Lig" firstAttribute="leading" secondItem="dh5-kt-LFH" secondAttribute="leading" id="qr9-52-TNC"/>
96+ <constraint firstAttribute="height" constant="70" id="tcS-2U-ecA"/>
97+ <constraint firstItem="zqK-OT-P26" firstAttribute="leading" secondItem="dh5-kt-LFH" secondAttribute="leading" id="yUf-Cj-HmN"/>
98+ </constraints>
99+ </view>
100+ </subviews>
101+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
102+ <constraints>
103+ <constraint firstAttribute="trailing" secondItem="dh5-kt-LFH" secondAttribute="trailing" id="Ogq-Ny-UtZ"/>
104+ <constraint firstItem="dh5-kt-LFH" firstAttribute="leading" secondItem="954-g3-FZD" secondAttribute="leading" id="aGL-6Q-gwb"/>
105+ <constraint firstAttribute="bottom" secondItem="dh5-kt-LFH" secondAttribute="bottom" id="sCl-ct-Awr"/>
106+ </constraints>
107+ </view>
108+ <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="TriangleDown" translatesAutoresizingMaskIntoConstraints="NO" id="5dC-Lq-opV">
109+ <rect key="frame" x="184.66666666666666" y="0.0" width="45" height="29"/>
110+ <constraints>
111+ <constraint firstAttribute="width" constant="45" id="Tsw-fS-8qs"/>
112+ <constraint firstAttribute="height" constant="29" id="YWD-ja-gD5"/>
113+ </constraints>
114+ </imageView>
115+ <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="TriangleUp" translatesAutoresizingMaskIntoConstraints="NO" id="Igw-O9-W92">
116+ <rect key="frame" x="184.66666666666666" y="196" width="45" height="29"/>
117+ <constraints>
118+ <constraint firstAttribute="height" constant="29" id="AdE-yJ-WLL"/>
119+ <constraint firstAttribute="width" constant="45" id="Eaa-P8-3mv"/>
120+ </constraints>
121+ </imageView>
122+ <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="a6N-9A-8y5">
123+ <rect key="frame" x="0.0" y="97" width="414" height="91"/>
124+ <subviews>
125+ <stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="z9p-OK-tL7">
126+ <rect key="frame" x="0.0" y="0.0" width="414" height="91"/>
127+ </stackView>
128+ </subviews>
129+ <constraints>
130+ <constraint firstItem="z9p-OK-tL7" firstAttribute="leading" secondItem="a6N-9A-8y5" secondAttribute="leading" id="66I-Xl-dE9"/>
131+ <constraint firstItem="z9p-OK-tL7" firstAttribute="width" secondItem="a6N-9A-8y5" secondAttribute="width" priority="250" id="8CZ-Pa-ROG"/>
132+ <constraint firstAttribute="bottom" secondItem="z9p-OK-tL7" secondAttribute="bottom" id="Bve-Ls-KhA"/>
133+ <constraint firstItem="z9p-OK-tL7" firstAttribute="top" secondItem="a6N-9A-8y5" secondAttribute="top" id="Qys-Ws-joK"/>
134+ <constraint firstItem="z9p-OK-tL7" firstAttribute="height" secondItem="a6N-9A-8y5" secondAttribute="height" id="a5D-0u-zAW"/>
135+ <constraint firstAttribute="trailing" secondItem="z9p-OK-tL7" secondAttribute="trailing" id="riY-nE-EUp"/>
136+ </constraints>
137+ <viewLayoutGuide key="contentLayoutGuide" id="Y0a-LG-dJH"/>
138+ <viewLayoutGuide key="frameLayoutGuide" id="bsE-qj-8ef"/>
139+ </scrollView>
140+ <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Y9G-UI-WV7">
141+ <rect key="frame" x="0.0" y="29" width="414" height="59"/>
142+ <subviews>
143+ <stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="52e-LM-cF6">
144+ <rect key="frame" x="0.0" y="0.0" width="414" height="59"/>
145+ </stackView>
146+ </subviews>
147+ <constraints>
148+ <constraint firstItem="52e-LM-cF6" firstAttribute="height" secondItem="Y9G-UI-WV7" secondAttribute="height" id="2wC-n3-oG9"/>
149+ <constraint firstItem="52e-LM-cF6" firstAttribute="leading" secondItem="Y9G-UI-WV7" secondAttribute="leading" id="GZW-kT-OtX"/>
150+ <constraint firstItem="52e-LM-cF6" firstAttribute="width" secondItem="Y9G-UI-WV7" secondAttribute="width" priority="250" id="Lb4-nJ-yt6"/>
151+ <constraint firstAttribute="trailing" secondItem="52e-LM-cF6" secondAttribute="trailing" id="edz-yx-1Bj"/>
152+ <constraint firstAttribute="bottom" secondItem="52e-LM-cF6" secondAttribute="bottom" id="qNo-Pi-gV4"/>
153+ <constraint firstItem="52e-LM-cF6" firstAttribute="top" secondItem="Y9G-UI-WV7" secondAttribute="top" id="wNf-Sa-CrY"/>
154+ </constraints>
155+ <viewLayoutGuide key="contentLayoutGuide" id="uZM-JL-J3w"/>
156+ <viewLayoutGuide key="frameLayoutGuide" id="dbW-UW-YAk"/>
157+ </scrollView>
158+ <view userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fvt-zf-8tU" customClass="RadialGradientView" customModule="Hookah_Tau" customModuleProvider="target">
159+ <rect key="frame" x="0.0" y="29" width="414" height="59"/>
160+ <subviews>
161+ <stackView opaque="NO" contentMode="scaleToFill" distribution="equalSpacing" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="OEF-Kc-d38">
162+ <rect key="frame" x="8" y="5.0000000000000009" width="398" height="14.333333333333336"/>
163+ <subviews>
164+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="November" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="5Mn-Mn-qaI">
165+ <rect key="frame" x="0.0" y="0.0" width="58.333333333333336" height="14.333333333333334"/>
166+ <fontDescription key="fontDescription" type="system" pointSize="12"/>
167+ <color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
168+ <nil key="highlightedColor"/>
169+ </label>
170+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="January" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Rfg-Ts-DX0">
171+ <rect key="frame" x="353" y="0.0" width="45" height="14.333333333333334"/>
172+ <fontDescription key="fontDescription" type="system" pointSize="12"/>
173+ <color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
174+ <nil key="highlightedColor"/>
175+ </label>
176+ </subviews>
177+ </stackView>
178+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="December" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Fbc-VX-43f">
179+ <rect key="frame" x="176.66666666666666" y="5.0000000000000009" width="60.666666666666657" height="14.333333333333336"/>
180+ <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="12"/>
181+ <color key="textColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
182+ <nil key="highlightedColor"/>
183+ </label>
184+ </subviews>
185+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
186+ <constraints>
187+ <constraint firstAttribute="trailing" secondItem="OEF-Kc-d38" secondAttribute="trailing" constant="8" id="BKe-IX-uiY"/>
188+ <constraint firstItem="OEF-Kc-d38" firstAttribute="leading" secondItem="fvt-zf-8tU" secondAttribute="leading" constant="8" id="Cc6-Iv-RT8"/>
189+ <constraint firstItem="Fbc-VX-43f" firstAttribute="top" secondItem="fvt-zf-8tU" secondAttribute="top" constant="5" id="jj2-kv-wnF"/>
190+ <constraint firstItem="OEF-Kc-d38" firstAttribute="top" secondItem="fvt-zf-8tU" secondAttribute="top" constant="5" id="pmQ-MG-28r"/>
191+ <constraint firstItem="Fbc-VX-43f" firstAttribute="centerX" secondItem="fvt-zf-8tU" secondAttribute="centerX" id="wAj-Z8-AP0"/>
192+ </constraints>
193+ <userDefinedRuntimeAttributes>
194+ <userDefinedRuntimeAttribute type="color" keyPath="outsideColor">
195+ <color key="value" white="1" alpha="0.88409674657534243" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
196+ </userDefinedRuntimeAttribute>
197+ <userDefinedRuntimeAttribute type="color" keyPath="insideColor">
198+ <color key="value" white="0.67000000000000004" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
199+ </userDefinedRuntimeAttribute>
200+ </userDefinedRuntimeAttributes>
201+ </view>
202+ </subviews>
203+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
204+ <constraints>
205+ <constraint firstItem="Y9G-UI-WV7" firstAttribute="trailing" secondItem="vUN-kp-3ea" secondAttribute="trailing" id="22z-Ck-vsZ"/>
206+ <constraint firstItem="a6N-9A-8y5" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="5c8-YB-dSB"/>
207+ <constraint firstAttribute="bottom" secondItem="954-g3-FZD" secondAttribute="bottom" constant="37" id="C3k-Mb-wT4"/>
208+ <constraint firstItem="Y9G-UI-WV7" firstAttribute="top" secondItem="5dC-Lq-opV" secondAttribute="bottom" id="E97-Lg-K2s"/>
209+ <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="fvt-zf-8tU" secondAttribute="trailing" id="Eeh-zB-KWe"/>
210+ <constraint firstItem="a6N-9A-8y5" firstAttribute="trailing" secondItem="vUN-kp-3ea" secondAttribute="trailing" id="F03-yv-41V"/>
211+ <constraint firstItem="pgz-8G-Kx4" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="G0y-2f-SkS"/>
212+ <constraint firstItem="5dC-Lq-opV" firstAttribute="centerX" secondItem="vUN-kp-3ea" secondAttribute="centerX" id="If5-v7-UAj"/>
213+ <constraint firstAttribute="bottom" secondItem="pgz-8G-Kx4" secondAttribute="bottom" constant="137" id="Lgn-Nv-wjD"/>
214+ <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="954-g3-FZD" secondAttribute="trailing" id="MGt-d4-Win"/>
215+ <constraint firstItem="fvt-zf-8tU" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="SaI-rP-2em"/>
216+ <constraint firstItem="Igw-O9-W92" firstAttribute="centerX" secondItem="vUN-kp-3ea" secondAttribute="centerX" id="TtM-Bc-V1U"/>
217+ <constraint firstItem="Igw-O9-W92" firstAttribute="bottom" secondItem="iN0-l3-epB" secondAttribute="bottom" id="V9u-Eu-VME"/>
218+ <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="pgz-8G-Kx4" secondAttribute="trailing" id="Z6D-PY-Jrh"/>
219+ <constraint firstItem="Igw-O9-W92" firstAttribute="top" secondItem="a6N-9A-8y5" secondAttribute="bottom" constant="8" id="f4D-nq-HMa"/>
220+ <constraint firstItem="Y9G-UI-WV7" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="gZw-8a-dTG"/>
221+ <constraint firstItem="954-g3-FZD" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="itw-0z-3i7"/>
222+ <constraint firstAttribute="bottom" secondItem="fvt-zf-8tU" secondAttribute="bottom" constant="137" id="lOx-ke-O6a"/>
223+ <constraint firstItem="z9p-OK-tL7" firstAttribute="top" secondItem="52e-LM-cF6" secondAttribute="bottom" constant="9" id="ldj-Qh-p0k"/>
224+ <constraint firstItem="fvt-zf-8tU" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="29" id="pSg-dd-INI"/>
225+ <constraint firstItem="pgz-8G-Kx4" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="29" id="vfO-LG-tqR"/>
226+ <constraint firstItem="954-g3-FZD" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="97" id="vhK-2E-j3k"/>
227+ <constraint firstItem="5dC-Lq-opV" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" id="xOt-l0-Vzy"/>
228+ <constraint firstItem="a6N-9A-8y5" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="97" id="yyf-QB-vfF"/>
229+ </constraints>
230+ <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
231+ <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
232+ <connections>
233+ <outlet property="currentMonthLabel" destination="Fbc-VX-43f" id="CJr-eH-QMp"/>
234+ <outlet property="datesScrollView" destination="Y9G-UI-WV7" id="dbT-BM-Ckq"/>
235+ <outlet property="datesStackView" destination="52e-LM-cF6" id="6Ni-IN-sm4"/>
236+ <outlet property="earlierThan12Label" destination="6DK-77-Npa" id="ftP-Jz-gzw"/>
237+ <outlet property="laterThan3Label" destination="qz5-ce-S3I" id="dzR-7k-uDq"/>
238+ <outlet property="nextMonthLabel" destination="Rfg-Ts-DX0" id="rpk-ci-pzX"/>
239+ <outlet property="previousMonthLabel" destination="5Mn-Mn-qaI" id="c0S-Yz-e83"/>
240+ <outlet property="scrollViewIntervals" destination="a6N-9A-8y5" id="uDW-QY-gNF"/>
241+ <outlet property="stackViewIntervals" destination="z9p-OK-tL7" id="0Mj-wX-kq7"/>
242+ </connections>
243+ <point key="canvasLocation" x="-110.40000000000001" y="63.418290854572717"/>
244+ </view>
245+ </objects>
246+ <resources>
247+ <image name="TriangleDown" width="40" height="41"/>
248+ <image name="TriangleUp" width="40" height="41"/>
249+ </resources>
250+</document>
Hookah Tau/Controllers/Client/Reservations/Steps/2/SecondStepReservationCoordinator.swiftnew file+62
@@ -0,0 +1,62 @@
1+//
2+// SecondStepReservationCoordinator.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 21/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+
12+class SecondStepReservationCoordinator: BaseCoordinator {
13+
14+ // MARK: - Properties
15+
16+ var didFinish: (() -> Void)?
17+
18+ var goBack: ((MapImageScroll?) -> Void)?
19+
20+ var model: SecondStepModel?
21+
22+ var mapView: MapImageScroll?
23+
24+ var onBooking: ((MapImageScroll, Reservation) -> Void)?
25+
26+ var vc: SecondStepReservationViewController?
27+
28+ // MARK: - Lifecycle
29+
30+ override func start() {
31+ let storyboard = UIStoryboard(name: "Reservation", bundle: nil)
32+ vc = storyboard.instantiateViewController(withIdentifier: "SecondStepReservationViewController") as? SecondStepReservationViewController
33+
34+ guard let viewController = vc else { return }
35+
36+ viewController.model = model
37+ viewController.mapView = mapView
38+ viewController.coordinator = self
39+
40+ navigationController?.pushViewController(viewController, animated: false)
41+ }
42+
43+ func cancel(_ mapView: MapImageScroll?) {
44+ goBack?(mapView)
45+ }
46+
47+ func book(_ mapView: MapImageScroll?, reservation: Reservation) {
48+ let coordinator = ThirdStepReservationCoordinator(navigationController: navigationController)
49+ addDependency(coordinator)
50+
51+ coordinator.didFinish = { [weak self] in
52+ self?.navigationController?.popViewController(animated: false)
53+ self?.removeDependency(coordinator)
54+ self?.didFinish?()
55+ }
56+
57+ coordinator.mapView = mapView
58+ coordinator.resrvation = reservation
59+
60+ coordinator.start()
61+ }
62+}
Hookah Tau/Controllers/Client/Reservations/Steps/2/SecondStepReservationViewController.swiftnew file+142
@@ -0,0 +1,142 @@
1+//
2+// SecondStepReservationViewController.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 21/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class SecondStepReservationViewController: BaseViewController {
12+
13+ // MARK: - Properties
14+
15+ @IBOutlet weak var childContentView: UIView!
16+
17+ var secondStepView: SecondStepView?
18+
19+ /// координатор
20+ weak var coordinator: SecondStepReservationCoordinator?
21+
22+ /// service для создания бронирования
23+ var reservationService: ReservationsService?
24+
25+ /// map view from the previous screen
26+ var mapView: MapImageScroll?
27+
28+ var model: SecondStepModel?
29+
30+ /// address of the building to display in nav bar title
31+ var address: String = "Покровский бр., 11"
32+
33+ let confirmButton: Button = {
34+ let button = Button()
35+ let style = BlackButtonStyle()
36+ style.apply(to: button, withTitle: "ПОДТВЕРДИТЬ")
37+ return button
38+ }()
39+
40+ let cancelButton: Button = {
41+ let button = Button()
42+ let style = BlackButtonStyle()
43+ style.apply(to: button, withTitle: "ОТМЕНИТЬ")
44+ return button
45+ }()
46+
47+ // MARK: - Lifecycle
48+
49+ override func viewDidLoad() {
50+ super.viewDidLoad()
51+
52+ setupNavBar()
53+ setupMap()
54+ setupChildContainer()
55+ setupButtons()
56+
57+ reservationService = ReservationsService(apiClient: APIClient.shared)
58+ }
59+
60+ override func viewWillAppear(_ animated: Bool) {
61+ super.viewWillAppear(animated)
62+
63+ tabBarController?.tabBar.isHidden = true
64+ }
65+
66+
67+ // MARK: - Setup
68+
69+ func setupNavBar() {
70+ navigationItem.hidesBackButton = true
71+ navigationItem.title = address
72+ }
73+
74+ func setupChildContainer() {
75+
76+ secondStepView = SecondStepView.loadFromNib()
77+ childContentView.addSubviewThatFills(secondStepView)
78+ guard let model = model else { return }
79+ secondStepView?.bind(model: model)
80+ }
81+
82+ func setupMap() {
83+ guard let map = mapView else { return }
84+ view.addSubview(map)
85+
86+ NSLayoutConstraint.activate([
87+ map.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10),
88+ map.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0),
89+ map.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0),
90+ map.bottomAnchor.constraint(equalTo: childContentView.topAnchor, constant: 0)
91+ ])
92+
93+ let width = view.frame.width
94+
95+ mapView?.scrollViewParent.setContentOffset(CGPoint(x: 0, y: 100), animated: false)
96+ mapView?.scrollViewParent.contentInset = UIEdgeInsets(top: 0,
97+ left: width / 2,
98+ bottom: -100,
99+ right: width / 2)
100+ mapView?.isUserInteractionEnabled = false
101+ view.sendSubviewToBack(map)
102+ }
103+
104+ override func viewDidAppear(_ animated: Bool) {
105+ super.viewDidAppear(animated)
106+
107+ mapView?.scrollToTable(table: model?.table ?? 0)
108+ }
109+
110+ func setupButtons() {
111+ addStackViewWithButtons(leftBtn: cancelButton,
112+ rightBtn: confirmButton,
113+ constant: -childContentView.frame.height - 20)
114+ cancelButton.addTarget(self, action: #selector(cancelChange), for: .touchUpInside)
115+ confirmButton.addTarget(self, action: #selector(book), for: .touchUpInside)
116+ }
117+
118+ // MARK: - Button handlers
119+
120+ @objc func cancelChange() {
121+ coordinator?.cancel(mapView)
122+ }
123+
124+ @objc func book() {
125+ guard let booking = secondStepView?.data else { return }
126+ confirmButton.loading = true
127+
128+ print(booking)
129+
130+ reservationService?.createReservation(data: booking, completion: { [weak self] (result) in
131+ switch result {
132+ case .failure:
133+ self?.displayAlert(with: "Не удалось совершить бронирование :(")
134+
135+ case .success(let reservation):
136+ self?.coordinator?.book(self?.mapView, reservation: reservation)
137+ }
138+
139+ self?.confirmButton.loading = false
140+ })
141+ }
142+}
Hookah Tau/Controllers/Client/Reservations/Steps/2/SecondStepView.swiftnew file+351
@@ -0,0 +1,351 @@
1+//
2+// SecondStepView.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 21/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+/// стол, начало, интервалы (???), establishment
12+struct SecondStepModel {
13+ var establishment: Int
14+ var table: Int
15+ var startTime: Date
16+ var endTime: Date? = nil
17+ var reservedintervals: [ReservationPeriod]
18+}
19+
20+class SecondStepView: UIView {
21+
22+ // MARK: - IBAOutlets
23+
24+ @IBOutlet weak var totalBookingLabel: UILabel!
25+
26+ @IBOutlet weak var specialLabel: UILabel! {
27+ didSet {
28+ let m = NSMutableAttributedString(string: "Если Вам нужно что-то особенное, Вы можете связаться с администратором ")
29+
30+ let image1Attachment = NSTextAttachment()
31+ image1Attachment.bounds = CGRect(x: 0, y: -3, width: 15, height: 15)
32+ image1Attachment.image = #imageLiteral(resourceName: "phone")
33+ let image1String = NSMutableAttributedString(attachment: image1Attachment)
34+ m.append(image1String)
35+ specialLabel.attributedText = m
36+
37+ let gr = UITapGestureRecognizer(target: self, action: #selector(callAlmin))
38+ specialLabel.addGestureRecognizer(gr)
39+ }
40+ }
41+
42+
43+ @IBOutlet weak var numberOfGuestsLabel: UILabel!
44+
45+ @IBOutlet weak var guestSlider: UISlider!
46+
47+ @IBOutlet weak var scrollView: UIScrollView!
48+
49+ @IBOutlet weak var intervalsStackView: UIStackView!
50+
51+ // MARK: - Peoperties
52+
53+ /// Время начала вертелки.
54+ /// Для клиента это 12:00 смены (надо вызвать метод для получения текущей смены).
55+ /// Для администратора это `t` - 24h
56+ var startDate: Date?
57+
58+ /// Модель вьюшки
59+ var data: ReservationData? {
60+ didSet {
61+ guard let d = data else { return }
62+ let (date, time) = Date.format(d.startTime, d.endTime)
63+ totalBookingLabel.text = "Забронировать \(d.reservedTable) столик на \(d.numberOfGuests) человека \(date) \(time)?"
64+ }
65+ }
66+
67+ /// Модель с которой мы приходим в эту вьюшку
68+ var model: SecondStepModel? {
69+ didSet {
70+ guard let model = model else { return }
71+
72+ data = ReservationData(establishment: model.establishment,
73+ startTime: model.startTime,
74+ endTime: model.endTime ?? model.startTime.addHours(2),
75+ numberOfGuests: Int(guestSlider.value),
76+ reservedTable: model.table)
77+
78+ // isAdmin
79+ let isAdmin = DataStorage.standard.getUserModel()?.isAdmin ?? false
80+
81+ let (start, _) = isAdmin ? model.startTime.getAdminStartingAndEndPoint() : model.startTime.getClientStartingAndEndPoint()
82+
83+ startDate = start
84+
85+ setUpIntervals(isAdmin)
86+ setUpReservedIntervals(model.reservedintervals, isAdmin)
87+ setUpBookingPeriod(model.startTime, model.endTime)
88+ }
89+ }
90+
91+ // констрейнты начала и конца интервала
92+ var startLeadingConstraint: NSLayoutConstraint!
93+ var endLeadingConstraint: NSLayoutConstraint!
94+
95+ var startPoint: UIImageView = {
96+ var view = UIImageView()
97+ view.isUserInteractionEnabled = true
98+ view.image = #imageLiteral(resourceName: "handle_left")
99+ view.translatesAutoresizingMaskIntoConstraints = false
100+ return view
101+ }()
102+
103+ var endPoint: UIImageView = {
104+ var view = UIImageView()
105+ view.isUserInteractionEnabled = true
106+ view.image = #imageLiteral(resourceName: "Group")
107+ view.translatesAutoresizingMaskIntoConstraints = false
108+ return view
109+ }()
110+
111+ /// Интервал
112+ var fillInterval: UIView = {
113+ var view = UIView()
114+ view.backgroundColor = #colorLiteral(red: 0, green: 0.5921568627, blue: 1, alpha: 0.3426262843)
115+ view.translatesAutoresizingMaskIntoConstraints = false
116+ return view
117+ }()
118+
119+ // MARK: - Lifecycle
120+
121+ override func awakeFromNib() {
122+ super.awakeFromNib()
123+
124+ scrollView.delegate = self
125+
126+ scrollView.showsHorizontalScrollIndicator = false
127+ }
128+
129+ @IBAction func sliderValueChanged(_ sender: UISlider) {
130+ let value = Int(sender.value)
131+ numberOfGuestsLabel.text = "Количество гостей: \(value)"
132+
133+ guard let d = data else { return }
134+
135+ data = ReservationData(establishment: d.establishment,
136+ startTime: d.startTime,
137+ endTime: d.endTime,
138+ numberOfGuests: value,
139+ reservedTable: d.reservedTable)
140+ }
141+
142+ @objc func callAlmin() {
143+ guard
144+ let m = model?.establishment,
145+ let phone = TotalStorage.standard.getEstablishment(m)?.admin,
146+ let urlPhone = URL(string: "tel://79\(phone)") else { return }
147+
148+ UIApplication.shared.open(urlPhone)
149+ }
150+
151+ // MARK: - Setup
152+
153+ /// Тут происходит установка интервалов. Разное для клиента и админа
154+ func setUpIntervals(_ isAdmin: Bool) {
155+ guard let startPoint = startDate else { return }
156+
157+ let startingPoint = isAdmin ? startPoint.getAdminsStartingPoint() : Constants.clientStartingPoint
158+ let endingPoint = startingPoint + (isAdmin ? Constants.adminDuration : Constants.clientDuration)
159+
160+ for period in startingPoint...endingPoint {
161+ let timePointView = TimePointView.loadFromNib()
162+ timePointView?.setUp(withTimePoint: period)
163+ let view = UIView(frame: CGRect(x: 0, y: 0, width: 35, height: 106))
164+ view.addSubviewThatFills(timePointView)
165+ intervalsStackView.addArrangedSubview(view)
166+ }
167+ }
168+
169+
170+ private func setUpBookingPeriod(_ startTime: Date, _ endTime: Date? = nil) {
171+ self.scrollView.addSubview(startPoint)
172+ self.scrollView.addSubview(endPoint)
173+ self.scrollView.addSubview(fillInterval)
174+
175+ guard let startDate = startDate else { return }
176+
177+ let startConstr = CGFloat(startTime.getMinutesPeriods(fromStart: startDate) * Constants.widthTimePoint)
178+
179+ scrollView.setContentOffset(CGPoint(x: startConstr - 50, y: 0), animated: true)
180+
181+ startLeadingConstraint = startPoint.leftAnchor.constraint(equalTo: scrollView.leftAnchor,
182+ constant: startConstr)
183+
184+ if let end = endTime {
185+ let endConstr = CGFloat(end.getMinutesPeriods(fromStart: startDate) * Constants.widthTimePoint)
186+ endLeadingConstraint = endPoint.leftAnchor.constraint(equalTo: scrollView.leftAnchor,
187+ constant: endConstr)
188+ } else {
189+ endLeadingConstraint = endPoint.leftAnchor.constraint(equalTo: scrollView.leftAnchor,
190+ constant:startConstr + Constants.minBookingLength)
191+ }
192+
193+ NSLayoutConstraint.activate([
194+
195+ startPoint.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0),
196+ endPoint.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0),
197+
198+ startPoint.widthAnchor.constraint(equalToConstant: Constants.pointWidth),
199+ endPoint.widthAnchor.constraint(equalToConstant: Constants.pointWidth),
200+
201+ startPoint.heightAnchor.constraint(equalToConstant: Constants.pointHeight),
202+ endPoint.heightAnchor.constraint(equalToConstant: Constants.pointHeight),
203+
204+ startLeadingConstraint,
205+ endLeadingConstraint,
206+
207+ fillInterval.leftAnchor.constraint(equalTo: startPoint.rightAnchor, constant: 0),
208+ fillInterval.rightAnchor.constraint(equalTo: endPoint.leftAnchor, constant: 0),
209+
210+ fillInterval.heightAnchor.constraint(equalToConstant: Constants.pointHeight),
211+ fillInterval.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0)
212+ ])
213+
214+ let scrollViewPanStartGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanStart(recognizer:)))
215+ startPoint.addGestureRecognizer(scrollViewPanStartGesture)
216+
217+ let scrollViewPanEndGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanEnd(recognizer:)))
218+ endPoint.addGestureRecognizer(scrollViewPanEndGesture)
219+ }
220+
221+ func setUpReservedIntervals(_ reservations: [ReservationPeriod], _ isAdmin: Bool) {
222+
223+ for reservation in reservations {
224+ let uiview = UIView(frame: CGRect(x: 0, y: 0, width: Constants.widthTimePoint * reservation.duration, height: Constants.heightIntervals))
225+ uiview.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 0.5)
226+
227+
228+ uiview.translatesAutoresizingMaskIntoConstraints = false
229+ scrollView.addSubview(uiview)
230+
231+ guard let startDate = startDate else { return }
232+
233+ let s = reservation.startTime.getMinutesPeriods(fromStart: startDate) * Constants.widthTimePoint
234+ let d = reservation.duration * Constants.widthTimePoint
235+
236+ let startTime = s < 0 ? 0 : s
237+
238+ let total = (isAdmin ? Constants.adminDuration : Constants.clientDuration) * Constants.widthTimePoint
239+ var duration = 0
240+ if s < 0 {
241+ duration = reservation.endTime.getMinutesPeriods(fromStart: startDate) * Constants.widthTimePoint
242+ } else {
243+ duration = (s + d) > total ? total - s : d
244+ }
245+
246+ NSLayoutConstraint.activate([
247+ uiview.bottomAnchor.constraint(equalToSystemSpacingBelow: scrollView.bottomAnchor, multiplier: 0),
248+ uiview.leftAnchor.constraint(equalTo: scrollView.leftAnchor,
249+ constant: CGFloat(Constants.widthTimePoint / 2 + startTime)),
250+ uiview.widthAnchor.constraint(equalToConstant: CGFloat(duration)),
251+ uiview.heightAnchor.constraint(equalToConstant: CGFloat(Constants.heightIntervals))
252+ ])
253+ }
254+ }
255+
256+ func bind(model: SecondStepModel) {
257+ self.model = model
258+
259+ guard let table = TotalStorage.standard.getTable(establishment: model.establishment,
260+ table: model.table) else { return }
261+ self.guestSlider.maximumValue = Float(table.maxGuestNumber)
262+ }
263+
264+ // MARK: - Pan
265+
266+ @objc func handlePanStart(recognizer:UIPanGestureRecognizer) {
267+
268+ let translation = recognizer.translation(in: self.intervalsStackView)
269+ let duration = abs(startLeadingConstraint.constant + translation.x - endLeadingConstraint.constant)
270+
271+ if let view = recognizer.view, duration >= Constants.minBookingLength {
272+ startLeadingConstraint.constant += translation.x
273+
274+ changeTime(startLeadingConstraint, .start)
275+
276+ view.setNeedsUpdateConstraints()
277+ }
278+
279+ recognizer.setTranslation(CGPoint.zero, in: self.intervalsStackView)
280+ }
281+
282+ @objc func handlePanEnd(recognizer:UIPanGestureRecognizer) {
283+
284+ let translation = recognizer.translation(in: self.intervalsStackView)
285+ let duration = abs(startLeadingConstraint.constant - (endLeadingConstraint.constant + translation.x))
286+
287+ if let view = recognizer.view, duration >= Constants.minBookingLength {
288+ endLeadingConstraint.constant += translation.x
289+
290+ changeTime(endLeadingConstraint, .end)
291+ view.setNeedsUpdateConstraints()
292+ }
293+
294+ recognizer.setTranslation(CGPoint.zero, in: self.intervalsStackView)
295+ }
296+
297+ func changeTime(_ constraint: NSLayoutConstraint, _ period: Period) {
298+ guard let startDate = startDate, let d = data else { return }
299+
300+ let offset = Int(constraint.constant) % Constants.widthTimePoint
301+
302+ if offset == 0 {
303+ let p = Int(constraint.constant) / Constants.widthTimePoint
304+ let time = startDate.addPeriods(p)
305+
306+ switch period {
307+ case .start:
308+ data = ReservationData(establishment: d.establishment,
309+ startTime: time,
310+ endTime: d.endTime,
311+ numberOfGuests: d.numberOfGuests,
312+ reservedTable: d.reservedTable)
313+ case .end:
314+ data = ReservationData(establishment: d.establishment,
315+ startTime: d.startTime,
316+ endTime: time,
317+ numberOfGuests: d.numberOfGuests,
318+ reservedTable: d.reservedTable)
319+ }
320+ }
321+ }
322+}
323+
324+extension SecondStepView: UIScrollViewDelegate {
325+
326+}
327+
328+// MARK: - Nested types
329+
330+extension SecondStepView {
331+ enum Constants {
332+ static let widthTimePoint = 11
333+
334+ static let adminDuration = 288
335+ static let clientDuration = 90
336+ // 12:00 = 12 * 6
337+ static let clientStartingPoint = 72
338+
339+ static let pointWidth: CGFloat = 19
340+ static let pointHeight: CGFloat = 85
341+
342+ static let minBookingLength: CGFloat = 132
343+
344+ static let heightIntervals = 85
345+ }
346+
347+ enum Period {
348+ case start
349+ case end
350+ }
351+}
Hookah Tau/Controllers/Client/Reservations/Steps/2/SecondStepView.xibnew file+150
@@ -0,0 +1,150 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3+ <device id="retina4_7" orientation="portrait" appearance="light"/>
4+ <dependencies>
5+ <deployment identifier="iOS"/>
6+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
7+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
8+ <capability name="Stack View standard spacing" minToolsVersion="9.0"/>
9+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
10+ </dependencies>
11+ <objects>
12+ <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
13+ <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
14+ <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="SecondStepView" customModule="Hookah_Tau" customModuleProvider="target">
15+ <rect key="frame" x="0.0" y="0.0" width="414" height="352"/>
16+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
17+ <subviews>
18+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="center" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="bJ1-1q-VXq">
19+ <rect key="frame" x="20" y="20" width="374" height="80"/>
20+ <subviews>
21+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" alignment="center" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="Dkh-B7-3Dl">
22+ <rect key="frame" x="16.5" y="0.0" width="341.5" height="41"/>
23+ <subviews>
24+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Забронировать 7 столик на 3 человека 29 марта с 3:00 до 5:30?" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7M9-lI-egu">
25+ <rect key="frame" x="0.0" y="0.0" width="341.5" height="41"/>
26+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
27+ <nil key="textColor"/>
28+ <nil key="highlightedColor"/>
29+ </label>
30+ </subviews>
31+ </stackView>
32+ <label opaque="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="PHb-Nt-9I2">
33+ <rect key="frame" x="47" y="51" width="280.5" height="29"/>
34+ <string key="text">Если Вам нужно что-то особенное, Вы можете
35+связаться с администратором. </string>
36+ <fontDescription key="fontDescription" name=".AppleSystemUIFont" family=".AppleSystemUIFont" pointSize="12"/>
37+ <color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
38+ <nil key="highlightedColor"/>
39+ </label>
40+ </subviews>
41+ </stackView>
42+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="9c7-IZ-n2p">
43+ <rect key="frame" x="20" y="120" width="373.5" height="70"/>
44+ <subviews>
45+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Количество гостей: 3" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="QuY-68-2ax">
46+ <rect key="frame" x="0.0" y="0.0" width="373.5" height="20.5"/>
47+ <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
48+ <nil key="textColor"/>
49+ <nil key="highlightedColor"/>
50+ </label>
51+ <slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="3" minValue="1" maxValue="10" translatesAutoresizingMaskIntoConstraints="NO" id="TrY-MC-Ty0">
52+ <rect key="frame" x="-2" y="30.5" width="377.5" height="40.5"/>
53+ <connections>
54+ <action selector="sliderValueChanged:" destination="iN0-l3-epB" eventType="valueChanged" id="ekZ-3h-EeB"/>
55+ </connections>
56+ </slider>
57+ </subviews>
58+ </stackView>
59+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="RJE-Hk-KNh">
60+ <rect key="frame" x="0.0" y="210" width="414" height="106"/>
61+ <subviews>
62+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="dmc-2e-y3T">
63+ <rect key="frame" x="0.0" y="21" width="414" height="85"/>
64+ <subviews>
65+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="lBc-2o-oni">
66+ <rect key="frame" x="0.0" y="0.0" width="414" height="1"/>
67+ <color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
68+ <constraints>
69+ <constraint firstAttribute="height" constant="1" id="dFH-fE-V61"/>
70+ </constraints>
71+ </view>
72+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7Du-Fg-z1t">
73+ <rect key="frame" x="0.0" y="84" width="414" height="1"/>
74+ <color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
75+ <constraints>
76+ <constraint firstAttribute="height" constant="1" id="hal-Zc-mvq"/>
77+ <constraint firstAttribute="height" relation="greaterThanOrEqual" constant="1" id="mCA-jf-NGh"/>
78+ </constraints>
79+ </view>
80+ </subviews>
81+ <color key="backgroundColor" red="0.89019607840000003" green="0.89019607840000003" blue="0.89019607840000003" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
82+ <constraints>
83+ <constraint firstAttribute="trailing" secondItem="7Du-Fg-z1t" secondAttribute="trailing" id="5fu-5i-psO"/>
84+ <constraint firstAttribute="trailing" secondItem="lBc-2o-oni" secondAttribute="trailing" id="7eP-jA-V9V"/>
85+ <constraint firstItem="lBc-2o-oni" firstAttribute="leading" secondItem="dmc-2e-y3T" secondAttribute="leading" id="CA6-4b-6ZK"/>
86+ <constraint firstItem="7Du-Fg-z1t" firstAttribute="leading" secondItem="dmc-2e-y3T" secondAttribute="leading" id="YwH-ns-ecE"/>
87+ <constraint firstItem="lBc-2o-oni" firstAttribute="top" secondItem="dmc-2e-y3T" secondAttribute="top" id="ja1-YQ-A2D"/>
88+ <constraint firstAttribute="height" constant="85" id="rbN-al-oWi"/>
89+ <constraint firstAttribute="bottom" secondItem="7Du-Fg-z1t" secondAttribute="bottom" id="wa8-GY-qKm"/>
90+ </constraints>
91+ </view>
92+ <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="zk1-3E-2kS">
93+ <rect key="frame" x="0.0" y="0.0" width="414" height="106"/>
94+ <subviews>
95+ <stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="IIO-je-eBt">
96+ <rect key="frame" x="10" y="0.0" width="414" height="106"/>
97+ </stackView>
98+ </subviews>
99+ <constraints>
100+ <constraint firstAttribute="bottom" secondItem="IIO-je-eBt" secondAttribute="bottom" id="FFo-Gu-FQi"/>
101+ <constraint firstItem="IIO-je-eBt" firstAttribute="top" secondItem="zk1-3E-2kS" secondAttribute="top" id="GCb-A1-Nm9"/>
102+ <constraint firstItem="IIO-je-eBt" firstAttribute="width" secondItem="zk1-3E-2kS" secondAttribute="width" priority="250" id="NNQ-JX-gov"/>
103+ <constraint firstItem="IIO-je-eBt" firstAttribute="leading" secondItem="zk1-3E-2kS" secondAttribute="leading" constant="10" id="SFN-at-Ckn"/>
104+ <constraint firstItem="IIO-je-eBt" firstAttribute="height" secondItem="zk1-3E-2kS" secondAttribute="height" id="rPM-R0-lcb"/>
105+ <constraint firstAttribute="trailing" secondItem="IIO-je-eBt" secondAttribute="trailing" constant="10" id="xBZ-av-Hao"/>
106+ </constraints>
107+ <viewLayoutGuide key="contentLayoutGuide" id="AEC-Ih-oKP"/>
108+ <viewLayoutGuide key="frameLayoutGuide" id="ciz-kC-nwk"/>
109+ </scrollView>
110+ </subviews>
111+ <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
112+ <constraints>
113+ <constraint firstAttribute="height" constant="106" id="DIa-m5-0fc"/>
114+ <constraint firstItem="zk1-3E-2kS" firstAttribute="top" secondItem="RJE-Hk-KNh" secondAttribute="top" id="gWD-k8-Ycq"/>
115+ <constraint firstAttribute="trailing" secondItem="zk1-3E-2kS" secondAttribute="trailing" id="iuc-Gd-ZUo"/>
116+ <constraint firstAttribute="bottom" secondItem="dmc-2e-y3T" secondAttribute="bottom" id="k3O-Eo-mm5"/>
117+ <constraint firstItem="zk1-3E-2kS" firstAttribute="leading" secondItem="RJE-Hk-KNh" secondAttribute="leading" id="qT5-vh-S0P"/>
118+ <constraint firstAttribute="trailing" secondItem="dmc-2e-y3T" secondAttribute="trailing" id="tTC-4l-vfe"/>
119+ <constraint firstAttribute="bottom" secondItem="zk1-3E-2kS" secondAttribute="bottom" id="whX-aj-8o9"/>
120+ <constraint firstItem="dmc-2e-y3T" firstAttribute="leading" secondItem="RJE-Hk-KNh" secondAttribute="leading" id="ymc-Tp-Lqg"/>
121+ </constraints>
122+ </view>
123+ </subviews>
124+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
125+ <constraints>
126+ <constraint firstAttribute="bottom" secondItem="RJE-Hk-KNh" secondAttribute="bottom" constant="36" id="0W3-e2-hCU"/>
127+ <constraint firstItem="9c7-IZ-n2p" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="20" id="94y-7J-ksb"/>
128+ <constraint firstItem="9c7-IZ-n2p" firstAttribute="top" secondItem="bJ1-1q-VXq" secondAttribute="bottom" constant="20" id="9TT-vO-7PZ"/>
129+ <constraint firstItem="RJE-Hk-KNh" firstAttribute="trailing" secondItem="vUN-kp-3ea" secondAttribute="trailing" id="AgS-kP-UNE"/>
130+ <constraint firstItem="RJE-Hk-KNh" firstAttribute="top" secondItem="9c7-IZ-n2p" secondAttribute="bottom" constant="20" id="FAI-xj-uWk"/>
131+ <constraint firstAttribute="trailing" secondItem="bJ1-1q-VXq" secondAttribute="trailing" constant="20" id="OCH-eC-kP5"/>
132+ <constraint firstItem="bJ1-1q-VXq" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="20" id="PCm-nY-M6p"/>
133+ <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="9c7-IZ-n2p" secondAttribute="trailing" constant="20.5" id="Rh7-V2-v2u"/>
134+ <constraint firstItem="bJ1-1q-VXq" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="20" id="SnG-Cy-hHq"/>
135+ <constraint firstItem="RJE-Hk-KNh" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="fW0-Tt-MpN"/>
136+ </constraints>
137+ <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
138+ <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
139+ <connections>
140+ <outlet property="guestSlider" destination="TrY-MC-Ty0" id="mR0-wv-AM7"/>
141+ <outlet property="intervalsStackView" destination="IIO-je-eBt" id="gaP-LX-Xed"/>
142+ <outlet property="numberOfGuestsLabel" destination="QuY-68-2ax" id="DvK-dr-c3J"/>
143+ <outlet property="scrollView" destination="zk1-3E-2kS" id="b8R-jY-2Q3"/>
144+ <outlet property="specialLabel" destination="PHb-Nt-9I2" id="hXL-Je-dax"/>
145+ <outlet property="totalBookingLabel" destination="7M9-lI-egu" id="7YU-yI-Gm4"/>
146+ </connections>
147+ <point key="canvasLocation" x="-8" y="88.15592203898052"/>
148+ </view>
149+ </objects>
150+</document>
Hookah Tau/Controllers/Client/Reservations/Steps/3/ThirdStepReservationCoordinator.swiftnew file+57
@@ -0,0 +1,57 @@
1+//
2+// ThirdStepReservationCoordinator.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 23/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class ThirdStepReservationCoordinator: BaseCoordinator {
12+
13+ // MARK: - Properties
14+
15+ var didFinish: (() -> Void)?
16+
17+ var mapView: MapImageScroll?
18+
19+ /// Объект регистрации, созданной или существующей
20+ var resrvation: Reservation?
21+
22+ var reservationVC: ThirdStepReservationViewController?
23+
24+ // MARK: - Lifecycle
25+
26+ override func start() {
27+ let storyboard = UIStoryboard(name: "Reservation", bundle: nil)
28+ reservationVC = storyboard.instantiateViewController(withIdentifier: "ThirdStepReservationViewController") as? ThirdStepReservationViewController
29+
30+ reservationVC?.coordinator = self
31+ reservationVC?.mapView = mapView
32+ reservationVC?.reservation = resrvation
33+
34+ guard let r = reservationVC else { return }
35+ navigationController?.pushViewController(r, animated: false)
36+ }
37+
38+ /// You need to pass reservation object and map describing booked table
39+ func change(_ reservation: Reservation) {
40+ let changeCoordinator = ChangeReservationCoordinator(navigationController: navigationController)
41+
42+ changeCoordinator.reservation = reservation
43+
44+ changeCoordinator.didFinish = { [weak self] res in
45+ self?.removeDependency(changeCoordinator)
46+ self?.navigationController?.popViewController(animated: false)
47+ self?.reservationVC?.reservation = res
48+ }
49+
50+ changeCoordinator.start()
51+ }
52+
53+ /// Goes back to `allReservations` screen
54+ func close() {
55+ didFinish?()
56+ }
57+}
Hookah Tau/Controllers/Client/Reservations/Steps/3/ThirdStepReservationViewController.swiftnew file+156
@@ -0,0 +1,156 @@
1+//
2+// ThirdStepReservationViewController.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 23/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class ThirdStepReservationViewController: BaseViewController {
12+
13+ // MARK: - IBAOutlets
14+
15+ @IBOutlet weak var containerView: UIView!
16+
17+ @IBOutlet weak var contentForMap: UIView!
18+
19+ // MARK: - Properties
20+
21+ var mapView: MapImageScroll?
22+
23+ weak var coordinator: ThirdStepReservationCoordinator?
24+
25+ var thirdStepView: ThirdStepView?
26+
27+ var reservation: Reservation? {
28+ didSet {
29+ guard let r = reservation,
30+ let data = TotalStorage.standard.getEstablishment(r.establishment)
31+ else { return }
32+
33+ navigationItem.title = data.address
34+
35+ guard let res = reservation else { return }
36+ thirdStepView?.bind(withModel: res)
37+ }
38+ }
39+
40+ var reservationService: ReservationsService?
41+
42+ /// "Отменить бронь"
43+ let cancelButton: Button = {
44+ let button = Button()
45+ let style = BlackButtonStyle()
46+ style.apply(to: button, withTitle: "ОТМЕНИТЬ БРОНЬ")
47+ return button
48+ }()
49+
50+ /// "Изменить бронь"
51+ let changeButton: Button = {
52+ let button = Button()
53+ let style = BlackButtonStyle()
54+ style.apply(to: button, withTitle: "ИЗМЕНИТЬ")
55+ return button
56+ }()
57+
58+ // MARK: - Lifecycle
59+
60+ override func viewDidLoad() {
61+ super.viewDidLoad()
62+
63+ navigationItem.hidesBackButton = true
64+ setupContainer()
65+ setupButtons()
66+ setupMap()
67+
68+ reservationService = ReservationsService(apiClient: APIClient.shared)
69+ navigationController?.navigationBar.prefersLargeTitles = false
70+ }
71+
72+ override func viewWillAppear(_ animated: Bool) {
73+ super.viewWillAppear(animated)
74+
75+ tabBarController?.tabBar.isHidden = true
76+ }
77+
78+ // MARK: - Setup
79+
80+ func setupMap() {
81+ guard let reservation = reservation else {
82+ return
83+ }
84+
85+ let width = view.frame.width
86+ if reservation.establishment == 1 {
87+ mapView = MapView1.loadFromNib()
88+ mapView?.scrollViewParent.contentInset = UIEdgeInsets(top: 0,
89+ left: width / 2,
90+ bottom: 200,
91+ right: width / 2)
92+
93+ mapView?.scrollViewParent.setContentOffset(CGPoint(x: 0, y: 50), animated: false)
94+ } else {
95+ mapView = MapView2.loadFromNib()
96+ mapView?.scrollViewParent.setContentOffset(CGPoint(x: 0, y: -100), animated: false)
97+ mapView?.scrollViewParent.contentInset = UIEdgeInsets(top: 100,
98+ left: width / 2,
99+ bottom: 100,
100+ right: width / 2)
101+ }
102+
103+ contentForMap.addSubviewThatFills(mapView)
104+
105+ mapView?.isUserInteractionEnabled = false
106+ mapView?.scrollToTable(table: reservation.reservedTable)
107+ }
108+
109+ func setupContainer() {
110+ thirdStepView = ThirdStepView.loadFromNib()
111+ containerView.addSubviewThatFills(thirdStepView)
112+
113+ guard let res = reservation else { return }
114+ thirdStepView?.bind(withModel: res)
115+ }
116+
117+ func setupButtons() {
118+ addStackViewWithButtons(leftBtn: cancelButton,
119+ rightBtn: changeButton,
120+ constant: -containerView.frame.height - 20)
121+
122+ changeButton.addTarget(self, action: #selector(change), for: .touchUpInside)
123+ cancelButton.addTarget(self, action: #selector(cancelBooking), for: .touchUpInside)
124+ thirdStepView?.closeButton.addTarget(self, action: #selector(close), for: .touchUpInside)
125+ }
126+
127+
128+ // MARK: - handlers for buttons
129+
130+ @objc func change() {
131+ guard
132+ let res = reservation
133+ else { return }
134+
135+ coordinator?.change(res)
136+ }
137+
138+ @objc func cancelBooking() {
139+ guard let res = reservation else { return }
140+
141+ cancelButton.loading = true
142+ reservationService?.deleteReservation(isAdmin: false, uuid: res.uuid, completion: { [weak self] (result) in
143+ self?.cancelButton.loading = false
144+
145+ if result {
146+ self?.close()
147+ } else {
148+ self?.displayAlert(with: "Не удалось отменить бронь, попробуйте еще раз!")
149+ }
150+ })
151+ }
152+
153+ @objc func close() {
154+ coordinator?.close()
155+ }
156+}
Hookah Tau/Controllers/Client/Reservations/Steps/3/ThirdStepView.swiftnew file+177
@@ -0,0 +1,177 @@
1+//
2+// ThirdStepView.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 23/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+struct ThirdStepViewModel {
12+ var timeIntervalView: UIView
13+}
14+
15+class ThirdStepView: UIView {
16+
17+ // MARK: - IBAOutlets
18+
19+ @IBOutlet weak var totalLabel: UILabel!
20+
21+ @IBOutlet weak var infoLabel: UILabel!
22+
23+ @IBOutlet weak var closeButton: Button! {
24+ didSet {
25+ let style = BlackButtonStyle()
26+ style.apply(to: closeButton, withTitle: "ЗАКРЫТЬ")
27+ }
28+ }
29+
30+ @IBOutlet weak var timeInterval: UIView!
31+
32+ @IBOutlet weak var stackView: UIStackView!
33+
34+ @IBOutlet weak var scrollView: UIScrollView!
35+
36+ // MARK: - Properties
37+
38+ /// Время начала вертелки.
39+ var startDate: Date?
40+
41+ // констрейнты начала и конца интервала
42+ var startLeadingConstraint: NSLayoutConstraint!
43+ var endLeadingConstraint: NSLayoutConstraint!
44+
45+ var reservation: Reservation? {
46+ didSet {
47+ // isAdmin
48+ let isAdmin = DataStorage.standard.getUserModel()?.isAdmin ?? false
49+ startDate = reservation?.startTime.addHours(-1)
50+
51+ setupStackView(isAdmin)
52+ setUpBookingPeriod()
53+ }
54+ }
55+
56+ // MARK: - bind
57+
58+ func bind(withModel model: Reservation) {
59+ reservation = model
60+ let date = format(model.startTime, model.endTime)
61+
62+ totalLabel.text = "Забронирован \(model.reservedTable) столик на \(model.numberOfGuests)-х\n \(date)"
63+
64+ infoLabel.text = "Минимальное количество кальянов: \(countTotalNumber(model.numberOfGuests))"
65+ }
66+
67+ private func format(_ startDate: Date, _ endDate: Date) -> String {
68+ let dateFormatter = DateFormatter()
69+ dateFormatter.calendar = Calendar.current
70+ dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
71+ dateFormatter.dateFormat = "MM.dd, с HH:mm по"
72+
73+ var newDateString = "\(dateFormatter.string(from: startDate))"
74+
75+ dateFormatter.dateFormat = "HH:mm"
76+ newDateString += " \(dateFormatter.string(from: endDate))"
77+
78+ return newDateString
79+ }
80+
81+ private func countTotalNumber(_ people: Int) -> Int {
82+ let num = people > 6 ? 2 : 1
83+ return num
84+ }
85+
86+ var startPoint: UIView = {
87+ var view = UIView()
88+ view.backgroundColor = #colorLiteral(red: 0.2901960784, green: 0.5647058824, blue: 0.8862745098, alpha: 1)
89+ view.translatesAutoresizingMaskIntoConstraints = false
90+ return view
91+ }()
92+
93+ var endPoint: UIView = {
94+ var view = UIView()
95+ view.backgroundColor = #colorLiteral(red: 0.2901960784, green: 0.5647058824, blue: 0.8862745098, alpha: 1)
96+ view.translatesAutoresizingMaskIntoConstraints = false
97+ return view
98+ }()
99+
100+ /// Интервал
101+ var fillInterval: UIView = {
102+ var view = UIView()
103+ view.backgroundColor = #colorLiteral(red: 0, green: 0.5921568627, blue: 1, alpha: 0.3426262843)
104+ view.translatesAutoresizingMaskIntoConstraints = false
105+ return view
106+ }()
107+
108+ func setupStackView(_ isAdmin: Bool) {
109+ guard let startPoint = startDate else { return }
110+
111+ let startingPoint = startPoint.getAdminsStartingPoint()
112+ let endingPoint = startingPoint + (isAdmin ? Constants.adminDuration : Constants.clientDuration)
113+
114+ for period in startingPoint...endingPoint {
115+ let timePointView = TimePointView.loadFromNib()
116+ timePointView?.setUp(withTimePoint: period)
117+ let view = UIView(frame: CGRect(x: 0, y: 0, width: 35, height: 106))
118+ view.addSubviewThatFills(timePointView)
119+ stackView.addArrangedSubview(view)
120+ }
121+ }
122+
123+ func setUpBookingPeriod() {
124+ self.scrollView.addSubview(startPoint)
125+ self.scrollView.addSubview(endPoint)
126+ self.scrollView.addSubview(fillInterval)
127+
128+ guard
129+ let startDate = startDate,
130+ let startTime = reservation?.startTime,
131+ let endTime = reservation?.endTime else { return }
132+
133+ let startConstr = CGFloat(startTime.getMinutesPeriods(fromStart: startDate) * Constants.widthTimePoint) + 3
134+ startLeadingConstraint = startPoint.leftAnchor.constraint(equalTo: scrollView.leftAnchor,
135+ constant: startConstr)
136+
137+ let endConstr = CGFloat(endTime.getMinutesPeriods(fromStart: startDate) * Constants.widthTimePoint) + 3
138+ endLeadingConstraint = endPoint.leftAnchor.constraint(equalTo: scrollView.leftAnchor,
139+ constant: endConstr)
140+
141+ scrollView.setContentOffset(CGPoint(x: startConstr - 50, y: 0), animated: true)
142+
143+ NSLayoutConstraint.activate([
144+
145+ startPoint.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0),
146+ endPoint.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0),
147+
148+ startPoint.widthAnchor.constraint(equalToConstant: Constants.pointWidth),
149+ endPoint.widthAnchor.constraint(equalToConstant: Constants.pointWidth),
150+
151+ startPoint.heightAnchor.constraint(equalToConstant: Constants.pointHeight),
152+ endPoint.heightAnchor.constraint(equalToConstant: Constants.pointHeight),
153+
154+ startLeadingConstraint,
155+ endLeadingConstraint,
156+
157+ fillInterval.leftAnchor.constraint(equalTo: startPoint.rightAnchor, constant: 0),
158+ fillInterval.rightAnchor.constraint(equalTo: endPoint.leftAnchor, constant: 0),
159+
160+ fillInterval.heightAnchor.constraint(equalToConstant: Constants.pointHeight),
161+ fillInterval.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0)
162+ ])
163+ }
164+}
165+
166+extension ThirdStepView {
167+ enum Constants {
168+ static let widthTimePoint = 11
169+ static let pointWidth: CGFloat = 5
170+ static let pointHeight: CGFloat = 85
171+
172+ static let adminDuration = 288
173+ static let clientDuration = 90
174+ // 12:00 = 12 * 6
175+ static let clientStartingPoint = 72
176+ }
177+}
Hookah Tau/Controllers/Client/Reservations/Steps/3/ThirdStepView.xibnew file+144
@@ -0,0 +1,144 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3+ <device id="retina4_7" orientation="portrait" appearance="light"/>
4+ <dependencies>
5+ <deployment identifier="iOS"/>
6+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
7+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
8+ <capability name="Stack View standard spacing" minToolsVersion="9.0"/>
9+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
10+ </dependencies>
11+ <objects>
12+ <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
13+ <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
14+ <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="ThirdStepView" customModule="Hookah_Tau" customModuleProvider="target">
15+ <rect key="frame" x="0.0" y="0.0" width="414" height="340"/>
16+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
17+ <subviews>
18+ <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" alignment="center" spacing="21" translatesAutoresizingMaskIntoConstraints="NO" id="DL4-0a-WxC">
19+ <rect key="frame" x="20" y="20" width="374" height="82.5"/>
20+ <subviews>
21+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Забронирован 7 столик на 3 человека 29 марта с 3:00 до 5:30!" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="xHF-9h-q9F">
22+ <rect key="frame" x="19.5" y="0.0" width="335" height="41"/>
23+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
24+ <nil key="textColor"/>
25+ <nil key="highlightedColor"/>
26+ </label>
27+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Минимальное количество кальянов: 2" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7xJ-nJ-IFN">
28+ <rect key="frame" x="27.5" y="62" width="319" height="20.5"/>
29+ <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="17"/>
30+ <nil key="textColor"/>
31+ <nil key="highlightedColor"/>
32+ </label>
33+ </subviews>
34+ </stackView>
35+ <stackView opaque="NO" contentMode="scaleToFill" alignment="center" spacingType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="0H2-jJ-gOj">
36+ <rect key="frame" x="20" y="132.5" width="374" height="45"/>
37+ <subviews>
38+ <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Tick" translatesAutoresizingMaskIntoConstraints="NO" id="lcW-xm-Gb7">
39+ <rect key="frame" x="0.0" y="0.0" width="230" height="45"/>
40+ </imageView>
41+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="QjM-My-aZ0" customClass="Button" customModule="Hookah_Tau" customModuleProvider="target">
42+ <rect key="frame" x="238" y="4" width="136" height="37"/>
43+ <color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
44+ <constraints>
45+ <constraint firstAttribute="width" constant="136" id="No6-wb-pIe"/>
46+ <constraint firstAttribute="height" constant="37" id="SOm-Gm-2i0"/>
47+ </constraints>
48+ <state key="normal" title="Button"/>
49+ </button>
50+ </subviews>
51+ </stackView>
52+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="ByV-f1-mVO">
53+ <rect key="frame" x="0.0" y="207.5" width="414" height="106"/>
54+ <subviews>
55+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="JpA-EH-PLI">
56+ <rect key="frame" x="0.0" y="21" width="414" height="85"/>
57+ <subviews>
58+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="TgV-cB-bMd">
59+ <rect key="frame" x="0.0" y="0.0" width="414" height="1"/>
60+ <color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
61+ <constraints>
62+ <constraint firstAttribute="height" constant="1" id="kOS-eB-bWd"/>
63+ </constraints>
64+ </view>
65+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="nzW-L7-eY8">
66+ <rect key="frame" x="0.0" y="84" width="414" height="1"/>
67+ <color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
68+ <constraints>
69+ <constraint firstAttribute="height" constant="1" id="BNR-Cz-HJe"/>
70+ </constraints>
71+ </view>
72+ </subviews>
73+ <color key="backgroundColor" red="0.8901960784313725" green="0.8901960784313725" blue="0.8901960784313725" alpha="1" colorSpace="calibratedRGB"/>
74+ <constraints>
75+ <constraint firstItem="TgV-cB-bMd" firstAttribute="leading" secondItem="JpA-EH-PLI" secondAttribute="leading" id="3ZT-oY-Rzz"/>
76+ <constraint firstAttribute="height" constant="85" id="7jH-zS-CoD"/>
77+ <constraint firstAttribute="trailing" secondItem="nzW-L7-eY8" secondAttribute="trailing" id="HSn-Vn-8Kb"/>
78+ <constraint firstItem="TgV-cB-bMd" firstAttribute="top" secondItem="JpA-EH-PLI" secondAttribute="top" id="K34-18-ZM8"/>
79+ <constraint firstAttribute="bottom" secondItem="nzW-L7-eY8" secondAttribute="bottom" id="hKT-AB-KQS"/>
80+ <constraint firstItem="nzW-L7-eY8" firstAttribute="leading" secondItem="JpA-EH-PLI" secondAttribute="leading" id="lqp-3j-OoO"/>
81+ <constraint firstAttribute="trailing" secondItem="TgV-cB-bMd" secondAttribute="trailing" id="r1u-XE-GXS"/>
82+ </constraints>
83+ </view>
84+ <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" userInteractionEnabled="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="gQW-4g-8T5">
85+ <rect key="frame" x="0.0" y="0.0" width="414" height="106"/>
86+ <subviews>
87+ <stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Gfj-mL-wQq">
88+ <rect key="frame" x="0.0" y="0.0" width="414" height="106"/>
89+ </stackView>
90+ </subviews>
91+ <constraints>
92+ <constraint firstItem="Gfj-mL-wQq" firstAttribute="width" secondItem="gQW-4g-8T5" secondAttribute="width" priority="250" id="DhE-CT-MGo"/>
93+ <constraint firstItem="Gfj-mL-wQq" firstAttribute="height" secondItem="gQW-4g-8T5" secondAttribute="height" id="JdJ-xb-2kM"/>
94+ <constraint firstAttribute="bottom" secondItem="Gfj-mL-wQq" secondAttribute="bottom" id="Pji-4z-R86"/>
95+ <constraint firstItem="Gfj-mL-wQq" firstAttribute="top" secondItem="gQW-4g-8T5" secondAttribute="top" id="UBW-vr-VEu"/>
96+ <constraint firstAttribute="trailing" secondItem="Gfj-mL-wQq" secondAttribute="trailing" id="gkv-lF-jaW"/>
97+ <constraint firstItem="Gfj-mL-wQq" firstAttribute="leading" secondItem="gQW-4g-8T5" secondAttribute="leading" id="yZy-o3-tDY"/>
98+ </constraints>
99+ <viewLayoutGuide key="contentLayoutGuide" id="jEg-I8-hwu"/>
100+ <viewLayoutGuide key="frameLayoutGuide" id="EFO-bp-t4s"/>
101+ </scrollView>
102+ </subviews>
103+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
104+ <constraints>
105+ <constraint firstItem="JpA-EH-PLI" firstAttribute="leading" secondItem="ByV-f1-mVO" secondAttribute="leading" id="0Yf-vL-RSK"/>
106+ <constraint firstAttribute="trailing" secondItem="JpA-EH-PLI" secondAttribute="trailing" id="N9T-dc-Auk"/>
107+ <constraint firstAttribute="bottom" secondItem="JpA-EH-PLI" secondAttribute="bottom" id="QPT-JY-erJ"/>
108+ <constraint firstItem="gQW-4g-8T5" firstAttribute="top" secondItem="ByV-f1-mVO" secondAttribute="top" id="Qz6-Bm-fcB"/>
109+ <constraint firstAttribute="height" constant="106" id="UWZ-LN-h4r"/>
110+ <constraint firstItem="gQW-4g-8T5" firstAttribute="leading" secondItem="ByV-f1-mVO" secondAttribute="leading" id="gZ2-dm-HGX"/>
111+ <constraint firstAttribute="bottom" secondItem="gQW-4g-8T5" secondAttribute="bottom" id="idD-XC-tGe"/>
112+ <constraint firstAttribute="trailing" secondItem="gQW-4g-8T5" secondAttribute="trailing" id="mWv-Uy-FiK"/>
113+ </constraints>
114+ </view>
115+ </subviews>
116+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
117+ <constraints>
118+ <constraint firstItem="ByV-f1-mVO" firstAttribute="trailing" secondItem="vUN-kp-3ea" secondAttribute="trailing" id="1g0-ab-erl"/>
119+ <constraint firstItem="ByV-f1-mVO" firstAttribute="top" secondItem="0H2-jJ-gOj" secondAttribute="bottom" constant="30" id="F84-ZL-MWp"/>
120+ <constraint firstItem="0H2-jJ-gOj" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="20" id="GR6-gI-ecm"/>
121+ <constraint firstItem="ByV-f1-mVO" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="KKj-7n-eQf"/>
122+ <constraint firstItem="0H2-jJ-gOj" firstAttribute="top" secondItem="DL4-0a-WxC" secondAttribute="bottom" constant="30" id="VbN-sb-Pko"/>
123+ <constraint firstItem="DL4-0a-WxC" firstAttribute="top" secondItem="iN0-l3-epB" secondAttribute="top" constant="20" id="cm0-tz-ta4"/>
124+ <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="0H2-jJ-gOj" secondAttribute="trailing" constant="20" id="i3D-sP-KlI"/>
125+ <constraint firstAttribute="trailing" secondItem="DL4-0a-WxC" secondAttribute="trailing" constant="20" id="kHp-8n-OTp"/>
126+ <constraint firstItem="DL4-0a-WxC" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="20" id="vOg-X1-y93"/>
127+ </constraints>
128+ <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
129+ <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
130+ <connections>
131+ <outlet property="closeButton" destination="QjM-My-aZ0" id="Z3P-aG-bf1"/>
132+ <outlet property="infoLabel" destination="7xJ-nJ-IFN" id="jlH-CO-a0K"/>
133+ <outlet property="scrollView" destination="gQW-4g-8T5" id="lLC-JE-ZUm"/>
134+ <outlet property="stackView" destination="Gfj-mL-wQq" id="k5A-hd-BiB"/>
135+ <outlet property="timeInterval" destination="ByV-f1-mVO" id="oB4-jH-8Ot"/>
136+ <outlet property="totalLabel" destination="xHF-9h-q9F" id="W94-2J-w0A"/>
137+ </connections>
138+ <point key="canvasLocation" x="137.59999999999999" y="365.21739130434787"/>
139+ </view>
140+ </objects>
141+ <resources>
142+ <image name="Tick" width="58" height="45"/>
143+ </resources>
144+</document>
Hookah Tau/Controllers/Client/Reservations/Steps/Common/DateView.swiftnew file+18
@@ -0,0 +1,18 @@
1+//
2+// DateView.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 15/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class DateView: UIView {
12+
13+ @IBOutlet weak var dateLabel: UILabel!
14+
15+ func setUp(with date: Date) {
16+ dateLabel.text = "\(date.getDay())"
17+ }
18+}
Hookah Tau/Controllers/Client/Reservations/Steps/Common/DateView.xibnew file+42
@@ -0,0 +1,42 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14868" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3+ <device id="retina4_7" orientation="portrait" appearance="light"/>
4+ <dependencies>
5+ <deployment identifier="iOS"/>
6+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14824"/>
7+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
8+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
9+ </dependencies>
10+ <objects>
11+ <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
12+ <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
13+ <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="DateView" customModule="Hookah_Tau" customModuleProvider="target">
14+ <rect key="frame" x="0.0" y="0.0" width="31" height="59"/>
15+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
16+ <subviews>
17+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="69" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hzu-lu-5Cc">
18+ <rect key="frame" x="5" y="29" width="21" height="20"/>
19+ <constraints>
20+ <constraint firstAttribute="width" constant="21" id="gdn-d6-auU"/>
21+ </constraints>
22+ <fontDescription key="fontDescription" type="system" weight="semibold" pointSize="16"/>
23+ <color key="textColor" red="0.60784313725490191" green="0.60784313725490191" blue="0.60784313725490191" alpha="1" colorSpace="calibratedRGB"/>
24+ <nil key="highlightedColor"/>
25+ </label>
26+ </subviews>
27+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
28+ <constraints>
29+ <constraint firstItem="hzu-lu-5Cc" firstAttribute="centerX" secondItem="vUN-kp-3ea" secondAttribute="centerX" id="fhT-nh-ejJ"/>
30+ <constraint firstItem="hzu-lu-5Cc" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" constant="5" id="mLo-I1-FSM"/>
31+ <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="hzu-lu-5Cc" secondAttribute="trailing" constant="5" id="qSb-Ok-1MH"/>
32+ <constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="hzu-lu-5Cc" secondAttribute="bottom" constant="10" id="vtm-8d-jCM"/>
33+ </constraints>
34+ <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
35+ <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
36+ <connections>
37+ <outlet property="dateLabel" destination="hzu-lu-5Cc" id="n0M-OL-lME"/>
38+ </connections>
39+ <point key="canvasLocation" x="101.59999999999999" y="456.52173913043481"/>
40+ </view>
41+ </objects>
42+</document>
Hookah Tau/Controllers/Client/Reservations/Steps/Common/TimePointView.swiftnew file+52
@@ -0,0 +1,52 @@
1+//
2+// TimePointView.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 15/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class TimePointView: UIView {
12+
13+ // MARK: - Properties
14+
15+ @IBOutlet weak var timePoint: UIView! {
16+ didSet {
17+ timePoint.layer.cornerRadius = 2
18+ }
19+ }
20+
21+ @IBOutlet weak var heightTimePoint: NSLayoutConstraint!
22+
23+
24+
25+ @IBOutlet weak var timePointContainer: UIView! {
26+ didSet {
27+ timePointContainer.layer.masksToBounds = false
28+ timePointContainer.layer.shadowOffset = CGSize(width: 0, height: -3)
29+ timePointContainer.layer.shadowRadius = 1
30+ }
31+ }
32+
33+ @IBOutlet weak var timeLabel: UILabel!
34+
35+ // MARK: - Set up
36+
37+ func setUp(withTimePoint time: Int) {
38+ if time % 6 != 0 {
39+ timeLabel.isHidden = true
40+ heightTimePoint.constant = 24
41+ } else {
42+ timeLabel.isHidden = false
43+ timeLabel.text = TimePointView.getTextTimeFromTimePoint(time)
44+ }
45+ }
46+
47+ // MARK: - Static
48+
49+ static func getTextTimeFromTimePoint(_ time: Int) -> String {
50+ return "\(Int(time / 6) % 24):00"
51+ }
52+}
Hookah Tau/Controllers/Client/Reservations/Steps/Common/TimePointView.xibnew file+83
@@ -0,0 +1,83 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14868" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3+ <device id="retina4_7" orientation="portrait" appearance="light"/>
4+ <dependencies>
5+ <deployment identifier="iOS"/>
6+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14824"/>
7+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
8+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
9+ </dependencies>
10+ <objects>
11+ <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
12+ <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
13+ <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="TimePointView" customModule="Hookah_Tau" customModuleProvider="target">
14+ <rect key="frame" x="0.0" y="0.0" width="11" height="92"/>
15+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
16+ <subviews>
17+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pWD-hx-gp6">
18+ <rect key="frame" x="0.0" y="21" width="11" height="71"/>
19+ <subviews>
20+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="yHi-OK-OdD">
21+ <rect key="frame" x="0.0" y="0.0" width="11" height="1"/>
22+ <color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
23+ <constraints>
24+ <constraint firstAttribute="height" constant="1" id="vFV-YQ-dLG"/>
25+ </constraints>
26+ </view>
27+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="D96-RH-9hA">
28+ <rect key="frame" x="4" y="13" width="3" height="45"/>
29+ <color key="backgroundColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
30+ <constraints>
31+ <constraint firstAttribute="width" constant="3" id="2jk-1V-VpK"/>
32+ <constraint firstAttribute="height" constant="45" id="TFz-jM-jzh"/>
33+ </constraints>
34+ </view>
35+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="xmA-Sn-VW1">
36+ <rect key="frame" x="0.0" y="70" width="11" height="1"/>
37+ <color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
38+ <constraints>
39+ <constraint firstAttribute="height" constant="1" id="opb-0M-WVx"/>
40+ </constraints>
41+ </view>
42+ </subviews>
43+ <color key="backgroundColor" red="0.8901960784313725" green="0.8901960784313725" blue="0.8901960784313725" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
44+ <constraints>
45+ <constraint firstItem="yHi-OK-OdD" firstAttribute="leading" secondItem="pWD-hx-gp6" secondAttribute="leading" id="B0T-9H-BtD"/>
46+ <constraint firstItem="yHi-OK-OdD" firstAttribute="top" secondItem="pWD-hx-gp6" secondAttribute="top" id="RNc-ds-Sgr"/>
47+ <constraint firstAttribute="trailing" secondItem="xmA-Sn-VW1" secondAttribute="trailing" id="UwV-GZ-ZJm"/>
48+ <constraint firstAttribute="bottom" secondItem="xmA-Sn-VW1" secondAttribute="bottom" id="jsu-wi-tiv"/>
49+ <constraint firstAttribute="trailing" secondItem="yHi-OK-OdD" secondAttribute="trailing" id="rV7-YM-CIi"/>
50+ <constraint firstItem="xmA-Sn-VW1" firstAttribute="leading" secondItem="pWD-hx-gp6" secondAttribute="leading" id="s0c-se-0fn"/>
51+ <constraint firstItem="D96-RH-9hA" firstAttribute="centerX" secondItem="pWD-hx-gp6" secondAttribute="centerX" id="udJ-PM-6Yh"/>
52+ <constraint firstItem="D96-RH-9hA" firstAttribute="centerY" secondItem="pWD-hx-gp6" secondAttribute="centerY" id="wez-7K-Bt1"/>
53+ <constraint firstAttribute="width" constant="11" id="y4G-wZ-gfe"/>
54+ </constraints>
55+ </view>
56+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="18:00" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="iIa-WK-OHd">
57+ <rect key="frame" x="-12" y="0.0" width="35" height="16"/>
58+ <fontDescription key="fontDescription" type="system" pointSize="13"/>
59+ <nil key="textColor"/>
60+ <nil key="highlightedColor"/>
61+ </label>
62+ </subviews>
63+ <color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
64+ <constraints>
65+ <constraint firstItem="pWD-hx-gp6" firstAttribute="bottom" secondItem="vUN-kp-3ea" secondAttribute="bottom" id="A6p-9g-tQu"/>
66+ <constraint firstItem="iIa-WK-OHd" firstAttribute="centerX" secondItem="vUN-kp-3ea" secondAttribute="centerX" id="Hss-kd-RUN"/>
67+ <constraint firstItem="pWD-hx-gp6" firstAttribute="trailing" secondItem="vUN-kp-3ea" secondAttribute="trailing" id="KBo-cb-dAz"/>
68+ <constraint firstItem="pWD-hx-gp6" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="NHW-lH-rza"/>
69+ <constraint firstItem="iIa-WK-OHd" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" id="Pfd-58-VXn"/>
70+ <constraint firstItem="pWD-hx-gp6" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="21" id="Wpp-se-wxM"/>
71+ </constraints>
72+ <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
73+ <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
74+ <connections>
75+ <outlet property="heightTimePoint" destination="TFz-jM-jzh" id="INI-mq-Xd4"/>
76+ <outlet property="timeLabel" destination="iIa-WK-OHd" id="jze-go-adl"/>
77+ <outlet property="timePoint" destination="D96-RH-9hA" id="Oby-Tn-zvq"/>
78+ <outlet property="timePointContainer" destination="pWD-hx-gp6" id="ir6-Na-9Ve"/>
79+ </connections>
80+ <point key="canvasLocation" x="130.40000000000001" y="440.7796101949026"/>
81+ </view>
82+ </objects>
83+</document>
Hookah Tau/Controllers/Client/Reservations/Steps/change /ChangeReservationCoordinator.swiftnew file+36
@@ -0,0 +1,36 @@
1+//
2+// ChangeReservationCoordinator.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 02/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class ChangeReservationCoordinator: BaseCoordinator {
12+
13+ // MARK: - Peoperties
14+
15+ var reservation: Reservation?
16+
17+ var didFinish: ((Reservation) -> Void)?
18+
19+ // MARK: - Lifecycle
20+
21+ override func start() {
22+ let storyboard = UIStoryboard(name: "Reservation", bundle: nil)
23+ let vc = storyboard.instantiateViewController(withIdentifier: "ChangeReservationViewController") as? ChangeReservationViewController
24+
25+ guard let viewController = vc else { return }
26+
27+ viewController.reservation = reservation
28+ viewController.coordinator = self
29+ navigationController?.pushViewController(viewController, animated: false)
30+ }
31+
32+ /// Закончили редактирование, все хорошо
33+ func finish(_ r: Reservation) {
34+ didFinish?(r)
35+ }
36+}
Hookah Tau/Controllers/Client/Reservations/Steps/change /ChangeReservationViewController.swiftnew file+152
@@ -0,0 +1,152 @@
1+//
2+// ChangeReservationViewController.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 02/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+class ChangeReservationViewController: BaseViewController {
12+
13+ // MARK: - IBAOutlets
14+
15+ @IBOutlet weak var childContentView: UIView!
16+
17+ @IBOutlet weak var mapContentView: UIView!
18+
19+ // MARK: - Peoperties
20+
21+ var reservation: Reservation?
22+
23+ var secondStepView: SecondStepView?
24+
25+ var mapView: MapImageScroll?
26+
27+ /// Как только получили ответ на запросах о бронирвоаниях, сразу обновляем
28+ var reservedIntervals: [ReservationPeriod] = [] {
29+ didSet {
30+ setupContainer(withReservedPeriods: reservedIntervals)
31+ }
32+ }
33+
34+ var reservationService: ReservationsService?
35+
36+ weak var coordinator: ChangeReservationCoordinator?
37+
38+ // MARK: - Buttons
39+
40+ var confirmButton: Button = {
41+ var button = Button()
42+ var style = BlackButtonStyle()
43+ style.apply(to: button, withTitle: "ПОДТВЕРДИТЬ")
44+ return button
45+ }()
46+
47+ // MARK: - Lifecycle
48+
49+ override func viewDidLoad() {
50+ super.viewDidLoad()
51+
52+ reservationService = ReservationsService(apiClient: APIClient.shared)
53+ setupButtons()
54+ setupMap()
55+ getReservations()
56+
57+ navigationItem.hidesBackButton = true
58+ // Do any additional setup after loading the view.
59+ }
60+
61+ // MARK: - setup
62+
63+ func setupButtons() {
64+ addStackViewWithButtons( rightBtn: confirmButton,
65+ constant: -childContentView.frame.height - 20)
66+
67+ confirmButton.addTarget(self, action: #selector(changeReservation), for: .touchUpInside)
68+ }
69+
70+ func setupContainer(withReservedPeriods resPer: [ReservationPeriod]) {
71+ secondStepView = SecondStepView.loadFromNib()
72+ childContentView.addSubviewThatFills(secondStepView)
73+
74+ guard let r = reservation else { return }
75+
76+ secondStepView?.bind(model: SecondStepModel(establishment: r.establishment,
77+ table: r.reservedTable,
78+ startTime: r.startTime,
79+ endTime: r.endTime,
80+ reservedintervals: resPer))
81+ }
82+
83+ func getReservations() {
84+ guard let r = reservation else { return }
85+ reservationService?.getAllReservations(establishmentID: r.establishment, completion: { (res) in
86+ switch res {
87+ case .failure(let err):
88+ self.displayAlert(forError: err, with: "Прости, не смогли загрузить доступные периоды для брони этого столика. Попробуй, пожалуйста, еще раз!")
89+ case .success(let allReservations):
90+ let isAdmin = DataStorage.standard.getUserModel()?.isAdmin ?? false
91+ let (start, end) = isAdmin ? r.startTime.getAdminStartingAndEndPoint() : r.startTime.getClientStartingAndEndPoint()
92+ let reservPeriods = HFunc.main.filterPeriodsStartEndDates(allReservations.reservations[r.reservedTable] ?? [],
93+ start,
94+ end)
95+
96+ self.reservedIntervals = reservPeriods
97+ }
98+ })
99+ }
100+
101+ func setupMap() {
102+ guard let reservation = reservation else {
103+ return
104+ }
105+
106+ let width = view.frame.width
107+ if reservation.establishment == 1 {
108+ mapView = MapView1.loadFromNib()
109+ mapView?.scrollViewParent.contentInset = UIEdgeInsets(top: 0,
110+ left: width / 2,
111+ bottom: 200,
112+ right: width / 2)
113+
114+ mapView?.scrollViewParent.setContentOffset(CGPoint(x: 0, y: 50), animated: false)
115+ } else {
116+ mapView = MapView2.loadFromNib()
117+ mapView?.scrollViewParent.setContentOffset(CGPoint(x: 0, y: -100), animated: false)
118+ mapView?.scrollViewParent.contentInset = UIEdgeInsets(top: 100,
119+ left: width / 2,
120+ bottom: 100,
121+ right: width / 2)
122+ }
123+
124+ mapContentView.addSubviewThatFills(mapView)
125+
126+ mapView?.isUserInteractionEnabled = false
127+ mapView?.scrollToTable(table: reservation.reservedTable)
128+ }
129+
130+ // MARK: - Handlers
131+
132+ @objc func changeReservation() {
133+ guard let r = secondStepView?.data, let uuid = reservation?.uuid else { return }
134+ confirmButton.loading = true
135+
136+ reservationService?.updateReservation(startTime: r.startTime,
137+ numberOfGuests: r.numberOfGuests,
138+ endTime: r.endTime,
139+ uuid: uuid,
140+ isAdmin: false,
141+ completion: { [weak self] (res) in
142+ if res {
143+ self?.coordinator?.finish(Reservation(uuid: uuid, reservationData: r))
144+ } else {
145+ self?.displayAlert(with: "Что-то пошло не так! Попробуешь еще раз?")
146+ }
147+
148+ self?.confirmButton.loading = false
149+ })
150+ }
151+
152+}
Hookah Tau/Controllers/Client/Tab bar/Client.storyboard+10−6
@@ -1,14 +1,14 @@
1<?xml version="1.0" encoding="UTF-8"?>
1<?xml version="1.0" encoding="UTF-8"?>
2−<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14868" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
2+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3 <device id="retina6_1" orientation="portrait" appearance="light"/>
3 <device id="retina6_1" orientation="portrait" appearance="light"/>
4 <dependencies>
4 <dependencies>
5 <deployment identifier="iOS"/>
5 <deployment identifier="iOS"/>
6− <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14824"/>
6+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
7 <capability name="Safe area layout guides" minToolsVersion="9.0"/>
7 <capability name="Safe area layout guides" minToolsVersion="9.0"/>
8 <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
8 <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
9 </dependencies>
9 </dependencies>
10 <scenes>
10 <scenes>
11− <!--Navigation Controller-->
11+ <!--Профиль-->
12 <scene sceneID="b0t-Wu-VhQ">
12 <scene sceneID="b0t-Wu-VhQ">
13 <objects>
13 <objects>
14 <viewController id="qqN-qg-VJ7" customClass="UINavigationController" sceneMemberID="viewController">
14 <viewController id="qqN-qg-VJ7" customClass="UINavigationController" sceneMemberID="viewController">
@@ -18,13 +18,13 @@
18 <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
18 <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
19 <viewLayoutGuide key="safeArea" id="WgY-fu-N23"/>
19 <viewLayoutGuide key="safeArea" id="WgY-fu-N23"/>
20 </view>
20 </view>
21− <tabBarItem key="tabBarItem" systemItem="contacts" id="vPo-1j-5WO"/>
21+ <tabBarItem key="tabBarItem" title="Профиль" image="person" id="vPo-1j-5WO"/>
22 </viewController>
22 </viewController>
23 <placeholder placeholderIdentifier="IBFirstResponder" id="zv0-gE-V7c" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
23 <placeholder placeholderIdentifier="IBFirstResponder" id="zv0-gE-V7c" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
24 </objects>
24 </objects>
25 <point key="canvasLocation" x="-166" y="195"/>
25 <point key="canvasLocation" x="-166" y="195"/>
26 </scene>
26 </scene>
27− <!--Navigation Controller-->
27+ <!--Брони-->
28 <scene sceneID="nQv-Ks-RjM">
28 <scene sceneID="nQv-Ks-RjM">
29 <objects>
29 <objects>
30 <viewController id="MGt-zZ-AbL" customClass="UINavigationController" sceneMemberID="viewController">
30 <viewController id="MGt-zZ-AbL" customClass="UINavigationController" sceneMemberID="viewController">
@@ -34,7 +34,7 @@
34 <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
34 <color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
35 <viewLayoutGuide key="safeArea" id="vjZ-Ji-o7T"/>
35 <viewLayoutGuide key="safeArea" id="vjZ-Ji-o7T"/>
36 </view>
36 </view>
37− <tabBarItem key="tabBarItem" systemItem="mostViewed" id="B7T-zH-VfU"/>
37+ <tabBarItem key="tabBarItem" title="Брони" image="stack" id="B7T-zH-VfU"/>
38 </viewController>
38 </viewController>
39 <placeholder placeholderIdentifier="IBFirstResponder" id="W83-e1-iNk" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
39 <placeholder placeholderIdentifier="IBFirstResponder" id="W83-e1-iNk" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
40 </objects>
40 </objects>
@@ -59,4 +59,8 @@
59 <point key="canvasLocation" x="-1081" y="-130"/>
59 <point key="canvasLocation" x="-1081" y="-130"/>
60 </scene>
60 </scene>
61 </scenes>
61 </scenes>
62+ <resources>
63+ <image name="person" width="25" height="24"/>
64+ <image name="stack" width="25" height="24"/>
65+ </resources>
62</document>
66</document>
Hookah Tau/Controllers/Client/Tab bar/ClientTabbarController.swift+6−6
@@ -10,18 +10,18 @@ import Foundation
10import UIKit
10import UIKit
11
11
12protocol TabbarView: class {
12protocol TabbarView: class {
13− var onReservationFlowSelect: ((UINavigationController) -> ())? { get set }
13+ var onReservationFlowSelect: TabbarClosure? { get set }
14− var onProfileFlowSelect: ((UINavigationController) -> ())? { get set }
14+ var onProfileFlowSelect: TabbarClosure? { get set }
15− var onViewDidLoad: ((UINavigationController) -> ())? { get set }
15+ var onViewDidLoad: TabbarClosure? { get set }
16}
16}
17
17
18final class ClientTabbarController: UITabBarController, UITabBarControllerDelegate, TabbarView {
18final class ClientTabbarController: UITabBarController, UITabBarControllerDelegate, TabbarView {
19
19
20 // MARK: - Properties
20 // MARK: - Properties
21
21
22− var onReservationFlowSelect: ((UINavigationController) -> ())?
22+ var onReservationFlowSelect: TabbarClosure?
23− var onProfileFlowSelect: ((UINavigationController) -> ())?
23+ var onProfileFlowSelect: TabbarClosure?
24− var onViewDidLoad: ((UINavigationController) -> ())?
24+ var onViewDidLoad: TabbarClosure?
25
25
26
26
27 // MARK: - Lifecycle
27 // MARK: - Lifecycle
Hookah Tau/Controllers/Client/Tab bar/ClientTabbarCoordinator.swift+7−7
@@ -8,14 +8,10 @@
8
8
9import UIKit
9import UIKit
10
10
11+typealias TabbarClosure = ((UINavigationController) -> ())
12+
11class ClientTabbarCoordinator: BaseCoordinator {
13class ClientTabbarCoordinator: BaseCoordinator {
12
14
13− typealias TabbarClosure = ((UINavigationController) -> ())
14−
15− // MARK: - Properties
16−
17− var didEndFlow: (()-> Void)?
18−
19 // MARK: - Lifecycle
15 // MARK: - Lifecycle
20
16
21 override func start() {
17 override func start() {
@@ -41,10 +37,14 @@ class ClientTabbarCoordinator: BaseCoordinator {
41 }
37 }
42 }
38 }
43
39
44− private func runProfileFlow() -> TabbarClosure{
40+ private func runProfileFlow() -> TabbarClosure {
45 return { navigationController in
41 return { navigationController in
46 if navigationController.viewControllers.isEmpty {
42 if navigationController.viewControllers.isEmpty {
47 let profileCoordinator = ProfileCoordinator(navigationController: navigationController)
43 let profileCoordinator = ProfileCoordinator(navigationController: navigationController)
44+ profileCoordinator.didEndFlow = { [weak self, weak profileCoordinator] in
45+ self?.removeDependency(profileCoordinator)
46+ self?.didEndFlow?()
47+ }
48 self.addDependency(profileCoordinator)
48 self.addDependency(profileCoordinator)
49 profileCoordinator.start()
49 profileCoordinator.start()
50 }
50 }
Hookah Tau/Controllers/Picker view/IntervalPickerView.swiftnew file+265
@@ -0,0 +1,265 @@
1+//
2+// IntervalPickerView.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 19/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+struct IntervalPickerModel {
12+ var startTime: Date
13+ var endTime: Date
14+}
15+
16+protocol PickerViewUpdater: class {
17+ func update(withModel model: IntervalPickerModel)
18+}
19+
20+class IntervalPickerView: UIView {
21+
22+ // MARK: - IBAOutlets
23+
24+ @IBOutlet weak var scrollView: UIScrollView!
25+
26+ @IBOutlet weak var intervalsStackView: UIStackView!
27+
28+ // MARK: - Properties
29+
30+ // констрейнты начала и конца интервала
31+ var startLeadingConstraint: NSLayoutConstraint!
32+ var endLeadingConstraint: NSLayoutConstraint!
33+
34+ var startPoint: UIImageView = {
35+ var view = UIImageView()
36+ view.isUserInteractionEnabled = true
37+ view.image = #imageLiteral(resourceName: "handle_left")
38+ view.translatesAutoresizingMaskIntoConstraints = false
39+ return view
40+ }()
41+
42+ var endPoint: UIImageView = {
43+ var view = UIImageView()
44+ view.isUserInteractionEnabled = true
45+ view.image = #imageLiteral(resourceName: "Group")
46+ view.translatesAutoresizingMaskIntoConstraints = false
47+ return view
48+ }()
49+
50+ /// Интервал
51+ var fillInterval: UIView = {
52+ var view = UIView()
53+ view.backgroundColor = #colorLiteral(red: 0, green: 0.5921568627, blue: 1, alpha: 0.3426262843)
54+ view.translatesAutoresizingMaskIntoConstraints = false
55+ return view
56+ }()
57+
58+ /// Время начала вертелки.
59+ /// Для клиента это 12:00 смены (надо вызвать метод для получения текущей смены).
60+ /// Для администратора это `t` - 24h
61+ var startDate: Date?
62+
63+ /// Моделька для смены промежутков
64+ var model: IntervalPickerModel? {
65+ didSet {
66+ guard let model = model else { return }
67+ updater?.update(withModel: model)
68+ }
69+ }
70+
71+ weak var updater: PickerViewUpdater?
72+
73+ // MARK: - bind
74+
75+ func bind(withModel model: ReservationData, user isAdmin: Bool, intervals: [ReservationPeriod]) {
76+ // isAdmin
77+ let isAdmin = DataStorage.standard.getUserModel()?.isAdmin ?? false
78+
79+ let (start, _) = isAdmin ? model.startTime.getAdminStartingAndEndPoint() : model.startTime.getClientStartingAndEndPoint()
80+
81+ startDate = start
82+
83+ setUpIntervals(isAdmin)
84+ setUpReservedIntervals(intervals, isAdmin)
85+ setUpBookingPeriod(model.startTime, model.endTime)
86+ }
87+
88+ /// Тут происходит установка интервалов. Разное для клиента и админа
89+ func setUpIntervals(_ isAdmin: Bool) {
90+ guard let startPoint = startDate else { return }
91+
92+ let startingPoint = isAdmin ? startPoint.getAdminsStartingPoint() : Constants.clientStartingPoint
93+ let endingPoint = startingPoint + (isAdmin ? Constants.adminDuration : Constants.clientDuration)
94+
95+ for period in startingPoint...endingPoint {
96+ let timePointView = TimePointView.loadFromNib()
97+ timePointView?.setUp(withTimePoint: period)
98+ let view = UIView(frame: CGRect(x: 0, y: 0, width: 35, height: 106))
99+ view.addSubviewThatFills(timePointView)
100+ intervalsStackView.addArrangedSubview(view)
101+ }
102+ }
103+
104+ private func setUpBookingPeriod(_ startTime: Date, _ endTime: Date? = nil) {
105+ self.scrollView.addSubview(startPoint)
106+ self.scrollView.addSubview(endPoint)
107+ self.scrollView.addSubview(fillInterval)
108+
109+ guard let startDate = startDate else { return }
110+
111+ let startConstr = CGFloat(startTime.getMinutesPeriods(fromStart: startDate) * Constants.widthTimePoint)
112+
113+ scrollView.setContentOffset(CGPoint(x: startConstr - 50, y: 0), animated: true)
114+
115+ startLeadingConstraint = startPoint.leftAnchor.constraint(equalTo: scrollView.leftAnchor,
116+ constant: startConstr)
117+
118+ if let end = endTime {
119+ let endConstr = CGFloat(end.getMinutesPeriods(fromStart: startDate) * Constants.widthTimePoint)
120+ endLeadingConstraint = endPoint.leftAnchor.constraint(equalTo: scrollView.leftAnchor,
121+ constant: endConstr)
122+ } else {
123+ endLeadingConstraint = endPoint.leftAnchor.constraint(equalTo: scrollView.leftAnchor,
124+ constant:startConstr + Constants.minBookingLength)
125+ }
126+
127+ NSLayoutConstraint.activate([
128+
129+ startPoint.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0),
130+ endPoint.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0),
131+
132+ startPoint.widthAnchor.constraint(equalToConstant: Constants.pointWidth),
133+ endPoint.widthAnchor.constraint(equalToConstant: Constants.pointWidth),
134+
135+ startPoint.heightAnchor.constraint(equalToConstant: Constants.pointHeight),
136+ endPoint.heightAnchor.constraint(equalToConstant: Constants.pointHeight),
137+
138+ startLeadingConstraint,
139+ endLeadingConstraint,
140+
141+ fillInterval.leftAnchor.constraint(equalTo: startPoint.rightAnchor, constant: 0),
142+ fillInterval.rightAnchor.constraint(equalTo: endPoint.leftAnchor, constant: 0),
143+
144+ fillInterval.heightAnchor.constraint(equalToConstant: Constants.pointHeight),
145+ fillInterval.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: 0)
146+ ])
147+
148+ let scrollViewPanStartGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanStart(recognizer:)))
149+ startPoint.addGestureRecognizer(scrollViewPanStartGesture)
150+
151+ let scrollViewPanEndGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanEnd(recognizer:)))
152+ endPoint.addGestureRecognizer(scrollViewPanEndGesture)
153+ }
154+
155+ func setUpReservedIntervals(_ reservations: [ReservationPeriod], _ isAdmin: Bool) {
156+
157+ for reservation in reservations {
158+ let uiview = UIView(frame: CGRect(x: 0, y: 0, width: Constants.widthTimePoint * reservation.duration, height: Constants.heightIntervals))
159+ uiview.backgroundColor = #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 0.5)
160+
161+
162+ uiview.translatesAutoresizingMaskIntoConstraints = false
163+ scrollView.addSubview(uiview)
164+
165+ guard let startDate = startDate else { return }
166+
167+ let s = reservation.startTime.getMinutesPeriods(fromStart: startDate) * Constants.widthTimePoint
168+ let d = reservation.duration * Constants.widthTimePoint
169+
170+ let startTime = s < 0 ? 0 : s
171+
172+ let total = (isAdmin ? Constants.adminDuration : Constants.clientDuration) * Constants.widthTimePoint
173+ var duration = 0
174+ if s < 0 {
175+ duration = reservation.endTime.getMinutesPeriods(fromStart: startDate) * Constants.widthTimePoint
176+ } else {
177+ duration = (s + d) > total ? total - s : d
178+ }
179+
180+ NSLayoutConstraint.activate([
181+ uiview.bottomAnchor.constraint(equalToSystemSpacingBelow: scrollView.bottomAnchor, multiplier: 0),
182+ uiview.leftAnchor.constraint(equalTo: scrollView.leftAnchor,
183+ constant: CGFloat(Constants.widthTimePoint / 2 + startTime)),
184+ uiview.widthAnchor.constraint(equalToConstant: CGFloat(duration)),
185+ uiview.heightAnchor.constraint(equalToConstant: CGFloat(Constants.heightIntervals))
186+ ])
187+ }
188+ }
189+
190+ // MARK: - Pan
191+
192+ @objc func handlePanStart(recognizer:UIPanGestureRecognizer) {
193+
194+ let translation = recognizer.translation(in: self.intervalsStackView)
195+ let duration = abs(startLeadingConstraint.constant + translation.x - endLeadingConstraint.constant)
196+
197+ if let view = recognizer.view, duration >= Constants.minBookingLength {
198+ startLeadingConstraint.constant += translation.x
199+
200+ changeTime(startLeadingConstraint, .start)
201+
202+ view.setNeedsUpdateConstraints()
203+ }
204+
205+ recognizer.setTranslation(CGPoint.zero, in: self.intervalsStackView)
206+ }
207+
208+ @objc func handlePanEnd(recognizer:UIPanGestureRecognizer) {
209+
210+ let translation = recognizer.translation(in: self.intervalsStackView)
211+ let duration = abs(startLeadingConstraint.constant - (endLeadingConstraint.constant + translation.x))
212+
213+ if let view = recognizer.view, duration >= Constants.minBookingLength {
214+ endLeadingConstraint.constant += translation.x
215+
216+ changeTime(endLeadingConstraint, .end)
217+ view.setNeedsUpdateConstraints()
218+ }
219+
220+ recognizer.setTranslation(CGPoint.zero, in: self.intervalsStackView)
221+ }
222+
223+ func changeTime(_ constraint: NSLayoutConstraint, _ period: Period) {
224+ guard let startDate = startDate, let m = model else { return }
225+
226+ let offset = Int(constraint.constant) % Constants.widthTimePoint
227+
228+ if offset == 0 {
229+ let p = Int(constraint.constant) / Constants.widthTimePoint
230+ let time = startDate.addPeriods(p)
231+
232+ switch period {
233+ case .start:
234+ model = IntervalPickerModel(startTime: time,
235+ endTime: m.endTime)
236+ case .end:
237+ model = IntervalPickerModel(startTime: m.startTime,
238+ endTime: time)
239+ }
240+ }
241+ }
242+}
243+
244+extension IntervalPickerView {
245+ enum Constants {
246+ static let widthTimePoint = 11
247+
248+ static let adminDuration = 288
249+ static let clientDuration = 90
250+ // 12:00 = 12 * 6
251+ static let clientStartingPoint = 72
252+
253+ static let pointWidth: CGFloat = 19
254+ static let pointHeight: CGFloat = 85
255+
256+ static let minBookingLength: CGFloat = 132
257+
258+ static let heightIntervals = 85
259+ }
260+
261+ enum Period {
262+ case start
263+ case end
264+ }
265+}
Hookah Tau/Controllers/Picker view/IntervalPickerView.xibnew file+84
@@ -0,0 +1,84 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15702" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
3+ <device id="retina4_7" orientation="portrait" appearance="light"/>
4+ <dependencies>
5+ <deployment identifier="iOS"/>
6+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15704"/>
7+ <capability name="Safe area layout guides" minToolsVersion="9.0"/>
8+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
9+ </dependencies>
10+ <objects>
11+ <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
12+ <placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
13+ <view contentMode="scaleToFill" id="iN0-l3-epB" customClass="IntervalPickerView" customModule="Hookah_Tau" customModuleProvider="target">
14+ <rect key="frame" x="0.0" y="0.0" width="414" height="106"/>
15+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
16+ <subviews>
17+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QgM-NY-puV">
18+ <rect key="frame" x="0.0" y="21" width="414" height="85"/>
19+ <subviews>
20+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="0TX-kl-OPO">
21+ <rect key="frame" x="0.0" y="84" width="414" height="1"/>
22+ <color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
23+ <constraints>
24+ <constraint firstAttribute="height" constant="1" id="Sf4-dJ-qsD"/>
25+ </constraints>
26+ </view>
27+ <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Wcb-MB-8F8">
28+ <rect key="frame" x="0.0" y="0.0" width="414" height="1"/>
29+ <color key="backgroundColor" white="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
30+ <constraints>
31+ <constraint firstAttribute="height" constant="1" id="DCx-L0-09t"/>
32+ </constraints>
33+ </view>
34+ </subviews>
35+ <color key="backgroundColor" red="0.89019607840000003" green="0.89019607840000003" blue="0.89019607840000003" alpha="1" colorSpace="custom" customColorSpace="calibratedRGB"/>
36+ <constraints>
37+ <constraint firstAttribute="height" constant="85" id="1jc-QZ-f8e"/>
38+ <constraint firstItem="Wcb-MB-8F8" firstAttribute="top" secondItem="QgM-NY-puV" secondAttribute="top" id="2tI-Or-OAH"/>
39+ <constraint firstItem="0TX-kl-OPO" firstAttribute="leading" secondItem="QgM-NY-puV" secondAttribute="leading" id="ZM5-CV-OL9"/>
40+ <constraint firstItem="Wcb-MB-8F8" firstAttribute="leading" secondItem="QgM-NY-puV" secondAttribute="leading" id="adB-mX-tf3"/>
41+ <constraint firstAttribute="bottom" secondItem="0TX-kl-OPO" secondAttribute="bottom" id="iji-xK-gU8"/>
42+ <constraint firstAttribute="trailing" secondItem="Wcb-MB-8F8" secondAttribute="trailing" id="kK5-X5-zT5"/>
43+ <constraint firstAttribute="trailing" secondItem="0TX-kl-OPO" secondAttribute="trailing" id="x2r-sO-Npw"/>
44+ </constraints>
45+ </view>
46+ <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="e97-Yk-hHi">
47+ <rect key="frame" x="0.0" y="0.0" width="414" height="106"/>
48+ <subviews>
49+ <stackView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="4n8-V3-m5t">
50+ <rect key="frame" x="0.0" y="0.0" width="414" height="106"/>
51+ </stackView>
52+ </subviews>
53+ <constraints>
54+ <constraint firstItem="4n8-V3-m5t" firstAttribute="height" secondItem="e97-Yk-hHi" secondAttribute="height" id="8p9-v6-iaH"/>
55+ <constraint firstAttribute="bottom" secondItem="4n8-V3-m5t" secondAttribute="bottom" id="ajS-3D-lcm"/>
56+ <constraint firstItem="4n8-V3-m5t" firstAttribute="top" secondItem="e97-Yk-hHi" secondAttribute="top" id="eN5-bZ-4Wt"/>
57+ <constraint firstItem="4n8-V3-m5t" firstAttribute="width" secondItem="e97-Yk-hHi" secondAttribute="width" id="enn-uv-GR2"/>
58+ <constraint firstAttribute="trailing" secondItem="4n8-V3-m5t" secondAttribute="trailing" id="fIk-7A-mrL"/>
59+ <constraint firstItem="4n8-V3-m5t" firstAttribute="leading" secondItem="e97-Yk-hHi" secondAttribute="leading" id="fcr-TD-pYK"/>
60+ </constraints>
61+ <viewLayoutGuide key="contentLayoutGuide" id="sxJ-gI-ilG"/>
62+ <viewLayoutGuide key="frameLayoutGuide" id="yj8-fL-J9D"/>
63+ </scrollView>
64+ </subviews>
65+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
66+ <constraints>
67+ <constraint firstItem="vUN-kp-3ea" firstAttribute="bottom" secondItem="QgM-NY-puV" secondAttribute="bottom" id="JdC-xm-YdY"/>
68+ <constraint firstItem="e97-Yk-hHi" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="Oqv-Y6-Qp3"/>
69+ <constraint firstItem="e97-Yk-hHi" firstAttribute="trailing" secondItem="vUN-kp-3ea" secondAttribute="trailing" id="YH5-He-gHk"/>
70+ <constraint firstItem="vUN-kp-3ea" firstAttribute="trailing" secondItem="QgM-NY-puV" secondAttribute="trailing" id="ewc-Nf-fIZ"/>
71+ <constraint firstItem="QgM-NY-puV" firstAttribute="leading" secondItem="vUN-kp-3ea" secondAttribute="leading" id="jGH-FL-IC0"/>
72+ <constraint firstItem="e97-Yk-hHi" firstAttribute="bottom" secondItem="vUN-kp-3ea" secondAttribute="bottom" id="lOW-0Q-pvW"/>
73+ <constraint firstItem="e97-Yk-hHi" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" symbolic="YES" id="x39-q5-hdD"/>
74+ </constraints>
75+ <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
76+ <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
77+ <connections>
78+ <outlet property="intervalsStackView" destination="4n8-V3-m5t" id="239-zR-iS1"/>
79+ <outlet property="scrollView" destination="e97-Yk-hHi" id="eED-qd-4Yw"/>
80+ </connections>
81+ <point key="canvasLocation" x="137.68115942028987" y="360.26785714285711"/>
82+ </view>
83+ </objects>
84+</document>
Hookah Tau/DB/DataStorage.swiftnew file+115
@@ -0,0 +1,115 @@
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 UIKit
10+
11+/// Класс для хранения промежуточного состояния регистрации,
12+/// данных пользователя, ...
13+class DataStorage {
14+
15+ enum Keys: String {
16+ case name
17+ case phone
18+ case isAdmin
19+ case loggedIn
20+ case cookies
21+ case chosenEstablishment // only for admin
22+ }
23+
24+ private init() {}
25+
26+ static let standard = DataStorage()
27+
28+ /// Номер телефона для процесса регистрации/авторизации
29+ var phone: String?
30+ /// Имя введенное при регистрации
31+ var name: String?
32+
33+ /// Метод для сохранения данных пользователя в приложении,
34+ /// используется для сохранения объекта, полученного после успешной
35+ /// авторизации, регистрации
36+ func saveUserModel(_ user: User) {
37+ let name = user.name
38+ let phone = user.phoneNumber
39+ let isAdmin = user.isAdmin
40+
41+ UserDefaults.standard.setValue(name, forKey: Keys.name.rawValue)
42+ UserDefaults.standard.setValue(phone, forKey: Keys.phone.rawValue)
43+ UserDefaults.standard.setValue(isAdmin, forKey: Keys.isAdmin.rawValue)
44+ }
45+
46+ /// Сохраняем выбранный администратором establishmentId
47+ func setupEstablishmentChoice(id: Int) {
48+ UserDefaults.standard.setValue(id, forKey: Keys.chosenEstablishment.rawValue)
49+ }
50+
51+ func getEstablishmentChoice() -> Int? {
52+ let val = UserDefaults.standard.integer(forKey: Keys.chosenEstablishment.rawValue)
53+ return val == 0 ? nil : val
54+ }
55+
56+ /// Метод для установки куков в хранилище
57+ /// - Parameter cookies: куки
58+ /// - Parameter url: url для куков
59+ func setCookies(_ cookies: [HTTPCookie], forURL url: URL) {
60+ let cookiesStorage = HTTPCookieStorage.shared
61+ let userDefaults = UserDefaults.standard
62+
63+ var cookieDict = [String: AnyObject]()
64+
65+ for cookie in cookiesStorage.cookies(for: url)! {
66+ cookieDict[cookie.name] = cookie.properties as AnyObject?
67+ }
68+
69+ userDefaults.set(cookieDict, forKey: Keys.cookies.rawValue)
70+ }
71+
72+
73+ /// Достаем из хранилища и устанавливаем куки
74+ /// Если не выходит, то вовзращаем false
75+ func getCookies() -> Bool {
76+ let cookiesStorage = HTTPCookieStorage.shared
77+ let userDefaults = UserDefaults.standard
78+
79+ if let cookieDictionary = userDefaults.dictionary(forKey: Keys.cookies.rawValue) {
80+
81+ for (_, cookieProperties) in cookieDictionary {
82+ if let cookie = HTTPCookie(properties: cookieProperties as! [HTTPCookiePropertyKey : Any] ) {
83+ cookiesStorage.setCookie(cookie)
84+ }
85+ }
86+ return true
87+ }
88+
89+ return false
90+ }
91+
92+ func getUserModel() -> User? {
93+ guard
94+ let name = UserDefaults.standard.string(forKey: Keys.name.rawValue),
95+ let phone = UserDefaults.standard.string(forKey: Keys.phone.rawValue)
96+ else { return nil }
97+
98+ let isAdmin = UserDefaults.standard.bool(forKey: Keys.isAdmin.rawValue)
99+
100+ return User(name: name, phoneNumber: phone, isAdmin: isAdmin)
101+ }
102+
103+ func isLoggedIn() -> Bool {
104+ return UserDefaults.standard.bool(forKey: Keys.loggedIn.rawValue) && getCookies()
105+ }
106+
107+ func setLoggedInState(_ value: Bool) {
108+ if !value {
109+ UserDefaults.standard.removeObject(forKey: Keys.name.rawValue)
110+ UserDefaults.standard.removeObject(forKey: Keys.phone.rawValue)
111+ UserDefaults.standard.removeObject(forKey: Keys.cookies.rawValue)
112+ }
113+ UserDefaults.standard.set(value, forKey: Keys.loggedIn.rawValue)
114+ }
115+}
Hookah Tau/DB/TotalStorage.swiftnew file+124
@@ -0,0 +1,124 @@
1+//
2+// TotalStorage.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 28/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+/// Класс для хранения `establishments`,
12+class TotalStorage {
13+
14+ // MARK: - properties
15+
16+ static let standard = TotalStorage()
17+
18+ // MARK: - Init
19+
20+ private init() {
21+ service = EstablishmentSevice(apiClient: APIClient.shared)
22+ setEstablishments()
23+ }
24+
25+ // MARK: - getters
26+
27+ func getEstablishment(_ id: Int) -> EstablishmentData? {
28+ return establishments[id]
29+ }
30+
31+ func getTable(establishment eId: Int, table tId: Int) -> Table? {
32+ return imagesForTables[eId]?[tId]
33+ }
34+
35+ // MARK: - Setters
36+
37+ private func setEstablishments() {
38+ service.loadAdmins { (result) in
39+ switch result {
40+ case .success(let phones):
41+ self.phones = phones
42+ default:
43+ break
44+ }
45+ }
46+ }
47+
48+ // MARK: - Private
49+
50+ private let service: EstablishmentSevice
51+
52+ private var establishments: [Int: EstablishmentData] = [
53+ 1: EstablishmentData(id: 1,
54+ image: #imageLiteral(resourceName: "1"),
55+ admin: "",
56+ address: "Гороховский пер. 12с5, 205",
57+ maxTableNumber: 8),
58+
59+ 2: EstablishmentData(id: 2,
60+ image: #imageLiteral(resourceName: "2"),
61+ admin: "",
62+ address: "ул. Рыбинская 7, кв. 2",
63+ maxTableNumber: 7)
64+ ]
65+
66+ private var imagesForTables: [Int: [Int: Table]] = [
67+ 1 :[
68+ 1: Table(image: #imageLiteral(resourceName: "1_1"), maxGuestNumber: 6),
69+ 2: Table(image: #imageLiteral(resourceName: "1_2"), maxGuestNumber: 8),
70+ 3: Table(image: #imageLiteral(resourceName: "1_3"), maxGuestNumber: 8),
71+ 4: Table(image: #imageLiteral(resourceName: "1_4"), maxGuestNumber: 6),
72+ 5: Table(image: #imageLiteral(resourceName: "1_5"), maxGuestNumber: 6),
73+ 6: Table(image: #imageLiteral(resourceName: "1_6"), maxGuestNumber: 6),
74+ 7: Table(image: #imageLiteral(resourceName: "1_7"), maxGuestNumber: 6),
75+ 8: Table(image: #imageLiteral(resourceName: "1_8"), maxGuestNumber: 2)
76+ ],
77+ 2 :[
78+ 2: Table(image: #imageLiteral(resourceName: "2_2"), maxGuestNumber: 9),
79+ 3: Table(image: #imageLiteral(resourceName: "2_3"), maxGuestNumber: 6),
80+ 4: Table(image: #imageLiteral(resourceName: "2_4"), maxGuestNumber: 2),
81+ 5: Table(image: #imageLiteral(resourceName: "2_5"), maxGuestNumber: 5),
82+ 6: Table(image: #imageLiteral(resourceName: "2_6"), maxGuestNumber: 5),
83+ 7: Table(image: #imageLiteral(resourceName: "2_7"), maxGuestNumber: 5),
84+ 8: Table(image: #imageLiteral(resourceName: "2_8"), maxGuestNumber: 2)
85+ ]
86+ ]
87+
88+ private var phones: [Int: String] = [:] {
89+ didSet {
90+ let newEstablishments = phones.compactMap { el -> (Int, EstablishmentData)? in
91+ if let es = establishments[el.key] {
92+
93+ return (el.key, EstablishmentData(id: es.id,
94+ image: es.image,
95+ admin: el.value,
96+ address: es.address,
97+ maxTableNumber: es.maxTableNumber))
98+ } else {
99+ return nil
100+ }
101+ }
102+
103+ establishments = Dictionary(uniqueKeysWithValues: newEstablishments)
104+ }
105+ }
106+}
107+
108+// MARK: - Nested types
109+
110+extension TotalStorage {
111+
112+ struct EstablishmentData {
113+ var id: Int
114+ var image: UIImage
115+ var admin: String
116+ var address: String
117+ var maxTableNumber: Int
118+ }
119+
120+ struct Table {
121+ var image: UIImage
122+ var maxGuestNumber: Int
123+ }
124+}
Hookah Tau/Extensions+Date.swiftnew file+237
@@ -0,0 +1,237 @@
1+//
2+// Extensions+Date.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 16/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+struct IntervalDate {
12+ var day: Int
13+ var month: Int
14+ var year: Int
15+
16+ var minutes: Int? = nil
17+}
18+
19+extension DateFormatter {
20+ static let iso8601Full: DateFormatter = {
21+ let formatter = DateFormatter()
22+ formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
23+ formatter.timeZone = TimeZone(abbreviation: "UTC")
24+ return formatter
25+ }()
26+
27+ static let dmy: DateFormatter = {
28+ let formatter = DateFormatter()
29+ formatter.dateFormat = "yyyy-MM-dd"
30+ formatter.timeZone = TimeZone(abbreviation: "UTC")
31+ return formatter
32+ }()
33+}
34+
35+
36+extension Date {
37+ static func getNext(days: Int) -> [Date] {
38+ var result = [Date]()
39+
40+ var date = Date()
41+ let calendar = Calendar.current
42+
43+ for _ in 1...days {
44+ result.append(date)
45+
46+ let newDate = calendar.date(byAdding: .day, value: 1, to: date)
47+ guard let nextDate = newDate else { break }
48+ date = nextDate
49+ }
50+
51+ return result
52+ }
53+
54+ static func getDateFromCurrent(days: Int = 0, periods: Int? = nil) -> Date? {
55+ let date = Date()
56+
57+ let utcDate = date.getUTCDate()
58+
59+ let newDate = Calendar.current.date(byAdding: .day, value: days, to: utcDate)
60+
61+ // case if periods are important
62+ if let minutes = periods, let date = newDate {
63+ return date.changeTime(periods: minutes)
64+ }
65+
66+ return newDate
67+ }
68+
69+ /// Get UTC format of date
70+ func getUTCDate() -> Date {
71+ let calendar = Calendar.current
72+
73+ let day = calendar.dateComponents([.year, .month, .day, .hour, .minute], from: self)
74+
75+ var dateCalendars = DateComponents()
76+ dateCalendars.hour = day.hour!
77+ dateCalendars.minute = day.minute!
78+ dateCalendars.day = day.day!
79+ dateCalendars.month = day.month!
80+ dateCalendars.year = day.year!
81+ dateCalendars.timeZone = TimeZone(abbreviation: "GMT")
82+
83+ return Calendar.current.date(from: dateCalendars)!
84+ }
85+
86+ func changeTime(periods: Int) -> Date? {
87+ let hours = (periods * 10 / 60) % 24
88+ let min = (periods * 10) % 60
89+
90+ return self.set(hours: hours, minutes: min, seconds: 0)
91+ }
92+
93+ func getDay() -> Int {
94+ let day = Calendar.current.dateComponents([.day], from: self)
95+ return day.day ?? 0
96+ }
97+
98+ func getDMY() -> Date {
99+ return set(hours: 0, minutes: 0, seconds: 0)!
100+ }
101+
102+ func addHours(_ h: Int) -> Date {
103+ return self.addingTimeInterval(Double(h) * 60.0 * 60.0)
104+ }
105+
106+ func addPeriods(_ p: Int) -> Date {
107+ let h = (p * 10 / 60) % 24
108+ let m = (p * 10) % 60
109+
110+ return self.addingTimeInterval(Double(h) * 60.0 * 60.0 + Double(m) * 60.0)
111+ }
112+
113+ func set(hours: Int, minutes: Int, seconds: Int) -> Date? {
114+
115+ let dateFormatter = DateFormatter()
116+ dateFormatter.calendar = Calendar.current
117+ dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
118+ dateFormatter.dateFormat = "yyyy-MM-dd"
119+
120+ let newDateString = "\(dateFormatter.string(from: self)) \(hours):\(minutes):\(seconds)"
121+
122+ dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
123+
124+ return dateFormatter.date(from: newDateString)
125+ }
126+
127+ func getMinutesPeriods(fromStart startDate: Date) -> Int {
128+ let diffInMins = Calendar.current.dateComponents([.minute], from: startDate, to: self).minute!
129+ let periods = diffInMins / 10
130+ return periods
131+ }
132+
133+ func getPrevCurrentNextMonth() -> (String, String, String) {
134+ let dateFormatter = DateFormatter()
135+ dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
136+ dateFormatter.dateFormat = "LLLL"
137+ let current = dateFormatter.string(from: self)
138+
139+ let nextDate = Calendar.current.date(byAdding: .month, value: 1, to: self)!
140+ let next = dateFormatter.string(from: nextDate)
141+
142+ let prevDate = Calendar.current.date(byAdding: .month, value: -1, to: self)!
143+ let prev = dateFormatter.string(from: prevDate)
144+
145+ print("\(prevDate) \(self) \(nextDate)")
146+ return (prev, current, next)
147+ }
148+}
149+
150+// MARK: - Admin vs Client
151+
152+extension Date {
153+
154+ /// Идем на день назад, чтобы найти точку начала с которой может забронировать администратор
155+ func getAdminStartingAndEndPoint() -> (Date, Date) {
156+ let utcDate = self.getUTCDate()
157+
158+ let startDate = Calendar.current.date(byAdding: .day, value: -1, to: utcDate)!
159+ let endDate = Calendar.current.date(byAdding: .day, value: 1, to: utcDate)!
160+
161+ return (startDate, endDate)
162+ }
163+
164+ func getAdminsStartingPoint() -> Int {
165+ var calendar = Calendar.current
166+ calendar.timeZone = TimeZone(abbreviation: "UTC")!
167+
168+ let comp = calendar.dateComponents([.hour, .minute], from: self)
169+ return (comp.hour! * 6 + comp.minute! / 10)
170+ }
171+
172+ func getClientStartingAndEndPoint() -> (Date, Date) {
173+ // час
174+ var calendar = Calendar.current
175+ calendar.timeZone = TimeZone(abbreviation: "UTC")!
176+
177+ let hour = calendar.dateComponents([.hour], from: self).hour!
178+
179+ if hour >= 0 && hour <= 3 { // [ 00:00 ... 3:00 ]
180+ let endDate = self.set(hours: 3, minutes: 0, seconds: 0)! // 3 00
181+ let start = calendar.date(byAdding: .day, value: -1, to: self)!
182+ let startDate = start.set(hours: 12, minutes: 0, seconds: 0)! // 12 00
183+
184+ return (startDate, endDate)
185+ } else { // [12:00 ... 23:50]
186+ let startDate = self.set(hours: 12, minutes: 0, seconds: 0)! // 12 00
187+ let end = calendar.date(byAdding: .day, value: 1, to: self)!
188+ let endDate = end.set(hours: 3, minutes: 0, seconds: 0)! // 3 00
189+
190+ return (startDate, endDate)
191+ }
192+ }
193+
194+ /// Определяем попадает ли в промежуток между [3:00, 12:00)
195+ func isInClosingHours() -> Bool {
196+ var calendar = Calendar.current
197+ calendar.timeZone = TimeZone(abbreviation: "UTC")!
198+
199+ let day = calendar.dateComponents([.hour, .day, .month, .year, .minute], from: self)
200+
201+ var dateCalendars = DateComponents()
202+ dateCalendars.hour = 3
203+ dateCalendars.minute = 0
204+ dateCalendars.day = day.day!
205+ dateCalendars.month = day.month!
206+ dateCalendars.year = day.year!
207+ dateCalendars.timeZone = TimeZone(abbreviation: "GMT")
208+
209+ let startClosing = calendar.date(from: dateCalendars)!
210+
211+ dateCalendars.hour = 11
212+ dateCalendars.minute = 50
213+
214+ let endClosing = calendar.date(from: dateCalendars)!
215+
216+ let intervalClosing = DateInterval(start: startClosing, end: endClosing)
217+
218+ return intervalClosing.contains(self)
219+ }
220+}
221+
222+extension Date {
223+ static func format(_ startDate: Date, _ endDate: Date) -> (date: String, time: String) {
224+ let dateFormatter = DateFormatter()
225+ dateFormatter.calendar = Calendar.current
226+ dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
227+ dateFormatter.dateFormat = "d MMMM"
228+ dateFormatter.locale = Locale(identifier: "ru_RU")
229+
230+ let dateStr = "\(dateFormatter.string(from: startDate))"
231+
232+ dateFormatter.dateFormat = "HH:mm"
233+ let timeStr = "\(dateFormatter.string(from: startDate)) — \(dateFormatter.string(from: endDate))"
234+
235+ return (dateStr, timeStr)
236+ }
237+}
Hookah Tau/Extensions+String.swiftnew file+36
@@ -0,0 +1,36 @@
1+//
2+// Extensions+String.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 10/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+
12+extension String {
13+
14+ /// Only works with 9-length strings, for ex 01 733 01 79: (2 3 2 2)
15+ func formattedNumber() -> String {
16+ let cleanPhoneNumber = self.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
17+
18+ var result = ""
19+ let phoneMask = "+7 (9XX) XXX-XX-XX"
20+ var index = cleanPhoneNumber.startIndex
21+ for ch in phoneMask where index < cleanPhoneNumber.endIndex {
22+ if ch == "X" {
23+ result.append(cleanPhoneNumber[index])
24+ index = cleanPhoneNumber.index(after: index)
25+ } else {
26+ result.append(ch)
27+ }
28+ }
29+ return result
30+ }
31+
32+ /// Gets +7 9(XX) XXX-XX-XX returns XXXXXXXXX
33+ func toApiPhoneNumberFormat() -> String {
34+ return String(self.filter{ $0.isNumber }.dropFirst(2))
35+ }
36+}
Hookah Tau/Extensions+UIView.swift+16
@@ -59,6 +59,22 @@ extension UIView {
59 setNeedsLayout()
59 setNeedsLayout()
60 layoutIfNeeded()
60 layoutIfNeeded()
61 }
61 }
62+
63+ func centerSubview(_ view: UIView?) {
64+ guard let view = view else {
65+ assert(false, "View mustn't be nil")
66+ return
67+ }
68+
69+ view.translatesAutoresizingMaskIntoConstraints = false
70+ addSubview(view)
71+
72+ view.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
73+ view.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
74+
75+ setNeedsLayout()
76+ layoutIfNeeded()
77+ }
62}
78}
63
79
64
80
Hookah Tau/Extenstions+IBDesignable.swiftnew file+25
@@ -0,0 +1,25 @@
1+//
2+// Extenstions+IBDesignable.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 16/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import UIKit
10+
11+@IBDesignable class RadialGradientView: UIView {
12+
13+ @IBInspectable var outsideColor: UIColor = UIColor.red
14+ @IBInspectable var insideColor: UIColor = UIColor.clear.withAlphaComponent(0)
15+
16+ override func draw(_ rect: CGRect) {
17+ let colors = [insideColor.cgColor, outsideColor.cgColor] as CFArray
18+ let endRadius = sqrt(pow(frame.width/2, 2) + pow(frame.height/2, 2))
19+ let center = CGPoint(x: bounds.size.width / 2, y: bounds.size.height / 2)
20+ let gradient = CGGradient(colorsSpace: nil, colors: colors, locations: nil)
21+ let context = UIGraphicsGetCurrentContext()
22+
23+ context?.drawRadialGradient(gradient!, startCenter: center, startRadius: 0.0, endCenter: center, endRadius: endRadius, options: CGGradientDrawingOptions.drawsBeforeStartLocation)
24+ }
25+}
Hookah Tau/HFunc.swiftnew file+68
@@ -0,0 +1,68 @@
1+//
2+// HFunc.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 02/12/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+
12+class HFunc {
13+
14+ private init() {}
15+
16+ static let main = HFunc()
17+
18+ func exportClientsToCSV(_ arr: [Client]) -> String {
19+ return arr.reduce(Client.csvHeading) { return funcToMapClientsIntoString($0, $1) }
20+ }
21+
22+ /// Filtering function to get every periods which intersects with given date
23+ /// - Parameter periods: all periods of bookings (for a particular table)
24+ /// - Parameter today: date
25+ func filterPeriodsInDate(_ periods: [ReservationPeriod], _ today: Date) -> [ReservationPeriod] {
26+ return periods.filter(filterPeriod(today))
27+ }
28+
29+
30+ /// Filtering func to get every intersecting period for the table. Useful for 2nd booning screen
31+ /// - Parameter periods: Periods for the table
32+ /// - Parameter start: for client 12:00, for admin t - 24h
33+ /// - Parameter end: for client 3:00, for admin t + 24h
34+ func filterPeriodsStartEndDates(_ periods: [ReservationPeriod], _ start: Date, _ end: Date) -> [ReservationPeriod] {
35+ return periods.filter(filterInInterval(start, end))
36+ }
37+
38+ private func filterPeriod(_ today: Date) -> ((ReservationPeriod) -> Bool) {
39+
40+ func datePredicate(_ start: Date, _ end: Date, _ current: Date) -> Bool {
41+ var calendar = Calendar.current
42+ calendar.timeZone = TimeZone(identifier: "GMT")!
43+
44+ let result = calendar.isDate(start, equalTo: current, toGranularity: .day)
45+ || calendar.isDate(end, equalTo: current, toGranularity: .day)
46+
47+ return result
48+ }
49+
50+ return { (period: ReservationPeriod) in datePredicate(period.startTime, period.endTime, today) }
51+ }
52+
53+ private func filterInInterval(_ start: Date, _ end: Date) -> ((ReservationPeriod) -> Bool) {
54+
55+ let dateInterval = DateInterval(start: start, end: end)
56+ func intervalChecking(_ sp: Date, _ ep: Date) -> Bool {
57+ let innerInterval = DateInterval(start: sp, end: ep)
58+
59+ return dateInterval.intersects(innerInterval)
60+ }
61+
62+ return { (period: ReservationPeriod) in intervalChecking(period.startTime, period.endTime) }
63+ }
64+
65+ private func funcToMapClientsIntoString(_ acc: String, _ el: Client) -> String {
66+ return acc + el.toString + "\n"
67+ }
68+}
Hookah Tau/Info.plist+5−3
@@ -2,8 +2,6 @@
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3<plist version="1.0">
3<plist version="1.0">
4<dict>
4<dict>
5− <key>UIUserInterfaceStyle</key>
6− <string>Ligth</string>
7 <key>CFBundleDevelopmentRegion</key>
5 <key>CFBundleDevelopmentRegion</key>
8 <string>$(DEVELOPMENT_LANGUAGE)</string>
6 <string>$(DEVELOPMENT_LANGUAGE)</string>
9 <key>CFBundleExecutable</key>
7 <key>CFBundleExecutable</key>
@@ -19,7 +17,9 @@
19 <key>CFBundleShortVersionString</key>
17 <key>CFBundleShortVersionString</key>
20 <string>1.0</string>
18 <string>1.0</string>
21 <key>CFBundleVersion</key>
19 <key>CFBundleVersion</key>
22− <string>1</string>
20+ <string>$(CURRENT_PROJECT_VERSION)</string>
21+ <key>ITSAppUsesNonExemptEncryption</key>
22+ <false/>
23 <key>LSRequiresIPhoneOS</key>
23 <key>LSRequiresIPhoneOS</key>
24 <true/>
24 <true/>
25 <key>UILaunchStoryboardName</key>
25 <key>UILaunchStoryboardName</key>
@@ -39,5 +39,7 @@
39 <string>UIInterfaceOrientationLandscapeLeft</string>
39 <string>UIInterfaceOrientationLandscapeLeft</string>
40 <string>UIInterfaceOrientationLandscapeRight</string>
40 <string>UIInterfaceOrientationLandscapeRight</string>
41 </array>
41 </array>
42+ <key>UIUserInterfaceStyle</key>
43+ <string>Light</string>
42</dict>
44</dict>
43</plist>
45</plist>
Hookah Tau/Models/AllReservations.swiftnew file+39
@@ -0,0 +1,39 @@
1+//
2+// AllReservations.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 15/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+struct AllReservations: MyCodable {
12+ var reservations: [Int: [ReservationPeriod]]
13+ var intervals: [Date: [Int]]
14+
15+ enum CodingKeys: CodingKey {
16+ case reservations
17+ case intervals
18+ }
19+
20+ init(from decoder: Decoder) throws {
21+ let container = try decoder.container(keyedBy: CodingKeys.self)
22+
23+ let intervalsWithStr = try container.decode([String: [Int]].self, forKey: .intervals)
24+ reservations = try container.decode([Int: [ReservationPeriod]].self, forKey: .reservations)
25+
26+ let intervArray = intervalsWithStr.map { (element) -> (Date, [Int]) in
27+ let key = DateFormatter.iso8601Full.date(from: element.key)!
28+ return (key, element.value)
29+ }
30+
31+ intervals = Dictionary(uniqueKeysWithValues: intervArray)
32+ }
33+}
34+
35+struct ReservationPeriod: MyCodable {
36+ var startTime: Date
37+ var duration: Int
38+ var endTime: Date
39+}
Hookah Tau/Models/Reservation.swiftnew file+87
@@ -0,0 +1,87 @@
1+//
2+// Reservation.swift
3+// Hookah Tau
4+//
5+// Created by cstore on 06/11/2019.
6+// Copyright © 2019 Daria Rednikina. All rights reserved.
7+//
8+
9+import Foundation
10+
11+
12+struct Reservation: MyCodable {
13+
14+ var uuid: String
15+ var establishment: Int
16+ var startTime: Date
17+ var endTime: Date
18+ var numberOfGuests: Int
19+ var reservedTable: Int
20+
21+ init(uuid: String, reservationData: ReservationData) {
22+ self.uuid = uuid
23+ self.establishment = reservationData.establishment
24+ self.startTime = reservationData.startTime
25+ self.endTime = reservationData.endTime
26+ self.numberOfGuests = reservationData.numberOfGuests
27+ self.reservedTable = reservationData.reservedTable
28+ }
29+
30+ init(uuid: String, establishment: Int, startTime: Date, endTime: Date, numberOfGuests: Int, reservedTable: Int) {
31+ self.uuid = uuid
32+ self.establishment = establishment
33+ self.startTime = startTime
34+ self.endTime = endTime
35+ self.numberOfGuests = numberOfGuests
36+ self.reservedTable = reservedTable
37+ }
38+}
39+
40+/// `Reservation` without `uuid`
41+struct ReservationData: MyCodable {
42+
43+ var establishment: Int
44+ var startTime: Date
45+ var endTime: Date
46+ var numberOfGuests: Int
47+ var reservedTable: Int
48+
49+}
50+
51+/// Структура для получения респонса на `GET /admin/reservations/{establishment id}`
52+struct ReservationWithUser: MyCodable {
53+
54+ var uuid: String?
55+ var establishment: Int
56+ var startTime: Date
57+ var endTime: Date
58+ var numberOfGuests: Int
59+ var reservedTable: Int
60+ var owner: FullUser
61+
62+ init(reservation: Reservation, user: FullUser) {
63+ self.uuid = reservation.uuid
64+ self.establishment = reservation.establishment
65+ self.startTime = reservation.startTime
66+ self.endTime = reservation.endTime
67+ self.numberOfGuests = reservation.numberOfGuests
68+ self.reservedTable = reservation.reservedTable
69+ self.owner = user
70+ }
71+
72+ init(uuid: String?, establishment: Int, startTime: Date, endTime: Date, numberOfGuests: Int, reservedTable: Int, owner: FullUser) {
73+ self.uuid = uuid
74+ self.establishment = establishment
75+ self.startTime = startTime
76+ self.endTime = endTime
77+ self.numberOfGuests = numberOfGuests
78+ self.reservedTable = reservedTable
79+ self.owner = owner
80+ }
81+}
82+
83+struct AdminCreateReservation: MyCodable {
84+ var reservation: ReservationData
85+ var user: String
86+}
87+
Hookah Tau/Models/User.swift+27
@@ -8,8 +8,35 @@
8
8
9import Foundation
9import Foundation
10
10
11+/// Используется в запросах при регистрации пользователя
11struct User: MyCodable {
12struct User: MyCodable {
12 var name: String
13 var name: String
13 var phoneNumber: String
14 var phoneNumber: String
14 var isAdmin: Bool
15 var isAdmin: Bool
15}
16}
17+
18+/// При запросах на список пользователей в аккаунте администратора.
19+struct FullUser: MyCodable {
20+ var uuid: String?
21+ var name: String?
22+ var phoneNumber: String
23+ var isAdmin: Bool
24+}
25+
26+/// `GET /admin/clients`
27+struct Client: MyCodable {
28+ var uuid: String
29+ var name: String?
30+ var phoneNumber: String
31+ var isAdmin: Bool
32+ var reservationCount: Int
33+ var isBlocked: Bool
34+
35+ static var csvHeading: String {
36+ return "uuid, имя, телефон, админ, количество активных бронирований, в черном списке\n"
37+ }
38+
39+ var toString: String {
40+ return "\(uuid), \(name ?? "❔"), +79\(phoneNumber), \(isAdmin), \(reservationCount), \(isBlocked)"
41+ }
42+}
Hookah Tau/Supporting Files/Assets.xcassets/1.imageset/Contents.jsonnew file+15
@@ -0,0 +1,15 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "place 1.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ },
12+ "properties" : {
13+ "preserves-vector-representation" : true
14+ }
15+}
Hookah Tau/Supporting Files/Assets.xcassets/1.imageset/place 1.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/1_1.imageset/1_1.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/1_1.imageset/Contents.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "1_1.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ }
12+}
Hookah Tau/Supporting Files/Assets.xcassets/1_2.imageset/1_2.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/1_2.imageset/Contents.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "1_2.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ }
12+}
Hookah Tau/Supporting Files/Assets.xcassets/1_3.imageset/1_3.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/1_3.imageset/Contents.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "1_3.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ }
12+}
Hookah Tau/Supporting Files/Assets.xcassets/1_4.imageset/1_4.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/1_4.imageset/Contents.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "1_4.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ }
12+}
Hookah Tau/Supporting Files/Assets.xcassets/1_5.imageset/1_5.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/1_5.imageset/Contents.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "1_5.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ }
12+}
Hookah Tau/Supporting Files/Assets.xcassets/1_6.imageset/1_6.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/1_6.imageset/Contents.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "1_6.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ }
12+}
Hookah Tau/Supporting Files/Assets.xcassets/1_7.imageset/1_7.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/1_7.imageset/Contents.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "1_7.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ }
12+}
Hookah Tau/Supporting Files/Assets.xcassets/1_8.imageset/1_8.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/1_8.imageset/Contents.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "1_8.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ }
12+}
Hookah Tau/Supporting Files/Assets.xcassets/2.imageset/Contents.jsonnew file+15
@@ -0,0 +1,15 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "place 2.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ },
12+ "properties" : {
13+ "preserves-vector-representation" : true
14+ }
15+}
Hookah Tau/Supporting Files/Assets.xcassets/2.imageset/place 2.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/2_2.imageset/2_2.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/2_2.imageset/Contents.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "2_2.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ }
12+}
Hookah Tau/Supporting Files/Assets.xcassets/2_3.imageset/2_3.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/2_3.imageset/Contents.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "2_3.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ }
12+}
Hookah Tau/Supporting Files/Assets.xcassets/2_4.imageset/2_4.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/2_4.imageset/Contents.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "2_4.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ }
12+}
Hookah Tau/Supporting Files/Assets.xcassets/2_5.imageset/2_5.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/2_5.imageset/Contents.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "2_5.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ }
12+}
Hookah Tau/Supporting Files/Assets.xcassets/2_6.imageset/2_6.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/2_6.imageset/Contents.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "2_6.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ }
12+}
Hookah Tau/Supporting Files/Assets.xcassets/2_7.imageset/2_7.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/2_7.imageset/Contents.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "2_7.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ }
12+}
Hookah Tau/Supporting Files/Assets.xcassets/2_8.imageset/2_8.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/2_8.imageset/Contents.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "2_8.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ }
12+}
Hookah Tau/Supporting Files/Assets.xcassets/AppIcon.appiconset/1024.pngnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/AppIcon.appiconset/120-1.pngnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/AppIcon.appiconset/120.pngnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/AppIcon.appiconset/152.pngnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/AppIcon.appiconset/167.pngnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/AppIcon.appiconset/180.pngnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/AppIcon.appiconset/20.pngnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/AppIcon.appiconset/29.pngnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/AppIcon.appiconset/40-1.pngnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/AppIcon.appiconset/40-2.pngnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/AppIcon.appiconset/40.pngnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/AppIcon.appiconset/58-1.pngnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/AppIcon.appiconset/58.pngnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/AppIcon.appiconset/60.pngnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/AppIcon.appiconset/76.pngnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/AppIcon.appiconset/80-1.pngnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/AppIcon.appiconset/80.pngnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/AppIcon.appiconset/87.pngnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/AppIcon.appiconset/Contents.json+36−18
@@ -1,93 +1,111 @@
1{
1{
2 "images" : [
2 "images" : [
3 {
3 {
4− "idiom" : "iphone",
5 "size" : "20x20",
4 "size" : "20x20",
5+ "idiom" : "iphone",
6+ "filename" : "40-1.png",
6 "scale" : "2x"
7 "scale" : "2x"
7 },
8 },
8 {
9 {
9− "idiom" : "iphone",
10 "size" : "20x20",
10 "size" : "20x20",
11+ "idiom" : "iphone",
12+ "filename" : "60.png",
11 "scale" : "3x"
13 "scale" : "3x"
12 },
14 },
13 {
15 {
14− "idiom" : "iphone",
15 "size" : "29x29",
16 "size" : "29x29",
17+ "idiom" : "iphone",
18+ "filename" : "58-1.png",
16 "scale" : "2x"
19 "scale" : "2x"
17 },
20 },
18 {
21 {
19− "idiom" : "iphone",
20 "size" : "29x29",
22 "size" : "29x29",
23+ "idiom" : "iphone",
24+ "filename" : "87.png",
21 "scale" : "3x"
25 "scale" : "3x"
22 },
26 },
23 {
27 {
24− "idiom" : "iphone",
25 "size" : "40x40",
28 "size" : "40x40",
29+ "idiom" : "iphone",
30+ "filename" : "80.png",
26 "scale" : "2x"
31 "scale" : "2x"
27 },
32 },
28 {
33 {
29− "idiom" : "iphone",
30 "size" : "40x40",
34 "size" : "40x40",
35+ "idiom" : "iphone",
36+ "filename" : "120.png",
31 "scale" : "3x"
37 "scale" : "3x"
32 },
38 },
33 {
39 {
34− "idiom" : "iphone",
35 "size" : "60x60",
40 "size" : "60x60",
41+ "idiom" : "iphone",
42+ "filename" : "120-1.png",
36 "scale" : "2x"
43 "scale" : "2x"
37 },
44 },
38 {
45 {
39− "idiom" : "iphone",
40 "size" : "60x60",
46 "size" : "60x60",
47+ "idiom" : "iphone",
48+ "filename" : "180.png",
41 "scale" : "3x"
49 "scale" : "3x"
42 },
50 },
43 {
51 {
44− "idiom" : "ipad",
45 "size" : "20x20",
52 "size" : "20x20",
53+ "idiom" : "ipad",
54+ "filename" : "20.png",
46 "scale" : "1x"
55 "scale" : "1x"
47 },
56 },
48 {
57 {
49− "idiom" : "ipad",
50 "size" : "20x20",
58 "size" : "20x20",
59+ "idiom" : "ipad",
60+ "filename" : "40.png",
51 "scale" : "2x"
61 "scale" : "2x"
52 },
62 },
53 {
63 {
54− "idiom" : "ipad",
55 "size" : "29x29",
64 "size" : "29x29",
65+ "idiom" : "ipad",
66+ "filename" : "29.png",
56 "scale" : "1x"
67 "scale" : "1x"
57 },
68 },
58 {
69 {
59− "idiom" : "ipad",
60 "size" : "29x29",
70 "size" : "29x29",
71+ "idiom" : "ipad",
72+ "filename" : "58.png",
61 "scale" : "2x"
73 "scale" : "2x"
62 },
74 },
63 {
75 {
64− "idiom" : "ipad",
65 "size" : "40x40",
76 "size" : "40x40",
77+ "idiom" : "ipad",
78+ "filename" : "40-2.png",
66 "scale" : "1x"
79 "scale" : "1x"
67 },
80 },
68 {
81 {
69− "idiom" : "ipad",
70 "size" : "40x40",
82 "size" : "40x40",
83+ "idiom" : "ipad",
84+ "filename" : "80-1.png",
71 "scale" : "2x"
85 "scale" : "2x"
72 },
86 },
73 {
87 {
74− "idiom" : "ipad",
75 "size" : "76x76",
88 "size" : "76x76",
89+ "idiom" : "ipad",
90+ "filename" : "76.png",
76 "scale" : "1x"
91 "scale" : "1x"
77 },
92 },
78 {
93 {
79− "idiom" : "ipad",
80 "size" : "76x76",
94 "size" : "76x76",
95+ "idiom" : "ipad",
96+ "filename" : "152.png",
81 "scale" : "2x"
97 "scale" : "2x"
82 },
98 },
83 {
99 {
84− "idiom" : "ipad",
85 "size" : "83.5x83.5",
100 "size" : "83.5x83.5",
101+ "idiom" : "ipad",
102+ "filename" : "167.png",
86 "scale" : "2x"
103 "scale" : "2x"
87 },
104 },
88 {
105 {
89− "idiom" : "ios-marketing",
90 "size" : "1024x1024",
106 "size" : "1024x1024",
107+ "idiom" : "ios-marketing",
108+ "filename" : "1024.png",
91 "scale" : "1x"
109 "scale" : "1x"
92 }
110 }
93 ],
111 ],
Hookah Tau/Supporting Files/Assets.xcassets/Map.imageset/Contents.json → Hookah Tau/Supporting Files/Assets.xcassets/Group.imageset/Contents.jsonrenamed+1−1
@@ -2,7 +2,7 @@
2 "images" : [
2 "images" : [
3 {
3 {
4 "idiom" : "universal",
4 "idiom" : "universal",
5− "filename" : "Map.pdf"
5+ "filename" : "Group.pdf"
6 }
6 }
7 ],
7 ],
8 "info" : {
8 "info" : {
Hookah Tau/Supporting Files/Assets.xcassets/Group.imageset/Group.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/Map.imageset/Map.pdfdeleted

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/Tick.imageset/Contents.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "Path.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ }
12+}
Hookah Tau/Supporting Files/Assets.xcassets/Tick.imageset/Path.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/TriangleDown.imageset/Contents.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "Triangle.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ }
12+}
Hookah Tau/Supporting Files/Assets.xcassets/TriangleDown.imageset/Triangle.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/TriangleUp.imageset/Contents.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "TriangleUp.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ }
12+}
Hookah Tau/Supporting Files/Assets.xcassets/TriangleUp.imageset/TriangleUp.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/admin.imageset/Contents.jsonnew file+12
@@ -0,0 +1,12 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "admin.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ }
12+}
Hookah Tau/Supporting Files/Assets.xcassets/admin.imageset/admin.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/arrow.imageset/Contents.jsonnew file+15
@@ -0,0 +1,15 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "arrow.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ },
12+ "properties" : {
13+ "preserves-vector-representation" : true
14+ }
15+}
Hookah Tau/Supporting Files/Assets.xcassets/arrow.imageset/arrow.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/blacklist.imageset/Contents.jsonnew file+15
@@ -0,0 +1,15 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "blacklist.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ },
12+ "properties" : {
13+ "preserves-vector-representation" : true
14+ }
15+}
Hookah Tau/Supporting Files/Assets.xcassets/blacklist.imageset/blacklist.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/handle_left.imageset/Contents.jsonnew file+15
@@ -0,0 +1,15 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "handle.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ },
12+ "properties" : {
13+ "preserves-vector-representation" : true
14+ }
15+}
Hookah Tau/Supporting Files/Assets.xcassets/handle_left.imageset/handle.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/map_1.imageset/Contents.jsonnew file+15
@@ -0,0 +1,15 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "map_1.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ },
12+ "properties" : {
13+ "preserves-vector-representation" : true
14+ }
15+}
Hookah Tau/Supporting Files/Assets.xcassets/map_1.imageset/map_1.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/map_2.imageset/Contents.jsonnew file+15
@@ -0,0 +1,15 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "place 2.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ },
12+ "properties" : {
13+ "preserves-vector-representation" : true
14+ }
15+}
Hookah Tau/Supporting Files/Assets.xcassets/map_2.imageset/place 2.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/peoplee.imageset/Contents.jsonnew file+15
@@ -0,0 +1,15 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "peoplee.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ },
12+ "properties" : {
13+ "template-rendering-intent" : "template"
14+ }
15+}
Hookah Tau/Supporting Files/Assets.xcassets/peoplee.imageset/peoplee.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/person.imageset/Contents.jsonnew file+15
@@ -0,0 +1,15 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "person.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ },
12+ "properties" : {
13+ "template-rendering-intent" : "template"
14+ }
15+}
Hookah Tau/Supporting Files/Assets.xcassets/person.imageset/person.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/phone.imageset/Contents.jsonnew file+15
@@ -0,0 +1,15 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "Medium-M.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ },
12+ "properties" : {
13+ "preserves-vector-representation" : true
14+ }
15+}
Hookah Tau/Supporting Files/Assets.xcassets/phone.imageset/Medium-M.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/stack.imageset/Contents.jsonnew file+15
@@ -0,0 +1,15 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "Light-S.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ },
12+ "properties" : {
13+ "template-rendering-intent" : "template"
14+ }
15+}
Hookah Tau/Supporting Files/Assets.xcassets/stack.imageset/Light-S.pdfnew file

Binary file not shown.

Hookah Tau/Supporting Files/Assets.xcassets/stack2.imageset/Contents.jsonnew file+15
@@ -0,0 +1,15 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "universal",
5+ "filename" : "stack2.pdf"
6+ }
7+ ],
8+ "info" : {
9+ "version" : 1,
10+ "author" : "xcode"
11+ },
12+ "properties" : {
13+ "preserves-vector-representation" : true
14+ }
15+}
Hookah Tau/Supporting Files/Assets.xcassets/stack2.imageset/stack2.pdfnew file

Binary file not shown.

Hookah Tau/Views/Code/CodeView.swift+52−20
@@ -11,37 +11,69 @@ import UIKit
11class CodeView: UIView {
11class CodeView: UIView {
12
12
13 // MARK: - Properties
13 // MARK: - Properties
14+
15+ @IBOutlet weak var firstTextField: UITextField?
14
16
15− @IBOutlet weak var fst: UITextField?
17+ @IBOutlet weak var lastTextField: UITextField?
16−
17− @IBOutlet weak var snd: UITextField?
18−
19− @IBOutlet weak var trd: UITextField?
20−
21− @IBOutlet weak var frt: UITextField?
22
18
23− @IBOutlet weak var fif: UITextField?
19+ @IBOutlet var codeTextFields: [UITextField]?
24
20
25− @IBOutlet weak var six: UITextField?
21+ var onComplete: (() -> Void)?
26−
27− private var allNumbers: [UITextField] = []
28
22
29 // MARK: - Lifecycle
23 // MARK: - Lifecycle
30
24
31 override func awakeFromNib() {
25 override func awakeFromNib() {
32 super.awakeFromNib()
26 super.awakeFromNib()
33
27
34− allNumbers = [fst,
28+ _ = codeTextFields?.map {
35− snd,
29+ $0.delegate = self
36− trd,
30+ $0.keyboardType = .asciiCapableNumberPad
37− frt,
31+ }
38− fif,
32+ }
39− six].compactMap{ $0 }
33+
34+ func getFullCode() -> String? {
35+ guard let codeArray = codeTextFields else { return nil }
36+ let code = codeArray.map { $0.text ?? "" }.reduce("", +)
40
37
41− _ = allNumbers.map {
38+ return code
42− $0.keyboardType = .numberPad
39+ }
43− $0.attributedPlaceholder = NSAttributedString(string:"8", attributes: [NSAttributedString.Key.foregroundColor: UIColor.lightGray])
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+ }
44 }
59 }
60+ return true
45 }
61 }
46
62
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+ }
47}
79}
Hookah Tau/Views/Code/CodeView.xib+33−31
@@ -15,69 +15,69 @@
15 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
15 <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
16 <subviews>
16 <subviews>
17 <stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="AvJ-ix-PaS">
17 <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"/>
19 <subviews>
19 <subviews>
20− <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="center" minimumFontSize="28" translatesAutoresizingMaskIntoConstraints="NO" id="bN8-Dx-tUT">
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="33" height="34"/>
21+ <rect key="frame" x="0.0" y="0.0" width="45" height="36"/>
22 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
22 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
23 <constraints>
23 <constraints>
24− <constraint firstAttribute="width" constant="33" id="Nob-jT-cth"/>
24+ <constraint firstAttribute="width" constant="45" id="Nob-jT-cth"/>
25− <constraint firstAttribute="height" constant="34" id="ifR-1l-U2V"/>
25+ <constraint firstAttribute="height" constant="36" id="ifR-1l-U2V"/>
26 </constraints>
26 </constraints>
27 <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
27 <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
28 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
28 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
29 <textInputTraits key="textInputTraits"/>
29 <textInputTraits key="textInputTraits"/>
30 </textField>
30 </textField>
31− <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="center" minimumFontSize="28" translatesAutoresizingMaskIntoConstraints="NO" id="FY0-pn-fCC">
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="41" y="0.0" width="33" height="34"/>
32+ <rect key="frame" x="53" y="0.0" width="45" height="36"/>
33 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
33 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
34 <constraints>
34 <constraints>
35− <constraint firstAttribute="height" constant="34" id="Ucv-mT-fyj"/>
35+ <constraint firstAttribute="height" constant="36" id="Ucv-mT-fyj"/>
36− <constraint firstAttribute="width" constant="33" id="ck1-wZ-IBT"/>
36+ <constraint firstAttribute="width" constant="45" id="ck1-wZ-IBT"/>
37 </constraints>
37 </constraints>
38 <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
38 <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
39 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
39 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
40 <textInputTraits key="textInputTraits"/>
40 <textInputTraits key="textInputTraits"/>
41 </textField>
41 </textField>
42− <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="center" minimumFontSize="28" translatesAutoresizingMaskIntoConstraints="NO" id="JKD-8I-UNU">
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="82" y="0.0" width="33" height="34"/>
43+ <rect key="frame" x="106" y="0.0" width="45" height="36"/>
44 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
44 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
45 <constraints>
45 <constraints>
46− <constraint firstAttribute="height" constant="34" id="ACy-Gx-Fuc"/>
46+ <constraint firstAttribute="height" constant="36" id="ACy-Gx-Fuc"/>
47− <constraint firstAttribute="width" constant="33" id="N0g-Tj-jiT"/>
47+ <constraint firstAttribute="width" constant="45" id="N0g-Tj-jiT"/>
48 </constraints>
48 </constraints>
49 <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
49 <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
50 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
50 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
51 <textInputTraits key="textInputTraits"/>
51 <textInputTraits key="textInputTraits"/>
52 </textField>
52 </textField>
53− <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="center" minimumFontSize="28" translatesAutoresizingMaskIntoConstraints="NO" id="m6q-G8-Kj1">
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="123" y="0.0" width="33" height="34"/>
54+ <rect key="frame" x="159" y="0.0" width="45" height="36"/>
55 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
55 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
56 <constraints>
56 <constraints>
57− <constraint firstAttribute="width" constant="33" id="doW-6X-9HY"/>
57+ <constraint firstAttribute="width" constant="45" id="doW-6X-9HY"/>
58− <constraint firstAttribute="height" constant="34" id="yjR-7R-KES"/>
58+ <constraint firstAttribute="height" constant="36" id="yjR-7R-KES"/>
59 </constraints>
59 </constraints>
60 <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
60 <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
61 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
61 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
62 <textInputTraits key="textInputTraits"/>
62 <textInputTraits key="textInputTraits"/>
63 </textField>
63 </textField>
64− <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="center" minimumFontSize="28" translatesAutoresizingMaskIntoConstraints="NO" id="FEc-xO-TH4">
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="164" y="0.0" width="33" height="34"/>
65+ <rect key="frame" x="212" y="0.0" width="45" height="36"/>
66 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
66 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
67 <constraints>
67 <constraints>
68− <constraint firstAttribute="width" constant="33" id="7tz-Mb-4xo"/>
68+ <constraint firstAttribute="width" constant="45" id="7tz-Mb-4xo"/>
69− <constraint firstAttribute="height" constant="34" id="sLJ-BN-xvF"/>
69+ <constraint firstAttribute="height" constant="36" id="sLJ-BN-xvF"/>
70 </constraints>
70 </constraints>
71 <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
71 <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
72 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
72 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
73 <textInputTraits key="textInputTraits"/>
73 <textInputTraits key="textInputTraits"/>
74 </textField>
74 </textField>
75− <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="center" minimumFontSize="28" translatesAutoresizingMaskIntoConstraints="NO" id="apy-G3-6LG">
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="205" y="0.0" width="33" height="34"/>
76+ <rect key="frame" x="265" y="0.0" width="45" height="36"/>
77 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
77 <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
78 <constraints>
78 <constraints>
79− <constraint firstAttribute="width" constant="33" id="2jK-JD-vJb"/>
79+ <constraint firstAttribute="width" constant="45" id="2jK-JD-vJb"/>
80− <constraint firstAttribute="height" constant="34" id="vmy-eE-VGc"/>
80+ <constraint firstAttribute="height" constant="36" id="vmy-eE-VGc"/>
81 </constraints>
81 </constraints>
82 <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
82 <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
83 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
83 <fontDescription key="fontDescription" type="boldSystem" pointSize="28"/>
@@ -94,12 +94,14 @@
94 <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
94 <freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
95 <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
95 <viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
96 <connections>
96 <connections>
97− <outlet property="fif" destination="FEc-xO-TH4" id="c6h-yp-y1w"/>
97+ <outlet property="firstTextField" destination="bN8-Dx-tUT" id="NtS-pl-Cv9"/>
98− <outlet property="frt" destination="m6q-G8-Kj1" id="FOj-Db-SUl"/>
98+ <outlet property="lastTextField" destination="apy-G3-6LG" id="k3e-7U-of7"/>
99− <outlet property="fst" destination="bN8-Dx-tUT" id="Poq-KO-RY3"/>
99+ <outletCollection property="codeTextFields" destination="bN8-Dx-tUT" collectionClass="NSMutableArray" id="HVa-7S-Mbr"/>
100− <outlet property="six" destination="apy-G3-6LG" id="Mkt-JK-pQ3"/>
101− <outlet property="snd" destination="FY0-pn-fCC" id="BVY-Ym-f69"/>
100+ <outletCollection property="codeTextFields" destination="FY0-pn-fCC" collectionClass="NSMutableArray" id="Yg8-ng-V2S"/>
102− <outlet property="trd" destination="JKD-8I-UNU" id="aFJ-hK-7k7"/>
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"/>
103 </connections>
105 </connections>
104 <point key="canvasLocation" x="138.40000000000001" y="372.41379310344831"/>
106 <point key="canvasLocation" x="138.40000000000001" y="372.41379310344831"/>
105 </view>
107 </view>
Hookah Tau/Views/Phone/PhoneView.swift+46−3
@@ -19,13 +19,56 @@ class PhoneView: UIView {
19 override func awakeFromNib() {
19 override func awakeFromNib() {
20 super.awakeFromNib()
20 super.awakeFromNib()
21
21
22+ phone?.delegate = self
22 phone?.keyboardType = .numberPad
23 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])
24 }
25 }
25
26
26− // TODO: - change in future
27 func bind(withModel model: String) {
27 func bind(withModel model: String) {
28− phone?.text = model
28+ phone?.text = model.formattedNumber()
29 }
29 }
30+}
31+
32+// MARK: - UITextFieldDelegate
30
33
34+extension PhoneView: UITextFieldDelegate {
35+ func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
36+ let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
37+ textField.text = formattedNumber(newString)
38+ return false
39+ }
40+
41+ private func formattedNumber(_ number: String) -> String {
42+ let cleanPhoneNumber = number.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
43+
44+ var result = ""
45+ let phoneMask = "+X (XXX) XXX XX XX"
46+ var index = cleanPhoneNumber.startIndex
47+ for ch in phoneMask where index < cleanPhoneNumber.endIndex {
48+ if ch == "X" {
49+ result.append(cleanPhoneNumber[index])
50+ index = cleanPhoneNumber.index(after: index)
51+ } else {
52+ result.append(ch)
53+ }
54+ }
55+ return result
56+ }
57+
58+ /// +o (oxx) xxx xx xx
59+ ///
60+ /// need tor return only `x`, length is 9. And it has to have prefix +7 (9...) or +8 (9..)
61+ func getFormattedNumber() -> String? {
62+ guard let number = phone?.text else { return nil }
63+
64+ let cleanPhoneNumber = String(number.components(separatedBy: CharacterSet.decimalDigits.inverted).joined())
65+ let hasPrefix = cleanPhoneNumber.hasPrefix("79") || cleanPhoneNumber.hasPrefix("89")
66+
67+ let result = String(cleanPhoneNumber.dropFirst(2))
68+ if result.count == 9 && hasPrefix {
69+ return result
70+ }
71+
72+ return nil
73+ }
31}
74}