diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index b05092af3af7aed3fd1c8784caf6f2621c9c1c6b..0000000000000000000000000000000000000000 --- a/.gitmodules +++ /dev/null @@ -1,8 +0,0 @@ -[submodule "Mobile-SDK-S3"] - path = externals/mobile-sdk-ext - url = https://github.com/NYNJA-MC/Mobile-SDK.git - branch = sprint3 -[submodule "Mobile-SDK-S4"] - path = externals/mobile-sdk-s4 - url = https://github.com/NYNJA-MC/Mobile-SDK.git - branch = sprint4 diff --git a/Frameworks/NynjaUIKit/NynjaUIKit.xcodeproj/project.pbxproj b/Frameworks/NynjaUIKit/NynjaUIKit.xcodeproj/project.pbxproj index 3cfbcb64137cf551dc6c197081c2586a5e8ebc26..d227aa9667c4a83d7a6505aa14378e2c445a08ab 100644 --- a/Frameworks/NynjaUIKit/NynjaUIKit.xcodeproj/project.pbxproj +++ b/Frameworks/NynjaUIKit/NynjaUIKit.xcodeproj/project.pbxproj @@ -328,7 +328,6 @@ 8514D4BD20EE27080002378A /* Frameworks */, 8514D4BE20EE27080002378A /* Headers */, 8514D4BF20EE27080002378A /* Resources */, - 46657F61D06BBC36D9E1E7E9 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -381,21 +380,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 46657F61D06BBC36D9E1E7E9 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/../../Pods/Target Support Files/Pods-NynjaUIKit/Pods-NynjaUIKit-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; 79BB170804CAF73DF4C1F030 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/Nynja-Share/Resources/Info.plist b/Nynja-Share/Resources/Info.plist index c11f81732b885ad6c7a511e6e1c9ef4cc9adda54..3b50d6f5d735547eac0978bbeb4eca44e5673d29 100644 --- a/Nynja-Share/Resources/Info.plist +++ b/Nynja-Share/Resources/Info.plist @@ -21,7 +21,7 @@ CFBundleShortVersionString 1.0 CFBundleVersion - 0.2.135 + 0.2.137 Config $(Config) ModelsVersion diff --git a/Nynja.xcodeproj/project.pbxproj b/Nynja.xcodeproj/project.pbxproj index 8666269d0aa64c28f52a50ebb707c16ca8be8956..f561ce05b4dce59ad418ff8e559c12084d68ef38 100644 --- a/Nynja.xcodeproj/project.pbxproj +++ b/Nynja.xcodeproj/project.pbxproj @@ -157,6 +157,7 @@ 2632139120D797F500C31144 /* TranslationViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2632139020D797F500C31144 /* TranslationViewProtocol.swift */; }; 2633EF6E205212F700DB3868 /* MemberDAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2633EF6D205212F700DB3868 /* MemberDAOProtocol.swift */; }; 2633EF702052130400DB3868 /* MemberDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2633EF6F2052130400DB3868 /* MemberDAO.swift */; }; + 263409342119CFE2002F8D8F /* RecordContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 263409332119CFE2002F8D8F /* RecordContainer.swift */; }; 26342CA020ECAA0700D2196B /* TranscribeNetworkRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26342C9F20ECAA0700D2196B /* TranscribeNetworkRouter.swift */; }; 26342CA920ECBAEF00D2196B /* TranscribeNetworkClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26342CA820ECBAEE00D2196B /* TranscribeNetworkClient.swift */; }; 26342CAB20ECBB0100D2196B /* TranscribeNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26342CAA20ECBB0100D2196B /* TranscribeNetworkService.swift */; }; @@ -414,7 +415,7 @@ 26EA201520BECDCA00FBB9CA /* ConversationLanguageSettingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EA201420BECDCA00FBB9CA /* ConversationLanguageSettingService.swift */; }; 26EAA2CB20D2497F005697CB /* TranslationAutoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EAA2CA20D2497F005697CB /* TranslationAutoView.swift */; }; 26ED2C1820042683002DBBE8 /* RepliesDS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26ED2C1720042683002DBBE8 /* RepliesDS.swift */; }; - 26ED2C1A2004276B002DBBE8 /* RepliesTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26ED2C192004276B002DBBE8 /* RepliesTableViewDelegate.swift */; }; + 26ED2C1A2004276B002DBBE8 /* RepliesCollectionViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26ED2C192004276B002DBBE8 /* RepliesCollectionViewDelegate.swift */; }; 26EEA5472091F84E0066D3B0 /* CollectionsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EEA5462091F84E0066D3B0 /* CollectionsExtensions.swift */; }; 26EEA5482091F84E0066D3B0 /* CollectionsExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26EEA5462091F84E0066D3B0 /* CollectionsExtensions.swift */; }; 26F03C0D20698B0000712CB0 /* ChatWheelItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 26F03C0C20698B0000712CB0 /* ChatWheelItemModel.swift */; }; @@ -498,7 +499,6 @@ 3A2A99831EFAD2FB002749B3 /* PageControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A2A99821EFAD2FB002749B3 /* PageControl.swift */; }; 3A3FD2831F39E0A000B6958F /* HistoryRequestModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A3FD2821F39E0A000B6958F /* HistoryRequestModel.swift */; }; 3A62B7D81F4CB9D100F45B51 /* BaseMQTTModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A62B7D71F4CB9D100F45B51 /* BaseMQTTModel.swift */; }; - 3A768F071ED4987300108F7C /* VoxService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A768F061ED4987300108F7C /* VoxService.swift */; }; 3A771CAA1F191B38008D968A /* ProfileHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A771CA91F191B38008D968A /* ProfileHandler.swift */; }; 3A771CB21F193945008D968A /* UpdateRosterModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A771CB11F193945008D968A /* UpdateRosterModel.swift */; }; 3A8045D21F60C8E200AED866 /* MQTTServiceFriend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3A8045CE1F60C8E200AED866 /* MQTTServiceFriend.swift */; }; @@ -606,6 +606,7 @@ 553819525871F7D28AB90364 /* GroupRulesPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705B62097A99515B3C778F35 /* GroupRulesPresenter.swift */; }; 5683555B8382F7F37FEE1AF5 /* ProfileWireframe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1BA66D21FFC1A74CFD2F63C4 /* ProfileWireframe.swift */; }; 5894F4C605B66B55F21D406E /* DateTimePickerInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CDA2BE900351F21464CE687 /* DateTimePickerInteractor.swift */; }; + 5A48445F21178E33000657ED /* AlertTextFieldViewControllerLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A48445E21178E33000657ED /* AlertTextFieldViewControllerLayout.swift */; }; 5A6237362268CC9BD4792230 /* EditUsernameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B772E08B9E40EB48DD87082 /* EditUsernameViewController.swift */; }; 5AD8110B5B87B1AB9F1C5B52 /* CreateGroupPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8CBACEAABEE65D7EC5572C4E /* CreateGroupPresenter.swift */; }; 5B5EE777EF301CFC1FDCF307 /* CreateGroupInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFC76E2B3DD0BCA0A622A5CD /* CreateGroupInteractor.swift */; }; @@ -787,6 +788,8 @@ 853FB0702049B396000996C5 /* SupportItemsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 853FB06F2049B396000996C5 /* SupportItemsFactory.swift */; }; 853FB0752049B4FF000996C5 /* TextTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 853FB0742049B4FF000996C5 /* TextTableViewCell.swift */; }; 853FB0772049B7CA000996C5 /* TextCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 853FB0762049B7CA000996C5 /* TextCellViewModel.swift */; }; + 8540A331211B34B4007F65AF /* MessageCollectionViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540A330211B34B4007F65AF /* MessageCollectionViewDataSource.swift */; }; + 8540A333211B35A4007F65AF /* MessageCollectionViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540A332211B35A4007F65AF /* MessageCollectionViewDelegate.swift */; }; 8541BD68206CE0220093EF1E /* ImagePlaceholderWheelItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8541BD67206CE0220093EF1E /* ImagePlaceholderWheelItemModel.swift */; }; 8541BD6B206CE3A40093EF1E /* ChatPlaceholderWheelItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8541BD6A206CE3A40093EF1E /* ChatPlaceholderWheelItemModel.swift */; }; 85433F22204D596D00B373A7 /* WebFullScreenPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85433F1D204D596D00B373A7 /* WebFullScreenPresenter.swift */; }; @@ -806,6 +809,7 @@ 854A4B302080D6C400759152 /* CellWithImageTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 854A4B2E2080D6C400759152 /* CellWithImageTableViewCell.swift */; }; 854A4B312080D6C400759152 /* CellWithImageCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 854A4B2F2080D6C400759152 /* CellWithImageCellModel.swift */; }; 854CFB08210704AE00FBC133 /* CGRectExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 854CFB07210704AE00FBC133 /* CGRectExtensions.swift */; }; + 854D13D8211B2E7200E139FC /* MessageCollectionViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 854D13D7211B2E7200E139FC /* MessageCollectionViewLayout.swift */; }; 854FC1CB204468FC00B12BE5 /* CarouselFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 854FC1CA204468FC00B12BE5 /* CarouselFlowLayout.swift */; }; 8557987F2093200D007050B8 /* StickerMenuActionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8557987D2093200D007050B8 /* StickerMenuActionCollectionViewCell.swift */; }; 855798802093200D007050B8 /* StickerMenuActionCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8557987E2093200D007050B8 /* StickerMenuActionCellModel.swift */; }; @@ -1397,8 +1401,6 @@ A45F113220B4218D00F45004 /* BaseChatCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = A45F10EF20B4218D00F45004 /* BaseChatCellLayout.swift */; }; A45F113320B4218D00F45004 /* MessageCellFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A45F10F020B4218D00F45004 /* MessageCellFactory.swift */; }; A45F113420B4218D00F45004 /* UnreadCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A45F10F120B4218D00F45004 /* UnreadCell.swift */; }; - A45F113520B4218D00F45004 /* MessageTableViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A45F10F220B4218D00F45004 /* MessageTableViewDelegate.swift */; }; - A45F113620B4218D00F45004 /* MessageDS.swift in Sources */ = {isa = PBXBuildFile; fileRef = A45F10F320B4218D00F45004 /* MessageDS.swift */; }; A45F113720B4218D00F45004 /* ReplyPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = A45F10F520B4218D00F45004 /* ReplyPreview.swift */; }; A45F113820B4218D00F45004 /* AvatarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A45F10F720B4218D00F45004 /* AvatarViewModel.swift */; }; A45F113920B4218D00F45004 /* AvatarViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = A45F10F820B4218D00F45004 /* AvatarViewLayout.swift */; }; @@ -1429,7 +1431,6 @@ A45F116120B422AF00F45004 /* Message+System.swift in Sources */ = {isa = PBXBuildFile; fileRef = A45F115D20B422AF00F45004 /* Message+System.swift */; }; A45F59AB205825FC00EAA780 /* RosterDAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A45F59AA205825FC00EAA780 /* RosterDAOProtocol.swift */; }; A45F59AD2058263F00EAA780 /* RosterDAO.swift in Sources */ = {isa = PBXBuildFile; fileRef = A45F59AC2058263F00EAA780 /* RosterDAO.swift */; }; - A45F59B02058317000EAA780 /* VICallExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A45F59AF2058317000EAA780 /* VICallExtension.swift */; }; A460324F2105C9A1009783DA /* InputsCachePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A460324E2105C9A1009783DA /* InputsCachePolicy.swift */; }; A46032502105D357009783DA /* InputsCachePolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A460324E2105C9A1009783DA /* InputsCachePolicy.swift */; }; A46032522105D3E1009783DA /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A46032512105D3E1009783DA /* TextView.swift */; }; @@ -1458,6 +1459,7 @@ A4679BBB20B305360021FE9C /* LinkField.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4679BB820B305360021FE9C /* LinkField.swift */; }; A4688DFA20650FF50013660D /* DBObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4688DF920650FF50013660D /* DBObserver.swift */; }; A4688DFC20652DE30013660D /* StorageChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = A4688DFB20652DE30013660D /* StorageChange.swift */; }; + A46CF04321147BAE0072F185 /* HistoryRequestModelTypeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A46CF04221147BAE0072F185 /* HistoryRequestModelTypeProtocol.swift */; }; A47785A220D18D4A0053E0D2 /* BaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E70402BC1FF6972B00182D81 /* BaseView.swift */; }; A47785A420D286680053E0D2 /* ChannelChatItemsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A47785A320D286680053E0D2 /* ChannelChatItemsFactory.swift */; }; A477CE7D2061236800081D34 /* MessageLinkDAOProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A477CE7C2061236800081D34 /* MessageLinkDAOProtocol.swift */; }; @@ -1574,11 +1576,53 @@ B723C632204D9E5100884FFD /* DataAndStorageOption.swift in Sources */ = {isa = PBXBuildFile; fileRef = B723C631204D9E5100884FFD /* DataAndStorageOption.swift */; }; B723C634204DA54200884FFD /* SettingsDataAndStorageTableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B723C633204DA54200884FFD /* SettingsDataAndStorageTableDataSource.swift */; }; B723C636204DA56600884FFD /* SettingsDataAndStorageTableDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B723C635204DA56600884FFD /* SettingsDataAndStorageTableDelegate.swift */; }; + B742053F211448BD004FDE16 /* AsigningInterpreterLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = B742053E211448BD004FDE16 /* AsigningInterpreterLayout.swift */; }; + B745F2E42109B9E500488A91 /* LanguagePickerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B745F2E32109B9E500488A91 /* LanguagePickerDelegate.swift */; }; + B745F2E62109BB0100488A91 /* InterpretationLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = B745F2E52109BB0100488A91 /* InterpretationLayout.swift */; }; + B74BAFFB21076AFA0049CD27 /* CircleMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = B74BAFEF21076AFA0049CD27 /* CircleMenu.swift */; }; + B74BAFFC21076AFA0049CD27 /* SectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B74BAFF121076AFA0049CD27 /* SectionView.swift */; }; + B74BAFFD21076AFA0049CD27 /* Sector.swift in Sources */ = {isa = PBXBuildFile; fileRef = B74BAFF221076AFA0049CD27 /* Sector.swift */; }; + B74BAFFF21076AFA0049CD27 /* CircleMenuDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B74BAFF421076AFA0049CD27 /* CircleMenuDelegate.swift */; }; + B74BB00021076AFA0049CD27 /* UIView+Mask.swift in Sources */ = {isa = PBXBuildFile; fileRef = B74BAFF621076AFA0049CD27 /* UIView+Mask.swift */; }; + B74BB00121076AFA0049CD27 /* CircleMenuSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B74BAFF821076AFA0049CD27 /* CircleMenuSet.swift */; }; + B74BB00221076AFA0049CD27 /* CircleMenuFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B74BAFF921076AFA0049CD27 /* CircleMenuFactory.swift */; }; + B74BB00321076AFA0049CD27 /* CircleMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B74BAFFA21076AFA0049CD27 /* CircleMenuItem.swift */; }; B750EF022046B24D00A99F9C /* TransferInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B750EF012046B24D00A99F9C /* TransferInfo.swift */; }; B750EF042046D69C00A99F9C /* SpeedMesurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = B750EF032046D69C00A99F9C /* SpeedMesurement.swift */; }; B750EF062046D7C700A99F9C /* SpeedStringRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B750EF052046D7C700A99F9C /* SpeedStringRepresentable.swift */; }; B763DD9320AA1C3400A30B63 /* ContactCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = B763DD9220AA1C3400A30B63 /* ContactCellLayout.swift */; }; + B77C11DB2109242200CCB42E /* AssigningInterpreterPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B77C11D62109242200CCB42E /* AssigningInterpreterPresenter.swift */; }; + B77C11DC2109242200CCB42E /* AssigningInterpreterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B77C11D72109242200CCB42E /* AssigningInterpreterViewController.swift */; }; + B77C11DD2109242200CCB42E /* AssigningInterpreterProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = B77C11D82109242200CCB42E /* AssigningInterpreterProtocols.swift */; }; + B77C11DE2109242200CCB42E /* AssigningInterpreterInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B77C11D92109242200CCB42E /* AssigningInterpreterInteractor.swift */; }; + B77C11DF2109242200CCB42E /* AssigningInterpreterWireFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = B77C11DA2109242200CCB42E /* AssigningInterpreterWireFrame.swift */; }; + B77C11E62109254800CCB42E /* InterpretationTypePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B77C11E12109254800CCB42E /* InterpretationTypePresenter.swift */; }; + B77C11E72109254800CCB42E /* InterpretationTypeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B77C11E22109254800CCB42E /* InterpretationTypeViewController.swift */; }; + B77C11E82109254800CCB42E /* InterpretationTypeProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = B77C11E32109254800CCB42E /* InterpretationTypeProtocols.swift */; }; + B77C11E92109254800CCB42E /* InterpretationTypeInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B77C11E42109254800CCB42E /* InterpretationTypeInteractor.swift */; }; + B77C11EA2109254800CCB42E /* InterpretationTypeWireFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = B77C11E52109254800CCB42E /* InterpretationTypeWireFrame.swift */; }; B79B996E20CA88D100BEF5DE /* InviteFriendsMessageComposeDelegateHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79B996D20CA88D100BEF5DE /* InviteFriendsMessageComposeDelegateHandler.swift */; }; + B79FA02B2107731400F286BF /* MarketplacePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79FA0262107731400F286BF /* MarketplacePresenter.swift */; }; + B79FA02C2107731400F286BF /* MarketplaceViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79FA0272107731400F286BF /* MarketplaceViewController.swift */; }; + B79FA02D2107731400F286BF /* MarketplaceProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79FA0282107731400F286BF /* MarketplaceProtocols.swift */; }; + B79FA02E2107731400F286BF /* MarketplaceInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79FA0292107731400F286BF /* MarketplaceInteractor.swift */; }; + B79FA02F2107731400F286BF /* MarketplaceWireFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79FA02A2107731400F286BF /* MarketplaceWireFrame.swift */; }; + B79FA03621091ED000F286BF /* InterpretationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79FA03121091ED000F286BF /* InterpretationPresenter.swift */; }; + B79FA03721091ED000F286BF /* InterpretationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79FA03221091ED000F286BF /* InterpretationViewController.swift */; }; + B79FA03821091ED000F286BF /* InterpretationProtocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79FA03321091ED000F286BF /* InterpretationProtocols.swift */; }; + B79FA03921091ED000F286BF /* InterpretationInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79FA03421091ED000F286BF /* InterpretationInteractor.swift */; }; + B79FA03A21091ED000F286BF /* InterpretationWireFrame.swift in Sources */ = {isa = PBXBuildFile; fileRef = B79FA03521091ED000F286BF /* InterpretationWireFrame.swift */; }; + B7B546AE210D9C8C002DCA55 /* CircleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7B546AD210D9C8C002DCA55 /* CircleView.swift */; }; + B7B546B0210DC68E002DCA55 /* CircleLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7B546AF210DC68E002DCA55 /* CircleLoadingView.swift */; }; + B7B546B3210DD1EC002DCA55 /* AlertTextFieldViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7B546B2210DD1EC002DCA55 /* AlertTextFieldViewController.swift */; }; + B7EF8ED0210C501F00E0E981 /* InterpretationTypeTableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7EF8ECF210C501F00E0E981 /* InterpretationTypeTableDataSource.swift */; }; + B7EF8ED2210C502D00E0E981 /* InterpretationTypeTableDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7EF8ED1210C502D00E0E981 /* InterpretationTypeTableDelegate.swift */; }; + B7EF8ED4210C511C00E0E981 /* InterpretationTypeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7EF8ED3210C511C00E0E981 /* InterpretationTypeCell.swift */; }; + B7EF8ED7210C598800E0E981 /* InterpretationTypeCellLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7EF8ED6210C598800E0E981 /* InterpretationTypeCellLayout.swift */; }; + B7EF8ED9210C71E800E0E981 /* InterpretationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7EF8ED8210C71E800E0E981 /* InterpretationType.swift */; }; + B7EF8EDB210C759400E0E981 /* InterpretationTypeCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7EF8EDA210C759300E0E981 /* InterpretationTypeCellModel.swift */; }; + B7EF8EDD210CB0A200E0E981 /* InterpretationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7EF8EDC210CB0A200E0E981 /* InterpretationModel.swift */; }; + B7F4C2AC211995D500E48A98 /* UseCaseValidationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F4C2AB211995D500E48A98 /* UseCaseValidationService.swift */; }; B7F505192061158800C28FA1 /* SettingsArrowCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F505182061158800C28FA1 /* SettingsArrowCellViewModel.swift */; }; B7F5051B20611A0900C28FA1 /* DownloadSettingsArrowModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F5051A20611A0900C28FA1 /* DownloadSettingsArrowModel.swift */; }; B7F5051D2061252100C28FA1 /* DataAndStorageItemsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B7F5051C2061252100C28FA1 /* DataAndStorageItemsFactory.swift */; }; @@ -2105,11 +2149,9 @@ 00D7B5C620285BA7004B0E2B /* ScheduleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleView.swift; sourceTree = ""; }; 00E4A65E201A287100CEC61F /* MapSearchDS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapSearchDS.swift; sourceTree = ""; }; 00E8513A2021E96E007DC792 /* GApiResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GApiResponse.swift; sourceTree = ""; }; - 00E864662049840A00844FF1 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 00E86468204D519600844FF1 /* LanguageSettingsItemsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageSettingsItemsFactory.swift; sourceTree = ""; }; 00E8646C204D788100844FF1 /* LanguageSettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageSettingsCell.swift; sourceTree = ""; }; 00E864712052B1BE00844FF1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; - 00E864732052B1C400844FF1 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; 00E9824B205C1E19008BF03D /* SecurityItemsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityItemsFactory.swift; sourceTree = ""; }; 00E9824D205C2604008BF03D /* SessionItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionItemView.swift; sourceTree = ""; }; 00E9824F205C2668008BF03D /* SessionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionHeaderView.swift; sourceTree = ""; }; @@ -2208,6 +2250,7 @@ 2632139220D7B71200C31144 /* TranslateConfig.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = TranslateConfig.xcconfig; sourceTree = ""; }; 2633EF6D205212F700DB3868 /* MemberDAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberDAOProtocol.swift; sourceTree = ""; }; 2633EF6F2052130400DB3868 /* MemberDAO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberDAO.swift; sourceTree = ""; }; + 263409332119CFE2002F8D8F /* RecordContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordContainer.swift; sourceTree = ""; }; 26342C9F20ECAA0700D2196B /* TranscribeNetworkRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranscribeNetworkRouter.swift; sourceTree = ""; }; 26342CA820ECBAEE00D2196B /* TranscribeNetworkClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranscribeNetworkClient.swift; sourceTree = ""; }; 26342CAA20ECBB0100D2196B /* TranscribeNetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranscribeNetworkService.swift; sourceTree = ""; }; @@ -2392,7 +2435,7 @@ 26EA201420BECDCA00FBB9CA /* ConversationLanguageSettingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationLanguageSettingService.swift; sourceTree = ""; }; 26EAA2CA20D2497F005697CB /* TranslationAutoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationAutoView.swift; sourceTree = ""; }; 26ED2C1720042683002DBBE8 /* RepliesDS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepliesDS.swift; sourceTree = ""; }; - 26ED2C192004276B002DBBE8 /* RepliesTableViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepliesTableViewDelegate.swift; sourceTree = ""; }; + 26ED2C192004276B002DBBE8 /* RepliesCollectionViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepliesCollectionViewDelegate.swift; sourceTree = ""; }; 26EEA5462091F84E0066D3B0 /* CollectionsExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionsExtensions.swift; sourceTree = ""; }; 26F03C0C20698B0000712CB0 /* ChatWheelItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatWheelItemModel.swift; sourceTree = ""; }; 26F47051201B7248005D3192 /* ReturnToCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReturnToCallView.swift; sourceTree = ""; }; @@ -2456,7 +2499,6 @@ 3A2A99821EFAD2FB002749B3 /* PageControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PageControl.swift; path = PageControl/PageControl.swift; sourceTree = ""; }; 3A3FD2821F39E0A000B6958F /* HistoryRequestModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = HistoryRequestModel.swift; path = Services/Models/HistoryRequestModel.swift; sourceTree = ""; }; 3A62B7D71F4CB9D100F45B51 /* BaseMQTTModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BaseMQTTModel.swift; path = Services/Models/BaseMQTTModel.swift; sourceTree = ""; }; - 3A768F061ED4987300108F7C /* VoxService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = VoxService.swift; path = Services/VoxService.swift; sourceTree = ""; wrapsLines = 0; }; 3A771CA91F191B38008D968A /* ProfileHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = ProfileHandler.swift; path = Services/HandleServices/ProfileHandler.swift; sourceTree = ""; }; 3A771CB11F193945008D968A /* UpdateRosterModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UpdateRosterModel.swift; path = Services/Models/UpdateRosterModel.swift; sourceTree = ""; }; 3A8045CD1F60C8E200AED866 /* MQTTService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MQTTService.swift; sourceTree = ""; }; @@ -2581,6 +2623,7 @@ 55EC130CCF07D992BC6DD435 /* MapSearchPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MapSearchPresenter.swift; sourceTree = ""; }; 5957BF589EEC24E6799EB4CF /* TimeZoneSelectorViewController.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TimeZoneSelectorViewController.swift; sourceTree = ""; }; 59C99DD8A060B0BE6802110F /* AddContactPresenter.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AddContactPresenter.swift; sourceTree = ""; }; + 5A48445E21178E33000657ED /* AlertTextFieldViewControllerLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertTextFieldViewControllerLayout.swift; sourceTree = ""; }; 5AEEB3D82E9CF02760DA4CE7 /* Pods-Nynja-Share.channels.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nynja-Share.channels.xcconfig"; path = "Pods/Target Support Files/Pods-Nynja-Share/Pods-Nynja-Share.channels.xcconfig"; sourceTree = ""; }; 5B377AA90A6B6BA0120C31F1 /* EditProfileProtocols.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = EditProfileProtocols.swift; sourceTree = ""; }; 5BC1D37220D3B3D8002A44B3 /* NynjaCommunicatorService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = NynjaCommunicatorService.swift; path = Services/NynjaCommunicatorService.swift; sourceTree = ""; }; @@ -2756,6 +2799,8 @@ 853FB06F2049B396000996C5 /* SupportItemsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SupportItemsFactory.swift; sourceTree = ""; }; 853FB0742049B4FF000996C5 /* TextTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextTableViewCell.swift; sourceTree = ""; }; 853FB0762049B7CA000996C5 /* TextCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextCellViewModel.swift; sourceTree = ""; }; + 8540A330211B34B4007F65AF /* MessageCollectionViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCollectionViewDataSource.swift; sourceTree = ""; }; + 8540A332211B35A4007F65AF /* MessageCollectionViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCollectionViewDelegate.swift; sourceTree = ""; }; 8541BD67206CE0220093EF1E /* ImagePlaceholderWheelItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePlaceholderWheelItemModel.swift; sourceTree = ""; }; 8541BD6A206CE3A40093EF1E /* ChatPlaceholderWheelItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPlaceholderWheelItemModel.swift; sourceTree = ""; }; 85433F1D204D596D00B373A7 /* WebFullScreenPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebFullScreenPresenter.swift; sourceTree = ""; }; @@ -2775,6 +2820,7 @@ 854A4B2E2080D6C400759152 /* CellWithImageTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellWithImageTableViewCell.swift; sourceTree = ""; }; 854A4B2F2080D6C400759152 /* CellWithImageCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellWithImageCellModel.swift; sourceTree = ""; }; 854CFB07210704AE00FBC133 /* CGRectExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGRectExtensions.swift; sourceTree = ""; }; + 854D13D7211B2E7200E139FC /* MessageCollectionViewLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCollectionViewLayout.swift; sourceTree = ""; }; 854FC1CA204468FC00B12BE5 /* CarouselFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselFlowLayout.swift; sourceTree = ""; }; 8557987D2093200D007050B8 /* StickerMenuActionCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerMenuActionCollectionViewCell.swift; sourceTree = ""; }; 8557987E2093200D007050B8 /* StickerMenuActionCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerMenuActionCellModel.swift; sourceTree = ""; }; @@ -3240,8 +3286,6 @@ A45F10EF20B4218D00F45004 /* BaseChatCellLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseChatCellLayout.swift; sourceTree = ""; }; A45F10F020B4218D00F45004 /* MessageCellFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCellFactory.swift; sourceTree = ""; }; A45F10F120B4218D00F45004 /* UnreadCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnreadCell.swift; sourceTree = ""; }; - A45F10F220B4218D00F45004 /* MessageTableViewDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageTableViewDelegate.swift; sourceTree = ""; }; - A45F10F320B4218D00F45004 /* MessageDS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageDS.swift; sourceTree = ""; }; A45F10F520B4218D00F45004 /* ReplyPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyPreview.swift; sourceTree = ""; }; A45F10F720B4218D00F45004 /* AvatarViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarViewModel.swift; sourceTree = ""; }; A45F10F820B4218D00F45004 /* AvatarViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarViewLayout.swift; sourceTree = ""; }; @@ -3261,7 +3305,6 @@ A45F115D20B422AF00F45004 /* Message+System.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Message+System.swift"; sourceTree = ""; }; A45F59AA205825FC00EAA780 /* RosterDAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RosterDAOProtocol.swift; sourceTree = ""; }; A45F59AC2058263F00EAA780 /* RosterDAO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RosterDAO.swift; sourceTree = ""; }; - A45F59AF2058317000EAA780 /* VICallExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VICallExtension.swift; sourceTree = ""; }; A460324E2105C9A1009783DA /* InputsCachePolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputsCachePolicy.swift; sourceTree = ""; }; A46032512105D3E1009783DA /* TextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = ""; }; A4626EAE20D96EF9000F37EE /* MainViewController+Gallery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainViewController+Gallery.swift"; sourceTree = ""; }; @@ -3287,6 +3330,7 @@ A4679BB820B305360021FE9C /* LinkField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkField.swift; sourceTree = ""; }; A4688DF920650FF50013660D /* DBObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBObserver.swift; sourceTree = ""; }; A4688DFB20652DE30013660D /* StorageChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageChange.swift; sourceTree = ""; }; + A46CF04221147BAE0072F185 /* HistoryRequestModelTypeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryRequestModelTypeProtocol.swift; sourceTree = ""; }; A47785A320D286680053E0D2 /* ChannelChatItemsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelChatItemsFactory.swift; sourceTree = ""; }; A477CE7C2061236800081D34 /* MessageLinkDAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageLinkDAOProtocol.swift; sourceTree = ""; }; A477CE8120613A0900081D34 /* StarMessageDAOProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarMessageDAOProtocol.swift; sourceTree = ""; }; @@ -3324,7 +3368,6 @@ A4B544E920EFB1A800EB7B0F /* errors_Spec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = errors_Spec.swift; sourceTree = ""; }; A4B544EE20EFB4DF00EB7B0F /* BertTupleExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BertTupleExtension.swift; sourceTree = ""; }; A4B544F920EFC0AD00EB7B0F /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/StatusCodes.strings; sourceTree = ""; }; - A4B544FB20EFC0BC00EB7B0F /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/StatusCodes.strings; sourceTree = ""; }; A4B544FE20EFC1BA00EB7B0F /* StatusCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusCode.swift; sourceTree = ""; }; A4BCEC6B20DBF2A40078B076 /* Link+DB.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Link+DB.swift"; sourceTree = ""; }; A4BE4AB42068E98C00C041D1 /* ALTextInputBar+Trim.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ALTextInputBar+Trim.swift"; sourceTree = ""; }; @@ -3392,12 +3435,54 @@ B723C631204D9E5100884FFD /* DataAndStorageOption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataAndStorageOption.swift; sourceTree = ""; }; B723C633204DA54200884FFD /* SettingsDataAndStorageTableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDataAndStorageTableDataSource.swift; sourceTree = ""; }; B723C635204DA56600884FFD /* SettingsDataAndStorageTableDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDataAndStorageTableDelegate.swift; sourceTree = ""; }; + B742053E211448BD004FDE16 /* AsigningInterpreterLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsigningInterpreterLayout.swift; sourceTree = ""; }; + B745F2E32109B9E500488A91 /* LanguagePickerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguagePickerDelegate.swift; sourceTree = ""; }; + B745F2E52109BB0100488A91 /* InterpretationLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterpretationLayout.swift; sourceTree = ""; }; + B74BAFEF21076AFA0049CD27 /* CircleMenu.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircleMenu.swift; sourceTree = ""; }; + B74BAFF121076AFA0049CD27 /* SectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionView.swift; sourceTree = ""; }; + B74BAFF221076AFA0049CD27 /* Sector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sector.swift; sourceTree = ""; }; + B74BAFF421076AFA0049CD27 /* CircleMenuDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircleMenuDelegate.swift; sourceTree = ""; }; + B74BAFF621076AFA0049CD27 /* UIView+Mask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Mask.swift"; sourceTree = ""; }; + B74BAFF821076AFA0049CD27 /* CircleMenuSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircleMenuSet.swift; sourceTree = ""; }; + B74BAFF921076AFA0049CD27 /* CircleMenuFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircleMenuFactory.swift; sourceTree = ""; }; + B74BAFFA21076AFA0049CD27 /* CircleMenuItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircleMenuItem.swift; sourceTree = ""; }; B750EF012046B24D00A99F9C /* TransferInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransferInfo.swift; sourceTree = ""; }; B750EF032046D69C00A99F9C /* SpeedMesurement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpeedMesurement.swift; sourceTree = ""; }; B750EF052046D7C700A99F9C /* SpeedStringRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpeedStringRepresentable.swift; sourceTree = ""; }; B763DD9220AA1C3400A30B63 /* ContactCellLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactCellLayout.swift; sourceTree = ""; }; + B77C11D62109242200CCB42E /* AssigningInterpreterPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssigningInterpreterPresenter.swift; sourceTree = ""; }; + B77C11D72109242200CCB42E /* AssigningInterpreterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssigningInterpreterViewController.swift; sourceTree = ""; }; + B77C11D82109242200CCB42E /* AssigningInterpreterProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssigningInterpreterProtocols.swift; sourceTree = ""; }; + B77C11D92109242200CCB42E /* AssigningInterpreterInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssigningInterpreterInteractor.swift; sourceTree = ""; }; + B77C11DA2109242200CCB42E /* AssigningInterpreterWireFrame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssigningInterpreterWireFrame.swift; sourceTree = ""; }; + B77C11E12109254800CCB42E /* InterpretationTypePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterpretationTypePresenter.swift; sourceTree = ""; }; + B77C11E22109254800CCB42E /* InterpretationTypeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterpretationTypeViewController.swift; sourceTree = ""; }; + B77C11E32109254800CCB42E /* InterpretationTypeProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterpretationTypeProtocols.swift; sourceTree = ""; }; + B77C11E42109254800CCB42E /* InterpretationTypeInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterpretationTypeInteractor.swift; sourceTree = ""; }; + B77C11E52109254800CCB42E /* InterpretationTypeWireFrame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterpretationTypeWireFrame.swift; sourceTree = ""; }; B79B996D20CA88D100BEF5DE /* InviteFriendsMessageComposeDelegateHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InviteFriendsMessageComposeDelegateHandler.swift; sourceTree = ""; }; B79D8BCE2020C35300184D5D /* UIEdgeInsets+Adjust.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIEdgeInsets+Adjust.swift"; sourceTree = ""; }; + B79FA0262107731400F286BF /* MarketplacePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketplacePresenter.swift; sourceTree = ""; }; + B79FA0272107731400F286BF /* MarketplaceViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketplaceViewController.swift; sourceTree = ""; }; + B79FA0282107731400F286BF /* MarketplaceProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketplaceProtocols.swift; sourceTree = ""; }; + B79FA0292107731400F286BF /* MarketplaceInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketplaceInteractor.swift; sourceTree = ""; }; + B79FA02A2107731400F286BF /* MarketplaceWireFrame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarketplaceWireFrame.swift; sourceTree = ""; }; + B79FA03121091ED000F286BF /* InterpretationPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterpretationPresenter.swift; sourceTree = ""; }; + B79FA03221091ED000F286BF /* InterpretationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterpretationViewController.swift; sourceTree = ""; }; + B79FA03321091ED000F286BF /* InterpretationProtocols.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterpretationProtocols.swift; sourceTree = ""; }; + B79FA03421091ED000F286BF /* InterpretationInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterpretationInteractor.swift; sourceTree = ""; }; + B79FA03521091ED000F286BF /* InterpretationWireFrame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterpretationWireFrame.swift; sourceTree = ""; }; + B7B546AD210D9C8C002DCA55 /* CircleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleView.swift; sourceTree = ""; }; + B7B546AF210DC68E002DCA55 /* CircleLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircleLoadingView.swift; sourceTree = ""; }; + B7B546B2210DD1EC002DCA55 /* AlertTextFieldViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertTextFieldViewController.swift; sourceTree = ""; }; + B7EF8ECF210C501F00E0E981 /* InterpretationTypeTableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterpretationTypeTableDataSource.swift; sourceTree = ""; }; + B7EF8ED1210C502D00E0E981 /* InterpretationTypeTableDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterpretationTypeTableDelegate.swift; sourceTree = ""; }; + B7EF8ED3210C511C00E0E981 /* InterpretationTypeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterpretationTypeCell.swift; sourceTree = ""; }; + B7EF8ED6210C598800E0E981 /* InterpretationTypeCellLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterpretationTypeCellLayout.swift; sourceTree = ""; }; + B7EF8ED8210C71E800E0E981 /* InterpretationType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterpretationType.swift; sourceTree = ""; }; + B7EF8EDA210C759300E0E981 /* InterpretationTypeCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterpretationTypeCellModel.swift; sourceTree = ""; }; + B7EF8EDC210CB0A200E0E981 /* InterpretationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InterpretationModel.swift; sourceTree = ""; }; + B7F4C2AB211995D500E48A98 /* UseCaseValidationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UseCaseValidationService.swift; sourceTree = ""; }; B7F505182061158800C28FA1 /* SettingsArrowCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsArrowCellViewModel.swift; sourceTree = ""; }; B7F5051A20611A0900C28FA1 /* DownloadSettingsArrowModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadSettingsArrowModel.swift; sourceTree = ""; }; B7F5051C2061252100C28FA1 /* DataAndStorageItemsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataAndStorageItemsFactory.swift; sourceTree = ""; }; @@ -4513,7 +4598,7 @@ children = ( 264638211FFFE253002590E6 /* RepliesHeaderView */, 26ED2C1720042683002DBBE8 /* RepliesDS.swift */, - 26ED2C192004276B002DBBE8 /* RepliesTableViewDelegate.swift */, + 26ED2C192004276B002DBBE8 /* RepliesCollectionViewDelegate.swift */, 2646381F1FFFD6A2002590E6 /* RepliesVC.swift */, 261F2E2D200EB0AD007D0813 /* RepliesVC+CellDelegate.swift */, ); @@ -5368,6 +5453,7 @@ 3A768DE41ECB3E7600108F7C /* Library */ = { isa = PBXGroup; children = ( + B74BAFED21076ADB0049CD27 /* CircleMenuControl */, A46679EF20F10B2B00DBC6B4 /* RequestModelFactory */, A458FAC620ECDAD90075D55E /* Base */, F11786D520A9AAB1007A9A1B /* Interfaces */, @@ -5435,7 +5521,6 @@ 85082DDB2045A864000AE4B2 /* UserSettings */, 851769D420D584CA008ACF6B /* Amazon */, B7121EB7205045F300AABBE6 /* MediaDownloadManager.swift */, - 3A768F061ED4987300108F7C /* VoxService.swift */, 5BC1D37220D3B3D8002A44B3 /* NynjaCommunicatorService.swift */, E70189BA1F9107AD00CA7005 /* ProximitySensorManager.swift */, 859C42A6205693F100AE3797 /* SoundService */, @@ -5455,7 +5540,7 @@ 8509AC61206A54420089089B /* ResponseResult.swift */, 26D35AB71FD0EFA800A5D513 /* AudioPlayer.swift */, 2631C511207A4C0C00F9AA55 /* AudioRecorder.swift */, - C921738120BADAFC00519A2D /* TextInputValidationService.swift */, + B7F4C2AA211995A500E48A98 /* Validation */, A42C44E220F340DA00BC3CBB /* StatusCodeManager.swift */, ); name = Services; @@ -5618,6 +5703,7 @@ 3AC321761EEAC4700068F3C8 /* Models */ = { isa = PBXGroup; children = ( + A46CF04121147B9D0072F185 /* HistoryRequestModel */, 3A62B7D71F4CB9D100F45B51 /* BaseMQTTModel.swift */, FBCE83D620E523A8003B7558 /* WalletMQTTModel.swift */, A4F3DAAE2084944900FF71C7 /* RoomModel.swift */, @@ -5626,7 +5712,6 @@ 3A771CB11F193945008D968A /* UpdateRosterModel.swift */, 3AA13C751F2252F900BE5D8F /* SearchModel.swift */, 3A2374DA1F26458300701045 /* FriendRequstModel.swift */, - 3A3FD2821F39E0A000B6958F /* HistoryRequestModel.swift */, 3A21EFFB1F3B154A00AE61EC /* SendModel.swift */, 265AEA161FE9AFD400AC4806 /* MemberModel.swift */, 263D662C1FE8D03400A509F8 /* TypingModel.swift */, @@ -5835,6 +5920,10 @@ 975DB2471671357A9EEBF65B /* TimeZoneSelector */, E1BF3560A2E8EE8B02A9A9FB /* DateTimePicker */, 859B86352048224B003272B2 /* Settings */, + B79FA025210772CF00F286BF /* Marketplace */, + B79FA03021091EA400F286BF /* Interpretation */, + B77C11E0210924F800CCB42E /* InterpretationType */, + B77C11D5210923F200CCB42E /* AssigningInterpreter */, ); path = Modules; sourceTree = ""; @@ -6235,6 +6324,15 @@ path = WireFrame; sourceTree = ""; }; + 5A48446021178E6B000657ED /* AlertTextFieldViewController */ = { + isa = PBXGroup; + children = ( + B7B546B2210DD1EC002DCA55 /* AlertTextFieldViewController.swift */, + 5A48445E21178E33000657ED /* AlertTextFieldViewControllerLayout.swift */, + ); + path = AlertTextFieldViewController; + sourceTree = ""; + }; 5B2B64C658BEC3CCC90B0DEF /* WireFrame */ = { isa = PBXGroup; children = ( @@ -7396,6 +7494,16 @@ path = Image; sourceTree = ""; }; + 854D13D6211B2E6200E139FC /* CollectionView */ = { + isa = PBXGroup; + children = ( + 854D13D7211B2E7200E139FC /* MessageCollectionViewLayout.swift */, + 8540A330211B34B4007F65AF /* MessageCollectionViewDataSource.swift */, + 8540A332211B35A4007F65AF /* MessageCollectionViewDelegate.swift */, + ); + path = CollectionView; + sourceTree = ""; + }; 8557987C20931FE8007050B8 /* Menu */ = { isa = PBXGroup; children = ( @@ -8861,6 +8969,7 @@ 8580BAE020BD99D200239D9D /* InputContent.swift */, A43B257520AB1DFA00FF8107 /* InputContentProtocol.swift */, A43B257820AB1DFA00FF8107 /* TextInputContent.swift */, + 263409332119CFE2002F8D8F /* RecordContainer.swift */, A43B257620AB1DFA00FF8107 /* RecordDisplayInputContent.swift */, A43B257720AB1DFA00FF8107 /* RecordingInputContent.swift */, A458FABA20EB87BF0075D55E /* ActionContainerContent.swift */, @@ -9202,6 +9311,7 @@ A45F10C820B4218D00F45004 /* Views */ = { isa = PBXGroup; children = ( + 854D13D6211B2E6200E139FC /* CollectionView */, 5BC1D37E20D3B54B002A44B3 /* CallInfoView */, A45F10C920B4218D00F45004 /* TableView */, A45F10F420B4218D00F45004 /* ReplyPreview */, @@ -9216,8 +9326,6 @@ isa = PBXGroup; children = ( A45F10CA20B4218D00F45004 /* Cells */, - A45F10F220B4218D00F45004 /* MessageTableViewDelegate.swift */, - A45F10F320B4218D00F45004 /* MessageDS.swift */, ); path = TableView; sourceTree = ""; @@ -9394,14 +9502,6 @@ name = Roster; sourceTree = ""; }; - A45F59AE2058316100EAA780 /* VoxImplant */ = { - isa = PBXGroup; - children = ( - A45F59AF2058317000EAA780 /* VICallExtension.swift */, - ); - path = VoxImplant; - sourceTree = ""; - }; A460324B2105C2CE009783DA /* TextField */ = { isa = PBXGroup; children = ( @@ -9518,6 +9618,15 @@ path = LinkField; sourceTree = ""; }; + A46CF04121147B9D0072F185 /* HistoryRequestModel */ = { + isa = PBXGroup; + children = ( + 3A3FD2821F39E0A000B6958F /* HistoryRequestModel.swift */, + A46CF04221147BAE0072F185 /* HistoryRequestModelTypeProtocol.swift */, + ); + name = HistoryRequestModel; + sourceTree = ""; + }; A477CE7B2061235700081D34 /* MessageLink */ = { isa = PBXGroup; children = ( @@ -9926,6 +10035,113 @@ path = TableView; sourceTree = ""; }; + B745F2DF2109B94300488A91 /* View */ = { + isa = PBXGroup; + children = ( + B79FA03221091ED000F286BF /* InterpretationViewController.swift */, + B745F2E52109BB0100488A91 /* InterpretationLayout.swift */, + B745F2E32109B9E500488A91 /* LanguagePickerDelegate.swift */, + 5A48446021178E6B000657ED /* AlertTextFieldViewController */, + ); + path = View; + sourceTree = ""; + }; + B745F2E02109B99F00488A91 /* Presenter */ = { + isa = PBXGroup; + children = ( + B79FA03121091ED000F286BF /* InterpretationPresenter.swift */, + ); + path = Presenter; + sourceTree = ""; + }; + B745F2E12109B9A800488A91 /* Interactor */ = { + isa = PBXGroup; + children = ( + B79FA03421091ED000F286BF /* InterpretationInteractor.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + B745F2E22109B9B600488A91 /* Wireframe */ = { + isa = PBXGroup; + children = ( + B79FA03521091ED000F286BF /* InterpretationWireFrame.swift */, + ); + path = Wireframe; + sourceTree = ""; + }; + B74BAFED21076ADB0049CD27 /* CircleMenuControl */ = { + isa = PBXGroup; + children = ( + B74BAFEE21076AFA0049CD27 /* Core */, + B74BAFF521076AFA0049CD27 /* Extension */, + B74BAFF721076AFA0049CD27 /* Model */, + ); + path = CircleMenuControl; + sourceTree = ""; + }; + B74BAFEE21076AFA0049CD27 /* Core */ = { + isa = PBXGroup; + children = ( + B74BAFEF21076AFA0049CD27 /* CircleMenu.swift */, + B74BAFF021076AFA0049CD27 /* Sector */, + B74BAFF421076AFA0049CD27 /* CircleMenuDelegate.swift */, + ); + path = Core; + sourceTree = ""; + }; + B74BAFF021076AFA0049CD27 /* Sector */ = { + isa = PBXGroup; + children = ( + B74BAFF121076AFA0049CD27 /* SectionView.swift */, + B74BAFF221076AFA0049CD27 /* Sector.swift */, + ); + path = Sector; + sourceTree = ""; + }; + B74BAFF521076AFA0049CD27 /* Extension */ = { + isa = PBXGroup; + children = ( + B74BAFF621076AFA0049CD27 /* UIView+Mask.swift */, + ); + path = Extension; + sourceTree = ""; + }; + B74BAFF721076AFA0049CD27 /* Model */ = { + isa = PBXGroup; + children = ( + B74BAFF821076AFA0049CD27 /* CircleMenuSet.swift */, + B74BAFF921076AFA0049CD27 /* CircleMenuFactory.swift */, + B74BAFFA21076AFA0049CD27 /* CircleMenuItem.swift */, + ); + path = Model; + sourceTree = ""; + }; + B77C11D5210923F200CCB42E /* AssigningInterpreter */ = { + isa = PBXGroup; + children = ( + B77C11D82109242200CCB42E /* AssigningInterpreterProtocols.swift */, + B7EF8EE0210CDCCE00E0E981 /* View */, + B7EF8EE1210CDCDF00E0E981 /* Presenter */, + B7EF8EE2210CDCF200E0E981 /* Interactor */, + B7EF8EE3210CDCFF00E0E981 /* Wireframe */, + ); + path = AssigningInterpreter; + sourceTree = ""; + }; + B77C11E0210924F800CCB42E /* InterpretationType */ = { + isa = PBXGroup; + children = ( + B77C11E32109254800CCB42E /* InterpretationTypeProtocols.swift */, + B7EF8ED8210C71E800E0E981 /* InterpretationType.swift */, + B7EF8ECA210C4E1400E0E981 /* View */, + B7EF8ECB210C4E1E00E0E981 /* Presenter */, + B7EF8ECC210C4E2800E0E981 /* Interactor */, + B7EF8ECD210C4E3000E0E981 /* Wireframe */, + ); + path = InterpretationType; + sourceTree = ""; + }; B79B996F20CA89BA00BEF5DE /* MessageComposeHandler */ = { isa = PBXGroup; children = ( @@ -9934,6 +10150,31 @@ path = MessageComposeHandler; sourceTree = ""; }; + B79FA025210772CF00F286BF /* Marketplace */ = { + isa = PBXGroup; + children = ( + B79FA0282107731400F286BF /* MarketplaceProtocols.swift */, + B79FA0272107731400F286BF /* MarketplaceViewController.swift */, + B79FA0262107731400F286BF /* MarketplacePresenter.swift */, + B79FA0292107731400F286BF /* MarketplaceInteractor.swift */, + B79FA02A2107731400F286BF /* MarketplaceWireFrame.swift */, + ); + path = Marketplace; + sourceTree = ""; + }; + B79FA03021091EA400F286BF /* Interpretation */ = { + isa = PBXGroup; + children = ( + B79FA03321091ED000F286BF /* InterpretationProtocols.swift */, + B7EF8EDC210CB0A200E0E981 /* InterpretationModel.swift */, + B745F2DF2109B94300488A91 /* View */, + B745F2E02109B99F00488A91 /* Presenter */, + B745F2E12109B9A800488A91 /* Interactor */, + B745F2E22109B9B600488A91 /* Wireframe */, + ); + path = Interpretation; + sourceTree = ""; + }; B7B3AAAF204D3F1400756B77 /* TableHeaderFooter */ = { isa = PBXGroup; children = ( @@ -9942,6 +10183,111 @@ path = TableHeaderFooter; sourceTree = ""; }; + B7B546B1210DCE24002DCA55 /* CircleLoadingView */ = { + isa = PBXGroup; + children = ( + B7B546AD210D9C8C002DCA55 /* CircleView.swift */, + B7B546AF210DC68E002DCA55 /* CircleLoadingView.swift */, + ); + path = CircleLoadingView; + sourceTree = ""; + }; + B7EF8ECA210C4E1400E0E981 /* View */ = { + isa = PBXGroup; + children = ( + B77C11E22109254800CCB42E /* InterpretationTypeViewController.swift */, + B7EF8ECE210C4FE500E0E981 /* TableView */, + ); + path = View; + sourceTree = ""; + }; + B7EF8ECB210C4E1E00E0E981 /* Presenter */ = { + isa = PBXGroup; + children = ( + B77C11E12109254800CCB42E /* InterpretationTypePresenter.swift */, + ); + path = Presenter; + sourceTree = ""; + }; + B7EF8ECC210C4E2800E0E981 /* Interactor */ = { + isa = PBXGroup; + children = ( + B77C11E42109254800CCB42E /* InterpretationTypeInteractor.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + B7EF8ECD210C4E3000E0E981 /* Wireframe */ = { + isa = PBXGroup; + children = ( + B77C11E52109254800CCB42E /* InterpretationTypeWireFrame.swift */, + ); + path = Wireframe; + sourceTree = ""; + }; + B7EF8ECE210C4FE500E0E981 /* TableView */ = { + isa = PBXGroup; + children = ( + B7EF8ECF210C501F00E0E981 /* InterpretationTypeTableDataSource.swift */, + B7EF8ED1210C502D00E0E981 /* InterpretationTypeTableDelegate.swift */, + B7EF8ED5210C593E00E0E981 /* Cell */, + ); + path = TableView; + sourceTree = ""; + }; + B7EF8ED5210C593E00E0E981 /* Cell */ = { + isa = PBXGroup; + children = ( + B7EF8ED3210C511C00E0E981 /* InterpretationTypeCell.swift */, + B7EF8EDA210C759300E0E981 /* InterpretationTypeCellModel.swift */, + B7EF8ED6210C598800E0E981 /* InterpretationTypeCellLayout.swift */, + ); + path = Cell; + sourceTree = ""; + }; + B7EF8EE0210CDCCE00E0E981 /* View */ = { + isa = PBXGroup; + children = ( + B77C11D72109242200CCB42E /* AssigningInterpreterViewController.swift */, + B742053E211448BD004FDE16 /* AsigningInterpreterLayout.swift */, + B7B546B1210DCE24002DCA55 /* CircleLoadingView */, + ); + path = View; + sourceTree = ""; + }; + B7EF8EE1210CDCDF00E0E981 /* Presenter */ = { + isa = PBXGroup; + children = ( + B77C11D62109242200CCB42E /* AssigningInterpreterPresenter.swift */, + ); + path = Presenter; + sourceTree = ""; + }; + B7EF8EE2210CDCF200E0E981 /* Interactor */ = { + isa = PBXGroup; + children = ( + B77C11D92109242200CCB42E /* AssigningInterpreterInteractor.swift */, + ); + path = Interactor; + sourceTree = ""; + }; + B7EF8EE3210CDCFF00E0E981 /* Wireframe */ = { + isa = PBXGroup; + children = ( + B77C11DA2109242200CCB42E /* AssigningInterpreterWireFrame.swift */, + ); + path = Wireframe; + sourceTree = ""; + }; + B7F4C2AA211995A500E48A98 /* Validation */ = { + isa = PBXGroup; + children = ( + C921738120BADAFC00519A2D /* TextInputValidationService.swift */, + B7F4C2AB211995D500E48A98 /* UseCaseValidationService.swift */, + ); + path = Validation; + sourceTree = ""; + }; B8DCBB4ACE8A650987F2D234 /* Presenter */ = { isa = PBXGroup; children = ( @@ -10889,7 +11235,6 @@ A4213AF120D923DD00B6BE7D /* Photos */, A43B25DD20AB1F5C00FF8107 /* RawRepresentable+Localized.swift */, A4597E0F20A4651600F7F0DE /* Formatters */, - A45F59AE2058316100EAA780 /* VoxImplant */, F105C6AA20A0DCB70091786A /* UIImagePickerControllerCameraCaptureMode */, A416DA5E207533FB00FBF1BA /* CoreLocation */, 2648C3E52069B48F00863614 /* UITextField+Extension.swift */, @@ -12178,7 +12523,6 @@ 3578099F1F9765CF00C9680C /* Sources */, 357809A01F9765CF00C9680C /* Frameworks */, 357809A11F9765CF00C9680C /* Resources */, - 61CA315D0DB89815DE10E66F /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -12314,8 +12658,6 @@ knownRegions = ( en, Base, - uk, - ru, ); mainGroup = 3ABCE8E41EC9330D00A80B15; productRefGroup = 3ABCE8EE1EC9330D00A80B15 /* Products */; @@ -12446,21 +12788,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 61CA315D0DB89815DE10E66F /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Nynja-Share/Pods-Nynja-Share-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; 6A031053FD7153DBCCD6098C /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -12504,11 +12831,8 @@ "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SQLCipher/SQLCipher.framework", "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", - "${BUILT_PRODUCTS_DIR}/SocketRocket/SocketRocket.framework", "${BUILT_PRODUCTS_DIR}/SwiftyJSON/SwiftyJSON.framework", "${BUILT_PRODUCTS_DIR}/SwiftyTimer/SwiftyTimer.framework", - "${PODS_ROOT}/VoxImplantSDK/VoxImplant.framework", - "${PODS_ROOT}/VoxImplantWebRTC/WebRTC.framework", "${BUILT_PRODUCTS_DIR}/libPhoneNumber-iOS/libPhoneNumber_iOS.framework", ); name = "[CP] Embed Pods Frameworks"; @@ -12531,11 +12855,8 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SQLCipher.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SocketRocket.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyTimer.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VoxImplant.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebRTC.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libPhoneNumber_iOS.framework", ); runOnlyForDeploymentPostprocessing = 0; @@ -12564,10 +12885,7 @@ "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SQLCipher/SQLCipher.framework", "${BUILT_PRODUCTS_DIR}/SnapKit/SnapKit.framework", - "${BUILT_PRODUCTS_DIR}/SocketRocket/SocketRocket.framework", "${BUILT_PRODUCTS_DIR}/SwiftyTimer/SwiftyTimer.framework", - "${PODS_ROOT}/VoxImplantSDK/VoxImplant.framework", - "${PODS_ROOT}/VoxImplantWebRTC/WebRTC.framework", "${BUILT_PRODUCTS_DIR}/libPhoneNumber-iOS/libPhoneNumber_iOS.framework", ); name = "[CP] Embed Pods Frameworks"; @@ -12586,10 +12904,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SQLCipher.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SnapKit.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SocketRocket.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyTimer.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/VoxImplant.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/WebRTC.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/libPhoneNumber_iOS.framework", ); runOnlyForDeploymentPostprocessing = 0; @@ -12987,6 +13302,7 @@ buildActionMask = 2147483647; files = ( A432CF1B20B4347D00993AFB /* MaterialTextView.swift in Sources */, + B7EF8ED0210C501F00E0E981 /* InterpretationTypeTableDataSource.swift in Sources */, F11DF06620BD96D000F3E005 /* GalleryFilterGroupType.swift in Sources */, F11786F120AC5482007A9A1B /* ServiceFactory.swift in Sources */, 4B7B81C62044790700C2EFCF /* TimeZoneLocal.swift in Sources */, @@ -13043,6 +13359,7 @@ E7EED2341F740BF3005DAE20 /* ChatsItem.swift in Sources */, 260313A620A0A4BA009AC66D /* SwitchableActionCellViewModel.swift in Sources */, A42D51AE206A361400EEB952 /* Message.swift in Sources */, + B79FA02C2107731400F286BF /* MarketplaceViewController.swift in Sources */, C9405159204C91E000D72B04 /* DataAndStorageTableDataSource.swift in Sources */, A45F112920B4218D00F45004 /* MessageContentAppearance.swift in Sources */, C9C694F9201FA4AB00A57297 /* SlideAnimatedTransitioning.swift in Sources */, @@ -13083,6 +13400,7 @@ 859B862C204820DC003272B2 /* ThemePickerPresenter.swift in Sources */, 2603139B20A0A4BA009AC66D /* LanguageSelectorViewController.swift in Sources */, 2686D3201FC3E39C0079CB75 /* ContentNavigationVC.swift in Sources */, + B7EF8EDB210C759400E0E981 /* InterpretationTypeCellModel.swift in Sources */, 0062D93F2062EC4100B915AC /* InviteFriendsCellLayout.swift in Sources */, 001F0CF5202C38FA006B4304 /* TimeZoneCell.swift in Sources */, C9C694FD201FA55800A57297 /* SwipeBackHelper.swift in Sources */, @@ -13110,7 +13428,6 @@ A42D51B4206A361400EEB952 /* container.swift in Sources */, 265F5D29209B6E1F008ACCC8 /* ParticipantsViewControllerLayout.swift in Sources */, 00E86469204D519600844FF1 /* LanguageSettingsItemsFactory.swift in Sources */, - 3A768F071ED4987300108F7C /* VoxService.swift in Sources */, A4F3DAB22084949F00FF71C7 /* MessageExtension+BERT.swift in Sources */, F105C6BA20A1347E0091786A /* PhotoPreviewProtocols.swift in Sources */, E721306F1F9A384900D88103 /* AlignableLabel.swift in Sources */, @@ -13189,10 +13506,12 @@ 859C42AA2056B05D00AE3797 /* SoundBundle.swift in Sources */, 265AEA151FE9AFA700AC4806 /* MemberHandler.swift in Sources */, E7C36C311FC4399B00740630 /* DBService.swift in Sources */, + B77C11DE2109242200CCB42E /* AssigningInterpreterInteractor.swift in Sources */, C940514D204C7FAF00D72B04 /* DataAndStorageWireFrame.swift in Sources */, 26DCB22D2064B872001EF0AB /* ContactsProtocols.swift in Sources */, A42D52CA206A53AB00EEB952 /* muc_Spec.swift in Sources */, 850C301D204DA87A00DB26C2 /* PrivacyListProtocols.swift in Sources */, + B77C11DF2109242200CCB42E /* AssigningInterpreterWireFrame.swift in Sources */, 8520040D20D513B8007C0036 /* OpponentMessageStickerRepliedView.swift in Sources */, A458FAC420EBA58A0075D55E /* MuteChatService.swift in Sources */, 2661D12F1F373D1700F3E125 /* BorderView.swift in Sources */, @@ -13224,6 +13543,7 @@ 26C1A3F02031D9E60009F7F0 /* OtherUserTableViewDS.swift in Sources */, A45F110D20B4218D00F45004 /* SendingStatus.swift in Sources */, A45F114720B421AB00F45004 /* MessageExtension.swift in Sources */, + B79FA02E2107731400F286BF /* MarketplaceInteractor.swift in Sources */, F10B0E1B20B4412100528E7A /* GalleryViewController.swift in Sources */, E78EFB891FC867B200C44975 /* DBMuc.swift in Sources */, 8EE53A12200543F40079CCA8 /* GroupCollectionConstants.swift in Sources */, @@ -13265,7 +13585,6 @@ 2648C41E2069B5B300863614 /* ChangeNumberItemsFactory.swift in Sources */, 260313AC20A0A4BA009AC66D /* LanguageSettings+Helper.swift in Sources */, A45F112D20B4218D00F45004 /* MessageContentView.swift in Sources */, - A45F113520B4218D00F45004 /* MessageTableViewDelegate.swift in Sources */, A42D52C3206A53AA00EEB952 /* Star_Spec.swift in Sources */, 26D35AB81FD0EFA800A5D513 /* AudioPlayer.swift in Sources */, 853FB0702049B396000996C5 /* SupportItemsFactory.swift in Sources */, @@ -13279,11 +13598,13 @@ 0062D9472062EC4100B915AC /* InviteFriendsViewController.swift in Sources */, 4B2D063C202E1A1500010A0C /* ContactsExpandedItemsFactory.swift in Sources */, 85D66A2120BD970400FBD803 /* BBCodeEntity.swift in Sources */, + B74BB00221076AFA0049CD27 /* CircleMenuFactory.swift in Sources */, 4B8996EC204EF35200DCB183 /* MessageActionDAO.swift in Sources */, E791178A1F97874D00462D68 /* GradientView.swift in Sources */, E79117901F97A3BC00462D68 /* ProfileDetailsView.swift in Sources */, 265F5D24209B6987008ACCC8 /* Place.swift in Sources */, E734831A1F9F39400090A4DB /* CellModel.swift in Sources */, + B74BB00321076AFA0049CD27 /* CircleMenuItem.swift in Sources */, 6D485DDF1F0ACA4700E12FB1 /* UIImageView+Rounded.swift in Sources */, 26CD3FDB2104D19D00597E62 /* TranscribeShortAudioOperation.swift in Sources */, 40C2631343E285717633ADFA /* AuthPresenter.swift in Sources */, @@ -13292,6 +13613,7 @@ C940514A204C7FAF00D72B04 /* DataAndStorageViewController.swift in Sources */, 26C1A3E32031A95D0009F7F0 /* OtherUserProtocols.swift in Sources */, 8503B528205046A6006F0593 /* NotificationSettingsInteractor.swift in Sources */, + B79FA02F2107731400F286BF /* MarketplaceWireFrame.swift in Sources */, A4C9300520B323B700D6FB0F /* Room+BaseChatModel.swift in Sources */, E73483211F9F78DC0090A4DB /* ProfileSectionFooterView.swift in Sources */, 3AE0A84B1F20321A008A04F3 /* Wheel.swift in Sources */, @@ -13309,6 +13631,7 @@ FBCE83D520E52397003B7558 /* MQTTServiceWallet.swift in Sources */, 853D55B820CE72C60080659F /* StickerContentDataSource.swift in Sources */, 85CB25DC20D723D300D5E565 /* StickerPackDAOProtocol.swift in Sources */, + 8540A333211B35A4007F65AF /* MessageCollectionViewDelegate.swift in Sources */, 4B06D3152028A537003B275B /* ContactsItemsFactory.swift in Sources */, A48C154420EF7A15002DA994 /* LinkHandler.swift in Sources */, A45F113320B4218D00F45004 /* MessageCellFactory.swift in Sources */, @@ -13371,6 +13694,7 @@ 4B4266C3204D923400194BC1 /* Array+UIView.swift in Sources */, A4CB15232103735200C3B68B /* JDFileBasedMechanism.swift in Sources */, 3A771CAA1F191B38008D968A /* ProfileHandler.swift in Sources */, + B74BAFFC21076AFA0049CD27 /* SectionView.swift in Sources */, E78EFB871FC867A900C44975 /* DBP2p.swift in Sources */, 8ED0F3CF1FBC5CF2004916AB /* GroupsListPresenter.swift in Sources */, 850A2BB22035AE5E00D68FDF /* ForwardCellViewModel.swift in Sources */, @@ -13379,6 +13703,7 @@ A45F112E20B4218D00F45004 /* MessageContentProtocol.swift in Sources */, 0008E9132032D5AC003E316E /* MQTTServiceSchedule.swift in Sources */, A432CF1A20B4347D00993AFB /* MaterialTextInput.swift in Sources */, + B77C11E62109254800CCB42E /* InterpretationTypePresenter.swift in Sources */, B750EF062046D7C700A99F9C /* SpeedStringRepresentable.swift in Sources */, 8580BAE220BD99D200239D9D /* InputContent.swift in Sources */, A43B25AD20AB1DFA00FF8107 /* MyTextField.swift in Sources */, @@ -13458,6 +13783,7 @@ E7C1D3681F683A7D007D4E1E /* MainNavigationItem.swift in Sources */, E7DD28311F8B6CB200174650 /* LoginViewLayout.swift in Sources */, E749C5671FD4490E0048DEAC /* DefaultMessageProcessingManager.swift in Sources */, + B7B546B0210DC68E002DCA55 /* CircleLoadingView.swift in Sources */, 85482848204EA56600DCBEC8 /* PrivacyListDataSource.swift in Sources */, 2600DAD6203479D000A2D4F7 /* ReturnToHomeHeaderView.swift in Sources */, F117FBD520FF9DAF00BA1F82 /* MediaInfoView.swift in Sources */, @@ -13573,7 +13899,6 @@ FBCE841420E525A6003B7558 /* NetworkService.swift in Sources */, A409B1CF2108D48E0051C20B /* QueryFactory.swift in Sources */, A42D52B7206A53AA00EEB952 /* reader_Spec.swift in Sources */, - A45F113620B4218D00F45004 /* MessageDS.swift in Sources */, F119E66A20D24B960043A532 /* MultiplePreviewProtocols.swift in Sources */, 850FC611203312FA00832D87 /* ForwardSelectorViewControllerLayout.swift in Sources */, 0062D93D2062EC4100B915AC /* InviteFriendsWireframe.swift in Sources */, @@ -13583,6 +13908,7 @@ A42D52CB206A53AB00EEB952 /* CDR_Spec.swift in Sources */, 2603139220A0A4B9009AC66D /* LanguageSettingProtocol.swift in Sources */, F105C6BD20A1347E0091786A /* PhotoPreviewViewController.swift in Sources */, + B79FA03921091ED000F286BF /* InterpretationInteractor.swift in Sources */, 26A856242074C50D00C642EA /* ActionsView+ScheduleAction.swift in Sources */, B1B8ED3EDB12866323C9EE74 /* QRCodeGeneratorInteractor.swift in Sources */, 85F0866220D6412300A7762E /* RemoteStorageDestination.swift in Sources */, @@ -13600,6 +13926,7 @@ A45F112120B4218D00F45004 /* InfoDateView.swift in Sources */, 9BD8E3F820EF8874001384EC /* CallInProgressNavigationController.swift in Sources */, A4688DFA20650FF50013660D /* DBObserver.swift in Sources */, + 263409342119CFE2002F8D8F /* RecordContainer.swift in Sources */, 853FB0682049B193000996C5 /* SupportProtocols.swift in Sources */, A42D51B1206A361400EEB952 /* Typing.swift in Sources */, A42D52D3206A53AB00EEB952 /* Test_Spec.swift in Sources */, @@ -13623,6 +13950,7 @@ 26C0C1EE2073DE1600C530DA /* ForwardSelectorProtocols+ShareExt.swift in Sources */, 8511D3742034596E00B2A620 /* Collection+ViewLayout.swift in Sources */, F10B0E1720B4401500528E7A /* GalleryPresenter.swift in Sources */, + B7EF8ED9210C71E800E0E981 /* InterpretationType.swift in Sources */, 6D5157D21F30B822002A27DB /* MicrophoneView.swift in Sources */, 260313AF20A0A50D009AC66D /* TranslationService.swift in Sources */, A42D51AD206A361400EEB952 /* cur.swift in Sources */, @@ -13728,6 +14056,7 @@ 263D66271FE829CC00A509F8 /* RoomExtension+BERT.swift in Sources */, A45F112320B4218D00F45004 /* MessageVoiceView.swift in Sources */, A44B4D5520CE9BDF00CA700A /* SwitchCellViewModel.swift in Sources */, + B74BAFFB21076AFA0049CD27 /* CircleMenu.swift in Sources */, F117872520ACF2DB007A9A1B /* QualityName.swift in Sources */, A45F111B20B4218D00F45004 /* MessagePlaceView.swift in Sources */, A42D52C4206A53AA00EEB952 /* error2_Spec.swift in Sources */, @@ -13739,6 +14068,8 @@ A42D52B9206A53AA00EEB952 /* Profile_Spec.swift in Sources */, 26DCB25420692237001EF0AB /* Array+Feature.swift in Sources */, F1607B2E20B2DE8A00BDF60A /* CameraQRPreviewInteractor.swift in Sources */, + B77C11EA2109254800CCB42E /* InterpretationTypeWireFrame.swift in Sources */, + B7F4C2AC211995D500E48A98 /* UseCaseValidationService.swift in Sources */, 850571222050B0AD00EDF794 /* NotificationAlertSoundsViewController.swift in Sources */, 6D6731101F29E1F4003E8F8F /* BottomCallView.swift in Sources */, 26245F40204EF58E00C8D3DD /* BaseViewProtocol.swift in Sources */, @@ -13752,6 +14083,7 @@ 00E9825E205FDB1A008BF03D /* AuthExtension.swift in Sources */, A45F116020B422AF00F45004 /* Message+DB.swift in Sources */, 8E23E086200614AB00A59B8C /* GroupVideosCell.swift in Sources */, + B74BB00121076AFA0049CD27 /* CircleMenuSet.swift in Sources */, A4679B8E20B2DA610021FE9C /* SelectorAvatarCell.swift in Sources */, 2648C41B2069B52100863614 /* ChangeNumberView.swift in Sources */, 2648C40E2069B52100863614 /* ChangeNumberStep1Interactor.swift in Sources */, @@ -13810,7 +14142,7 @@ 3A1AAFCE1F3DF0470098780A /* DateExtensions.swift in Sources */, 26C061C01FEAA04A00A2EBE4 /* FeatureExtension+BERT.swift in Sources */, 2651093F20ADB81100F1B38B /* NotificationSettingProtocol.swift in Sources */, - 26ED2C1A2004276B002DBBE8 /* RepliesTableViewDelegate.swift in Sources */, + 26ED2C1A2004276B002DBBE8 /* RepliesCollectionViewDelegate.swift in Sources */, 8514D52220EE48930002378A /* NynjaContextMenuItemsFactory+Design.swift in Sources */, A45F112820B4218D00F45004 /* MessageViewFactory.swift in Sources */, F117871D20ACF1D0007A9A1B /* CameraSettingsService.swift in Sources */, @@ -13841,6 +14173,8 @@ 26610F5B2015476C00609F77 /* LocationFullWheelItemModel.swift in Sources */, F117872920ACF2DB007A9A1B /* CameraSetting.swift in Sources */, E77D58971F98B91600FBE926 /* ProfileTableViewDelegate.swift in Sources */, + B79FA02D2107731400F286BF /* MarketplaceProtocols.swift in Sources */, + B77C11DC2109242200CCB42E /* AssigningInterpreterViewController.swift in Sources */, A45F111420B4218D00F45004 /* SystemCell.swift in Sources */, 26C1A3E92031AAA30009F7F0 /* OtherUserViewController.swift in Sources */, 2606F3BC20BFE20500CF7F15 /* MessageInteractor+Translation.swift in Sources */, @@ -13862,6 +14196,7 @@ F10AFEB420F7B1B000C7CE83 /* WheelPreviewProtocol.swift in Sources */, A45F111220B4218D00F45004 /* MessageVC.swift in Sources */, 26DCB256206924B3001EF0AB /* FeatureFactory.swift in Sources */, + B74BAFFF21076AFA0049CD27 /* CircleMenuDelegate.swift in Sources */, A432CF1420B4347D00993AFB /* InputInfo.swift in Sources */, 85D66A0720BD963C00FBD803 /* MessagePayloadRenderer.swift in Sources */, 8EE9BC181FFBE0F800ECBBC7 /* Array+Message.swift in Sources */, @@ -13888,6 +14223,7 @@ E72906E72011156B007C5C5B /* UITableViewExtensions.swift in Sources */, 2603139320A0A4B9009AC66D /* LanguageSelectorProtocols.swift in Sources */, E7EE893B1F83CEF5009D37F9 /* MainViewControllerLayout.swift in Sources */, + 5A48445F21178E33000657ED /* AlertTextFieldViewControllerLayout.swift in Sources */, 32868DDD1F31CB5D0028B260 /* ChatsListPresenter.swift in Sources */, A42D519D206A361400EEB952 /* muc.swift in Sources */, A407348C20B712E9005762D5 /* UIView+Hierarchy.swift in Sources */, @@ -13926,6 +14262,7 @@ A4679B8620B2DA550021FE9C /* Array+Grouped.swift in Sources */, A432CF1D20B4427000993AFB /* MTIConfigProtocol.swift in Sources */, 4B06D3102028A472003B275B /* P2pChatsItemsFactory.swift in Sources */, + B77C11E92109254800CCB42E /* InterpretationTypeInteractor.swift in Sources */, 26DCB23E2064B9A7001EF0AB /* ContactsWireframe.swift in Sources */, 853E594F20D6AED2007799B9 /* Desc+Messages.swift in Sources */, A460324F2105C9A1009783DA /* InputsCachePolicy.swift in Sources */, @@ -13951,6 +14288,7 @@ FE58F9B3208F0583004AFDD3 /* DBMessageEditAction.swift in Sources */, 26FA420C2017AE3300E6F6EC /* StarMessageCellLayout.swift in Sources */, 2633EF6E205212F700DB3868 /* MemberDAOProtocol.swift in Sources */, + B7EF8EDD210CB0A200E0E981 /* InterpretationModel.swift in Sources */, E79117921F97A48900462D68 /* ProfileDetailsViewLayout.swift in Sources */, 8595E0DC204863DB00178171 /* CarouselPickerCellModel.swift in Sources */, 2648C41C2069B52100863614 /* ChangeNumberStep2Interactor.swift in Sources */, @@ -13970,9 +14308,11 @@ 3AE0A84C1F20321A008A04F3 /* WheelItemModel.swift in Sources */, A42D51BB206A361400EEB952 /* userTask.swift in Sources */, 2686D3271FC640440079CB75 /* DBSyncFile.swift in Sources */, + B742053F211448BD004FDE16 /* AsigningInterpreterLayout.swift in Sources */, A40F18B920BFD81B0091B09E /* EmptyStateView.swift in Sources */, 06E67B3C3ED6CE5F6A913762 /* MainProtocols.swift in Sources */, 85082DDF2045A8C2000AE4B2 /* WheelPosition.swift in Sources */, + B79FA03721091ED000F286BF /* InterpretationViewController.swift in Sources */, F1607B2820B2DE4D00BDF60A /* CameraQRPreviewPresenter.swift in Sources */, A42D51AB206A361400EEB952 /* Person.swift in Sources */, A415131E20DBD10400C2C01F /* MessageLinkDAO.swift in Sources */, @@ -13986,6 +14326,7 @@ 4B8996CD204ED33400DCB183 /* StarDAOProtocol.swift in Sources */, 85D669EC20BD962800FBD803 /* MessageVCLayout.swift in Sources */, 8EDDB08A200529C6000B7EC2 /* GroupStorageCollectionVC.swift in Sources */, + B74BB00021076AFA0049CD27 /* UIView+Mask.swift in Sources */, A42D51CD206A361400EEB952 /* operation.swift in Sources */, 265F5D25209B6987008ACCC8 /* LocationType.swift in Sources */, 4B2D063A202DDA2000010A0C /* BackSwipable.swift in Sources */, @@ -14001,6 +14342,7 @@ A408A0BA20C174040029F54B /* ChannelsListPresenter.swift in Sources */, 850FC60F203310D200832D87 /* SelectionAvatarView.swift in Sources */, 8E6C4BE41FF6A7AD009C8374 /* GroupStorageListItems.swift in Sources */, + B7B546B3210DD1EC002DCA55 /* AlertTextFieldViewController.swift in Sources */, F117871220ACF018007A9A1B /* CameraSettingsProtocols.swift in Sources */, 8503B526205046A6006F0593 /* NotificationSettingsViewController.swift in Sources */, 8580BAC820BD983400239D9D /* MentionCounterInteractive.swift in Sources */, @@ -14045,12 +14387,15 @@ 8557988720932401007050B8 /* StickerStaticMenuActionCollectionViewCell.swift in Sources */, 00E9825A205FC324008BF03D /* SessionDescView.swift in Sources */, E7229A4A1F8CAD72003AEE04 /* TutorialViewControllerLayout.swift in Sources */, + B79FA03821091ED000F286BF /* InterpretationProtocols.swift in Sources */, 85CB25DA20D723B900D5E565 /* StickerPackDAO.swift in Sources */, 2603068D20FFF9CA00C10DD9 /* MessageCallView.swift in Sources */, E70402BD1FF6972B00182D81 /* BaseView.swift in Sources */, A45F59AD2058263F00EAA780 /* RosterDAO.swift in Sources */, 4C5EEA13EBC6A8398F08DCD1 /* MainWireframe.swift in Sources */, A42D52CF206A53AB00EEB952 /* Person_Spec.swift in Sources */, + B77C11E82109254800CCB42E /* InterpretationTypeProtocols.swift in Sources */, + B77C11E72109254800CCB42E /* InterpretationTypeViewController.swift in Sources */, 005886C92030F13100FE2E89 /* NynjaTimeHoursDelegate.swift in Sources */, 4BAB9CE02035CAE700385520 /* ScheduleInfo.swift in Sources */, 8ECC067E1FC5BCC6002CF225 /* TransferManager.swift in Sources */, @@ -14118,7 +14463,6 @@ 851EBD7F20B418890065C644 /* StickersInputView.swift in Sources */, 267C1D5920404EDB0087808F /* AlertImageViewController.swift in Sources */, 0008E91B20333A38003E316E /* JobHandler.swift in Sources */, - A45F59B02058317000EAA780 /* VICallExtension.swift in Sources */, B750EF042046D69C00A99F9C /* SpeedMesurement.swift in Sources */, 853D55B220CE66180080659F /* StickersInputData.swift in Sources */, 260313A320A0A4BA009AC66D /* ActionCellViewModel.swift in Sources */, @@ -14155,6 +14499,7 @@ E7E6E3DE1FB2F37900401D9E /* ParticipantsDelegate.swift in Sources */, A42D51AA206A361400EEB952 /* error.swift in Sources */, 628E2C26BE0854DB1DF64990 /* SplashWireframe.swift in Sources */, + B77C11DD2109242200CCB42E /* AssigningInterpreterProtocols.swift in Sources */, 26FA420E201812D600E6F6EC /* StarTableDS.swift in Sources */, F10B0E2820B519B700528E7A /* GalleryPhotoItemCollectionViewCell.swift in Sources */, E7C36C351FC448CB00740630 /* DBFeature.swift in Sources */, @@ -14187,6 +14532,7 @@ 73BFE52F809536A538E6A55E /* ImagePreviewViewController.swift in Sources */, 850C0B2620E00C3E003341D0 /* UIScreen+Keyboard.swift in Sources */, 8503B51B205036F2006F0593 /* BaseNynjaButton.swift in Sources */, + A46CF04321147BAE0072F185 /* HistoryRequestModelTypeProtocol.swift in Sources */, 8538012C2052E29D002C6960 /* SoundTableHeaderView.swift in Sources */, 3A8045D31F60C8E200AED866 /* MQTTServiceProfile.swift in Sources */, B7F505192061158800C28FA1 /* SettingsArrowCellViewModel.swift in Sources */, @@ -14195,6 +14541,7 @@ E09CECE79892CABEF8793389 /* ImagePreviewInteractor.swift in Sources */, 2603139820A0A4B9009AC66D /* LangCellViewModel.swift in Sources */, A4BCEC6C20DBF2A40078B076 /* Link+DB.swift in Sources */, + B79FA02B2107731400F286BF /* MarketplacePresenter.swift in Sources */, 0008E9282036F480003E316E /* ScheduledMessage.swift in Sources */, 851872C120CD45B3007CD6CA /* StickersProvider.swift in Sources */, 26E7D04C1FCB8A72001C69B7 /* UIImageView+SetImage.swift in Sources */, @@ -14255,6 +14602,7 @@ 54FFFD58388E2B660C1E5A05 /* MapPresenter.swift in Sources */, A42D52D6206A53AB00EEB952 /* serviceTask_Spec.swift in Sources */, 0AB08BA89A51118248FA3233 /* MapInteractor.swift in Sources */, + 8540A331211B34B4007F65AF /* MessageCollectionViewDataSource.swift in Sources */, A48C154220EF76EE002DA994 /* LinkExtension.swift in Sources */, A42D52AD206A53AA00EEB952 /* log_Spec.swift in Sources */, F6150A15F8A3E399EEB2C724 /* MapWireframe.swift in Sources */, @@ -14275,6 +14623,7 @@ A45F110320B4218D00F45004 /* MessagePresenter.swift in Sources */, 26541F742007B9A200AAEACF /* MessageActionTable.swift in Sources */, 8502DB532061030100613C8C /* WheelPositionCollectionViewCell.swift in Sources */, + B79FA03621091ED000F286BF /* InterpretationPresenter.swift in Sources */, 8FD597B66534DDBD1DDB58AC /* FavoritesInteractor.swift in Sources */, 82FCF48AA4A8C04CC8B0B5B6 /* FavoritesWireframe.swift in Sources */, 26AD28391FFB0AF9009E4580 /* StorageObserver.swift in Sources */, @@ -14338,6 +14687,7 @@ 2689CDEA20C48AD8007816B9 /* TranslationManualView.swift in Sources */, 6C25C4720B043D98729C02C8 /* TopUpAccountPresenter.swift in Sources */, A42D51CE206A361400EEB952 /* Search.swift in Sources */, + B77C11DB2109242200CCB42E /* AssigningInterpreterPresenter.swift in Sources */, 26342CB220ECDDC400D2196B /* Encodable+Dictionary.swift in Sources */, 3CDA490701EC3FEAAC2E9AFE /* TopUpAccountInteractor.swift in Sources */, 26B32B691FE1715500888A0A /* WeakRef.swift in Sources */, @@ -14348,6 +14698,7 @@ 8580BAC620BD983400239D9D /* MessageProtocols.swift in Sources */, 2683F77A203F38E30003181A /* UIPickerView.swift in Sources */, A4679BAB20B2DD100021FE9C /* SubscribersCollectionDataSource.swift in Sources */, + B7EF8ED7210C598800E0E981 /* InterpretationTypeCellLayout.swift in Sources */, 2648C40D2069B52100863614 /* ChangeNumberStep1Protocols.swift in Sources */, A43B25B920AB1E7600FF8107 /* String+Range.swift in Sources */, E7C9CEC51FCC245F0090C2E0 /* P2pExtension.swift in Sources */, @@ -14378,6 +14729,7 @@ 87DE79674FF430A52D2A0BB7 /* MyGroupAliasProtocols.swift in Sources */, A45F111E20B4218D00F45004 /* FileTransferInfoView.swift in Sources */, 0B79E13E95305A80847AA99F /* MyGroupAliasViewController.swift in Sources */, + B745F2E42109B9E500488A91 /* LanguagePickerDelegate.swift in Sources */, 990A25B2C84CE09B4CE64533 /* MyGroupAliasPresenter.swift in Sources */, D839883F9B7A8CD245A85701 /* MyGroupAliasInteractor.swift in Sources */, 4B06D3202028A9B1003B275B /* P2pChatItemsFactory.swift in Sources */, @@ -14428,6 +14780,7 @@ 85D669E420BD956000FBD803 /* Int+AnyObject.swift in Sources */, A4569873060C49904EF8C555 /* EditGroupPhotoViewController.swift in Sources */, 8502DB502061030000613C8C /* WheelPositionPickerWireFrame.swift in Sources */, + 854D13D8211B2E7200E139FC /* MessageCollectionViewLayout.swift in Sources */, 0062D9452062EC4100B915AC /* InviteFriendsDS.swift in Sources */, F117872620ACF2DB007A9A1B /* VideoQuality.swift in Sources */, FBDA34E920921079009F4FB6 /* KeyboardLayoutGuide.swift in Sources */, @@ -14438,6 +14791,7 @@ 896D51F07E2F79C8B5502DBF /* EditGroupPhotoInteractor.swift in Sources */, A4ED79AA20C704F500A41F67 /* MyChannelsItemsFactory.swift in Sources */, 5BC1D38420D3B670002A44B3 /* CallCreatorMediator.swift in Sources */, + B7EF8ED2210C502D00E0E981 /* InterpretationTypeTableDelegate.swift in Sources */, 1325429A6216D23E2E67B6B7 /* EditGroupPhotoWireframe.swift in Sources */, 2603139720A0A4B9009AC66D /* LangCell.swift in Sources */, 99B9D27D2F0EFE051E6581ED /* CreateGroupProtocols.swift in Sources */, @@ -14461,6 +14815,7 @@ 8562853220D140FC000C9739 /* InputBar+ButtonType.swift in Sources */, A43B25AC20AB1DFA00FF8107 /* BaseInputView.swift in Sources */, F127F3BA20BF03BF007A6F87 /* DateFormatterExtension.swift in Sources */, + B7B546AE210D9C8C002DCA55 /* CircleView.swift in Sources */, E79061B81FBF2243009FD83A /* FeatureTable.swift in Sources */, 5B5EE777EF301CFC1FDCF307 /* CreateGroupInteractor.swift in Sources */, 8580BAD820BD98E700239D9D /* CounterView.swift in Sources */, @@ -14533,9 +14888,11 @@ 5ED473EC698E99DC021E553A /* MapSearchInteractor.swift in Sources */, F1607B2A20B2DE6500BDF60A /* CameraQRPreviewWireframe.swift in Sources */, E3BE59F069959DA2523EF3DC /* MapSearchWireframe.swift in Sources */, + B7EF8ED4210C511C00E0E981 /* InterpretationTypeCell.swift in Sources */, 8572C3BE2092368600E4840C /* StickerDataSource.swift in Sources */, F1313B0220888FE600E04092 /* ThirdPartyServices.swift in Sources */, 9BD8E40720F3576F001384EC /* CallInProgressWireframe.swift in Sources */, + B745F2E62109BB0100488A91 /* InterpretationLayout.swift in Sources */, 8548340E207769E800604051 /* DocumentInteractionInput.swift in Sources */, BDC42BA204F86F13E9FE24FA /* ScheduleMessageProtocols.swift in Sources */, 0062D9462062EC4100B915AC /* InviteFriendsViewControllerLayout.swift in Sources */, @@ -14555,6 +14912,7 @@ 8F4E135F7485898D06EEABAB /* ScheduleMessageWireframe.swift in Sources */, B83D907D324553FB792968EE /* TimeZoneSelectorProtocols.swift in Sources */, 06084879DD92F39E637C21F2 /* TimeZoneSelectorViewController.swift in Sources */, + B79FA03A21091ED000F286BF /* InterpretationWireFrame.swift in Sources */, 2648C4142069B52100863614 /* ChangeNumberStep3Protocols.swift in Sources */, A839130C3B1AEFC6EBDA71A4 /* TimeZoneSelectorPresenter.swift in Sources */, A81BC507BA308AEE05A9B5E1 /* TimeZoneSelectorInteractor.swift in Sources */, @@ -14585,6 +14943,7 @@ 8580BAED20BD9A7100239D9D /* LinkLongPressGestureRecognizer.swift in Sources */, C6B308C6734EFB77892832A0 /* SecurityPresenter.swift in Sources */, A42D52B4206A53AA00EEB952 /* ok_Spec.swift in Sources */, + B74BAFFD21076AFA0049CD27 /* Sector.swift in Sources */, 8E54E93EA25B11D417A6100E /* SecurityInteractor.swift in Sources */, A43B259420AB1DFA00FF8107 /* InputBar.swift in Sources */, F11DF06820BD996200F3E005 /* NavigationProtocol.swift in Sources */, @@ -14681,7 +15040,6 @@ isa = PBXVariantGroup; children = ( 00E864712052B1BE00844FF1 /* en */, - 00E864732052B1C400844FF1 /* ru */, ); name = InfoPlist.strings; sourceTree = ""; @@ -14698,7 +15056,6 @@ isa = PBXVariantGroup; children = ( 6D36F8E31F0ADBD300FA1AC8 /* en */, - 00E864662049840A00844FF1 /* ru */, ); name = Localizable.strings; sourceTree = ""; @@ -14707,7 +15064,6 @@ isa = PBXVariantGroup; children = ( A4B544F920EFC0AD00EB7B0F /* en */, - A4B544FB20EFC0BC00EB7B0F /* ru */, ); name = StatusCodes.strings; sourceTree = ""; @@ -15112,7 +15468,7 @@ OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" -DSHARE_EXTENSION"; PRODUCT_BUNDLE_IDENTIFIER = "$(ExtensionBundleIdentifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = "3d8b88dd-22c4-457b-864c-32d6378f5f64"; + PROVISIONING_PROFILE = "69f3dc99-df33-4a29-8a8a-8d93926a3535"; PROVISIONING_PROFILE_SPECIFIER = DevBundle_DevExt; SKIP_INSTALL = YES; SWIFT_VERSION = 4.0; @@ -15285,7 +15641,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(BundleIdentifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = "8a9243cd-515b-4739-b9d6-f73ba97c3403"; + PROVISIONING_PROFILE = "dc81b7c8-7ca8-4e18-9fee-d0f512f7c8ca"; PROVISIONING_PROFILE_SPECIFIER = DevBundle_Dev; SWIFT_OBJC_BRIDGING_HEADER = "Nynja-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -15405,7 +15761,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "$(BundleIdentifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = "056407e9-6a56-4005-9a4c-8c142132e54f"; + PROVISIONING_PROFILE = "dc81b7c8-7ca8-4e18-9fee-d0f512f7c8ca"; PROVISIONING_PROFILE_SPECIFIER = DevBundle_Dev; SWIFT_OBJC_BRIDGING_HEADER = "Nynja-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -15958,8 +16314,8 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIconRC; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Nynja/Resources/Nynja.entitlements; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 9GKQ5AMF2B; @@ -15970,8 +16326,8 @@ MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = "$(BundleIdentifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = "d22f9bd2-3299-4c7a-ac33-6a12c4c3cdaa"; - PROVISIONING_PROFILE_SPECIFIER = NynjaRC_dev; + PROVISIONING_PROFILE = "3d9e361e-de0d-4189-be64-4bea82318684"; + PROVISIONING_PROFILE_SPECIFIER = NynjaRC_adhoc; SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; SWIFT_OBJC_BRIDGING_HEADER = "Nynja-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -15989,8 +16345,8 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = "Nynja-Share/Resources/Nynja-Share.entitlements"; - CODE_SIGN_IDENTITY = "iPhone Developer"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = "iPhone Distribution"; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 9GKQ5AMF2B; @@ -16002,8 +16358,8 @@ OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" -DSHARE_EXTENSION"; PRODUCT_BUNDLE_IDENTIFIER = "$(ExtensionBundleIdentifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = "1fc6c35e-7400-4bfc-8831-e2bba8e2d5e0"; - PROVISIONING_PROFILE_SPECIFIER = NynjaRC_devExt; + PROVISIONING_PROFILE = "f232c367-7000-49c5-b32c-0efb36c5f2e3"; + PROVISIONING_PROFILE_SPECIFIER = NynjaRC_adhocExt; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 4.0; diff --git a/Nynja/AppDelegate.swift b/Nynja/AppDelegate.swift index e000e94ca2984364de0cdbcf4b9316e98aed628e..ade8b9e232d644aaf1122e2ff3e462a69d2b69b3 100644 --- a/Nynja/AppDelegate.swift +++ b/Nynja/AppDelegate.swift @@ -94,9 +94,8 @@ private extension AppDelegate { self.window?.rootViewController = navigation self.window?.makeKeyAndVisible() } - - - func configureDependencies() { + + private func configureDependencies() { setupTestFairy() setupCrashlytics() setupGoogleMaps() diff --git a/Nynja/ChatService.swift b/Nynja/ChatService.swift index 3fa4ac54c00146bc8d0148b3406c927bf51e674c..b6e279d3f4661af1000445493555290476d45a22 100644 --- a/Nynja/ChatService.swift +++ b/Nynja/ChatService.swift @@ -50,7 +50,7 @@ final class ChatService { } @discardableResult - static func updateLastMessage(_ message: Message, shouldUpdate: ((Int64) -> Bool)? = nil) -> Bool { + static func updateLastMessage(_ message: Message, shouldChangeUnread: Bool = true, shouldUpdate: ((Int64) -> Bool)? = nil) -> Bool { guard let chatModel = ChatService.fetchChatModel(from: message) else { return false } if let shouldUpdate = shouldUpdate, let lastId = chatModel.last_msg?.id, !shouldUpdate(lastId) { @@ -58,8 +58,8 @@ final class ChatService { } chatModel.last_msg = message - - let shouldIncrementUnread = (!message.isOwn || message.isCursor) && !message.isEdited + + let shouldIncrementUnread = (!message.isOwn || message.isCursor) && !message.isEdited && shouldChangeUnread if shouldIncrementUnread { let counter = chatModel.unreadCount + 1 chatModel.unread = counter @@ -81,15 +81,50 @@ final class ChatService { } } + // MARK: - Remove message + static func removeMessages(_ messages: [Message]) { + messages.forEach { removeMessage($0, shouldUpdateChat: false) } + } + + static func removeMessage(_ message: Message, shouldUpdateChat: Bool = true) { + if MessageDAO.removeMessage(using: message) { + guard let id = message.link else { + return + } + + if let action = MessageActionDAO.fetchMessageAction(by: id) { + try? StorageService.sharedInstance.perform(action: .delete, with: action) + } + + if let fetchType = self.fetchType(from: message), + let lastMessage = MessageDAO.fetchLastMessage(of: fetchType) { + + ChatService.updateLastMessage(lastMessage, shouldChangeUnread: false) { (lastMessageId) -> Bool in + return id == lastMessageId + } + } + } + } + + private static func fetchType(from message: Message) -> FetchType? { + if let p2p = message.p2pFeed, let from = p2p.from, let to = p2p.to { + return .p2p(from: from, to: to) + } else if let muc = message.mucFeed, let name = muc.name { + return .muc(name: name) + } + + return nil + } + // MARK: - Clear History static func clearHistory(_ message: Message) { do { if let muc = message.feed_id as? muc, let name = muc.name { try MessageDAO.clearHistory(FetchType.muc(name: name)) - updateChatModel(message) + updateChatModelAfterClear(with: message) } else if let p2p = message.feed_id as? p2p, let from = p2p.from, let to = p2p.to { try MessageDAO.clearHistory(FetchType.p2p(from: from, to: to)) - updateChatModel(message) + updateChatModelAfterClear(with: message) } } catch {} } @@ -109,7 +144,7 @@ final class ChatService { } catch {} } - private static func updateChatModel(_ message: Message) { + private static func updateChatModelAfterClear(with message: Message) { do { try storageService.perform(action: .save, with: message) diff --git a/Nynja/CircleMenuControl/Core/CircleMenu.swift b/Nynja/CircleMenuControl/Core/CircleMenu.swift new file mode 100644 index 0000000000000000000000000000000000000000..7dc1e98d7de7f64f530abd55bbc880f063fa1548 --- /dev/null +++ b/Nynja/CircleMenuControl/Core/CircleMenu.swift @@ -0,0 +1,270 @@ +// +// CircleMenu.swift +// CircleMenuControl +// +// Created by Roman Chopovenko on 7/23/18. +// Copyright © 2018 Roman Chopovenko. All rights reserved. +// + +import UIKit + +class CircleMenu: UIControl { + + weak var delegate: CircleMenuDelegate? + + private var container: UIView! + private var sectors = [Sector]() + private(set) var currentSectorIndex: Int? + private let separatorPadding: CGFloat = 30 + + private var menuNavigationStack = [CircleMenuSet]() { + didSet { + if let data = self.dataSource { + self.delegate?.didChangeCurrentSet(self, title: data.title) + } + self.sectors = [Sector]() + self.currentSectorIndex = nil + self.container.removeFromSuperview() + self.container = nil + self.setNeedsDisplay() + } + } + + private var dataSource: CircleMenuSet? { + return self.menuNavigationStack.last + } + + private var containerCenter: CGPoint { + return CGPoint(x: self.container.bounds.width/2, y: self.container.bounds.height/2) + } + + private var segmentAngle: CGFloat { + guard let items = self.dataSource?.items else { return 0 } + return (2 * .pi) / CGFloat(items.count) + } + + + //MARK: Public methods + + public func updateMenu(with newMenuSet: CircleMenuSet) { + self.menuNavigationStack.append(newMenuSet) + } + + public func navigateBackInMenuStack() -> Bool { + if self.menuNavigationStack.count > 1 { + let lastIndex = self.menuNavigationStack.count - 1 + self.menuNavigationStack.remove(at: lastIndex) + return true + } + return false + } + + //MARK: - Init + + required init(rect: CGRect, delegate: CircleMenuDelegate, menuSet: CircleMenuSet) { + super.init(frame: rect) + self.delegate = delegate + self.menuNavigationStack.append(menuSet) + self.backgroundColor = .clear + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + //MARK: - Override + + override func draw(_ rect: CGRect) { + guard self.container == nil else { return } + guard let items = self.dataSource?.items else { return } + self.container = UIView(frame: rect) + self.container.backgroundColor = .clear + + for index in 0.. Bool { + let touchPoint: CGPoint = touch.location(in: self) + + if let sector = self.detectSelectedSegment(from: touchPoint), let data = self.dataSource { + if data.items[sector.index].isEnabled { + self.selectSegment(at: sector.index) + } + } + return self.ignoreTaps(for: touchPoint) + } + + override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + if let selectedIndex = self.currentSectorIndex, let section = self.sectors[selectedIndex].section { + self.delegate?.didSelectItem(self, type: section.model.type) + } + self.deSelectSegment(at: self.currentSectorIndex) + } + + + //MARK: - Construct Menu UI + + private func createSector(at index: Int, itemsCount: Int, angle: CGFloat) -> Sector { + var endAngle = self.segmentAngle * CGFloat(index) + angle/2 + + if itemsCount == 2 { + endAngle = angle * CGFloat(index) + } + + let startAngle = endAngle - angle + let midAngle = startAngle + angle/2 + + let sector = Sector(index: index, + min: startAngle, + mid: midAngle, + max: endAngle) + return sector + } + + private func createSeparator(padding: CGFloat, height: CGFloat, angle: CGFloat, center: CGPoint, color: UIColor) -> UIView { + let separator = UIView(frame: CGRect(origin: .zero, size: CGSize(width: 1, height: height))) + separator.backgroundColor = color + separator.layer.anchorPoint = CGPoint(x: 0.5, y: 1) + + let separatorStartPoint = self.getPointCoordinate(withCenterIn: center, + radius: padding, + angle: angle - .pi/2) + separator.layer.position = separatorStartPoint + separator.transform = CGAffineTransform(rotationAngle: angle) + return separator + } + + private func createSection(in view: UIView, rotationAngle: CGFloat, model: CircleMenuItem) -> SectionView { + let sectionRect = CGRect(origin: .zero, size: CGSize(width: view.bounds.width, height: view.bounds.height/2)) + let section = SectionView(frame: sectionRect, model: model, angle: 2 * .pi - rotationAngle) + + section.backgroundColor = .clear + section.isHighlighted = false + + section.layer.anchorPoint = CGPoint(x: 0.5, y: 1) + section.layer.position = view.center + section.transform = CGAffineTransform(rotationAngle: rotationAngle) + return section + } + + //MARK: Private methods + + private func selectSegment(at index: Int) { + if let section = self.sectors[index].section { + self.currentSectorIndex = index + section.isHighlighted = true + } + } + + private func deSelectSegment(at index: Int?) { + if let thisIndex = index, let section = self.sectors[thisIndex].section { + self.currentSectorIndex = nil + section.isHighlighted = false + } + } + + private func detectSelectedSegment(from touchPoint: CGPoint) -> Sector? { + // Get inverted touch angle value + let dx = container.center.x - touchPoint.x + let dy = container.center.y - touchPoint.y + let angle = -atan2(dx, dy) + + for sector in self.sectors { + let newAngle = angle.toPositiveRadians + + let min = sector.minValue.toPositiveRadians + let max = sector.maxValue.toPositiveRadians + + let regularCondition = newAngle > min && newAngle < max + let firstElementCondition = min > max && (newAngle > min && newAngle <= 2 * .pi || newAngle < max) + + if regularCondition || firstElementCondition { + return sector + } + } + return nil + } + + private func calculateDistance(fromCenter point: CGPoint) -> CGFloat { + let center = CGPoint(x: bounds.size.width/2, y: bounds.size.height/2) + let dx = CGFloat(point.x - center.x) + let dy = CGFloat(point.y - center.y) + + return sqrt(pow(dx, 2) + pow(dy, 2)) + } + + private func ignoreTaps(for touchPoint: CGPoint) -> Bool { + let dist: CGFloat = calculateDistance(fromCenter: touchPoint) + + if dist < self.separatorPadding || dist > self.container.bounds.width/2 { + self.deSelectSegment(at: self.currentSectorIndex) + return false + } + return true + } + + private func getPointCoordinate(withCenterIn center: CGPoint, radius: CGFloat, angle: CGFloat) -> CGPoint { + return CGPoint(x: center.x + radius*cos(angle), y: center.y + radius*sin(angle)) + } + + private func drawSegmentPoints(inView view: UIView, innerPadding: CGFloat, side: CGFloat, leadingAngle: CGFloat, trailingAngle: CGFloat) { + let bottomCenter = CGPoint(x: view.bounds.width/2, y: view.bounds.height) + + let leadingBottomPoint = self.getPointCoordinate(withCenterIn: bottomCenter, + radius: innerPadding, + angle: leadingAngle) + let leadingTopPoint = self.getPointCoordinate(withCenterIn: bottomCenter, + radius: innerPadding + side, + angle: leadingAngle) + + let trailingBottomPoint = self.getPointCoordinate(withCenterIn: bottomCenter, + radius: innerPadding, + angle: trailingAngle) + let path = UIBezierPath() + path.move(to: leadingBottomPoint) + path.addLine(to: leadingTopPoint) + path.addArc(withCenter: bottomCenter, radius: innerPadding + side, startAngle: leadingAngle, endAngle: trailingAngle, clockwise: true) + path.addLine(to: trailingBottomPoint) + path.addArc(withCenter: bottomCenter, radius: innerPadding, startAngle: trailingAngle, endAngle: leadingAngle, clockwise: false) + path.close() + + view.applyMask(withPath: path, inverse: false) + } +} diff --git a/Nynja/CircleMenuControl/Core/CircleMenuDelegate.swift b/Nynja/CircleMenuControl/Core/CircleMenuDelegate.swift new file mode 100644 index 0000000000000000000000000000000000000000..135f692ca1e5ea6b7c9b097248390ce8640a62b4 --- /dev/null +++ b/Nynja/CircleMenuControl/Core/CircleMenuDelegate.swift @@ -0,0 +1,14 @@ +// +// CircleMenuDelegate.swift +// CircleMenuControl +// +// Created by Roman Chopovenko on 7/24/18. +// Copyright © 2018 Roman Chopovenko. All rights reserved. +// + +import Foundation + +protocol CircleMenuDelegate: class { + func didSelectItem(_ menu: CircleMenu, type: CircleMenuItemType) + func didChangeCurrentSet(_ menu: CircleMenu, title: String) +} diff --git a/Nynja/CircleMenuControl/Core/Sector/SectionView.swift b/Nynja/CircleMenuControl/Core/Sector/SectionView.swift new file mode 100644 index 0000000000000000000000000000000000000000..761af4af652245239d5b3aa6d2aca803abb555be --- /dev/null +++ b/Nynja/CircleMenuControl/Core/Sector/SectionView.swift @@ -0,0 +1,138 @@ +// +// SectionView.swift +// rrrrrr +// +// Created by Roman Chopovenko on 7/23/18. +// Copyright © 2018 Roman Chopovenko. All rights reserved. +// + +import UIKit + +fileprivate let iconSide: CGFloat = 34 +fileprivate let padding: CGFloat = 8 +fileprivate let labelHeight: CGFloat = 22 + +fileprivate let height = iconSide + padding + labelHeight + +class SectionView: UIView { + + var angle: CGFloat! + var model: CircleMenuItem! + + var isHighlighted: Bool = false { + didSet { + self.backgroundView.alpha = isHighlighted ? 1.0 : 0 + self.backgroundView.setNeedsDisplay() + } + } + + // MARK: - Views + lazy var backgroundView: UIView = { + let view = GradientView(colors: [Colors.gradientView.start, + Colors.gradientView.end]) + self.addSubview(view) + view.snp.makeConstraints({ (make) in + make.edges.equalToSuperview() + }) + return view + }() + + private lazy var buttonContentHolder: UIView = { + let view = UIView() + + view.backgroundColor = .clear + view.transform = CGAffineTransform(rotationAngle: self.angle) + + let shiftCenterY: CGFloat = 10.0 + let width: CGFloat = 2*height + + self.addSubview(view) + view.snp.makeConstraints({ (make) in + make.centerX.equalToSuperview() + make.centerY.equalToSuperview().offset(-shiftCenterY) + make.width.equalTo(width) + make.height.equalTo(height) + }) + return view + }() + + private lazy var iconView: UIImageView = { + let icon = self.createIconView(with: self.model) + + buttonContentHolder.addSubview(icon) + icon.snp.makeConstraints({ (make) in + make.width.height.equalTo(iconSide) + make.top.centerX.equalToSuperview() + }) + return icon + }() + + private lazy var titleLabel: UILabel = { + let label = self.createTitleLabel(with: self.model) + + buttonContentHolder.addSubview(label) + label.snp.makeConstraints({ (make) in + make.height.equalTo(labelHeight) + make.top.equalTo(self.iconView.snp.bottom).offset(padding) + make.left.right.bottom.equalToSuperview() + }) + return label + }() + + // MARK: - Init + required init(frame: CGRect, model: CircleMenuItem, angle: CGFloat) { + super.init(frame: frame) + self.model = model + self.angle = angle + self.setupUI() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func setupUI() { + self.backgroundView.isHidden = false + self.titleLabel.isHidden = false + } + + // MARK: Private methods + private func createIconView(with model: CircleMenuItem) -> UIImageView { + let imageView = UIImageView(frame: .zero) + imageView.contentMode = .scaleAspectFit + imageView.image = model.icon.withRenderingMode(.alwaysTemplate) + imageView.tintColor = model.isEnabled ? Colors.iconView.enabled : Colors.iconView.disabled + return imageView + } + + private func createTitleLabel(with model: CircleMenuItem) -> UILabel { + let label = UILabel(frame: .zero) + + label.numberOfLines = 1 + label.textAlignment = .center + let font = UIFont(name: Constants.fonts.medium, size: 12) + label.font = font + label.text = model.title + label.textColor = model.isEnabled ? Colors.titleLabel.enabled : Colors.titleLabel.disabled + return label + } +} + +extension SectionView { + struct Colors { + struct titleLabel { + static let enabled: UIColor = Constants.colors.marketplaceMunu.activeTitle.getColor() + static let disabled: UIColor = Constants.colors.marketplaceMunu.inActiveTitle.getColor() + } + + struct iconView { + static let enabled: UIColor = Constants.colors.marketplaceMunu.activeIcon.getColor() + static let disabled: UIColor = Constants.colors.marketplaceMunu.inactiveIcon.getColor() + } + + struct gradientView { + static let start: UIColor = Constants.colors.marketplaceMunu.gradient.getColor(withAlpha: 0.1) + static let end: UIColor = Constants.colors.marketplaceMunu.gradient.getColor(withAlpha: 0.5) + } + } +} diff --git a/Nynja/CircleMenuControl/Core/Sector/Sector.swift b/Nynja/CircleMenuControl/Core/Sector/Sector.swift new file mode 100644 index 0000000000000000000000000000000000000000..7b72100653ce58247932869bf7f08d9f7f95d6ad --- /dev/null +++ b/Nynja/CircleMenuControl/Core/Sector/Sector.swift @@ -0,0 +1,25 @@ +// +// Sector.swift +// rrrrrr +// +// Created by Roman Chopovenko on 7/19/18. +// Copyright © 2018 Roman Chopovenko. All rights reserved. +// + +import UIKit + +class Sector { + var minValue: CGFloat + var midValue: CGFloat + var maxValue: CGFloat + var index: Int + + var section: SectionView? + + required init(index: Int, min: CGFloat, mid: CGFloat, max: CGFloat) { + self.index = index + self.minValue = min + self.midValue = mid + self.maxValue = max + } +} diff --git a/Nynja/CircleMenuControl/Core/Sector/TheGradientView.swift b/Nynja/CircleMenuControl/Core/Sector/TheGradientView.swift new file mode 100644 index 0000000000000000000000000000000000000000..336adce7e1d38ffcd4f9654b677422baa0113356 --- /dev/null +++ b/Nynja/CircleMenuControl/Core/Sector/TheGradientView.swift @@ -0,0 +1,43 @@ +// +// TheGradientView.swift +// rrrrrr +// +// Created by Roman Chopovenko on 7/21/18. +// Copyright © 2018 Roman Chopovenko. All rights reserved. +// + +import UIKit + +class TheGradientView: UIView { + + var startColor: UIColor = Constants.colors.marketplaceMunu.gradient.getColor(withAlpha: 0.1) + var endColor: UIColor = Constants.colors.marketplaceMunu.gradient.getColor(withAlpha: 0.5) + override init(frame: CGRect) { + super.init(frame: frame) + self.backgroundColor = .clear + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func draw(_ rect: CGRect) { + let context = UIGraphicsGetCurrentContext()! + let colors = [startColor.cgColor, endColor.cgColor] + + let colorSpace = CGColorSpaceCreateDeviceRGB() + + let colorLocations: [CGFloat] = [0.0, 0.8] + + let gradient = CGGradient(colorsSpace: colorSpace, + colors: colors as CFArray, + locations: colorLocations)! + + let startPoint = CGPoint.zero + let endPoint = CGPoint(x: 0, y: bounds.height) + context.drawLinearGradient(gradient, + start: startPoint, + end: endPoint, + options: []) + } +} diff --git a/Nynja/CircleMenuControl/Extension/UIView+Mask.swift b/Nynja/CircleMenuControl/Extension/UIView+Mask.swift new file mode 100644 index 0000000000000000000000000000000000000000..deb0a6efc3a6bcd51098dc296205e61f6c9480da --- /dev/null +++ b/Nynja/CircleMenuControl/Extension/UIView+Mask.swift @@ -0,0 +1,50 @@ +// +// UIView+Mask.swift +// rrrrrr +// +// Created by Roman Chopovenko on 7/23/18. +// Copyright © 2018 Roman Chopovenko. All rights reserved. +// + +import UIKit + +extension UIView { + func applyMask(withRect rect: CGRect, inverse: Bool = false) { + let path = UIBezierPath(rect: rect) + let maskLayer = CAShapeLayer() + + if inverse { + path.append(UIBezierPath(rect: self.bounds)) + maskLayer.fillRule = kCAFillRuleEvenOdd + } + + maskLayer.path = path.cgPath + + self.layer.mask = maskLayer + } + + func applyMask(withPath path: UIBezierPath, inverse: Bool = false) { + let path = path + let maskLayer = CAShapeLayer() + + if inverse { + path.append(UIBezierPath(rect: self.bounds)) + maskLayer.fillRule = kCAFillRuleEvenOdd + } + + maskLayer.path = path.cgPath + + self.layer.mask = maskLayer + } +} + +extension BinaryInteger { + var degreesToRadians: CGFloat { return CGFloat(Int(self)) * .pi / 180 } +} + +extension FloatingPoint { + var degreesToRadians: Self { return self * .pi / 180 } + var radiansToDegrees: Self { return self * 180 / .pi } + + var toPositiveRadians: Self { return self < 0 ? 2 * .pi + self : self} +} diff --git a/Nynja/CircleMenuControl/Model/CircleMenuFactory.swift b/Nynja/CircleMenuControl/Model/CircleMenuFactory.swift new file mode 100644 index 0000000000000000000000000000000000000000..5f76e7c61c1f3001cb7a5dc330a9635bb8a8218f --- /dev/null +++ b/Nynja/CircleMenuControl/Model/CircleMenuFactory.swift @@ -0,0 +1,81 @@ +// +// CircleMenuFactory.swift +// CircleMenuControl +// +// Created by Roman Chopovenko on 7/23/18. +// Copyright © 2018 Roman Chopovenko. All rights reserved. +// + +import UIKit + + +enum CircleMenuItemType: String { + case marketplace + case freelance + case accessMarketplace + case virtualGoods + case stickers + case mediaContent + case groupsAndChannels + case bots + case apps + case interpretation + case nynjaSupport + case design + var icon: UIImage { + + switch self { + case .freelance: return #imageLiteral(resourceName: "marketplace_freelance") + case .accessMarketplace: return #imageLiteral(resourceName: "marketplace_access") + case .virtualGoods: return #imageLiteral(resourceName: "marketplace_virtual_goods") + case .stickers: return #imageLiteral(resourceName: "marketplace_sticker") + case .mediaContent: return #imageLiteral(resourceName: "marketplace_media_content") + case .groupsAndChannels: return #imageLiteral(resourceName: "marketplace_groups_channels") + case .bots: return #imageLiteral(resourceName: "marketplace_bots") + case .apps: return #imageLiteral(resourceName: "marketplace_apps") + case .interpretation: return #imageLiteral(resourceName: "marketplace_interpretation") + case .nynjaSupport: return #imageLiteral(resourceName: "marketplace_support") + case .design: return #imageLiteral(resourceName: "marketplace_design") + default:return #imageLiteral(resourceName: "ava_placeholder") + } + } +} + +class CircleMenuFactory { + + var marketplace: CircleMenuSet { + let freelance = CircleMenuItem(type: .freelance, isEnabled: true) + let accessMarketPlace = CircleMenuItem(type: .accessMarketplace, isEnabled: true) + let virtualGoods = CircleMenuItem(type: .virtualGoods, isEnabled: true) + + return CircleMenuSet(title: CircleMenuItemType.marketplace.localized.uppercased(), items: [freelance, accessMarketPlace, virtualGoods]) + } + + var virtualGoods: CircleMenuSet { + let stickers = CircleMenuItem(type: .stickers, isEnabled: false) + let mediaContent = CircleMenuItem(type: .mediaContent, isEnabled: false) + return CircleMenuSet(title: CircleMenuItemType.virtualGoods.localized.uppercased(), items: [mediaContent, stickers]) + } + + var accessMarketPlace: CircleMenuSet { + let groupsAndChannels = CircleMenuItem(type: .groupsAndChannels, isEnabled: false) + let apps = CircleMenuItem(type: .apps, isEnabled: false) + let bots = CircleMenuItem(type: .bots, isEnabled: false) + return CircleMenuSet(title: CircleMenuItemType.accessMarketplace.localized.uppercased(), items: [groupsAndChannels, apps, bots]) + } + + var freelance: CircleMenuSet { + let interpretation = CircleMenuItem(type: .interpretation, isEnabled: true) + let nynjaSupport = CircleMenuItem(type: .nynjaSupport, isEnabled: false) + let design = CircleMenuItem(type: .design, isEnabled: false) + let title = CircleMenuItemType.freelance.localized + " " + CircleMenuItemType.marketplace.localized + return CircleMenuSet(title: title.uppercased(), items: [interpretation, nynjaSupport, design]) + } + + var allElementsDictionary: [CircleMenuItemType : CircleMenuSet] { + return [.marketplace : self.marketplace, + .virtualGoods : self.virtualGoods, + .accessMarketplace : self.accessMarketPlace, + .freelance : self.freelance] + } +} diff --git a/Nynja/CircleMenuControl/Model/CircleMenuItem.swift b/Nynja/CircleMenuControl/Model/CircleMenuItem.swift new file mode 100644 index 0000000000000000000000000000000000000000..ec488799c9fd1efb2d50c7628fcecd2f484c1769 --- /dev/null +++ b/Nynja/CircleMenuControl/Model/CircleMenuItem.swift @@ -0,0 +1,17 @@ +// +// CircleMenuItem.swift +// CircleMenuControl +// +// Created by Roman Chopovenko on 7/24/18. +// Copyright © 2018 Roman Chopovenko. All rights reserved. +// + +import UIKit + +struct CircleMenuItem { + let type: CircleMenuItemType + let isEnabled: Bool + + var icon: UIImage { return type.icon } + var title: String { return type.localized } +} diff --git a/Nynja/CircleMenuControl/Model/CircleMenuSet.swift b/Nynja/CircleMenuControl/Model/CircleMenuSet.swift new file mode 100644 index 0000000000000000000000000000000000000000..11fd3cae6b23ae29e88c9bd4596842ddcdb7e3c6 --- /dev/null +++ b/Nynja/CircleMenuControl/Model/CircleMenuSet.swift @@ -0,0 +1,14 @@ +// +// CircleMenuSet.swift +// CircleMenuControl +// +// Created by Roman Chopovenko on 7/24/18. +// Copyright © 2018 Roman Chopovenko. All rights reserved. +// + +import Foundation + +struct CircleMenuSet { + let title: String + let items: [CircleMenuItem] +} diff --git a/Nynja/DB/Models/DBMessage.swift b/Nynja/DB/Models/DBMessage.swift index d345acc4ce67342be001be36475cd271e0599d6c..e936ee5966efa425d5bd0f9d2361f38fb0538ed5 100644 --- a/Nynja/DB/Models/DBMessage.swift +++ b/Nynja/DB/Models/DBMessage.swift @@ -8,6 +8,8 @@ import GRDBCipher +private let tableName = MessageTable.name + class DBMessage: Record, DBModelProtocol { var id: Int64? @@ -63,7 +65,7 @@ class DBMessage: Record, DBModelProtocol { // MARK: - Record override static var databaseTableName: String { - return MessageTable.name + return tableName } required init(row: Row) { @@ -179,41 +181,39 @@ class DBMessage: Record, DBModelProtocol { return message } + static func penultimateMessage(_ db: Database, ofType fetchType: FetchType) throws -> DBMessage? { + guard let lastMessage = try lastMessage(db, ofType: fetchType), + let lastMessageServerId = lastMessage.serverId else { + return nil + } + + let condition = conditionBefore(messageServerId: lastMessageServerId) + + var sql: String + + switch fetchType { + case let .p2p(from, to): + sql = sqlP2p(from: from, to: to, conditions: condition, ordered: .desc) + case let .muc(mucName): + sql = sqlMuc(name: mucName, conditions: condition, ordered: .desc) + } + + let message = try SQLRequest(sql).asRequest(of: DBMessage.self).fetchOne(db) + try message?.construct(db) + return message + } + static func lastMessage(_ db: Database, ofType fetchType: FetchType) throws -> DBMessage? { - let message: DBMessage? + var sql: String switch fetchType { case let .p2p(from, to): - let messageTable = MessageTable.name - let p2pTable = P2pTable.name - - let sql = """ - select \(messageTable).* - from \(messageTable) - inner join \(p2pTable) on \(messageTable).[\(MessageTable.Column.feedId.title)] = \(p2pTable).[\(P2pTable.Column.id.title)] - where \(p2pTable).[\(P2pTable.Column.from.title)] = '\(from)' - and \(p2pTable).[\(P2pTable.Column.to.title)] = '\(to)' - and \(messageTable).[\(MessageTable.Column.feedType.title)] = \(FeedType.p2p.rawValue) - and \(messageTable).[\(MessageTable.Column.status.title)] != 'delete' - order by \(messageTable).[\(MessageTable.Column.created.title)] DESC - """ - - message = try SQLRequest(sql).asRequest(of: DBMessage.self).fetchOne(db) - + sql = sqlP2p(from: from, to: to, ordered: .desc) case let .muc(mucName): - guard let feedId = try DBMuc.muc(db, name: mucName)?.id else { return nil } - - let sql = """ - select * - from \(MessageTable.name) - where \(MessageTable.Column.feedId) = \(feedId) - and \(MessageTable.Column.feedType.title) = '\(FeedType.muc.rawValue)' - and \(MessageTable.Column.status.title) != 'delete' - order by \(MessageTable.Column.created.title) DESC - """ - - message = try SQLRequest(sql).asRequest(of: DBMessage.self).fetchOne(db) + sql = sqlMuc(name: mucName, ordered: .desc) } + + let message = try SQLRequest(sql).asRequest(of: DBMessage.self).fetchOne(db) try message?.construct(db) return message } @@ -227,7 +227,7 @@ class DBMessage: Record, DBModelProtocol { if !serverIds.isEmpty { let ids = serverIds.joinedByComma() - conditions = "and \(MessageTable.name).\(MessageTable.Column.serverId.title) in (\(ids))" + conditions = "and \(tableName).\(MessageTable.Column.serverId.title) in (\(ids))" return try messages(db, fetchType: fetchType, conditions: conditions) } else { return [] @@ -235,7 +235,7 @@ class DBMessage: Record, DBModelProtocol { } static func messages(_ db: Database, fetchType: FetchType, before messageServerId: Int64) throws -> [DBMessage] { - let condition = "and \(MessageTable.name).\(MessageTable.Column.serverId.title) < \(messageServerId)" + let condition = conditionBefore(messageServerId: messageServerId) return try messages(db, fetchType: fetchType, conditions: condition) } @@ -305,12 +305,6 @@ class DBMessage: Record, DBModelProtocol { } self.id = id - let rooms = try DBRoom.filter(Column(RoomTable.Column.messageId.title) == id).fetchAll(db) - try rooms.forEach { $0.messageId = nil; try $0.saveAggregate(db) } - - let contacts = try DBContact.filter(Column(ContactTable.Column.messageId.title) == id).fetchAll(db) - try contacts.forEach { $0.messageId = nil; try $0.saveAggregate(db) } - try DBDesc.deleteAll(db, targetId: String(id), targetType: .message) return try delete(db) @@ -323,7 +317,7 @@ class DBMessage: Record, DBModelProtocol { } static private func requestUnsentMessages(from: String) -> AnyTypedRequest { - let messageTable = MessageTable.name + let messageTable = tableName let sql = """ select \(messageTable).* @@ -331,24 +325,40 @@ class DBMessage: Record, DBModelProtocol { where \(messageTable).[\(MessageTable.Column.from.title)] = '\(from)' and \(messageTable).[\(MessageTable.Column.serverId.title)] is null and \(messageTable).\(MessageTable.Column.feedType.title) = \(FeedType.p2p.rawValue) - order by \(messageTable).[\(MessageTable.Column.created.title)] + \(orderedBy(.asc)) """ return SQLRequest(sql).asRequest(of: DBMessage.self) } static private func requestP2pMessages(from: String, to: String, conditions: String = "") -> AnyTypedRequest { - let sql = String(format: sqlP2p(from: from, to: to), conditions) + let sql = sqlP2p(from: from, to: to, conditions: conditions, ordered: .asc) return SQLRequest(sql).asRequest(of: DBMessage.self) } static private func requestMucMessages(name: String, conditions: String = "") -> AnyTypedRequest { - let sql = String(format: sqlMuc(name: name), conditions) + let sql = sqlMuc(name: name, conditions: conditions, ordered: .asc) return SQLRequest(sql).asRequest(of: DBMessage.self) } - static private func sqlP2p(from: String, to: String) -> String { - let messageTable = MessageTable.name + + // MARK: - Raw SQL + + static private var skippedStatuses: String { + let statusColumn = "\(tableName).\(MessageTable.Column.status.title)" + return "(\(statusColumn) is null or \(statusColumn) not in ('delete', 'edit', 'deleted'))" + } + + static private func conditionBefore(messageServerId: MessageServerId) -> String { + return "and \(tableName).\(MessageTable.Column.serverId.title) < \(messageServerId)" + } + + static private func orderedBy(_ ordered: Ordered) -> String { + return "order by \(tableName).[\(MessageTable.Column.created.title)] \(ordered.rawValue)" + } + + static private func sqlP2p(from: String, to: String, conditions: String = "", ordered: Ordered) -> String { + let messageTable = tableName let p2pTable = P2pTable.name let sql = """ @@ -358,15 +368,16 @@ class DBMessage: Record, DBModelProtocol { where \(p2pTable).[\(P2pTable.Column.from.title)] = '\(from)' and \(p2pTable).[\(P2pTable.Column.to.title)] = '\(to)' and \(messageTable).\(MessageTable.Column.feedType.title) = \(FeedType.p2p.rawValue) + and \(skippedStatuses) %@ - order by \(messageTable).[\(MessageTable.Column.created.title)] + \(orderedBy(ordered)) """ - return sql + return String(format: sql, conditions) } - static private func sqlMuc(name: String) -> String { - let messageTable = MessageTable.name + static private func sqlMuc(name: String, conditions: String = "", ordered: Ordered) -> String { + let messageTable = tableName let mucTable = MucTable.name let sql = """ @@ -375,10 +386,20 @@ class DBMessage: Record, DBModelProtocol { left join \(messageTable) on \(mucTable).id = \(messageTable).feedId where \(mucTable).[\(MucTable.Column.name.title)] = '\(name)' and \(messageTable).\(MessageTable.Column.feedType.title) = \(FeedType.muc.rawValue) + and \(skippedStatuses) %@ - order by \(messageTable).[\(MessageTable.Column.created.title)] + \(orderedBy(ordered)) """ - return sql + return String(format: sql, conditions) } } + +private extension DBMessage { + + enum Ordered: String { + case asc + case desc + } + +} diff --git a/Nynja/DB/Tables/ContactTable.swift b/Nynja/DB/Tables/ContactTable.swift index d69ed6d915ce886d3c3042be5862052d9f7c4f0a..9285b90ee22b3e66de4ed0d471730f6f036c6337 100644 --- a/Nynja/DB/Tables/ContactTable.swift +++ b/Nynja/DB/Tables/ContactTable.swift @@ -15,7 +15,7 @@ class ContactTable: Table { } static func create(in db: Database) throws { - try db.create(table: ContactTable.name, body: { (t) in + try db.create(table: ContactTable.name) { t in t.column(Column.phoneId, .text).notNull().primaryKey() t.column(Column.avatar, .text) t.column(Column.names, .text) @@ -28,9 +28,9 @@ class ContactTable: Table { t.column(Column.presence, .text) t.column(Column.status, .text) - t.column(Column.messageId, .integer).references(MessageTable.name) + t.column(Column.messageId, .integer).references(MessageTable.name, onDelete: .setNull) t.column(Column.rosterId, .integer).references(RosterTable.name, onDelete: .cascade) - }) + } } } diff --git a/Nynja/DB/Tables/LinkTable.swift b/Nynja/DB/Tables/LinkTable.swift index 5b2b743b00e7d450a7da82a658324851bfbbc4dc..36884f8bd6b9193416c6c70b0de14e7a62c7bfd7 100644 --- a/Nynja/DB/Tables/LinkTable.swift +++ b/Nynja/DB/Tables/LinkTable.swift @@ -15,13 +15,13 @@ final class LinkTable: Table { } static func create(in db: Database) throws { - try db.create(table: LinkTable.name, body: { (table) in + try db.create(table: LinkTable.name) { table in table.column(Column.id, .text).notNull().primaryKey() table.column(Column.name, .text).notNull() - table.column(Column.roomId, .text).notNull().references(RoomTable.name) + table.column(Column.roomId, .text).notNull().references(RoomTable.name, onDelete: .cascade) table.column(Column.created, .integer).notNull() table.column(Column.status, .text) - }) + } } } diff --git a/Nynja/DB/Tables/MessageActionTable.swift b/Nynja/DB/Tables/MessageActionTable.swift index b558a55deda5a5d9ea04d5edc7835877dfbe5aff..816b7e56175d12d6a55ca8a74aea014f7c71c7ff 100644 --- a/Nynja/DB/Tables/MessageActionTable.swift +++ b/Nynja/DB/Tables/MessageActionTable.swift @@ -16,15 +16,10 @@ class MessageActionTable: Table { static func create(in db: Database) throws { try db.create(table: MessageActionTable.name, body: { (t) in - t.column(Column.messageId, .integer) + t.column(Column.messageId, .integer).primaryKey(onConflict: .replace, autoincrement: false) t.column(Column.seenBy, .integer) t.column(Column.phoneId, .text).defaults(to: "") t.column(Column.action, .text).defaults(to: false) - - t.uniqueKey([Column.messageId.title], onConflict: .replace) -// t.uniqueKey([Column.messageId.title, Column.seenBy.title, Column.action.title], onConflict: .replace) - -// t.foreignKey([Column.messageId.title], references: MessageTable.name, columns: nil, onDelete: .cascade, onUpdate: nil, deferred: false) }) } diff --git a/Nynja/DB/Tables/RoomMemberTable.swift b/Nynja/DB/Tables/RoomMemberTable.swift index dafffaaf6e04de6f382c99a0ee7a0b56f5d8db7e..9c27d55337f8b8e9bd33b6601766ab2bb34196ee 100644 --- a/Nynja/DB/Tables/RoomMemberTable.swift +++ b/Nynja/DB/Tables/RoomMemberTable.swift @@ -15,16 +15,16 @@ class RoomMemberTable: Table { } static func create(in db: Database) throws { - try db.create(table: RoomMemberTable.name, body: { (t) in + try db.create(table: RoomMemberTable.name) { t in t.column(Column.roomId, .text) t.column(Column.memberId, .integer) t.column(Column.isAdmin, .boolean).defaults(to: false) t.uniqueKey([Column.roomId.title, Column.memberId.title, Column.isAdmin.title], onConflict: .replace) - t.foreignKey([Column.roomId.title], references: RoomTable.name, columns: nil, onDelete: .cascade, onUpdate: nil, deferred: false) - t.foreignKey([Column.memberId.title], references: MemberTable.name, columns: nil, onDelete: .cascade, onUpdate: nil, deferred: false) - }) + t.foreignKey([Column.roomId.title], references: RoomTable.name, onDelete: .cascade) + t.foreignKey([Column.memberId.title], references: MemberTable.name, onDelete: .cascade) + } } } diff --git a/Nynja/DB/Tables/RoomTable.swift b/Nynja/DB/Tables/RoomTable.swift index 035c4b71801d279528a61849dec11e15fe9e994b..8cea93bff816eaccb984f72c7d45ed967fb3215e 100644 --- a/Nynja/DB/Tables/RoomTable.swift +++ b/Nynja/DB/Tables/RoomTable.swift @@ -15,7 +15,7 @@ class RoomTable: Table { } static func create(in db: Database) throws { - try db.create(table: RoomTable.name) { (t) in + try db.create(table: RoomTable.name) { t in t.column(Column.id, .text).primaryKey() t.column(Column.name, .text).notNull() t.column(Column.description, .text) @@ -31,7 +31,7 @@ class RoomTable: Table { t.column(Column.created, .integer).notNull().defaults(to: 0) t.column(Column.status, .text) - t.column(Column.messageId, .integer).references(MessageTable.name) + t.column(Column.messageId, .integer).references(MessageTable.name, onDelete: .setNull) t.column(Column.rosterId, .integer).references(RosterTable.name) } } diff --git a/Nynja/DatabaseManager.swift b/Nynja/DatabaseManager.swift index 545449390d72e89802643fb802b2701035c7ac1d..98d1d65329094a7a0bdfc795f18d3902f0979f3a 100644 --- a/Nynja/DatabaseManager.swift +++ b/Nynja/DatabaseManager.swift @@ -139,13 +139,6 @@ final class DatabaseManager: DBManagerProtocol { try values.forEach { (value) in try database.execute("insert into grdb_migrations (identifier) values('\(value)')") } - //let statement = "INSERT INTO \(tableName) (\(identifierColumn)) VALUES (\();" - -// let statements = values.map { -// return "INSERT INTO \(tableName) (\(identifierColumn)) VALUES (`\($0)`)" -// } -// -// try database.execute(statement, arguments: values) } } diff --git a/Nynja/Extensions/SwiftLibrary/String/String+Search.swift b/Nynja/Extensions/SwiftLibrary/String/String+Search.swift index 7e7afc53010f5b18e00b339122ce30956841d46d..b17ec6e7b225baf4930c85e1334c7948761d3093 100644 --- a/Nynja/Extensions/SwiftLibrary/String/String+Search.swift +++ b/Nynja/Extensions/SwiftLibrary/String/String+Search.swift @@ -7,26 +7,21 @@ // extension String { - func isIn(string: String, options: String.CompareOptions) -> Bool { - return isIn(strings: [string], options: options) + return string.contains(substring: self, options: options) } func isIn(strings: [String], options: String.CompareOptions) -> Bool { return strings.contains(substring: self, options: options) } + func contains(substring: String, options: String.CompareOptions) -> Bool { + return range(of: substring, options: options) != nil + } } - extension Array where Element == String { - func contains(substring: String, options: String.CompareOptions) -> Bool { - guard let _ = self.first(where: { $0.range(of: substring, options: options) != nil }) else { - return false - } - - return true + return contains { $0.contains(substring: substring, options: options) } } - } diff --git a/Nynja/Extensions/UIEdgeInsets+Adjust.swift b/Nynja/Extensions/UIEdgeInsets+Adjust.swift index f1b8ecc2df1ce6de796873fae245a7cb23228504..b507e9d7da9958f6d6d419971a52cce5dbf4885c 100644 --- a/Nynja/Extensions/UIEdgeInsets+Adjust.swift +++ b/Nynja/Extensions/UIEdgeInsets+Adjust.swift @@ -43,4 +43,7 @@ extension UIEdgeInsets { right: lhs.right + rhs.right) } + func flippedVertically() -> UIEdgeInsets { + return UIEdgeInsets(top: bottom, left: left, bottom: top, right: right) + } } diff --git a/Nynja/Extensions/VoxImplant/VICallExtension.swift b/Nynja/Extensions/VoxImplant/VICallExtension.swift deleted file mode 100644 index 09bc26899fd251e4b5d53e12f79ab5745fd38a7d..0000000000000000000000000000000000000000 --- a/Nynja/Extensions/VoxImplant/VICallExtension.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// VICallExtension.swift -// Nynja -// -// Created by Volodymyr Hryhoriev on 3/13/18. -// Copyright © 2018 TecSynt Solutions. All rights reserved. -// - -import VoxImplant - -extension VICall { - - var voxId: String? { - return self.endpoints?.first?.user - } - -} diff --git a/Nynja/HistoryRequestModelTypeProtocol.swift b/Nynja/HistoryRequestModelTypeProtocol.swift new file mode 100644 index 0000000000000000000000000000000000000000..99c88972c0d9cbac61555bba2e502b19744a6c9f --- /dev/null +++ b/Nynja/HistoryRequestModelTypeProtocol.swift @@ -0,0 +1,24 @@ +// +// HistoryRequestModelTypeProtocol.swift +// Nynja +// +// Created by Volodymyr Hryhoriev on 8/3/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +protocol HistoryRequestModelTypeProtocol { + var historyRequestModelType: HistoryRequestModel.RequestInput.HistoryType? { get } +} + +extension Contact: HistoryRequestModelTypeProtocol { + var historyRequestModelType: HistoryRequestModel.RequestInput.HistoryType? { + return phone_id.flatMap { .p2p(opponentId: $0) } + } +} + +extension Room: HistoryRequestModelTypeProtocol { + var historyRequestModelType: HistoryRequestModel.RequestInput.HistoryType? { + return id.flatMap { .muc(mucId: $0) } + } +} + diff --git a/Nynja/Library/Interfaces/NavigationProtocol.swift b/Nynja/Library/Interfaces/NavigationProtocol.swift index 46486c57346c70317d1d5089cc0e31b5d9787478..81a48066a8b63a590f3209a5718107802b6e5ac7 100644 --- a/Nynja/Library/Interfaces/NavigationProtocol.swift +++ b/Nynja/Library/Interfaces/NavigationProtocol.swift @@ -9,6 +9,6 @@ import Foundation -protocol NavigationProtocol { +protocol NavigationProtocol: class { func back() } diff --git a/Nynja/Library/UI/AlertManager.swift b/Nynja/Library/UI/AlertManager.swift index 0de42f44c05dc26547dc5059a2162ef2d5cdcdbd..52b3dab16cd5de2973c9fe343f2c0bf87e7a3f05 100644 --- a/Nynja/Library/UI/AlertManager.swift +++ b/Nynja/Library/UI/AlertManager.swift @@ -223,3 +223,12 @@ extension AlertManager { } } } + +//MARK: - TimeFieldViewController +extension AlertManager { + func showTimeFieldAlert(with initialValue: Int, completion: TimeCallback?) { + let alertControler = AlertTextFieldViewController(initialValue: initialValue) + alertControler.completion = completion + presentingController?.present(alertControler, animated: true, completion: nil) + } +} diff --git a/Nynja/Library/UI/ContextMenu/NynjaContextMenuItemsFactory+Messages.swift b/Nynja/Library/UI/ContextMenu/NynjaContextMenuItemsFactory+Messages.swift index 02f8934885012218515ed38b905a3135d54e388b..b9106e6717027989fb8d03aeff408a389e5c4c5b 100644 --- a/Nynja/Library/UI/ContextMenu/NynjaContextMenuItemsFactory+Messages.swift +++ b/Nynja/Library/UI/ContextMenu/NynjaContextMenuItemsFactory+Messages.swift @@ -180,7 +180,7 @@ extension NynjaContextMenuItemsFactory { } private static func audioCallMessageItems(for model: BaseChatCellModel) -> [ContextMenuRow] { - return [ ContextMenuRow(items: [marketPlace()])] + return [ ContextMenuRow(items: [marketplace()])] } private static func translatedMessageItems(for model: BaseChatCellModel) -> [ContextMenuRow] { @@ -256,8 +256,8 @@ extension NynjaContextMenuItemsFactory { layout: layout) } - static func marketPlace(with layout: ContextMenuItem.LayoutRepresentation = .default) -> ContextMenuItem { - return ContextMenuItem(content: .item(title: Strings.marketPlace.localized, icon: #imageLiteral(resourceName: "ic_marketplace_context_menu"), action: .marketPlace), + static func marketplace(with layout: ContextMenuItem.LayoutRepresentation = .double) -> ContextMenuItem { + return ContextMenuItem(content: .item(title: Strings.marketplace.localized, icon: #imageLiteral(resourceName: "ic_marketplace_context_menu"), action: .marketPlace), layout: layout) } @@ -362,7 +362,7 @@ private enum Strings: String { case share = "share" case saveToGallery = "save_to_gallery" case saveToDownloads = "save_to_downloads" - case marketPlace = "Market Place" + case marketplace = "marketplace" var localized: String { return rawValue.localized diff --git a/Nynja/Library/UI/Extensions/Localizable.swift b/Nynja/Library/UI/Extensions/Localizable.swift index 521411cb3417d8cf0c29b3fdff3b67b6f7c929f3..57dd6a27659e2319135f15bcf539867953cffbd2 100644 --- a/Nynja/Library/UI/Extensions/Localizable.swift +++ b/Nynja/Library/UI/Extensions/Localizable.swift @@ -26,6 +26,7 @@ enum Language: String { case chinese_simplified = "zh-Hans" case japanese = "ja" case korean = "ko" + case empty_default = "empty" static let allValues = [english, german, spanish, french, italian, portuguese, @@ -46,6 +47,12 @@ enum Language: String { } } + static var allValuesWithDefault: [Language] { + var languages = Language.allValues + languages.append(.empty_default) + return languages + } + var longValue : String { switch self { case .english: return "English" @@ -59,6 +66,7 @@ enum Language: String { case .chinese_simplified: return "Chinese Simplified (简体中文)" case .japanese: return "Japanese (日本語)" case .korean: return "Korean (한국어)" + case .empty_default: return "-" } } diff --git a/Nynja/Library/UI/ReturnToCallContentView.swift b/Nynja/Library/UI/ReturnToCallContentView.swift index 68e828e360b193103f53a01bc87fe09deb93c7a7..540dbb750acc07456d77e1f565d99b2b0a7dbea5 100644 --- a/Nynja/Library/UI/ReturnToCallContentView.swift +++ b/Nynja/Library/UI/ReturnToCallContentView.swift @@ -7,7 +7,6 @@ // import Foundation -import VoxImplant extension ReturnToCallContentView { struct Constraints { @@ -28,7 +27,6 @@ extension ReturnToCallContentView { class ReturnToCallContentView: UIView { var timer:Timer? - weak var call: VICall? weak var nynCall: NYNCall? @@ -77,40 +75,35 @@ class ReturnToCallContentView: UIView { }) return lbl }() - - func setup(call: VICall) { - self.call = call - self.startTimer() - self.backgroundColor = Constants.colors.greenForReturnToCallColor.getColor() - img.isHidden = false - hing.isHidden = false - } func setup(call: NYNCall) { self.nynCall = call - self.startTimer() +// self.startTimer() self.backgroundColor = Constants.colors.greenForReturnToCallColor.getColor() img.isHidden = false hing.isHidden = false } - func startTimer() { - timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(runTimedCode), userInfo: nil, repeats: true) - } - @objc func runTimedCode() { - if let call = self.call { - let durationInt = Int(call.duration()) - let minutes = durationInt / 60 - let seconds = durationInt % 60 - - if let status = call.callStatus { - if status == .ongoing { - self.time.text = String.localizedStringWithFormat("%02u:%02u", minutes,seconds) - } else { - self.time.text = status.rawValue - } - } - } - } + //TODO: ASK ANGEL + +// func startTimer() { +// timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(runTimedCode), userInfo: nil, repeats: true) +// } + +// @objc func runTimedCode() { +// if let call = self.call { +// let durationInt = Int(call.duration()) +// let minutes = durationInt / 60 +// let seconds = durationInt % 60 +// +// if let status = call.callStatus { +// if status == .ongoing { +// self.time.text = String.localizedStringWithFormat("%02u:%02u", minutes,seconds) +// } else { +// self.time.text = status.rawValue +// } +// } +// } +// } } diff --git a/Nynja/Library/UI/SeparatorView/SeparatorView.swift b/Nynja/Library/UI/SeparatorView/SeparatorView.swift index 22bc1591250badb3c6b62edd8906f5f380fa0687..4a620639ee9d9c930925ccfc5aca3f8954113053 100644 --- a/Nynja/Library/UI/SeparatorView/SeparatorView.swift +++ b/Nynja/Library/UI/SeparatorView/SeparatorView.swift @@ -36,6 +36,7 @@ final class SeparatorView: UIView { // MARK: - Setup private func setup() { + backgroundColor = color snp.makeConstraints { maker in maker.height.equalTo(SeparatorView.height) } diff --git a/Nynja/Library/UI/TextInput/InputBar/Extensions/InputBar+ContentType.swift b/Nynja/Library/UI/TextInput/InputBar/Extensions/InputBar+ContentType.swift index cf2101d0325e5f48f786d54500e5b3ff976686b0..bf773eafc6394d9ce3d2ca0e53e2cae0d7ea793b 100644 --- a/Nynja/Library/UI/TextInput/InputBar/Extensions/InputBar+ContentType.swift +++ b/Nynja/Library/UI/TextInput/InputBar/Extensions/InputBar+ContentType.swift @@ -41,6 +41,15 @@ extension InputBar { static func ==(lhs: ContentType, rhs: ContentType) -> Bool { return lhs.hashValue == rhs.hashValue } + + var shouldRemovePreviousContent: Bool { + switch self { + case .recording, .recordDisplay: + return false + default: + return true + } + } } } diff --git a/Nynja/Library/UI/TextInput/InputBar/InputBar.swift b/Nynja/Library/UI/TextInput/InputBar/InputBar.swift index 825a03733256ecffe0d87e634ee0919d6bbb6e5b..645d2669c104d4746f3b7ed516c3819026947804 100644 --- a/Nynja/Library/UI/TextInput/InputBar/InputBar.swift +++ b/Nynja/Library/UI/TextInput/InputBar/InputBar.swift @@ -86,7 +86,7 @@ final class InputBar: UIView { private var contentType: ContentType = .text(nil) { didSet { if oldValue != contentType { - setupContent(contentType) + setupContent(contentType, oldType: oldValue) setupTestingKeys() } else { updateContent(contentType) @@ -135,6 +135,8 @@ final class InputBar: UIView { return view }() + private var recordingContainer: RecordContainer? + private lazy var rightButton: ScheduleButton = { let width = Constraints.Main.RightButton.width let height = Constraints.Main.RightButton.height @@ -203,16 +205,39 @@ final class InputBar: UIView { backgroundColor = .clear setupButton(.voice) - setupContent(.text(nil)) + setupContent(.text(nil), oldType: .text(nil)) + setupTestingKeys() } // MARK: - Setup // MARK: - Content - private func setupContent(_ type: ContentType) { - inputContentContainer.subviews.removeFromSuperview() - + private func setupContent(_ type: ContentType, oldType: ContentType) { + //TODO: - Keep calm and don't read that shit + if type.shouldRemovePreviousContent { + if oldType.shouldRemovePreviousContent { + inputContentContainer.subviews.removeFromSuperview() + } else { + recordingContainer?.removeFromSuperview() + recordingContainer = nil + + if let textInput = inputContentContainer.subviews.first as? TextInputContent { + textInput.isHidden = false + textInput.delegate = self + textInput.textView.textViewDidChange() + inputContent = textInput + inputContent?.startInteraction() + return + } + } + } else { + if let textInput = inputContent as? TextInputContent { + textInput.isHidden = true + textInput.delegate = nil + } + } + guard let inputContentView = makeInputContentView(for: type) else { return } @@ -260,6 +285,28 @@ final class InputBar: UIView { return content } + private func makeRecordingContainer(_ view: InputContentView) -> RecordContainer { + let containerMaker: () -> RecordContainer = { + let container = RecordContainer() + container.content = view + return container + } + return recordingContainer ?? containerMaker() + } + + private func wrapRecordingContentToContainer(_ view: InputContentView) -> RecordContainer { + let container = makeRecordingContainer(view) + container.subviews.removeFromSuperview() + container.addSubview(view) + view.snp.makeConstraints { maker in + maker.edges.equalToSuperview() + } + + recordingContainer = container + + return container + } + private func makeRecordingContent() -> InputContentView? { guard let content = RecordingInputContent(recordingStatusHandler: { [weak self] status in self?.sendTypingHandler?(status) @@ -281,13 +328,7 @@ final class InputBar: UIView { return nil } - inputContentContainer.addSubview(content) - content.snp.makeConstraints { make in - make.edges.equalToSuperview() - } - inputContent = content - - return content + return wrapRecordingContentToContainer(content) } private func makeRecordDisplayContent(with url: URL) -> InputContentView { @@ -299,7 +340,7 @@ final class InputBar: UIView { self?.buttonType = .voice } - return content + return wrapRecordingContentToContainer(content) } private func makeNynjaAction(with info: DisplayMode.ActionInfo) -> InputContentView { @@ -622,12 +663,10 @@ final class InputBar: UIView { rightButton.backgroundColor = Constants.colors.red.getColor() rightButton.isEnabled = true } - } - - - // MARK: - ALTextInputBarDelegate +} - extension InputBar: ALTextInputBarDelegate { +//MARK: - ALTextInputBarDelegate +extension InputBar: ALTextInputBarDelegate { func textViewDidChange(textView: ALTextView) { if case .edit(let text) = displayMode { @@ -704,8 +743,7 @@ final class InputBar: UIView { } -// MARK: - Layout - +//MARK: - Layout extension InputBar { enum Constraints { enum Main { @@ -732,8 +770,7 @@ extension InputBar { } -// MARK: - Testable - +//MARK: - Testable extension InputBar: TestableViewProtocol { private enum Keys: String { case sendButton = "input_bar_send_button" @@ -753,4 +790,3 @@ extension InputBar: TestableViewProtocol { actionButton?.accessibilityIdentifier = Keys.actionButton.rawValue } } - diff --git a/Nynja/Library/UI/TextInput/InputBar/InputContent/RecordContainer.swift b/Nynja/Library/UI/TextInput/InputBar/InputContent/RecordContainer.swift new file mode 100644 index 0000000000000000000000000000000000000000..5e29e56cb3878abeb11d917a1a500bc4ed0fc76d --- /dev/null +++ b/Nynja/Library/UI/TextInput/InputBar/InputContent/RecordContainer.swift @@ -0,0 +1,20 @@ +// +// RecordContainer.swift +// Nynja +// +// Created by Andrey Reznik on 07.08.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +class RecordContainer: UIView, InputContentProtocol { + var content: InputContentProtocol? + + func startInteraction() { + content?.startInteraction() + } + func cancelInteraction() { + content?.cancelInteraction() + } +} diff --git a/Nynja/Library/UI/TextInput/Material/MaterialTextField.swift b/Nynja/Library/UI/TextInput/Material/MaterialTextField.swift index 21b13e4d40fdbbf693107244e7d6aa2418c03f41..22015ccb3985bc86e9ba103a70f85790d9a8f664 100644 --- a/Nynja/Library/UI/TextInput/Material/MaterialTextField.swift +++ b/Nynja/Library/UI/TextInput/Material/MaterialTextField.swift @@ -62,6 +62,10 @@ class MaterialTextField: MaterialTextContainer { textField.accessibilityIdentifier = "material_text_field" } + override func becomeFirstResponder() -> Bool { + return self.textField.becomeFirstResponder() + } + // MARK: - Actions diff --git a/Nynja/Library/UI/UITableViewCells/ChatSettingCell/AvatarCell.swift b/Nynja/Library/UI/UITableViewCells/ChatSettingCell/AvatarCell.swift index 62739e65facf191db38f6759f571c9cb41bf934b..dc89bfc4ec7d601eb5b497b7dd34a14b3b281800 100644 --- a/Nynja/Library/UI/UITableViewCells/ChatSettingCell/AvatarCell.swift +++ b/Nynja/Library/UI/UITableViewCells/ChatSettingCell/AvatarCell.swift @@ -114,7 +114,7 @@ class AvatarCell: BaseSettingCell, ConfigurableCell { // MARK: - Actions @objc private func avatarTapped(_ recognizer: UITapGestureRecognizer) { - delegate?.didTapSetting(.Avatar) + delegate?.didTapSetting(.Avatar(avatar)) } @objc private func nameGroupTapped(_ recognizer: UITapGestureRecognizer) { diff --git a/Nynja/Library/UI/UITableViewCells/ChatSettingCell/SettingViewModelProtocol.swift b/Nynja/Library/UI/UITableViewCells/ChatSettingCell/SettingViewModelProtocol.swift index d45ffc9bf11c7984aea4ace37add2e35a4fb0652..4e45469c806f6f5f42f74314f37321756802ad07 100644 --- a/Nynja/Library/UI/UITableViewCells/ChatSettingCell/SettingViewModelProtocol.swift +++ b/Nynja/Library/UI/UITableViewCells/ChatSettingCell/SettingViewModelProtocol.swift @@ -8,7 +8,7 @@ enum SettingViewModelType { - case Avatar + case Avatar(UIImageView) case Name case Alias case Rules diff --git a/Nynja/Library/UI/View/NavigationView/NavigationView.swift b/Nynja/Library/UI/View/NavigationView/NavigationView.swift index 8789e7a190ece44faaf1bc37016de242a50835fc..e2cc6a7c051ef137e14efc143645cbf47f0cd3cd 100644 --- a/Nynja/Library/UI/View/NavigationView/NavigationView.swift +++ b/Nynja/Library/UI/View/NavigationView/NavigationView.swift @@ -44,7 +44,7 @@ class NavigationView: BaseView, Configurable { } private var title: String? - private var navigationHandler: NavigationProtocol? + private weak var navigationHandler: NavigationProtocol? private var backButtonImage: UIImage? = UIImage.backButtonImage { didSet { backButton?.setImage(backButtonImage, for: .normal) } } diff --git a/Nynja/MQTTModels/MessageExtension+BERT.swift b/Nynja/MQTTModels/MessageExtension+BERT.swift index 484da32c76d6e44654885d5acac82244fad331ca..970f0ec6467201463f0c3d1b762fd3deb52f9d00 100644 --- a/Nynja/MQTTModels/MessageExtension+BERT.swift +++ b/Nynja/MQTTModels/MessageExtension+BERT.swift @@ -10,7 +10,7 @@ import Foundation extension Message { - func getBert(chain : String? = "chain") -> BertObject { + func getBert(chain: String? = "chain") -> BertObject { let topic = BertAtom(fromString: "Message") let _from = Bert.getBin(self.from) let _to = Bert.getBin(self.to) diff --git a/Nynja/MessageDAO.swift b/Nynja/MessageDAO.swift index 7ffbb77fd534b4e69d200b42a2bdfb6292d06b16..67b936cbb5b312e9a9a80f189c28c3b50f91f846 100644 --- a/Nynja/MessageDAO.swift +++ b/Nynja/MessageDAO.swift @@ -18,38 +18,44 @@ class MessageDAO: MessageDAOProtocol { // MARK: - Fetch // MARK: -- Message static func fetchMessage(by rowId: Int64) -> Message? { - guard let message = dbManager.fetch({ db in + return fetchMessage { db in return try DBMessage.message(from: db, rowId: rowId) - }) else { - return nil } - - return Message(message: message) } static func fetchMessage(primaryId: Int64) -> Message? { - guard let message = dbManager.fetch({ db in + return fetchMessage { db in return try DBMessage.message(db, id: primaryId) - }) else { - return nil } - - return Message(message: message) } static func fetchMessage(serverId: Int64) -> Message? { - guard let message = dbManager.fetch({ db in + return fetchMessage { db in return try DBMessage.message(db, serverId: serverId) - }) else { - return nil } - - return Message(message: message) } static func fetchMessage(localId: String) -> Message? { - guard let message = dbManager.fetch({ db in + return fetchMessage { db in return try DBMessage.message(db, localId: localId) + } + } + + static func fetchLastMessage(of type: FetchType) -> Message? { + return fetchMessage { db in + return try DBMessage.lastMessage(db, ofType: type) + } + } + + static func fetchPenultimateMessage(of type: FetchType) -> Message? { + return fetchMessage { db in + return try DBMessage.penultimateMessage(db, ofType: type) + } + } + + private static func fetchMessage(using closure: (Database) throws -> DBMessage?) -> Message? { + guard let message = dbManager.fetch({ db in + return try closure(db) }) else { return nil } @@ -125,48 +131,50 @@ class MessageDAO: MessageDAOProtocol { } // MARK: - Remove - static func removeMessage(message: Message, notify: ((_ message: Message) -> Void)? = nil) { - let msg = Message() - msg.id = message.link - msg.feed_id = message.feed_id - if message.feed_id as? p2p != nil { - if let ids = message.seenby as? [Int64] { - let res = ids.filter { (value) -> Bool in - return value == -1 - }.count - if res > 0 { - removeMessage(msg: msg) - notify?(msg) - return - } - } - if let ids = message.seenby as? [String], let phoneId = ids.first { - if phoneId == StorageService.sharedInstance.phoneId { - removeMessage(msg: msg) - notify?(msg) - return - } - } - return + + static func removeMessage(using message: Message) -> Bool { + guard let id = message.link, + let deletedMessage = fetchMessage(serverId: id) else { + return false + } + + let shouldMarkMessageAsDelete = self.shouldMarkMessageAsDelete(message) + if shouldMarkMessageAsDelete { + deletedMessage.status = StringAtom(string: "deleted") + } + deletedMessage.seenby = message.seenby + + do { + try dbManager.perform(action: .save, with: deletedMessage) + } catch { + return false + } + + return shouldMarkMessageAsDelete + } + + private static func shouldMarkMessageAsDelete(_ message: Message) -> Bool { + let stringIds = message.seenby as? [String] ?? [] + let intIds = message.seenby as? [Int64] ?? [] + + guard !stringIds.contains("-1"), + !intIds.contains(-1) else { + return true + } + + guard let phoneId = StorageService.sharedInstance.phoneId else { + assertionFailure("Check phoneId") + return false } - if let id = (message.feed_id as? muc)?.name { - if let ids = message.seenby as? [Int64] { - let res = ids.filter { (value) -> Bool in - return value == -1 - }.count - if res > 0 { - removeMessage(msg: msg) - notify?(msg) - return - } - if let isNeedRemove = RoomDAO.containsMembers(with: ids, roomId: id), isNeedRemove { - removeMessage(msg: msg) - notify?(msg) - return - } - } - return + + if let _ = message.p2pFeed { + return stringIds.contains(phoneId) + } else if let roomId = message.mucFeed?.name, + let _ = MemberDAO.findMemberBy(roomId: roomId, phoneId: phoneId) { + return true } + + return false } private static func findLocalId(serverId: Int64) -> String? { @@ -182,58 +190,6 @@ class MessageDAO: MessageDAOProtocol { } } - private static func removeMessage(msg: Message) { - var message = msg - message = updateLocalMessageId(for: message) - try? StorageService.sharedInstance.perform(action: .delete, with: message) - if let feed = msg.feed_id { - updateLastMessage(feed: feed) - } - } - - private static func updateLastMessage(feed: AnyObject) { - if let m = feed as? muc, let name = m.name { - guard let lastMessage = lastMessage(of: .muc(name: name)), - let room = RoomDAO.fetchRoom(by: name) else { - return - } - - room.messageId = lastMessage.id - - let action: DatabaseAction = .updateColumns([RoomTable.Column.messageId.title]) - try? StorageService.sharedInstance.perform(action: action, with: room) - } - if let p = feed as? p2p, let to = p.to, let fr = p.from { - guard let target = p.opponentId, - let lastMessage = lastMessage(of: .p2p(from: fr, to: to)), - let contact = ContactDAO.fetchContact(by: target) else { - return - } - - contact.messageId = lastMessage.id - - let action: DatabaseAction = .updateColumns([ContactTable.Column.messageId.title]) - try? StorageService.sharedInstance.perform(action: action, with: contact) - } - } - - private static func lastMessage(of fetchType: FetchType) -> DBMessage? { - return dbManager.fetch { db in - return try DBMessage.lastMessage(db, ofType: fetchType) - } - } - - // MARK: - Update - static func updateLocalMessageId(for message: Message) -> Message { - var localID: String? = nil - - if let id = message.id, let res = findLocalId(serverId: id) { - localID = res - } - - message.msg_id = localID - return message - } // MARK: - Helpers diff --git a/Nynja/MessageDAOProtocol.swift b/Nynja/MessageDAOProtocol.swift index 831db64c8b4abb7532d91541aab3cb90bbf3f969..0db692d17c78d116c0a38c8ad6929c4027dc5138 100644 --- a/Nynja/MessageDAOProtocol.swift +++ b/Nynja/MessageDAOProtocol.swift @@ -23,6 +23,9 @@ protocol MessageDAOProtocol: DAOProtocol { static func fetchMessage(serverId: Int64) -> Message? static func fetchMessage(localId: String) -> Message? + static func fetchLastMessage(of type: FetchType) -> Message? + static func fetchPenultimateMessage(of type: FetchType) -> Message? + // MARK: -- Unsent static func fetchUnsentMessages(from: String) -> [Message] @@ -40,9 +43,6 @@ protocol MessageDAOProtocol: DAOProtocol { static func clearHistory(_ type: FetchType) throws // MARK: - Remove - static func removeMessage(message: Message, notify: ((_ message: Message) -> Void)?) - - // MARK: - Update - static func updateLocalMessageId(for message: Message) -> Message + static func removeMessage(using message: Message) -> Bool } diff --git a/Nynja/MigrationManager.swift b/Nynja/MigrationManager.swift index 5e32d88dceff843ae6a9eb36b34419dbf7c59e6a..238d326c1fb3520c5bfbd1a4b43fcd60e879eee1 100644 --- a/Nynja/MigrationManager.swift +++ b/Nynja/MigrationManager.swift @@ -19,6 +19,8 @@ enum Migration: Int, Describable { case renameLinksToMessageLink case createLinkForRoom case renameChannelFeatureKeys + case updateMessageIdForeignKeyOnContactRoomTables + case primaryKeyMessageAction static var allTitles: [String] = { var i = 0 @@ -202,6 +204,33 @@ class MigrationManager { try self.replaceFeatureKey("SubscribersCount", newKey: .subscribersCount, db: db) try self.replaceFeatureKey("AdminsCount", newKey: .adminsCount, db: db) } + + migrator.registerMigration(Migration.updateMessageIdForeignKeyOnContactRoomTables.title) { db in + let rooms = try DBRoom.fetchAll(db) + let contacts = try DBContact.fetchAll(db) + let roomMembers = try DBRoomMember.fetchAll(db) + let links = try DBLink.fetchAll(db) + + try db.drop(table: LinkTable.name) + try db.drop(table: RoomTable.name) + try db.drop(table: ContactTable.name) + + try RoomTable.create(in: db) + try ContactTable.create(in: db) + try LinkTable.create(in: db) + + try rooms.forEach { try $0.save(db) } + try contacts.forEach { try $0.save(db) } + try links.forEach { try $0.save(db) } + try roomMembers.forEach { try $0.save(db) } + } + + migrator.registerMigration(Migration.primaryKeyMessageAction.title) { db in + let actions = try DBMessageAction.fetchAll(db) + try db.drop(table: MessageActionTable.name) + try MessageActionTable.create(in: db) + try actions.forEach { try $0.saveAggregate(db) } + } } private func recreateDescTable(_ db: Database, closure: ((DBDesc) -> Void)? = nil) throws { diff --git a/Nynja/Models/ChatModel.swift b/Nynja/Models/ChatModel.swift index 56cea723d1e835ac54eea8119d62165685b94ce1..bb50bb25c7291c109c018a972d4b9fbdc2b6670a 100644 --- a/Nynja/Models/ChatModel.swift +++ b/Nynja/Models/ChatModel.swift @@ -6,22 +6,4 @@ // Copyright © 2018 TecSynt Solutions. All rights reserved. // -protocol HistoryRequestModelTypeProtocol { - var historyRequestModelType: HistoryRequestModel.RequestInput.HistoryType? { get } -} - typealias ChatModel = BaseChatModel & DBModelConvertible & HistoryRequestModelTypeProtocol - -extension Contact: HistoryRequestModelTypeProtocol { - var historyRequestModelType: HistoryRequestModel.RequestInput.HistoryType? { - guard let phone_id = phone_id else { return nil } - return .p2p(opponentId: phone_id) - } -} - -extension Room: HistoryRequestModelTypeProtocol { - var historyRequestModelType: HistoryRequestModel.RequestInput.HistoryType? { - guard let id = id else { return nil } - return .muc(mucId: id) - } -} diff --git a/Nynja/Modules/AddParticipants/View/AddParticipantsViewController.swift b/Nynja/Modules/AddParticipants/View/AddParticipantsViewController.swift index 3fba25e6885c23418c7742effbd86f57b1d49c61..f130142853afa5af381e685a1a420accce18ad2d 100644 --- a/Nynja/Modules/AddParticipants/View/AddParticipantsViewController.swift +++ b/Nynja/Modules/AddParticipants/View/AddParticipantsViewController.swift @@ -61,7 +61,7 @@ class AddParticipantsViewController: BaseVC, AddParticipantsViewProtocol { return button }() - // MARK: Views + // MARK: - Views private lazy var avatarsView: UICollectionView = { let collView = UICollectionView(frame: CGRect.zero, collectionViewLayout: createLayout()) @@ -77,8 +77,20 @@ class AddParticipantsViewController: BaseVC, AddParticipantsViewProtocol { return collView }() + + private lazy var separatorView: SeparatorView = { + let separatorView = SeparatorView() + + view.addSubview(separatorView) + separatorView.snp.makeConstraints { maker in + maker.left.right.equalToSuperview() + maker.top.equalTo(avatarsView.snp.bottom).offset(Constraints.separator.topInset.adjustedByWidth) + } + + return separatorView + }() - lazy var tableView: UITableView = { + private lazy var tableView: UITableView = { let tblView = UITableView.default tblView.rowHeight = SelectorCell.Constraints.height @@ -86,11 +98,16 @@ class AddParticipantsViewController: BaseVC, AddParticipantsViewProtocol { tblView.sectionHeaderHeight = ParticipantsHeaderView.height tblView.estimatedSectionHeaderHeight = tblView.sectionHeaderHeight + tblView.contentInset = UIEdgeInsets(top: CGFloat(Constraints.tableView.topInset.adjustedByWidth), + left: 0, + bottom: 0, + right: 0) + self.view.addSubview(tblView) - tblView.snp.makeConstraints({ (make) in - make.top.equalTo(avatarsView.snp.bottom).offset(Constraints.tableView.topInset.adjustedByHeight) + tblView.snp.makeConstraints { (make) in + make.top.equalTo(separatorView.snp.bottom) make.left.right.equalTo(avatarsView) - }) + } return tblView }() @@ -154,17 +171,17 @@ class AddParticipantsViewController: BaseVC, AddParticipantsViewProtocol { title = "delete_participants".localized.uppercased() } else if presenter.participantsMode == .createGroupCall { title = "add_members_to_call".localized.uppercased() - backBtnImage = UIImage(named:"ic_close_clear") + backBtnImage = .closeImage navHandler = presenter self.selectAllButtonVisible = true } else if presenter.participantsMode == .updateGroupCall { title = "add_members_to_call".localized.uppercased() - backBtnImage = UIImage(named:"ic_close_clear") + backBtnImage = .closeImage navHandler = presenter self.selectAllButtonVisible = true } else if presenter.participantsMode == .createConferenceCall { title = "add_members_to_call".localized.uppercased() - backBtnImage = UIImage(named:"ic_close_clear") + backBtnImage = .closeImage navHandler = presenter self.selectAllButtonVisible = true } else if presenter.participantsMode == .admins { @@ -229,6 +246,10 @@ class AddParticipantsViewController: BaseVC, AddParticipantsViewProtocol { tableView.register(headerFooter: ParticipantsHeaderView.self) participantsDataSource = ParticipantsDataSource() + if self.presenter.participantsMode == .create { + participantsDataSource.selectedParticipantsDelegate = self + } + tableView.dataSource = participantsDataSource avatarsView.dataSource = participantsDataSource @@ -458,3 +479,9 @@ extension AddParticipantsViewController: TestableViewControllerProtocol { } } } + +extension AddParticipantsViewController: NotifySelectedParticipantsProtocol { + func notify(selectedParticipants count: Int) { + self.doneButton.isEnabled = count != 0 + } +} diff --git a/Nynja/Modules/AddParticipants/View/AddPaticipantsViewControllerLayout.swift b/Nynja/Modules/AddParticipants/View/AddPaticipantsViewControllerLayout.swift index d53d76df2e5ec122fe753666877265f8ddd52501..d452402adcd2a17d83bddf35afe1a1b0514d9343 100644 --- a/Nynja/Modules/AddParticipants/View/AddPaticipantsViewControllerLayout.swift +++ b/Nynja/Modules/AddParticipants/View/AddPaticipantsViewControllerLayout.swift @@ -8,24 +8,28 @@ extension AddParticipantsViewController { - struct Constraints { + enum Constraints { - struct avatarsView { + enum avatarsView { static let height: CGFloat = 44.0 static let topInset = 8.0 static let horizontalInset: CGFloat = 16.0 } - struct tableView { + enum tableView { static let topInset = 16.0 } - struct controlsContainerView { + enum separator { + static let topInset = 8.0 + } + + enum controlsContainerView { static let bottomInset: CGFloat = 28.0 } - struct doneButton { + enum doneButton { static let width: CGFloat = 82.0 static let height: CGFloat = 44.0 @@ -33,11 +37,10 @@ extension AddParticipantsViewController { static let contentRightInset: CGFloat = 16.0 } - struct SelectAllButton { + enum SelectAllButton { static let side = 44.0.adjustedByWidth static let leftInset = -2.0.adjustedByWidth static let rightInset = 6.0.adjustedByWidth } - } } diff --git a/Nynja/Modules/AddParticipants/View/TableView/ParticipantsDataSource.swift b/Nynja/Modules/AddParticipants/View/TableView/ParticipantsDataSource.swift index 7f91530f939c3617257d922ef7534093c6e1a99c..5f0f62d5e854c374936a6a052f0529ea1f96010e 100644 --- a/Nynja/Modules/AddParticipants/View/TableView/ParticipantsDataSource.swift +++ b/Nynja/Modules/AddParticipants/View/TableView/ParticipantsDataSource.swift @@ -5,8 +5,14 @@ // Created by Volodymyr Hryhoriev on 11/8/17. // +protocol NotifySelectedParticipantsProtocol: class { + func notify(selectedParticipants count: Int) +} + class ParticipantsDataSource: NSObject, UITableViewDataSource, UICollectionViewDataSource { + weak var selectedParticipantsDelegate: NotifySelectedParticipantsProtocol? + var groupedParticipants: GroupedParticipants = [:] { didSet { sortedLetters = Array(groupedParticipants.keys).sorted() @@ -15,7 +21,11 @@ class ParticipantsDataSource: NSObject, UITableViewDataSource, UICollectionViewD var sortedLetters: [String] = [] - var selectedParticipants: [Participant] = [] + var selectedParticipants: [Participant] = [] { + didSet { + self.selectedParticipantsDelegate?.notify(selectedParticipants: self.selectedParticipants.count) + } + } var emptySelectionText: String = "no_participant_selected".localized { didSet { diff --git a/Nynja/Modules/AssigningInterpreter/AssigningInterpreterInteractor.swift b/Nynja/Modules/AssigningInterpreter/AssigningInterpreterInteractor.swift new file mode 100644 index 0000000000000000000000000000000000000000..2f7d232476c403b7f02a20084c50803ea4b10116 --- /dev/null +++ b/Nynja/Modules/AssigningInterpreter/AssigningInterpreterInteractor.swift @@ -0,0 +1,24 @@ +// +// AssigningInterpreterInteractor.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class AssigningInterpreterInteractor: AssigningInterpreterInteractorInputProtocol { + + weak var presenter: AssigningInterpreterInteractorOutputProtocol! +} + +extension AssigningInterpreterInteractor: SetInjectable { + func inject(dependencies: AssigningInterpreterInteractor.Dependencies) { + self.presenter = dependencies.presenter + } + + struct Dependencies { + let presenter: AssigningInterpreterInteractorOutputProtocol + } +} diff --git a/Nynja/Modules/AssigningInterpreter/AssigningInterpreterPresenter.swift b/Nynja/Modules/AssigningInterpreter/AssigningInterpreterPresenter.swift new file mode 100644 index 0000000000000000000000000000000000000000..70094e9e995e6d89975344038eff68ccdfaa85fa --- /dev/null +++ b/Nynja/Modules/AssigningInterpreter/AssigningInterpreterPresenter.swift @@ -0,0 +1,34 @@ +// +// AssigningInterpreterPresenter.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class AssigningInterpreterPresenter: BasePresenter, AssigningInterpreterPresenterProtocol, AssigningInterpreterInteractorOutputProtocol { + + weak var view: AssigningInterpreterViewProtocol! + var interactor: AssigningInterpreterInteractorInputProtocol! + var wireFrame: AssigningInterpreterWireFrameProtocol! + + func showed() { + + } +} + +extension AssigningInterpreterPresenter: SetInjectable { + func inject(dependencies: AssigningInterpreterPresenter.Dependencies) { + self.view = dependencies.view + self.interactor = dependencies.interactor + self.wireFrame = dependencies.wireFrame + } + + struct Dependencies { + let view: AssigningInterpreterViewProtocol + let interactor: AssigningInterpreterInteractorInputProtocol + let wireFrame: AssigningInterpreterWireFrameProtocol + } +} diff --git a/Nynja/Modules/AssigningInterpreter/AssigningInterpreterProtocols.swift b/Nynja/Modules/AssigningInterpreter/AssigningInterpreterProtocols.swift new file mode 100644 index 0000000000000000000000000000000000000000..74cadbfa0cedfbb349c53a743d82332c55567101 --- /dev/null +++ b/Nynja/Modules/AssigningInterpreter/AssigningInterpreterProtocols.swift @@ -0,0 +1,57 @@ +// +// AssigningInterpreterProtocols.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +protocol AssigningInterpreterWireFrameProtocol: class { + + func presentAssigningInterpreter(navigation: UINavigationController) + + /** + * Add here your methods for communication PRESENTER -> WIREFRAME + */ + func dismiss() +} + +protocol AssigningInterpreterViewProtocol: class { + + var presenter: AssigningInterpreterPresenterProtocol! { get set } + + /** + * Add here your methods for communication PRESENTER -> VIEW + */ + +} + +protocol AssigningInterpreterPresenterProtocol: BasePresenterProtocol { + + var view: AssigningInterpreterViewProtocol! { get set } + var interactor: AssigningInterpreterInteractorInputProtocol! { get set } + var wireFrame: AssigningInterpreterWireFrameProtocol! { get set } + + /** + * Add here your methods for communication VIEW -> PRESENTER + */ + func dismiss() +} + +protocol AssigningInterpreterInteractorOutputProtocol: class { + + /** + * Add here your methods for communication INTERACTOR -> PRESENTER + */ +} + +protocol AssigningInterpreterInteractorInputProtocol: class { + + var presenter: AssigningInterpreterInteractorOutputProtocol! { get set } + + /** + * Add here your methods for communication PRESENTER -> INTERACTOR + */ +} diff --git a/Nynja/Modules/AssigningInterpreter/AssigningInterpreterViewController.swift b/Nynja/Modules/AssigningInterpreter/AssigningInterpreterViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..40fc10c209ad53350d543c8835222c8e89ddfa1e --- /dev/null +++ b/Nynja/Modules/AssigningInterpreter/AssigningInterpreterViewController.swift @@ -0,0 +1,45 @@ +// +// AssigningInterpreterViewController.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + +final class AssigningInterpreterViewController: BaseVC, AssigningInterpreterViewProtocol { + + var presenter: AssigningInterpreterPresenterProtocol! { + didSet { + _presenter = presenter + } + } + + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + presenter.showed() + } + + + // MARK: - UI Setup + + private func setupUI() { + } + +} + +extension AssigningInterpreterViewController: SetInjectable { + func inject(dependencies: AssigningInterpreterViewController.Dependencies) { + self.presenter = dependencies.presenter + } + + struct Dependencies { + let presenter: AssigningInterpreterPresenterProtocol + } +} diff --git a/Nynja/Modules/AssigningInterpreter/AssigningInterpreterWireFrame.swift b/Nynja/Modules/AssigningInterpreter/AssigningInterpreterWireFrame.swift new file mode 100644 index 0000000000000000000000000000000000000000..951c71b200bbecab3184da1633c94e3023c3f9bf --- /dev/null +++ b/Nynja/Modules/AssigningInterpreter/AssigningInterpreterWireFrame.swift @@ -0,0 +1,35 @@ +// +// AssigningInterpreterWireFrame.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +final class AssigningInterpreterWireFrame: AssigningInterpreterWireFrameProtocol { + + weak var navigation: UINavigationController? + + func presentAssigningInterpreter(navigation: UINavigationController) { + self.navigation = navigation + + let view = AssigningInterpreterViewController() + let presenter = AssigningInterpreterPresenter() + let interactor = AssigningInterpreterInteractor() + + // Connecting + let viewDependencies = AssigningInterpreterViewController.Dependencies(presenter: presenter) + view.inject(dependencies: viewDependencies) + let presenterDependencies = AssigningInterpreterPresenter.Dependencies(view: view, interactor: interactor, wireFrame: self) + presenter.inject(dependencies: presenterDependencies) + let interactorDependencies = AssigningInterpreterInteractor.Dependencies(presenter: presenter) + interactor.inject(dependencies: interactorDependencies) + + view.modalTransitionStyle = .crossDissolve + view.modalPresentationStyle = .overCurrentContext + navigation.pushViewController(view as UIViewController, animated: true) + } + +} diff --git a/Nynja/Modules/AssigningInterpreter/Interactor/AssigningInterpreterInteractor.swift b/Nynja/Modules/AssigningInterpreter/Interactor/AssigningInterpreterInteractor.swift new file mode 100644 index 0000000000000000000000000000000000000000..2f7d232476c403b7f02a20084c50803ea4b10116 --- /dev/null +++ b/Nynja/Modules/AssigningInterpreter/Interactor/AssigningInterpreterInteractor.swift @@ -0,0 +1,24 @@ +// +// AssigningInterpreterInteractor.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class AssigningInterpreterInteractor: AssigningInterpreterInteractorInputProtocol { + + weak var presenter: AssigningInterpreterInteractorOutputProtocol! +} + +extension AssigningInterpreterInteractor: SetInjectable { + func inject(dependencies: AssigningInterpreterInteractor.Dependencies) { + self.presenter = dependencies.presenter + } + + struct Dependencies { + let presenter: AssigningInterpreterInteractorOutputProtocol + } +} diff --git a/Nynja/Modules/AssigningInterpreter/Presenter/AssigningInterpreterPresenter.swift b/Nynja/Modules/AssigningInterpreter/Presenter/AssigningInterpreterPresenter.swift new file mode 100644 index 0000000000000000000000000000000000000000..2d589e246847b41800e62505e9a7b036500c7482 --- /dev/null +++ b/Nynja/Modules/AssigningInterpreter/Presenter/AssigningInterpreterPresenter.swift @@ -0,0 +1,33 @@ +// +// AssigningInterpreterPresenter.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class AssigningInterpreterPresenter: BasePresenter, AssigningInterpreterPresenterProtocol, AssigningInterpreterInteractorOutputProtocol { + weak var view: AssigningInterpreterViewProtocol! + var interactor: AssigningInterpreterInteractorInputProtocol! + var wireFrame: AssigningInterpreterWireFrameProtocol! + + func dismiss() { + self.wireFrame.dismiss() + } +} + +extension AssigningInterpreterPresenter: SetInjectable { + func inject(dependencies: AssigningInterpreterPresenter.Dependencies) { + self.view = dependencies.view + self.interactor = dependencies.interactor + self.wireFrame = dependencies.wireFrame + } + + struct Dependencies { + let view: AssigningInterpreterViewProtocol + let interactor: AssigningInterpreterInteractorInputProtocol + let wireFrame: AssigningInterpreterWireFrameProtocol + } +} diff --git a/Nynja/Modules/AssigningInterpreter/View/AsigningInterpreterLayout.swift b/Nynja/Modules/AssigningInterpreter/View/AsigningInterpreterLayout.swift new file mode 100644 index 0000000000000000000000000000000000000000..195b7817a2544ad71248fa55c3e82e2d325866d7 --- /dev/null +++ b/Nynja/Modules/AssigningInterpreter/View/AsigningInterpreterLayout.swift @@ -0,0 +1,33 @@ +// +// AsigningInterpreterLayout.swift +// Nynja +// +// Created by Roman Chopovenko on 8/3/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +extension AssigningInterpreterViewController { + + struct Constraints { + static let sidePadding = 32.adjustedByWidth + struct loadingView { + static let bottomPadding = 24.adjustedByWidth + } + } + + struct Texts { + struct bottomLabel { + static let height = CGFloat(33.adjustedByWidth) + static let font = UIFont(fontName: Constants.fonts.medium, height: height) + static let color = Constants.colors.red.getColor() + } + + struct descriptionLabel { + static let height = CGFloat(22.adjustedByWidth) + static let font = UIFont(fontName: Constants.fonts.medium, height: height) + static let color = Constants.colors.darkGray.getColor() + } + } +} diff --git a/Nynja/Modules/AssigningInterpreter/View/AssigningInterpreterViewController.swift b/Nynja/Modules/AssigningInterpreter/View/AssigningInterpreterViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..3ab8ed9644cd1b16a00d661d99749ea5af2c6358 --- /dev/null +++ b/Nynja/Modules/AssigningInterpreter/View/AssigningInterpreterViewController.swift @@ -0,0 +1,109 @@ +// +// AssigningInterpreterViewController.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + +final class AssigningInterpreterViewController: BaseVC, AssigningInterpreterViewProtocol { + + var presenter: AssigningInterpreterPresenterProtocol! { + didSet { + _presenter = presenter + } + } + + lazy var loadingView: CircleLoadingView = { + + let lineWidth: CGFloat = 8 + + let view = CircleLoadingView(frame: .zero, color: Constants.colors.red.getColor(), lineWidth: lineWidth) + view.backgroundColor = .clear + + self.view.addSubview(view) + + view.snp.makeConstraints({ (make) in + make.width.height.equalTo(self.view.snp.width).dividedBy(3) + make.centerX.equalToSuperview() + make.bottom.equalTo(self.view.snp.centerY) + make.bottom.equalTo(self.descriptionLabel.snp.top).offset(-Constraints.loadingView.bottomPadding) + }) + + return view + }() + + lazy var descriptionLabel: UILabel = { + let label = UILabel() + label.numberOfLines = 0 + label.textAlignment = .center + label.text = "asigning_interpreter_description".localized + label.font = Texts.descriptionLabel.font + label.textColor = Texts.descriptionLabel.color + + self.view.addSubview(label) + + label.snp.makeConstraints({ (make) in + make.left.equalToSuperview().offset(Constraints.sidePadding) + make.right.equalToSuperview().offset(-Constraints.sidePadding) + }) + return label + }() + + lazy var bottomLabel: UILabel = { + let label = UILabel() + label.text = "coming_soon".localized.uppercased() + label.font = Texts.bottomLabel.font + label.textColor = Texts.bottomLabel.color + + self.view.addSubview(label) + + label.snp.makeConstraints({ (make) in + make.bottom.equalToSuperview().offset(-Constraints.sidePadding) + make.centerX.equalToSuperview() + }) + return label + }() + + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + } + + + // MARK: - UI Setup + + private func setupUI() { + self.navigationView.configure(config: NavigationView.Config( + isVisibleSeparator: navigationView.isSeparatorVisible ?? false, + isVisibleBackButton: true, + title: title, + navigationHandler: self, + backButtonImage: UIImage(named:"ic_back_navigation"))) + self.screenTitle = "assigning_interpreter".localized.uppercased() + self.loadingView.isHidden = false + self.bottomLabel.isHidden = false + } +} + +extension AssigningInterpreterViewController: NavigationProtocol { + func back() { + self.presenter.dismiss() + } +} + +extension AssigningInterpreterViewController: SetInjectable { + func inject(dependencies: AssigningInterpreterViewController.Dependencies) { + self.presenter = dependencies.presenter + } + + struct Dependencies { + let presenter: AssigningInterpreterPresenterProtocol + } +} diff --git a/Nynja/Modules/AssigningInterpreter/View/CircleLoadingView/CircleLoadingView.swift b/Nynja/Modules/AssigningInterpreter/View/CircleLoadingView/CircleLoadingView.swift new file mode 100644 index 0000000000000000000000000000000000000000..43c328950c990f107f34120dab397a7efa654d82 --- /dev/null +++ b/Nynja/Modules/AssigningInterpreter/View/CircleLoadingView/CircleLoadingView.swift @@ -0,0 +1,104 @@ +// +// CircleLoadingView.swift +// Nynja +// +// Created by Roman Chopovenko on 7/29/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +class CircleLoadingView: UIView { + + private var color: UIColor = .red + private var lineWidth: CGFloat = 0 + + private var leftView: CircleView! + private var centerView: CircleView! + private var rightView: CircleView! + + //MARK: - Init + + required init(frame: CGRect, color: UIColor, lineWidth: CGFloat) { + super.init(frame: frame) + self.color = color + self.lineWidth = lineWidth + self.backgroundColor = .clear + self.addNotifications() + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + NotificationCenter.default.removeObserver(self) + } + + //MARK: - Override + + override func layoutSubviews() { + super.layoutSubviews() + + for subview in self.subviews { + subview.removeFromSuperview() + } + + let rect = self.bounds + let outer = CircleView(frame: rect, color: self.color, lineWidth: self.lineWidth) + self.addSubview(outer) + + let innerSide: CGFloat = 1.5 * self.lineWidth + let offset: CGFloat = 1.5 * innerSide + + let dx = rect.width/2 - innerSide/2 + let dy = rect.height/2 - innerSide/2 + + let centerRect = CGRect(x: dx, y: dy, width: innerSide, height: innerSide) + let center = CircleView(frame: centerRect, color: color) + self.centerView = center + self.addSubview(center) + + let leftRect = CGRect(x: dx - offset, y: dy, width: innerSide, height: innerSide) + let left = CircleView(frame: leftRect, color: color) + self.leftView = left + self.addSubview(left) + + let rightRect = CGRect(x: dx + offset, y: dy, width: innerSide, height: innerSide) + let right = CircleView(frame: rightRect, color: color) + self.rightView = right + self.addSubview(right) + + self.setupAnimations() + } + + //MARK: - Private methods + + private func setupAnimations() { + let duration = 0.6 + let delay = duration / 3 + + self.animateView(view: self.leftView, duration: duration, delay: 0) + + self.animateView(view: self.centerView, duration: duration, delay: delay) + + self.animateView(view: self.rightView, duration: duration, delay: delay * 2) + } + + private func animateView(view: UIView, duration: Double, delay: Double){ + UIView.animate(withDuration: duration, delay: delay, options: [.curveEaseInOut, .repeat, .autoreverse], animations: { + view.transform = CGAffineTransform(scaleX: 0.2, y: 0.2) + view.alpha = 0.3 + }) + } + + private func addNotifications() { + NotificationCenter.default.addObserver(self, selector: #selector(self.applicationDidBecomeActive), name: Notification.Name.UIApplicationDidBecomeActive, object: nil) + } + + @objc + private func applicationDidBecomeActive() { + self.setNeedsLayout() + self.layoutIfNeeded() + } +} diff --git a/Nynja/Modules/AssigningInterpreter/View/CircleLoadingView/CircleView.swift b/Nynja/Modules/AssigningInterpreter/View/CircleLoadingView/CircleView.swift new file mode 100644 index 0000000000000000000000000000000000000000..03ecd78ad3a968d191351fe89c2bd73bd1f1b6a1 --- /dev/null +++ b/Nynja/Modules/AssigningInterpreter/View/CircleLoadingView/CircleView.swift @@ -0,0 +1,44 @@ +// +// CircleView.swift +// Nynja +// +// Created by Roman Chopovenko on 7/29/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +class CircleView: UIView { + + private var color: UIColor = .red + private var lineWidth: CGFloat? + + required init(frame: CGRect, color: UIColor, lineWidth: CGFloat? = nil) { + super.init(frame: frame) + self.color = color + self.lineWidth = lineWidth + self.backgroundColor = .clear + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func draw(_ rect: CGRect) { + let center = CGPoint(x: rect.width/2,y: rect.height/2) + + let circlePath = UIBezierPath(arcCenter: center, radius: rect.width/2 - (self.lineWidth ?? 0), startAngle: CGFloat(0), endAngle:CGFloat(Double.pi * 2), clockwise: true) + + let shapeLayer = CAShapeLayer() + shapeLayer.path = circlePath.cgPath + + if let lineWidth = lineWidth { + shapeLayer.fillColor = UIColor.clear.cgColor + shapeLayer.strokeColor = color.cgColor + shapeLayer.lineWidth = lineWidth + } else { + shapeLayer.fillColor = color.cgColor + } + layer.addSublayer(shapeLayer) + } +} diff --git a/Nynja/Modules/AssigningInterpreter/Wireframe/AssigningInterpreterWireFrame.swift b/Nynja/Modules/AssigningInterpreter/Wireframe/AssigningInterpreterWireFrame.swift new file mode 100644 index 0000000000000000000000000000000000000000..7b826285989c96553abd4481d412450a08fa58c6 --- /dev/null +++ b/Nynja/Modules/AssigningInterpreter/Wireframe/AssigningInterpreterWireFrame.swift @@ -0,0 +1,39 @@ +// +// AssigningInterpreterWireFrame.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +final class AssigningInterpreterWireFrame: AssigningInterpreterWireFrameProtocol { + + weak var navigation: UINavigationController? + + func presentAssigningInterpreter(navigation: UINavigationController) { + self.navigation = navigation + + let view = AssigningInterpreterViewController() + let presenter = AssigningInterpreterPresenter() + let interactor = AssigningInterpreterInteractor() + + // Connecting + let viewDependencies = AssigningInterpreterViewController.Dependencies.init(presenter: presenter) + view.inject(dependencies: viewDependencies) + let presenterDependencies = AssigningInterpreterPresenter.Dependencies.init(view: view, interactor: interactor, wireFrame: self) + presenter.inject(dependencies: presenterDependencies) + let interactorDependencies = AssigningInterpreterInteractor.Dependencies.init(presenter: presenter) + interactor.inject(dependencies: interactorDependencies) + + view.modalTransitionStyle = .crossDissolve + view.modalPresentationStyle = .overCurrentContext + navigation.pushViewController(view as UIViewController, animated: true) + } + + func dismiss() { + self.navigation?.popViewController(animated: true) + } + +} diff --git a/Nynja/Modules/Call/CallInProgressProtocols.swift b/Nynja/Modules/Call/CallInProgressProtocols.swift index a3e92e39ce5d269143c10e41b2d4b1182925191e..28a07f4623858627335ef45f959b0a88cafb5f20 100644 --- a/Nynja/Modules/Call/CallInProgressProtocols.swift +++ b/Nynja/Modules/Call/CallInProgressProtocols.swift @@ -7,11 +7,10 @@ // import UIKit -import VoxImplant protocol CallInProgressWireFrameProtocol: class { - func presentCall(navigation: UINavigationController, callInProgressMode: CallInProgressMode, contact: Contact, call: VICall?, main: MainWireFrame?) + func presentCall(navigation: UINavigationController, callInProgressMode: CallInProgressMode, contact: Contact, main: MainWireFrame?) /** * Add here your methods for communication PRESENTER -> WIREFRAME @@ -56,7 +55,6 @@ protocol CallInProgressPresenterProtocol: class { */ func willShow() - func setupViews(myView: UIView, remoteView: UIView) func acceptCall(withVideo: Bool) func declineCall() @@ -67,7 +65,6 @@ protocol CallInProgressPresenterProtocol: class { func toggleMicrophone() func isMuted()->Bool func switchCamera() - func disableVideo() func viewShowed() func rejectCall() func updateCallParticipants() @@ -102,8 +99,6 @@ protocol CallInProgressInteractorOutputProtocol: class { protocol CallInProgressInteractorInputProtocol: class { var presenter: CallInProgressInteractorOutputProtocol! { get set } - var call: VICall? { get set } - var contact: Contact? { get } var room: Room? { get } /** @@ -117,9 +112,6 @@ protocol CallInProgressInteractorInputProtocol: class { func toggleMicrophone() func isMuted()->Bool func switchCamera() - func setupViews(myView: UIView, remoteView: UIView) - func disableVideo() - func setupDelegate() func rejectCall() func updateGroupCall(contacts: [Contact]) func removeCallMember(memberId: String) diff --git a/Nynja/Modules/Call/CallScreens/CallInProgress/Interactor/CallInProgressInteractor.swift b/Nynja/Modules/Call/CallScreens/CallInProgress/Interactor/CallInProgressInteractor.swift index f3a59004d2112ef44b20b04dd75e65325bb17a93..31ec6174bfe454a77382660e4742f6837fe6b8df 100644 --- a/Nynja/Modules/Call/CallScreens/CallInProgress/Interactor/CallInProgressInteractor.swift +++ b/Nynja/Modules/Call/CallScreens/CallInProgress/Interactor/CallInProgressInteractor.swift @@ -6,22 +6,18 @@ // Copyright © 2018 TecSynt Solutions. All rights reserved. // -import VoxImplant -class CallInProgressInteractor: CallInProgressInteractorInputProtocol, VoxServiceDelegate, NynjaCallDelegate { +class CallInProgressInteractor: CallInProgressInteractorInputProtocol, NynjaCallDelegate { weak var presenter: CallInProgressInteractorOutputProtocol! - weak var call: VICall? - var vox = VoxService.sharedInstance var callService = NynjaCommunicatorService.sharedInstance var timer: Timer? - - var contact: Contact? { - guard let voxId = call?.voxId else { return nil } - return ContactDAO.findContactBy(voxId: voxId) - } + init() { + callService.callDelegate = self + } + var room: Room? { guard let roomId = nynCall?.externalInfo else { return nil } return RoomDAO.findRoom(by: roomId) @@ -58,18 +54,10 @@ class CallInProgressInteractor: CallInProgressInteractorInputProtocol, VoxServic return service }() - - func setupDelegate() { - vox.delegate = self - callService.callDelegate = self - } func acceptCall(withVideo: Bool) { - if let id = call { - vox.answer(call: id, withVideo: withVideo) - } else if let ncall = self.nynCall { - callService.acceptConference(call: ncall) - } + guard let ncall = self.nynCall else { return } + callService.acceptConference(call: ncall) } func rejectCall() { @@ -135,36 +123,17 @@ class CallInProgressInteractor: CallInProgressInteractorInputProtocol, VoxServic } } - func disableVideo() { - if let id = call { - vox.disableVideo(call: id) - if id.duration() > 0 { - self.presenter.callConnected(withVideo: false) - } else { - self.presenter.setRingingWithoutVideo() - } - vox.withVideo = false - } - } func startRinging() { presenter.setRingingStatus() } func microphoneAction() { - if let id = call { - vox.switchMicrophone(call: id) - } - if let nc = self.nynCall { - nc.toggleMicrophone() - } + nynCall?.toggleMicrophone() } func toggleMicrophone() { - - if let nc = self.nynCall { - nc.toggleMicrophone() - } + nynCall?.toggleMicrophone() } func isMuted()->Bool { @@ -187,45 +156,20 @@ class CallInProgressInteractor: CallInProgressInteractorInputProtocol, VoxServic presenter.remoteVideoStreamStopped() } - func callClosed(call: VICall, isError: Bool) { - self.presenter.callClosed() - } - - func callConnected(call: VICall,withVideo: Bool) { - self.presenter.callConnected(withVideo: withVideo) - self.startTimer() - } - - func setupViews(myView: UIView, remoteView: UIView) { - vox.remoteView = remoteView - vox.myView = myView - } - func startTimer() { timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(runTimedCode), userInfo: nil, repeats: true) } @objc func runTimedCode() { - if let call = self.call { - let durationInt = Int(call.duration()) - let minutes = durationInt / 60 - let seconds = durationInt % 60 - - let text = String.localizedStringWithFormat("%u:%02u", minutes,seconds) - if self.presenter != nil { - self.presenter.updateTime(text:text) - } - } else if nil != self.nynCall { - - duration += 1 - let durationInt = Int(duration) - let minutes = durationInt / 60 - let seconds = durationInt % 60 - - let text = String.localizedStringWithFormat("%u:%02u", minutes,seconds) - if self.presenter != nil { - self.presenter.updateTime(text:text) - } + guard self.nynCall != nil else { return } + duration += 1 + let durationInt = Int(duration) + let minutes = durationInt / 60 + let seconds = durationInt % 60 + + let text = String.localizedStringWithFormat("%u:%02u", minutes,seconds) + if self.presenter != nil { + self.presenter.updateTime(text:text) } } diff --git a/Nynja/Modules/Call/CallScreens/CallInProgress/Presenter/CallInProgressPresenter.swift b/Nynja/Modules/Call/CallScreens/CallInProgress/Presenter/CallInProgressPresenter.swift index a0ff75729a7c30449ecd2951178f68f3a91916e6..8f9b6b1d1fa3891aa39b7bfd2e7135f7ffa2f6f5 100644 --- a/Nynja/Modules/Call/CallScreens/CallInProgress/Presenter/CallInProgressPresenter.swift +++ b/Nynja/Modules/Call/CallScreens/CallInProgress/Presenter/CallInProgressPresenter.swift @@ -21,9 +21,6 @@ class CallInProgressPresenter: CallInProgressPresenterProtocol, CallInProgressIn interactor.acceptCall(withVideo: withVideo) } - func disableVideo() { - interactor.disableVideo() - } func declineCall() { interactor.declineCall() @@ -150,9 +147,6 @@ class CallInProgressPresenter: CallInProgressPresenterProtocol, CallInProgressIn self.view.setupUI() } - func setupViews(myView: UIView, remoteView: UIView) { - self.interactor.setupViews(myView: myView, remoteView: remoteView) - } func updateTime(text: String) { self.view.updateTime(text: text) diff --git a/Nynja/Modules/Call/CallScreens/CallInProgress/WireFrame/CallInProgressWireframe.swift b/Nynja/Modules/Call/CallScreens/CallInProgress/WireFrame/CallInProgressWireframe.swift index 53c92b659014f6f527b1b19b12f71b146a5522f8..d1303d33bdf9abcd267c17691eb0c05baa722e8b 100644 --- a/Nynja/Modules/Call/CallScreens/CallInProgress/WireFrame/CallInProgressWireframe.swift +++ b/Nynja/Modules/Call/CallScreens/CallInProgress/WireFrame/CallInProgressWireframe.swift @@ -7,18 +7,16 @@ // import UIKit -import VoxImplant class CallInProgressWireframe: CallInProgressWireFrameProtocol { weak var navigation : UINavigationController? weak var mainWF: MainWireFrame? - weak var call: VICall? weak var view: CallInProgressViewProtocol! weak var nynCall: NYNCall? weak var external: EditParticipantsDelegate? - func presentCall(navigation: UINavigationController, callInProgressMode: CallInProgressMode, contact: Contact, call: VICall?, main: MainWireFrame?) { + func presentCall(navigation: UINavigationController, callInProgressMode: CallInProgressMode, contact: Contact, main: MainWireFrame?) { self.navigation = navigation self.mainWF = main @@ -26,9 +24,6 @@ class CallInProgressWireframe: CallInProgressWireFrameProtocol { let view = CallInProgressViewController() let presenter = CallInProgressPresenter() let interactor = CallInProgressInteractor() - - interactor.call = call - view.callInProgressMode = callInProgressMode self.view = view @@ -45,7 +40,6 @@ class CallInProgressWireframe: CallInProgressWireFrameProtocol { interactor.presenter = presenter navigation.pushViewController(view as UIViewController, animated: true) - interactor.setupDelegate() } func presentDialInCall(navigation: UINavigationController, callInProgressMode: CallInProgressMode, contact: Contact?, call: NYNCall? = nil, main: MainWireFrame?) { @@ -72,7 +66,6 @@ class CallInProgressWireframe: CallInProgressWireFrameProtocol { interactor.presenter = presenter navigation.pushViewController(view as UIViewController, animated: true) - interactor.setupDelegate() } func presentCreateGroupCall(navigation: UINavigationController, callInProgressMode: CallInProgressMode, main: MainWireFrame?, call: NYNCall) { @@ -102,7 +95,6 @@ class CallInProgressWireframe: CallInProgressWireFrameProtocol { interactor.presenter = presenter navigation.pushViewController(view as UIViewController, animated: true) - interactor.setupDelegate() } func messageActionWith(room: Room, isVideo: Bool) { diff --git a/Nynja/Modules/Channel/NewChannel/Interactor/NewChannelInteractor.swift b/Nynja/Modules/Channel/NewChannel/Interactor/NewChannelInteractor.swift index 55f7c177e2952ce202d00318f1486e5122aa5195..a0d5d7a8859b69428c25e7e9fe176cb77aa89c67 100644 --- a/Nynja/Modules/Channel/NewChannel/Interactor/NewChannelInteractor.swift +++ b/Nynja/Modules/Channel/NewChannel/Interactor/NewChannelInteractor.swift @@ -18,6 +18,7 @@ final class NewChannelInteractor: BaseInteractor, NewChannelInteractorInputProto weak var presenter: NewChannelInteractorOutputProtocol! + private var createChannelAction: (() -> Void)? // MARK: - Dependencies @@ -105,7 +106,12 @@ final class NewChannelInteractor: BaseInteractor, NewChannelInteractorInputProto private func sendCreateChannel(avatarUrl: URL?, info: CreateChannelInfo) { let room = makeRoom(with: avatarUrl, info: info) - mqttService.createRoom(room, kind: .channel) + if mqttService.isConnectedSuccess { + mqttService.createRoom(room, kind: .channel) + } + createChannelAction = { [weak self] in + self?.mqttService.createRoom(room, kind: .channel) + } } private func makeRoom(with avatarUrl: URL?, info: CreateChannelInfo) -> Room { @@ -139,15 +145,16 @@ final class NewChannelInteractor: BaseInteractor, NewChannelInteractorInputProto // MARK: - StorageSubscriber override func update(with changes: [StorageChange], type: SubscribeType) { - if case .room = type, + guard case .room = type, let change = changes.first, change.kind == .insert, let dbRoom = change.entity as? DBRoom, - dbRoom.id == localId { - - let room = Room(room: dbRoom) - presenter?.channelCreated(room) + dbRoom.id == localId else { + return } + createChannelAction = nil + let room = Room(room: dbRoom) + presenter?.channelCreated(room) } } @@ -173,7 +180,6 @@ extension NewChannelInteractor { private func isAbleToHandleLink(_ link: Link) -> Bool { return link.room_id == localId } - } @@ -182,9 +188,18 @@ extension NewChannelInteractor { extension NewChannelInteractor { func didConnect(_ mqttService: MQTTService) { - presenter.didConnectToServer() + presenter?.didConnectToServer() + + guard let action = createChannelAction else { + return + } + presenter?.showHUD() + action() } + func didDisconnect(_ mqttService: MQTTService) { + presenter?.hideHUD() + } } diff --git a/Nynja/Modules/Channel/NewChannel/NewChannelProtocols.swift b/Nynja/Modules/Channel/NewChannel/NewChannelProtocols.swift index f722ad10d06f31910c050449e976e62f4a5d8eb1..81fe468023334be70192c87a757d1750f891e2df 100644 --- a/Nynja/Modules/Channel/NewChannel/NewChannelProtocols.swift +++ b/Nynja/Modules/Channel/NewChannel/NewChannelProtocols.swift @@ -82,6 +82,8 @@ protocol NewChannelInteractorOutputProtocol: class { func checkedLinkIsAvailable(_ link: String) func somethingWrongWithLink(_ link: String, code: StatusCode) + func showHUD() + func hideHUD() func didConnectToServer() } diff --git a/Nynja/Modules/Channel/NewChannel/Presenter/NewChannelPresenter.swift b/Nynja/Modules/Channel/NewChannel/Presenter/NewChannelPresenter.swift index 4b5bfe6aad3cd1f9ab5b4e79081565af2d3abd11..04dc2f264385b4894d5bb750e2d4b7dfa291ad3d 100644 --- a/Nynja/Modules/Channel/NewChannel/Presenter/NewChannelPresenter.swift +++ b/Nynja/Modules/Channel/NewChannel/Presenter/NewChannelPresenter.swift @@ -232,6 +232,14 @@ final class NewChannelPresenter: BasePresenter, NewChannelPresenterProtocol, New } } + func showHUD() { + view.shouldShowSpinner = true + } + + func hideHUD() { + view.shouldShowSpinner = false + } + func didConnectToServer() { if shouldShowGeneratedLink { interactor.generateLink() diff --git a/Nynja/Modules/Channel/SubscribersSelector/View/SubscribersSelectorViewController.swift b/Nynja/Modules/Channel/SubscribersSelector/View/SubscribersSelectorViewController.swift index 3922e10072f3bea9eb0a3db777e9fe8bdb79cbe1..4d5c17aa34faed4a8cf7c61c19668dee66cb1d43 100644 --- a/Nynja/Modules/Channel/SubscribersSelector/View/SubscribersSelectorViewController.swift +++ b/Nynja/Modules/Channel/SubscribersSelector/View/SubscribersSelectorViewController.swift @@ -67,14 +67,10 @@ final class SubscribersSelectorViewController: BaseVC, SubscribersSelectorViewPr }() private lazy var separator: UIView = { - let separator = UIView() - - separator.backgroundColor = Constants.colors.separatorGrayColor.getColor() + let separator = SeparatorView() self.view.addSubview(separator) separator.snp.makeConstraints { make in - make.height.equalTo(Constraints.Separator.height) - make.top.equalTo(avatarsView.snp.bottom).offset(Constraints.Separator.topInset) make.left.right.equalToSuperview() } @@ -391,7 +387,6 @@ extension SubscribersSelectorViewController { } enum Separator { - static let height = 1.0 static let topInset = 8.0.adjustedByWidth } diff --git a/Nynja/Modules/CreateGroup/CreateGroupProtocols.swift b/Nynja/Modules/CreateGroup/CreateGroupProtocols.swift index 7694d0428359fbf4b8952c3926c9cf006ac18197..0ce0d54b621b4ffd4a95f0dedbedd54d1a731f92 100644 --- a/Nynja/Modules/CreateGroup/CreateGroupProtocols.swift +++ b/Nynja/Modules/CreateGroup/CreateGroupProtocols.swift @@ -59,6 +59,8 @@ protocol CreateGroupInteractorOutputProtocol: class { /** * Add here your methods for communication INTERACTOR -> PRESENTER */ + func showHUD() + func hideHUD() func created(room: Room) } diff --git a/Nynja/Modules/CreateGroup/Interactor/CreateGroupInteractor.swift b/Nynja/Modules/CreateGroup/Interactor/CreateGroupInteractor.swift index 4631716e730d9e1c1cb4035771d5cdfdecc28fd5..ef479e969f9a5f65bbb5db298a385e4291a23702 100644 --- a/Nynja/Modules/CreateGroup/Interactor/CreateGroupInteractor.swift +++ b/Nynja/Modules/CreateGroup/Interactor/CreateGroupInteractor.swift @@ -17,25 +17,44 @@ class CreateGroupInteractor: BaseInteractor, CreateGroupInteractorInputProtocol return ContactDAO.currentContact } + private let mqttService = MQTTService.sharedInstance + private var avatarUrl: URL? private let localId = IdBuilder(format: .defaultId).build() + private var sendRoomAction: (() -> Void)? + + override init() { + super.init() + mqttService.addSubscriber(self) + } + + deinit { + mqttService.removeSubscriber(self) + } func createRoom(name: String, avatar: UIImage?, members: [Member]) { - if let url = self.avatarUrl { - let sync = SyncFileManager.sharedInstance - sync.downloader = AmazonManager.shared - SyncFileManager.sharedInstance.saveExternalFileLink(localUrl: url.path) { (ext, progress, request) in - if let extUrl = ext { - self.sendRoom(with: name, avatar: String(describing: extUrl), members: members) - } - } - } else { + guard let url = self.avatarUrl else { sendRoom(with: name, avatar: nil, members: members) + return + } + let sync = SyncFileManager.sharedInstance + sync.downloader = AmazonManager.shared + SyncFileManager.sharedInstance.saveExternalFileLink(localUrl: url.path) { (ext, progress, request) in + guard let extUrl = ext else { + return + } + self.sendRoom(with: name, avatar: String(describing: extUrl), members: members) } } func sendRoom(with name: String, avatar: String?, members: [Member]) { - MQTTService.sharedInstance.createRoom(id: localId, name: name, avatar: avatar, members: members) + if mqttService.isConnectedSuccess { + mqttService.createRoom(id: localId, name: name, avatar: avatar, members: members) + } + let roomId = localId + sendRoomAction = { [weak self] in + self?.mqttService.createRoom(id: roomId, name: name, avatar: avatar, members: members) + } } func avatarChanged(with url: URL) { @@ -44,11 +63,26 @@ class CreateGroupInteractor: BaseInteractor, CreateGroupInteractorInputProtocol // MARK: - StorageSubscriber override func update(with changes: [StorageChange], type: SubscribeType) { - if case .room = type, let dbRoom = changes.first?.entity as? DBRoom, dbRoom.id == localId { - let room = Room(room: dbRoom) - presenter?.created(room: room) + guard case .room = type, let dbRoom = changes.first?.entity as? DBRoom, dbRoom.id == localId else { + return } + sendRoomAction = nil + let room = Room(room: dbRoom) + presenter?.created(room: room) } - } +extension CreateGroupInteractor: MQTTServiceDelegate { + + func didConnect(_ mqttService: MQTTService) { + guard let action = sendRoomAction else { + return + } + presenter?.showHUD() + action() + } + + func didDisconnect(_ mqttService: MQTTService) { + presenter?.hideHUD() + } +} diff --git a/Nynja/Modules/CreateGroup/Presenter/CreateGroupPresenter.swift b/Nynja/Modules/CreateGroup/Presenter/CreateGroupPresenter.swift index 8fbc78735037601635cc9d80cee7abd7620e1838..2105d1f1c3b271f99192244ae587cb9ea3b738fd 100644 --- a/Nynja/Modules/CreateGroup/Presenter/CreateGroupPresenter.swift +++ b/Nynja/Modules/CreateGroup/Presenter/CreateGroupPresenter.swift @@ -55,11 +55,11 @@ class CreateGroupPresenter: BasePresenter, CreateGroupPresenterProtocol, CreateG return } if room.name != "" { - (wireFrame as? CreateGroupWireFrame)?.mainWireFrame?.view?.showSpinner() + showHUD() interactor.createRoom(name: room.name!, avatar: avatar, members: room.members!) disableAction = true } else { - (wireFrame as? CreateGroupWireFrame)?.mainWireFrame?.view?.hideSpinner() + hideHUD() AlertManager.sharedInstance.showAlertOk(message: "Group_Name_Empty_Message".localized) } } @@ -113,11 +113,21 @@ class CreateGroupPresenter: BasePresenter, CreateGroupPresenterProtocol, CreateG self.view.setup(room: room, avatar: avatar, myAlias: myAlias, badgeNumber: contacts.count) } + //MARK: CreateGroupInteractorOutputProtocol + func created(room: Room) { - (wireFrame as? CreateGroupWireFrame)?.mainWireFrame?.view?.hideSpinner() + hideHUD() wireFrame.showGroupChat(room: room) } + func showHUD() { + (wireFrame as? CreateGroupWireFrame)?.mainWireFrame?.view?.showSpinner() + } + + func hideHUD() { + (wireFrame as? CreateGroupWireFrame)?.mainWireFrame?.view?.hideSpinner() + } + // MARK: - EditGroupPhotoDelegate func photoSavedAtUrl(url: URL) { if let data = try? Data(contentsOf: url) { diff --git a/Nynja/Modules/EditGroupName/EditGroupNameProtocols.swift b/Nynja/Modules/EditGroupName/EditGroupNameProtocols.swift index f171dbfac4e4994176e5a6ad2898b404ba0b869f..3cf57dfd3fbd024b20261f608932d69bb5f42cdb 100644 --- a/Nynja/Modules/EditGroupName/EditGroupNameProtocols.swift +++ b/Nynja/Modules/EditGroupName/EditGroupNameProtocols.swift @@ -19,6 +19,7 @@ protocol EditGroupNameWireFrameProtocol: class { /** * Add here your methods for communication PRESENTER -> WIREFRAME */ + func dismiss() } protocol EditGroupNameViewProtocol: class { @@ -30,11 +31,10 @@ protocol EditGroupNameViewProtocol: class { */ func setup(groupName: String) + func hideKeyboard() } -protocol EditGroupNamePresenterProtocol: AnyObject, BasePresenterProtocol { - - var mode: GroupMode! { get set } +protocol EditGroupNamePresenterProtocol: BasePresenterProtocol { var view: EditGroupNameViewProtocol! { get set } var interactor: EditGroupNameInteractorInputProtocol! { get set } @@ -45,7 +45,7 @@ protocol EditGroupNamePresenterProtocol: AnyObject, BasePresenterProtocol { */ func showed() - func saveGroupName(_ name:String) + func handleSaveTap(for name: String?) } protocol EditGroupNameInteractorOutputProtocol: class { @@ -53,15 +53,18 @@ protocol EditGroupNameInteractorOutputProtocol: class { /** * Add here your methods for communication INTERACTOR -> PRESENTER */ + func nameSaved() + func showAlert(with message: String) } protocol EditGroupNameInteractorInputProtocol: class { var presenter: EditGroupNameInteractorOutputProtocol! { get set } - + var oldName: String! { get } /** * Add here your methods for communication PRESENTER -> INTERACTOR */ func notifyDelegate(newName: String) + func validate(_ name: String?) } diff --git a/Nynja/Modules/EditGroupName/Interactor/EditGroupNameInteractor.swift b/Nynja/Modules/EditGroupName/Interactor/EditGroupNameInteractor.swift index 73d1b68c5162c9187a9b9d2d064a452e0e924b02..d44e5b541fc24c08f6c5e49a60d6c5dfd45a554f 100644 --- a/Nynja/Modules/EditGroupName/Interactor/EditGroupNameInteractor.swift +++ b/Nynja/Modules/EditGroupName/Interactor/EditGroupNameInteractor.swift @@ -7,12 +7,43 @@ // class EditGroupNameInteractor: EditGroupNameInteractorInputProtocol { - + weak var presenter: EditGroupNameInteractorOutputProtocol! + private var validationService: TextInputValidationServiceProtocol! + private weak var delegate: GroupNameEditorDelegate? + var oldName:String! - weak var delegate: GroupNameEditorDelegate? + required init(withName oldName: String, delegate: GroupNameEditorDelegate?) { + self.oldName = oldName + self.delegate = delegate + } func notifyDelegate(newName: String) { - delegate?.groupNameWasChanged(newValue: newName) + self.delegate?.groupNameWasChanged(newValue: newName) + } + + func validate(_ name: String?) { + let groupName = name?.trimmingCharacters(in: .whitespaces) + do { + let groupName = try self.validationService.validateGroupName(groupName) + if self.oldName != groupName { + self.notifyDelegate(newName: groupName) + } + self.presenter.nameSaved() + } catch { + self.presenter.showAlert(with: error.localizedDescription) + } + } +} + +extension EditGroupNameInteractor: SetInjectable { + func inject(dependencies: EditGroupNameInteractor.Dependencies) { + self.presenter = dependencies.presenter + self.validationService = dependencies.validationService + } + + struct Dependencies { + let presenter: EditGroupNameInteractorOutputProtocol + let validationService: TextInputValidationServiceProtocol } } diff --git a/Nynja/Modules/EditGroupName/Presenter/EditGroupNamePresenter.swift b/Nynja/Modules/EditGroupName/Presenter/EditGroupNamePresenter.swift index ef2f0518f45f594b247cb5333e32b96b4eca5b7d..2f76db340e5b2a30d36d234125eac9d5146cdb52 100644 --- a/Nynja/Modules/EditGroupName/Presenter/EditGroupNamePresenter.swift +++ b/Nynja/Modules/EditGroupName/Presenter/EditGroupNamePresenter.swift @@ -16,22 +16,44 @@ class EditGroupNamePresenter: BasePresenter, EditGroupNamePresenterProtocol, Edi } } - var mode: GroupMode! + private var mode: GroupMode! + + required init(with mode: GroupMode) { + self.mode = mode + } weak var view: EditGroupNameViewProtocol! var interactor: EditGroupNameInteractorInputProtocol! var wireFrame: EditGroupNameWireFrameProtocol! - - var oldName:String! func showed() { - view.setup(groupName: oldName) + view.setup(groupName: self.interactor.oldName) } - func saveGroupName(_ name: String) { - if oldName != name { - interactor.notifyDelegate(newName: name) - } - (view as? UIViewController)?.navigationController?.popViewController(animated: false) + func handleSaveTap(for name: String?) { + self.interactor.validate(name) + } + + func showAlert(with message: String) { + AlertManager.sharedInstance.showAlertOk(message: message) + } + + func nameSaved() { + self.wireFrame.dismiss() + } + +} + +extension EditGroupNamePresenter: SetInjectable { + func inject(dependencies: EditGroupNamePresenter.Dependencies) { + self.view = dependencies.view + self.interactor = dependencies.interactor + self.wireFrame = dependencies.wireFrame + } + + struct Dependencies { + let view: EditGroupNameViewProtocol + let interactor: EditGroupNameInteractorInputProtocol + let wireFrame: EditGroupNameWireFrameProtocol } } diff --git a/Nynja/Modules/EditGroupName/View/EditGroupNameViewController.swift b/Nynja/Modules/EditGroupName/View/EditGroupNameViewController.swift index 857b8fd40daffb0467a794832544d91d24e69ace..bd8b9096ccb551080c726555b172f63a4756a836 100644 --- a/Nynja/Modules/EditGroupName/View/EditGroupNameViewController.swift +++ b/Nynja/Modules/EditGroupName/View/EditGroupNameViewController.swift @@ -117,21 +117,11 @@ class EditGroupNameViewController: BaseVC, EditGroupNameViewProtocol { } @objc func saveTapped() { - let name = (nameField.input.text ?? "") - if valid(groupName: name.trimmingCharacters(in: .whitespaces)) { - self.view.endEditing(true) - self.presenter.saveGroupName(name) - } + self.presenter.handleSaveTap(for: nameField.input.text) } - // MARK: Utils - private func valid(groupName name: String) -> Bool { - if name.count < 2 { - AlertManager.sharedInstance.showAlertOk(message: Strings.groupNameEmptyMessage.localized) - return false - } - - return true + func hideKeyboard() { + self.view.endEditing(true) } // MARK: Private @@ -202,6 +192,16 @@ class EditGroupNameViewController: BaseVC, EditGroupNameViewProtocol { } } +extension EditGroupNameViewController: SetInjectable { + func inject(dependencies: EditGroupNameViewController.Dependencies) { + self.presenter = dependencies.presenter + } + + struct Dependencies { + let presenter: EditGroupNamePresenterProtocol + } +} + // MARK: - Testable extension EditGroupNameViewController: TestableViewControllerProtocol { diff --git a/Nynja/Modules/EditGroupName/WireFrame/EditGroupNameWireframe.swift b/Nynja/Modules/EditGroupName/WireFrame/EditGroupNameWireframe.swift index cb64a69f387974a3ff4845e18edeeb1443b68dd0..b8739c1e74fb0eb0ffda943afe024506c3eb6039 100644 --- a/Nynja/Modules/EditGroupName/WireFrame/EditGroupNameWireframe.swift +++ b/Nynja/Modules/EditGroupName/WireFrame/EditGroupNameWireframe.swift @@ -13,23 +13,29 @@ class EditGroupNameWireFrame: EditGroupNameWireFrameProtocol { weak var navigation : UINavigationController? func presentEditGroupName(navigation: UINavigationController, currentName:String, delegate:GroupNameEditorDelegate?, mode: GroupMode) { - let view = EditGroupNameViewController() - let presenter = EditGroupNamePresenter() - let interactor = EditGroupNameInteractor() self.navigation = navigation - presenter.oldName = currentName - interactor.delegate = delegate - // Connecting - view.presenter = presenter - presenter.mode = mode - presenter.view = view - presenter.wireFrame = self - presenter.interactor = interactor - interactor.presenter = presenter + let view = EditGroupNameViewController() + let presenter = EditGroupNamePresenter(with: mode) + let interactor = EditGroupNameInteractor(withName: currentName, delegate: delegate) + + let presenterDependencies = EditGroupNamePresenter.Dependencies(view: view, interactor: interactor, wireFrame: self) + presenter.inject(dependencies: presenterDependencies) - navigation.pushViewController(view as UIViewController, animated: true) + let viewDependencies = EditGroupNameViewController.Dependencies(presenter: presenter) + view.inject(dependencies: viewDependencies) + let serviceFactory = ServiceFactory() + let validationService = serviceFactory.makeTextInputValidationService() + + let interactorDependencies = EditGroupNameInteractor.Dependencies(presenter: presenter, validationService: validationService) + interactor.inject(dependencies: interactorDependencies) + + navigation.pushViewController(view as UIViewController, animated: true) + } + + func dismiss() { + self.navigation?.popViewController(animated: false) } } diff --git a/Nynja/Modules/Interpretation/Interactor/InterpretationInteractor.swift b/Nynja/Modules/Interpretation/Interactor/InterpretationInteractor.swift new file mode 100644 index 0000000000000000000000000000000000000000..a660e82cd76564f4965c2f6330923155f85adaae --- /dev/null +++ b/Nynja/Modules/Interpretation/Interactor/InterpretationInteractor.swift @@ -0,0 +1,24 @@ +// +// InterpretationInteractor.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class InterpretationInteractor: InterpretationInteractorInputProtocol { + + weak var presenter: InterpretationInteractorOutputProtocol! +} + +extension InterpretationInteractor: SetInjectable { + func inject(dependencies: InterpretationInteractor.Dependencies) { + self.presenter = dependencies.presenter + } + + struct Dependencies { + let presenter: InterpretationInteractorOutputProtocol + } +} diff --git a/Nynja/Modules/Interpretation/InterpretationModel.swift b/Nynja/Modules/Interpretation/InterpretationModel.swift new file mode 100644 index 0000000000000000000000000000000000000000..7554b732adb00bbd0bb1f48d5046bfbd2e919f76 --- /dev/null +++ b/Nynja/Modules/Interpretation/InterpretationModel.swift @@ -0,0 +1,21 @@ +// +// InterpretationModel.swift +// Nynja +// +// Created by Roman Chopovenko on 7/28/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +class InterpretationModel { + var type: InterpretationType = .general + var fromLang: Language = Language.empty_default + var toLang: Language = Language.current + var time: Int = 30 + var price: NYNMoney { + let result = Double(time) * NSDecimalNumber(decimal: type.price.amount).doubleValue + let decimalResult = Decimal(result) + return NYNMoney(decimalResult) + } +} diff --git a/Nynja/Modules/Interpretation/InterpretationProtocols.swift b/Nynja/Modules/Interpretation/InterpretationProtocols.swift new file mode 100644 index 0000000000000000000000000000000000000000..c813bfa884ed49d65ed5385f6beceb171e0ebddd --- /dev/null +++ b/Nynja/Modules/Interpretation/InterpretationProtocols.swift @@ -0,0 +1,66 @@ +// +// InterpretationProtocols.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +protocol InterpretationWireFrameProtocol: class { + + func presentInterpretation(navigation: UINavigationController) + + /** + * Add here your methods for communication PRESENTER -> WIREFRAME + */ + func openInterpretationType(delegate: SelectInterpretationTypeDelegate) + func openAssigningInterpreter() + func dismiss() +} + +protocol InterpretationViewProtocol: class { + + var presenter: InterpretationPresenterProtocol! { get set } + + /** + * Add here your methods for communication PRESENTER -> VIEW + */ + + func setInterpretationType(_ type: InterpretationType) +} + +protocol InterpretationPresenterProtocol: BasePresenterProtocol { + + var view: InterpretationViewProtocol! { get set } + var interactor: InterpretationInteractorInputProtocol! { get set } + var wireFrame: InterpretationWireFrameProtocol! { get set } + + /** + * Add here your methods for communication VIEW -> PRESENTER + */ + func openAssigningInterpreter(with model: InterpretationModel) + func openInterpretationType() + func dismiss() +} + +protocol InterpretationInteractorOutputProtocol: class { + + /** + * Add here your methods for communication INTERACTOR -> PRESENTER + */ +} + +protocol InterpretationInteractorInputProtocol: class { + + var presenter: InterpretationInteractorOutputProtocol! { get set } + + /** + * Add here your methods for communication PRESENTER -> INTERACTOR + */ +} + +protocol SelectInterpretationTypeDelegate: class { + func typeSelected(_ type: InterpretationType) +} diff --git a/Nynja/Modules/Interpretation/Presenter/InterpretationPresenter.swift b/Nynja/Modules/Interpretation/Presenter/InterpretationPresenter.swift new file mode 100644 index 0000000000000000000000000000000000000000..ec553175996412d0f7991fb1d7aebb7658454de0 --- /dev/null +++ b/Nynja/Modules/Interpretation/Presenter/InterpretationPresenter.swift @@ -0,0 +1,60 @@ +// +// InterpretationPresenter.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class InterpretationPresenter: BasePresenter, InterpretationPresenterProtocol, InterpretationInteractorOutputProtocol { + + weak var view: InterpretationViewProtocol! + var interactor: InterpretationInteractorInputProtocol! + var wireFrame: InterpretationWireFrameProtocol! + private var useCaseValidationService: UseCaseValidationServiceProtocol! + + func openInterpretationType() { + self.wireFrame.openInterpretationType(delegate: self) + } + + func openAssigningInterpreter(with model: InterpretationModel) { + self.validateInterpretationConditions(model: model) + } + + func dismiss() { + self.wireFrame.dismiss() + } + + private func validateInterpretationConditions(model: InterpretationModel) { + do { + try self.useCaseValidationService.validateInterpretationLanguages(model.fromLang, toLanguage: model.toLang) + self.wireFrame.openAssigningInterpreter() + } catch { + AlertManager.sharedInstance.showAlertOk(message: error.localizedDescription) + } + } +} + +extension InterpretationPresenter: SelectInterpretationTypeDelegate { + func typeSelected(_ type: InterpretationType) { + self.view.setInterpretationType(type) + } +} + +extension InterpretationPresenter: SetInjectable { + func inject(dependencies: InterpretationPresenter.Dependencies) { + self.view = dependencies.view + self.interactor = dependencies.interactor + self.wireFrame = dependencies.wireFrame + self.useCaseValidationService = dependencies.useCaseValidationService + } + + struct Dependencies { + let view: InterpretationViewProtocol + let interactor: InterpretationInteractorInputProtocol + let wireFrame: InterpretationWireFrameProtocol + let useCaseValidationService: UseCaseValidationServiceProtocol + } +} diff --git a/Nynja/Modules/Interpretation/View/AlertTextFieldViewController/AlertTextFieldViewController.swift b/Nynja/Modules/Interpretation/View/AlertTextFieldViewController/AlertTextFieldViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..b374e1ff49468c284b1e326a21314af9e41f1cb3 --- /dev/null +++ b/Nynja/Modules/Interpretation/View/AlertTextFieldViewController/AlertTextFieldViewController.swift @@ -0,0 +1,192 @@ +// +// AlertTextFieldViewController.swift +// Nynja +// +// Created by Roman Chopovenko on 7/29/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +typealias TimeCallback = (Int) -> () +typealias EmptyCallBack = ()->() + +class AlertTextFieldViewController: BaseVC { + + var completion: TimeCallback? + private var initTimeValue: Int! + private let validationService = ServiceFactory().makeTextInputValidationService() + + // MARK: - Views + + lazy var backView: UIView = { + let view = UIView() + self.view.addSubview(view) + + view.snp.makeConstraints({ (make) in + make.top.left.right.equalToSuperview() + make.bottom.equalTo(self.view.keyboardLayoutGuide.snp.top) + }) + return view + }() + + lazy var containerView: UIView = { + let view = UIView() + view.setContentHuggingPriority(.defaultHigh, for: .vertical) + view.backgroundColor = Constants.colors.darkLight.getColor() + self.backView.addSubview(view) + + view.snp.makeConstraints({ (make) in + make.centerX.centerY.equalToSuperview() + make.width.equalTo(Constraints.titleLabel.width) + }) + return view + }() + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.text = Strings.titleLabelText + label.font = Texts.titleLabel.font + label.textColor = Texts.titleLabel.color + self.containerView.addSubview(label) + + label.snp.makeConstraints({ (make) in + make.top.equalToSuperview().offset(Constraints.titleLabel.top) + make.height.equalTo(Constraints.titleLabel.height) + make.left.equalToSuperview().offset(Constraints.sidePadding) + make.right.equalToSuperview().inset(Constraints.sidePadding) + make.bottom.equalTo(inputField.snp.top) + }) + return label + }() + + lazy var inputField: MaterialTextField = { + let field = MaterialTextField() + field.keyboardType = .numberPad + + self.containerView.addSubview(field) + field.snp.makeConstraints { (make) in + make.left.equalToSuperview().offset(Constraints.sidePadding) + make.right.equalToSuperview().offset(-Constraints.sidePadding) + make.bottom.equalTo(okButton.snp.top) + } + return field + }() + + lazy var okButton: UIButton = { + let button = UIButton() + self.containerView.addSubview(button) + let title = "ok".localized.uppercased() + button.setTitle(title, for: .normal) + button.titleLabel?.font = Texts.buttons.font + button.setTitleColor(Texts.buttons.color, for: .normal) + button.addTarget(self, action: #selector(okButtonTapped(_:)), for: .touchUpInside) + + button.snp.makeConstraints({ (make) in + make.width.equalTo(Constraints.okButton.width) + make.height.equalTo(Constraints.okButton.height) + make.right.equalToSuperview().inset(Constraints.sidePadding) + make.left.equalTo(cancelButton.snp.right).offset(Constraints.cancelButton.rightPadding) + make.centerY.equalTo(cancelButton) + make.bottom.equalToSuperview().offset(-Constraints.okButton.bottom) + }) + return button + }() + + lazy var cancelButton: UIButton = { + let button = UIButton() + self.containerView.addSubview(button) + let title = "cancel".localized.uppercased() + button.setTitle(title, for: .normal) + button.titleLabel?.font = Texts.buttons.font + button.titleLabel?.textAlignment = .right + button.setTitleColor(Texts.buttons.color, for: .normal) + button.addTarget(self, action: #selector(cancelButtonTapped(_:)), for: .touchUpInside) + + button.snp.makeConstraints({ (make) in + make.width.equalTo(Constraints.cancelButton.width).priority(200) + make.height.equalTo(Constraints.cancelButton.height) + }) + return button + }() + + + // MARK: - Init + + convenience init(initialValue: Int) { + self.init() + initTimeValue = initialValue + + self.modalTransitionStyle = .crossDissolve + self.modalPresentationStyle = .overCurrentContext + } + + override func viewDidLoad() { + super.viewDidLoad() + self.baseSetup() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + let _ = self.inputField.becomeFirstResponder() + } + + private func baseSetup() { + self.backImage.image = nil + self.view.backgroundColor = Constants.colors.black.getColor(withAlpha: 0.4) + self.view.isOpaque = false + self.containerView.isHidden = false + self.titleLabel.isHidden = false + + self.inputField.text = String(self.initTimeValue) + + self.inputField.textChanged = { [unowned self] input in + if input.text.isEmpty { + self.validate(0) + } else if let num = Int(input.text) { + self.validate(num) + } + } + self.inputField.shouldTextChanged = { (input, range, string) in + if string == "0" && range == NSRange(location: 0, length: 0) { return false } + let allowedCharacters = CharacterSet(charactersIn:"0123456789") + let characterSet = CharacterSet(charactersIn: string) + return allowedCharacters.isSuperset(of: characterSet) + } + } + + // MARK: - Actions + + @objc + func okButtonTapped(_ sender: UIButton) { + if let number = Int(self.inputField.text) { + self.completion?(number) + self.dismiss(animated: true) + } + } + + @objc + func cancelButtonTapped(_ sender: UIButton) { + self.dismiss(animated: true) + } + + private func validate(_ number: Int) { + do { + let _ = try self.validationService.validateInterpretationTime(number) + self.updateInfo(with: "") + self.okButton.isEnabled = true + } catch { + self.updateInfo(with: error.localizedDescription) + self.okButton.isEnabled = false + } + } + + func updateInfo(with message: String?) { + guard let message = message else { + inputField.info = nil + return + } + let inputInfo = InputInfo(text: message, kind: .warning) + inputField.info = inputInfo + } +} diff --git a/Nynja/Modules/Interpretation/View/AlertTextFieldViewController/AlertTextFieldViewControllerLayout.swift b/Nynja/Modules/Interpretation/View/AlertTextFieldViewController/AlertTextFieldViewControllerLayout.swift new file mode 100644 index 0000000000000000000000000000000000000000..22b7088ddcc2f87fbb8dd4390aaaabcf3eadf074 --- /dev/null +++ b/Nynja/Modules/Interpretation/View/AlertTextFieldViewController/AlertTextFieldViewControllerLayout.swift @@ -0,0 +1,62 @@ +// +// AlertTextFieldViewControllerLayout.swift +// Nynja +// +// Created by Roman Chopovenko on 8/5/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +extension AlertTextFieldViewController { + + struct Strings { + static let titleLabelText = "enter_interpretation_time".localized + } + + struct Constraints { + + static let sidePadding = 24.adjustedByWidth + + struct titleLabel { + static let height = Texts.titleLabel.height + static let width = 275.adjustedByWidth + static let top = 16.adjustedByWidth + } + + struct inputField { + static let height = 30.adjustedByWidth + } + + struct okButton { + static let height = 32.adjustedByWidth + static let width = 32.adjustedByWidth + static let bottom = 8.adjustedByWidth + } + + struct cancelButton { + static let height = 32.adjustedByWidth + static let width = 52.adjustedByWidth + static let rightPadding = 8.adjustedByWidth + } + } + + struct Texts { + struct dafaultWhite { + static let height = 20.adjustedByWidth + static let font = UIFont(fontName: Constants.fonts.medium, height: CGFloat(height)) + static let color = Constants.colors.white.getColor() + } + struct titleLabel { + static let height = 17.adjustedByWidth + static let font = UIFont(fontName: Constants.fonts.regular, height: CGFloat(height)) + static let color = Constants.colors.darkGray.getColor() + } + + struct buttons { + static let titleHeight = 22.adjustedByWidth + static let font = UIFont(fontName: Constants.fonts.medium, height: CGFloat(titleHeight)) + static let color = Constants.colors.red.getColor() + } + } +} diff --git a/Nynja/Modules/Interpretation/View/InterpretationLayout.swift b/Nynja/Modules/Interpretation/View/InterpretationLayout.swift new file mode 100644 index 0000000000000000000000000000000000000000..7aa7cdbbfcbc482097a900cae9209af00e694eec --- /dev/null +++ b/Nynja/Modules/Interpretation/View/InterpretationLayout.swift @@ -0,0 +1,99 @@ +// +// InterpretationLayout.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +extension InterpretationViewController { + + // MARK: Text + struct Text { + struct defaultWhite { + static let height = CGFloat(22.adjustedByWidth) + static let font = UIFont(fontName: Constants.fonts.medium, height: height) + static let fontColor = Constants.colors.white.getColor() + } + struct defaultGray { + static let height = CGFloat(20.adjustedByWidth) + static let font = UIFont(fontName: Constants.fonts.regular, height: height) + static let fontColor = Constants.colors.gray.getColor() + } + } + + struct Constraints { + + static let sidePadding = 16.adjustedByWidth + static let interitemPadding = 8.adjustedByWidth + static let separatorHeight = 1 + + struct fromLangPickerView { + static let widthOffset = Constraints.swapButton.side/2 + Constraints.sidePadding + static let height = 158.adjustedByWidth + } + + struct langForInterpretationLabel { + static let bottomPadding = 8.adjustedByWidth + } + + struct toLangPickerView { + static let bottomPadding = 16.adjustedByWidth + } + + struct swapButton { + static let side = 42.adjustedByWidth + static let topPadding = 8.adjustedByWidth + } + + struct fromLabel { + static let rightPadding = Constraints.swapButton.side + } + + struct interpretationTypeLabel { + static let bottomPadding = 8.adjustedByWidth + } + + struct interpretationTypeButton { + static let width = 30.adjustedByWidth + } + + struct interpretationTimeButton { + static let width = 30.adjustedByWidth + } + + struct totalPriceValueLabel { + static let width = 30.adjustedByWidth + } + + struct searchButton { + static let height = 44.adjustedByWidth + static let topPading = 24.adjustedByWidth + static let bottomPadding = 16.adjustedByWidth + } + + struct separatorBottom { + static let topPading = 8.adjustedByWidth + } + } + + // MARK: String + enum Strings: String { + case interpretationTime = "interpretation_time" + case langForInterpretation = "lang_for_interpretation" + case from = "from" + case to = "to" + case interpretationType = "interpretation_type" + case totalPrice = "total_price" + case search = "search_interpreter" + } + + struct Colors { + struct gradientView { + static let colors: [UIColor] = [Constants.colors.marketplaceMunu.gradient.getColor(withAlpha: 0.1), + Constants.colors.marketplaceMunu.gradient.getColor(withAlpha: 0.5)] + } + } +} diff --git a/Nynja/Modules/Interpretation/View/InterpretationViewController.swift b/Nynja/Modules/Interpretation/View/InterpretationViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..f6745652f3cb2bee1fcd11bcea074b42faf63c67 --- /dev/null +++ b/Nynja/Modules/Interpretation/View/InterpretationViewController.swift @@ -0,0 +1,431 @@ +// +// InterpretationViewController.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + +final class InterpretationViewController: BaseVC, InterpretationViewProtocol { + + var presenter: InterpretationPresenterProtocol! { + didSet { + _presenter = presenter + } + } + + private let model = InterpretationModel() + + private var fromLangDelegate: LanguagePickerDelegate! + private var toLangDelegate: LanguagePickerDelegate! + + lazy var interpretationTimeLabel: UILabel = { + let label = UILabel() + label.text = Strings.interpretationTime.localized + label.font = Text.defaultWhite.font + label.textColor = Text.defaultWhite.fontColor + + self.view.addSubview(label) + + label.snp.makeConstraints({ (make) in + make.top.equalTo(navigationView.snp.bottomMargin).offset(Constraints.sidePadding) + make.left.equalToSuperview().offset(Constraints.sidePadding) + make.right.equalTo(interpretationTimeButton.snp.left).offset(-Constraints.interitemPadding) + make.height.equalTo(Text.defaultWhite.height) + make.bottom.equalTo(separatorTop.snp.top).offset(-Constraints.sidePadding) + }) + return label + }() + + lazy var interpretationTimeButton: UIButton = { + let button = UIButton() + button.titleLabel?.font = Text.defaultGray.font + button.setTitleColor(Text.defaultGray.fontColor, for: .normal) + button.addTarget(self, action: #selector(interpretationTimeButtonTapped), for: .touchUpInside) + + self.view.addSubview(button) + + button.snp.makeConstraints({ (make) in + make.width.equalTo(Constraints.interpretationTimeButton.width).priority(200) + make.top.equalTo(navigationView.snp.bottomMargin).offset(Constraints.sidePadding) + make.height.equalTo(Text.defaultGray.height) + make.right.equalToSuperview().inset(Constraints.sidePadding) + }) + return button + }() + + lazy var separatorTop: UIView = { + let separator = UIView() + + separator.backgroundColor = Constants.colors.separatorGrayColor.getColor() + + self.view.addSubview(separator) + separator.snp.makeConstraints({ (make) in + make.height.equalTo(Constraints.separatorHeight) + make.left.equalToSuperview().offset(Constraints.sidePadding) + make.right.equalToSuperview().inset(Constraints.sidePadding) + make.bottom.equalTo(langForInterpretationLabel.snp.top).offset(-Constraints.sidePadding) + }) + return separator + }() + + lazy var langForInterpretationLabel: UILabel = { + let label = UILabel() + label.text = Strings.langForInterpretation.localized + label.font = Text.defaultWhite.font + label.textColor = Text.defaultWhite.fontColor + + self.view.addSubview(label) + + label.snp.makeConstraints({ (make) in + make.left.equalToSuperview().offset(Constraints.sidePadding) + make.right.equalToSuperview().offset(-Constraints.sidePadding) + make.height.equalTo(Text.defaultWhite.height) + make.bottom.equalTo(fromLabel.snp.top).offset(-Constraints.langForInterpretationLabel.bottomPadding) + }) + return label + }() + + lazy var fromLabel: UILabel = { + let label = UILabel() + label.text = Strings.from.localized + label.font = Text.defaultGray.font + label.textColor = Text.defaultGray.fontColor + + self.view.addSubview(label) + + label.snp.makeConstraints({ (make) in + make.height.equalTo(Text.defaultGray.height) + make.width.equalTo(fromLangPickerView) + make.left.equalToSuperview().offset(Constraints.sidePadding) + make.right.equalTo(toLabel.snp.left).offset(-Constraints.fromLabel.rightPadding) + make.top.equalTo(toLabel) + make.bottom.equalTo(fromLangPickerView.snp.top) + make.bottom.equalTo(swapTopLine.snp.top) + }) + return label + }() + + lazy var toLabel: UILabel = { + let label = UILabel() + label.text = Strings.to.localized + label.font = Text.defaultGray.font + label.textColor = Text.defaultGray.fontColor + + self.view.addSubview(label) + + label.snp.makeConstraints({ (make) in + make.height.equalTo(Text.defaultGray.height) + make.right.equalToSuperview().offset(-Constraints.sidePadding) + make.bottom.equalTo(toLangPickerView.snp.top) + }) + return label + }() + + lazy var fromLangPickerView: UIPickerView = { + let picker = UIPickerView() + + self.view.addSubview(picker) + picker.snp.makeConstraints({ (make) in + make.left.equalToSuperview().offset(Constraints.sidePadding) + make.height.equalTo(Constraints.fromLangPickerView.height) + make.width.equalToSuperview().dividedBy(2).offset(-Constraints.fromLangPickerView.widthOffset) + make.right.equalTo(swapButton.snp.left) + make.centerY.equalTo(swapButton) + + make.bottom.equalTo(interpretationTypeLabel.snp.top).offset(-Constraints.sidePadding) + make.bottom.equalTo(swapBottomLine.snp.bottom) + }) + return picker + }() + + lazy var swapButton: UIButton = { + let button = UIButton() + self.view.addSubview(button) + button.setImage(#imageLiteral(resourceName: "marketplace_swap_button"), for: .normal) + button.addTarget(self, action: #selector(swapButtonTapped), for: .touchUpInside) + + button.snp.makeConstraints({ (make) in + make.width.height.equalTo(Constraints.swapButton.side) + make.right.equalTo(toLangPickerView.snp.left) + make.top.equalTo(swapTopLine.snp.bottom).offset(Constraints.swapButton.topPadding) + make.centerX.equalTo(swapTopLine) + make.centerX.equalTo(swapBottomLine) + make.bottom.equalTo(swapBottomLine.snp.top) + }) + return button + }() + + lazy var swapTopLine: GradientView = { + let view = GradientView(colors: Colors.gradientView.colors) + self.view.addSubview(view) + + view.snp.makeConstraints({ (make) in + make.width.equalTo(Constraints.separatorHeight) + }) + return view + }() + + lazy var swapBottomLine: GradientView = { + let view = GradientView(colors: Colors.gradientView.colors) + view.transform = CGAffineTransform(rotationAngle: .pi) + self.view.addSubview(view) + + view.snp.makeConstraints({ (make) in + make.width.equalTo(Constraints.separatorHeight) + }) + return view + }() + + lazy var toLangPickerView: UIPickerView = { + let picker = UIPickerView() + self.view.addSubview(picker) + picker.snp.makeConstraints({ (make) in + make.height.equalTo(Constraints.fromLangPickerView.height) + make.right.equalToSuperview().offset(-Constraints.sidePadding) + make.bottom.equalTo(interpretationTypeButton.snp.top).offset(-Constraints.toLangPickerView.bottomPadding) + }) + return picker + }() + + lazy var interpretationTypeLabel: UILabel = { + let label = UILabel() + label.text = Strings.interpretationType.localized + label.font = Text.defaultWhite.font + label.textColor = Text.defaultWhite.fontColor + + self.view.addSubview(label) + + label.snp.makeConstraints({ (make) in + make.height.equalTo(Text.defaultWhite.height) + make.left.equalToSuperview().offset(Constraints.sidePadding) + make.right.equalTo(interpretationTypeButton.snp.left).offset(-Constraints.interitemPadding) + make.bottom.equalTo(separatorMiddle.snp.top).offset(-Constraints.interpretationTypeLabel.bottomPadding) + }) + return label + }() + + lazy var interpretationTypeButton: UIButton = { + let button = UIButton() + button.setTitle(self.model.type.localized, for: .normal) + button.titleLabel?.font = Text.defaultGray.font + button.setTitleColor(Text.defaultGray.fontColor, for: .normal) + + button.addTarget(self, action: #selector(interpretationTypeButtonTapped), for: .touchUpInside) + self.view.addSubview(button) + + button.snp.makeConstraints({ (make) in + make.height.equalTo(Text.defaultGray.height) + make.width.equalTo(Constraints.interpretationTypeButton.width).priority(200) + make.right.equalToSuperview().inset(Constraints.sidePadding) + }) + return button + }() + + lazy var separatorMiddle: UIView = { + let separator = UIView() + + separator.backgroundColor = Constants.colors.separatorGrayColor.getColor() + + view.addSubview(separator) + separator.snp.makeConstraints({ (make) in + make.height.equalTo(Constraints.separatorHeight) + make.left.equalToSuperview().offset(Constraints.sidePadding) + make.right.equalToSuperview().inset(Constraints.sidePadding) + }) + return separator + }() + + private lazy var searchButton: NynjaButton = { + let button = NynjaButton(height: CGFloat(Constraints.searchButton.height)) + button.titleLabel?.font = Text.defaultWhite.font + button.setTitle(Strings.search.localized.uppercased(), for: .normal) + self.view.addSubview(button) + + button.addTarget(self, action: #selector(searchButtonTapped), for: .touchUpInside) + + button.snp.makeConstraints({ (make) in + make.height.equalTo(Constraints.searchButton.height) + make.left.equalToSuperview().offset(Constraints.sidePadding) + make.right.equalToSuperview().offset(-Constraints.sidePadding) + make.bottom.equalToSuperview().offset(-Constraints.searchButton.bottomPadding) + make.top.equalTo(separatorBottom.snp.bottom).offset(Constraints.searchButton.topPading) + }) + return button + }() + + lazy var separatorBottom: UIView = { + let separator = UIView() + + separator.backgroundColor = Constants.colors.separatorGrayColor.getColor() + + view.addSubview(separator) + separator.snp.makeConstraints({ (make) in + make.height.equalTo(Constraints.separatorHeight) + make.left.equalToSuperview().offset(Constraints.sidePadding) + make.right.equalToSuperview().inset(Constraints.sidePadding) + make.top.equalTo(totalPriceTitleLabel.snp.bottom).offset(Constraints.separatorBottom.topPading) + make.top.equalTo(totalPriceValueLabel.snp.bottom).offset(Constraints.separatorBottom.topPading) + }) + return separator + }() + + lazy var totalPriceTitleLabel: UILabel = { + let label = UILabel() + label.text = Strings.totalPrice.localized + label.font = Text.defaultWhite.font + label.textColor = Text.defaultWhite.fontColor + + self.view.addSubview(label) + + label.snp.makeConstraints({ (make) in + make.left.equalToSuperview().offset(Constraints.sidePadding) + make.height.equalTo(Text.defaultWhite.height) + make.right.equalTo(totalPriceValueLabel.snp.left).offset(-Constraints.interitemPadding) + }) + return label + }() + + lazy var totalPriceValueLabel: UILabel = { + let label = UILabel() + label.font = Text.defaultWhite.font + label.textColor = Constants.colors.red.getColor() + + self.view.addSubview(label) + + label.snp.makeConstraints({ (make) in + make.height.equalTo(Text.defaultWhite.height) + make.width.equalTo(Constraints.totalPriceValueLabel.width).priority(200) + make.right.equalToSuperview().offset(-Constraints.sidePadding) + }) + return label + }() + + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + } + + + // MARK: - UI Setup + + private func setupUI() { + self.navigationView.configure(config: NavigationView.Config( + isVisibleSeparator: navigationView.isSeparatorVisible ?? false, + isVisibleBackButton: true, + title: title, + navigationHandler: self, + backButtonImage: UIImage(named:"ic_back_navigation"))) + self.screenTitle = "interpretation".localized.uppercased() + self.interpretationTimeLabel.isHidden = false + self.searchButton.isHidden = false + self.setupPickers() + self.interpretationTimeButton.setTitle("\(self.model.time) min", for: .normal) + self.interpretationTypeButton.setTitle(self.model.type.localized, for: .normal) + self.updateTotalPrice() + } + + private func setupPickers() { + self.setupLanguagePicker(picker: self.fromLangPickerView, with: fromLangDelegate, callback: self) + self.setupLanguagePicker(picker: self.toLangPickerView, with: self.toLangDelegate, callback: self) + + self.selectRow(with: self.model.fromLang, in: self.fromLangPickerView, animated: false) + self.selectRow(with: self.model.toLang, in: self.toLangPickerView, animated: false) + } + + private func setupLanguagePicker(picker: UIPickerView, with delegate: LanguagePickerDelegate, callback: LanguagePickerDelegateCallback) { + delegate.callback = callback + picker.dataSource = delegate + picker.delegate = delegate + } + + private func selectRow(with language: Language, in picker: UIPickerView, animated: Bool) { + var row: Int? = fromLangDelegate.positionIn(value: language) + if picker == self.toLangPickerView { + row = toLangDelegate.positionIn(value: language) + } + picker.selectRow(row ?? 0, inComponent: 0, animated: animated) + } + + // MARK: - Actions + @objc + private func swapButtonTapped() { + guard self.model.fromLang != .empty_default, self.model.fromLang != self.model.toLang else { return } + let temp = self.model.fromLang + + self.model.fromLang = self.model.toLang + self.model.toLang = temp + + self.selectRow(with: self.model.fromLang, in: self.fromLangPickerView, animated: true) + self.selectRow(with: self.model.toLang, in: toLangPickerView, animated: true) + } + + @objc + private func interpretationTimeButtonTapped() { + AlertManager().showTimeFieldAlert(with: self.model.time) { [unowned self] (time) in + self.setInterpretationTime(time) + } + } + + @objc + private func interpretationTypeButtonTapped() { + self.presenter.openInterpretationType() + } + + @objc + private func searchButtonTapped() { + self.presenter.openAssigningInterpreter(with: self.model) + } + + func setInterpretationType(_ type: InterpretationType) { + self.model.type = type + self.interpretationTypeButton.setTitle(type.localized, for: .normal) + self.updateTotalPrice() + } + + private func setInterpretationTime(_ time: Int) { + self.model.time = time + self.interpretationTimeButton.setTitle("\(time) min", for: .normal) + self.updateTotalPrice() + } + + func updateTotalPrice() { + self.totalPriceValueLabel.text = self.model.price.formattedCurrency + } +} + +extension InterpretationViewController: NavigationProtocol { + func back() { + self.presenter.dismiss() + } +} + +extension InterpretationViewController: LanguagePickerDelegateCallback { + func didSelectLanguage(_ pickerView: UIPickerView, language: Language) { + if pickerView == self.fromLangPickerView { + self.model.fromLang = language + } else if pickerView == self.toLangPickerView { + self.model.toLang = language + } + } +} + +extension InterpretationViewController: SetInjectable { + func inject(dependencies: InterpretationViewController.Dependencies) { + self.presenter = dependencies.presenter + self.fromLangDelegate = dependencies.fromLangDelegate + self.toLangDelegate = dependencies.toLangDelegate + } + + struct Dependencies { + let presenter: InterpretationPresenterProtocol + let fromLangDelegate: LanguagePickerDelegate + let toLangDelegate: LanguagePickerDelegate + } +} diff --git a/Nynja/Modules/Interpretation/View/LanguagePickerDelegate.swift b/Nynja/Modules/Interpretation/View/LanguagePickerDelegate.swift new file mode 100644 index 0000000000000000000000000000000000000000..5fd82bb474d5b9186e04a113b937f9de914c67c3 --- /dev/null +++ b/Nynja/Modules/Interpretation/View/LanguagePickerDelegate.swift @@ -0,0 +1,78 @@ +// +// LanguagePickerDelegate.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +protocol LanguagePickerDelegateCallback : class { + func didSelectLanguage(_ pickerView: UIPickerView, language : Language) +} + +class LanguagePickerDelegate : NSObject, UIPickerViewDataSource, UIPickerViewDelegate { + private let rows = 100000 + var pickerViewMiddle: Int { return ((self.rows / self.data.count) / 2) * self.data.count} + + weak var callback : LanguagePickerDelegateCallback? + + private var data: [Language]! + + //MARK: - Init + required init(defaultEmptyLanguage: Bool) { + self.data = defaultEmptyLanguage ? Language.allValuesWithDefault : Language.allValues + } + + //MARK: - UIPickerViewDataSource + func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return rows + } + + //MARK: - UIPickerViewDelegate + func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { + return CGFloat(Values.height) + } + + func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { + pickerView.hideLines() + return self.createLabel(forRow: row, pickerView: pickerView) + } + + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + callback?.didSelectLanguage(pickerView, language: valueForRow(row)) + } + + func positionIn(value : Language) -> Int? { + if let i = data.index(of: value) { + return pickerViewMiddle + i + } + return nil + } + + private func createLabel(forRow row: Int, pickerView: UIPickerView) -> UILabel { + let size = CGSize(width: pickerView.bounds.width, + height: Values.height) + let label = UILabel(frame: CGRect(origin: .zero, size: size)) + label.text = valueForRow(row).longValue + label.font = Values.font + label.textAlignment = NSTextAlignment.center + label.textColor = Values.fontColor + return label + } + + private func valueForRow(_ row: Int) -> Language { + return self.data[row % data.count] + } + + struct Values { + static let height = CGFloat(24.adjustedByWidth) + static let font = UIFont(fontName: Constants.fonts.medium, height: height) + static let fontColor = Constants.colors.white.getColor() + } +} diff --git a/Nynja/Modules/Interpretation/Wireframe/InterpretationWireFrame.swift b/Nynja/Modules/Interpretation/Wireframe/InterpretationWireFrame.swift new file mode 100644 index 0000000000000000000000000000000000000000..4c02b27e3474e9c27d39c8eb956a2d291137b825 --- /dev/null +++ b/Nynja/Modules/Interpretation/Wireframe/InterpretationWireFrame.swift @@ -0,0 +1,51 @@ +// +// InterpretationWireFrame.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +final class InterpretationWireFrame: InterpretationWireFrameProtocol { + weak var navigation: UINavigationController? + + func presentInterpretation(navigation: UINavigationController) { + self.navigation = navigation + + let view = InterpretationViewController() + let presenter = InterpretationPresenter() + let interactor = InterpretationInteractor() + + // Connecting + + let viewDependencies = InterpretationViewController.Dependencies(presenter: presenter, fromLangDelegate: LanguagePickerDelegate(defaultEmptyLanguage: true), toLangDelegate: LanguagePickerDelegate(defaultEmptyLanguage: false)) + view.inject(dependencies: viewDependencies) + + let serviceFactory = ServiceFactory() + let useCaseValidationService = serviceFactory.makeUseCaseValidationServise() + let presenterDependencies = InterpretationPresenter.Dependencies(view: view, interactor: interactor, wireFrame: self, useCaseValidationService: useCaseValidationService) + presenter.inject(dependencies: presenterDependencies) + let interactorDependencies = InterpretationInteractor.Dependencies(presenter: presenter) + interactor.inject(dependencies: interactorDependencies) + + view.modalTransitionStyle = .crossDissolve + view.modalPresentationStyle = .overCurrentContext + navigation.pushViewController(view as UIViewController, animated: true) + } + + func openInterpretationType(delegate: SelectInterpretationTypeDelegate) { + guard let navigation = self.navigation else { return } + InterpretationTypeWireFrame().presentInterpretationType(navigation: navigation, delegate: delegate) + } + + func openAssigningInterpreter() { + guard let navigation = self.navigation else { return } + AssigningInterpreterWireFrame().presentAssigningInterpreter(navigation: navigation) + } + + func dismiss() { + self.navigation?.popViewController(animated: true) + } +} diff --git a/Nynja/Modules/InterpretationType/Interactor/InterpretationTypeInteractor.swift b/Nynja/Modules/InterpretationType/Interactor/InterpretationTypeInteractor.swift new file mode 100644 index 0000000000000000000000000000000000000000..e2c61b8140d89cad89d30a866c424544f1617311 --- /dev/null +++ b/Nynja/Modules/InterpretationType/Interactor/InterpretationTypeInteractor.swift @@ -0,0 +1,23 @@ +// +// InterpretationTypeInteractor.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class InterpretationTypeInteractor: InterpretationTypeInteractorInputProtocol { + weak var presenter: InterpretationTypeInteractorOutputProtocol! +} + +extension InterpretationTypeInteractor: SetInjectable { + func inject(dependencies: InterpretationTypeInteractor.Dependencies) { + self.presenter = dependencies.presenter + } + + struct Dependencies { + let presenter: InterpretationTypeInteractorOutputProtocol + } +} diff --git a/Nynja/Modules/InterpretationType/InterpretationType.swift b/Nynja/Modules/InterpretationType/InterpretationType.swift new file mode 100644 index 0000000000000000000000000000000000000000..f85ac5b1339265cff9e435516e34bd8655b150d0 --- /dev/null +++ b/Nynja/Modules/InterpretationType/InterpretationType.swift @@ -0,0 +1,42 @@ +// +// InterpretationType.swift +// Nynja +// +// Created by Roman Chopovenko on 7/28/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +enum InterpretationType: String { + case general = "interpretation_type_general" + case technology = "interpretation_type_technology" + case legal = "interpretation_type_legal" + case medical = "interpretation_type_medical" + + var description: String { + switch self { + case .general: + return "interpretation_type_description_general".localized + case .technology: + return "interpretation_type_description_technology".localized + case .legal: + return "interpretation_type_description_legal".localized + case .medical: + return "interpretation_type_description_medical".localized + } + } + + var price: NYNMoney { + switch self { + case .general: + return NYNMoney(0.40) + case .technology: + return NYNMoney(0.50) + case .legal: + return NYNMoney(0.63) + case .medical: + return NYNMoney(0.75) + } + } +} diff --git a/Nynja/Modules/InterpretationType/InterpretationTypeInteractor.swift b/Nynja/Modules/InterpretationType/InterpretationTypeInteractor.swift new file mode 100644 index 0000000000000000000000000000000000000000..85be4ae294471f21098788bf51b35cf413d053d7 --- /dev/null +++ b/Nynja/Modules/InterpretationType/InterpretationTypeInteractor.swift @@ -0,0 +1,24 @@ +// +// InterpretationTypeInteractor.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class InterpretationTypeInteractor: InterpretationTypeInteractorInputProtocol { + + weak var presenter: InterpretationTypeInteractorOutputProtocol! +} + +extension InterpretationTypeInteractor: SetInjectable { + func inject(dependencies: InterpretationTypeInteractor.Dependencies) { + self.presenter = dependencies.presenter + } + + struct Dependencies { + let presenter: InterpretationTypeInteractorOutputProtocol + } +} diff --git a/Nynja/Modules/InterpretationType/InterpretationTypePresenter.swift b/Nynja/Modules/InterpretationType/InterpretationTypePresenter.swift new file mode 100644 index 0000000000000000000000000000000000000000..99e2ea5f256e0e018be3e692edf962dd9dd6d162 --- /dev/null +++ b/Nynja/Modules/InterpretationType/InterpretationTypePresenter.swift @@ -0,0 +1,34 @@ +// +// InterpretationTypePresenter.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class InterpretationTypePresenter: BasePresenter, InterpretationTypePresenterProtocol, InterpretationTypeInteractorOutputProtocol { + + weak var view: InterpretationTypeViewProtocol! + var interactor: InterpretationTypeInteractorInputProtocol! + var wireFrame: InterpretationTypeWireFrameProtocol! + + func showed() { + + } +} + +extension InterpretationTypePresenter: SetInjectable { + func inject(dependencies: InterpretationTypePresenter.Dependencies) { + self.view = dependencies.view + self.interactor = dependencies.interactor + self.wireFrame = dependencies.wireFrame + } + + struct Dependencies { + let view: InterpretationTypeViewProtocol + let interactor: InterpretationTypeInteractorInputProtocol + let wireFrame: InterpretationTypeWireFrameProtocol + } +} diff --git a/Nynja/Modules/InterpretationType/InterpretationTypeProtocols.swift b/Nynja/Modules/InterpretationType/InterpretationTypeProtocols.swift new file mode 100644 index 0000000000000000000000000000000000000000..2bb64d5c32c2b55606f638f4fbcc1549daf2387e --- /dev/null +++ b/Nynja/Modules/InterpretationType/InterpretationTypeProtocols.swift @@ -0,0 +1,56 @@ +// +// InterpretationTypeProtocols.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +protocol InterpretationTypeWireFrameProtocol: class { + + func presentInterpretationType(navigation: UINavigationController, delegate: SelectInterpretationTypeDelegate) + + /** + * Add here your methods for communication PRESENTER -> WIREFRAME + */ + func dismiss() +} + +protocol InterpretationTypeViewProtocol: class { + var presenter: InterpretationTypePresenterProtocol! { get set } + /** + * Add here your methods for communication PRESENTER -> VIEW + */ +} + +protocol InterpretationTypePresenterProtocol: BasePresenterProtocol { + + var view: InterpretationTypeViewProtocol! { get set } + var interactor: InterpretationTypeInteractorInputProtocol! { get set } + var wireFrame: InterpretationTypeWireFrameProtocol! { get set } + var delegate: SelectInterpretationTypeDelegate? { get set } + /** + * Add here your methods for communication VIEW -> PRESENTER + */ + + func selectItem(type: InterpretationType) + func dismiss() +} + +protocol InterpretationTypeInteractorOutputProtocol: class { + + /** + * Add here your methods for communication INTERACTOR -> PRESENTER + */ +} + +protocol InterpretationTypeInteractorInputProtocol: class { + + var presenter: InterpretationTypeInteractorOutputProtocol! { get set } + + /** + * Add here your methods for communication PRESENTER -> INTERACTOR + */ +} diff --git a/Nynja/Modules/InterpretationType/InterpretationTypeViewController.swift b/Nynja/Modules/InterpretationType/InterpretationTypeViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..f1196dcabb7cf9d970e8fef31d3456926891d359 --- /dev/null +++ b/Nynja/Modules/InterpretationType/InterpretationTypeViewController.swift @@ -0,0 +1,45 @@ +// +// InterpretationTypeViewController.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + +final class InterpretationTypeViewController: BaseVC, InterpretationTypeViewProtocol { + + var presenter: InterpretationTypePresenterProtocol! { + didSet { + _presenter = presenter + } + } + + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + presenter.showed() + } + + + // MARK: - UI Setup + + private func setupUI() { + } + +} + +extension InterpretationTypeViewController: SetInjectable { + func inject(dependencies: InterpretationTypeViewController.Dependencies) { + self.presenter = dependencies.presenter + } + + struct Dependencies { + let presenter: InterpretationTypePresenterProtocol + } +} diff --git a/Nynja/Modules/InterpretationType/InterpretationTypeWireFrame.swift b/Nynja/Modules/InterpretationType/InterpretationTypeWireFrame.swift new file mode 100644 index 0000000000000000000000000000000000000000..65fb6d8528d8c58f7029be0c4070df391e5da773 --- /dev/null +++ b/Nynja/Modules/InterpretationType/InterpretationTypeWireFrame.swift @@ -0,0 +1,35 @@ +// +// InterpretationTypeWireFrame.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +final class InterpretationTypeWireFrame: InterpretationTypeWireFrameProtocol { + + weak var navigation: UINavigationController? + + func presentInterpretationType(navigation: UINavigationController) { + self.navigation = navigation + + let view = InterpretationTypeViewController() + let presenter = InterpretationTypePresenter() + let interactor = InterpretationTypeInteractor() + + // Connecting + let viewDependencies = InterpretationTypeViewController.Dependencies(presenter: presenter) + view.inject(dependencies: viewDependencies) + let presenterDependencies = InterpretationTypePresenter.Dependencies(view: view, interactor: interactor, wireFrame: self) + presenter.inject(dependencies: presenterDependencies) + let interactorDependencies = InterpretationTypeInteractor.Dependencies(presenter: presenter) + interactor.inject(dependencies: interactorDependencies) + + view.modalTransitionStyle = .crossDissolve + view.modalPresentationStyle = .overCurrentContext + navigation.pushViewController(view as UIViewController, animated: true) + } + +} diff --git a/Nynja/Modules/InterpretationType/Presenter/InterpretationTypePresenter.swift b/Nynja/Modules/InterpretationType/Presenter/InterpretationTypePresenter.swift new file mode 100644 index 0000000000000000000000000000000000000000..bfdaf80e575bc3eaeb939e14e5d6af41ac63cc0a --- /dev/null +++ b/Nynja/Modules/InterpretationType/Presenter/InterpretationTypePresenter.swift @@ -0,0 +1,43 @@ +// +// InterpretationTypePresenter.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class InterpretationTypePresenter: BasePresenter, InterpretationTypePresenterProtocol, InterpretationTypeInteractorOutputProtocol { + + + weak var view: InterpretationTypeViewProtocol! + var interactor: InterpretationTypeInteractorInputProtocol! + var wireFrame: InterpretationTypeWireFrameProtocol! + weak var delegate: SelectInterpretationTypeDelegate? + + func selectItem(type: InterpretationType) { + self.delegate?.typeSelected(type) + self.wireFrame.dismiss() + } + + func dismiss() { + self.wireFrame.dismiss() + } +} + +extension InterpretationTypePresenter: SetInjectable { + func inject(dependencies: InterpretationTypePresenter.Dependencies) { + self.view = dependencies.view + self.interactor = dependencies.interactor + self.wireFrame = dependencies.wireFrame + self.delegate = dependencies.delegate + } + + struct Dependencies { + let view: InterpretationTypeViewProtocol + let interactor: InterpretationTypeInteractorInputProtocol + let wireFrame: InterpretationTypeWireFrameProtocol + let delegate: SelectInterpretationTypeDelegate + } +} diff --git a/Nynja/Modules/InterpretationType/View/InterpretationTypeViewController.swift b/Nynja/Modules/InterpretationType/View/InterpretationTypeViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..03e291930a37df26bced919ce8ca26b4c2bfdbf9 --- /dev/null +++ b/Nynja/Modules/InterpretationType/View/InterpretationTypeViewController.swift @@ -0,0 +1,83 @@ +// +// InterpretationTypeViewController.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + +final class InterpretationTypeViewController: BaseVC, InterpretationTypeViewProtocol { + + var presenter: InterpretationTypePresenterProtocol! { + didSet { + _presenter = presenter + } + } + + private var tableDataSource: InterpretationTypeTableDataSource! + private var tableDelegate: InterpretationTypeTableDelegate! + + private lazy var tableView: UITableView = { + let table = UITableView() + table.backgroundColor = .clear + table.separatorStyle = .none + table.bounces = false + table.register(viewModel: InterpretationTypeCellModel.self) + + self.view.addSubview(table) + table.snp.makeConstraints({ (make) in + make.top.equalTo(navigationView.snp.bottom) + make.left.right.bottom.equalToSuperview() + }) + return table + }() + + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + } + + // MARK: - UI Setup + + private func setupUI() { + self.navigationView.configure(config: NavigationView.Config( + isVisibleSeparator: navigationView.isSeparatorVisible ?? false, + isVisibleBackButton: true, + title: title, + navigationHandler: self, + backButtonImage: UIImage(named:"ic_back_navigation"))) + self.screenTitle = "interpretation_type".localized.uppercased() + self.tableView.dataSource = self.tableDataSource + self.tableView.delegate = self.tableDelegate + + tableDelegate.selectAction = { [unowned self] type in + self.presenter.selectItem(type: type) + } + } +} + +extension InterpretationTypeViewController: NavigationProtocol { + func back() { + self.presenter.dismiss() + } +} + +extension InterpretationTypeViewController: SetInjectable { + func inject(dependencies: InterpretationTypeViewController.Dependencies) { + self.presenter = dependencies.presenter + self.tableDataSource = dependencies.tableDataSource + self.tableDelegate = dependencies.tableDelegate + } + + struct Dependencies { + let presenter: InterpretationTypePresenterProtocol + let tableDataSource: InterpretationTypeTableDataSource + let tableDelegate: InterpretationTypeTableDelegate + } +} diff --git a/Nynja/Modules/InterpretationType/View/TableView/Cell/InterpretationTypeCell.swift b/Nynja/Modules/InterpretationType/View/TableView/Cell/InterpretationTypeCell.swift new file mode 100644 index 0000000000000000000000000000000000000000..730cf08b92757ca88392bb651205e765b9bfd9fb --- /dev/null +++ b/Nynja/Modules/InterpretationType/View/TableView/Cell/InterpretationTypeCell.swift @@ -0,0 +1,121 @@ +// +// InterpretationTypeCell.swift +// Nynja +// +// Created by Roman Chopovenko on 7/28/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +class InterpretationTypeCell: UITableViewCell { + + lazy var titleLabel: UILabel = { + let label = UILabel() + label.isUserInteractionEnabled = true + label.font = Text.defaultWhite.font + label.textColor = Text.defaultWhite.color + label.setContentHuggingPriority(.defaultHigh, for: .vertical) + self.addSubview(label) + + label.snp.makeConstraints({ (make) in + make.top.equalToSuperview().offset(Constarints.titleLabel.topPadding) + make.left.equalToSuperview().offset(Constarints.sidePadding) + make.right.equalToSuperview().offset(-Constarints.sidePadding) + make.height.equalTo(Constarints.titleLabel.height) + make.bottom.equalTo(self.descriptionLabel.snp.top) + + }) + return label + }() + + lazy var descriptionLabel: UILabel = { + let label = UILabel() + label.isUserInteractionEnabled = true + label.numberOfLines = 0 + label.font = Text.defaultGray.font + label.textColor = Text.defaultGray.color + + self.addSubview(label) + + label.snp.makeConstraints({ (make) in + make.left.equalToSuperview().offset(Constarints.sidePadding) + make.right.equalToSuperview().offset(-Constarints.sidePadding) + make.bottom.equalTo(self.priceTitleLabel.snp.top).offset(-Constarints.descriptionLabel.bottomPadding) + make.bottom.equalTo(self.priceValueLabel.snp.top).offset(-Constarints.descriptionLabel.bottomPadding) + }) + return label + }() + + lazy var priceTitleLabel: UILabel = { + let label = UILabel() + label.isUserInteractionEnabled = true + label.text = "price_per_minute".localized + label.font = Text.defaultWhite.font + label.textColor = Text.defaultWhite.color + + self.addSubview(label) + + label.snp.makeConstraints({ (make) in + make.height.equalTo(Constarints.priceTitleLabel.height) + make.left.equalToSuperview().offset(Constarints.sidePadding) + make.right.equalTo(self.priceValueLabel.snp.left).offset(-Constarints.priceTitleLabel.rightPadding) + make.bottom.equalTo(self.separator.snp.top).offset(-Constarints.priceTitleLabel.bottomPadding) + }) + return label + }() + + lazy var priceValueLabel: UILabel = { + let label = UILabel() + label.isUserInteractionEnabled = true + label.font = Text.defaultRed.font + label.textColor = Text.defaultRed.color + + self.addSubview(label) + + label.snp.makeConstraints({ (make) in + make.height.equalTo(Constarints.priceTitleLabel.height) + make.width.equalTo(Constarints.priceValueLabel.width).priority(200) + make.right.equalToSuperview().offset(-Constarints.sidePadding) + }) + return label + }() + + lazy var separator: UIView = { + let separator = UIView() + + separator.backgroundColor = Constants.colors.separatorGrayColor.getColor() + + self.addSubview(separator) + separator.snp.makeConstraints({ (make) in + make.height.equalTo(Constarints.separatorHeight) + make.left.equalToSuperview().offset(Constarints.sidePadding) + make.right.equalToSuperview().offset(-Constarints.sidePadding) + make.bottom.equalToSuperview() + }) + return separator + }() + + // MARK: Init + override init(style: UITableViewCellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + baseSetup() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + baseSetup() + } + + private func baseSetup() { + self.backgroundColor = UIColor.clear + self.selectionStyle = .none + } + + // MARK: ConfigurableCell + func setup(with type: InterpretationType) { + self.titleLabel.text = type.localized + self.descriptionLabel.text = type.description + self.priceValueLabel.text = type.price.formattedCurrency + } +} diff --git a/Nynja/Modules/InterpretationType/View/TableView/Cell/InterpretationTypeCellLayout.swift b/Nynja/Modules/InterpretationType/View/TableView/Cell/InterpretationTypeCellLayout.swift new file mode 100644 index 0000000000000000000000000000000000000000..89c87015df6c3af4afc4e8c0e0eec0ee87ca71a0 --- /dev/null +++ b/Nynja/Modules/InterpretationType/View/TableView/Cell/InterpretationTypeCellLayout.swift @@ -0,0 +1,65 @@ +// +// InterpretationTypeCellLayout.swift +// Nynja +// +// Created by Roman Chopovenko on 7/28/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +extension InterpretationTypeCell { + struct Constarints { + + static let sidePadding = 16.adjustedByWidth + static let separatorHeight = 1 + + struct titleLabel { + static let topPadding = 8.adjustedByWidth + static let height = Text.defaultWhite.height + static let bottomPadding = 16.adjustedByWidth + } + + struct descriptionLabel { + static let bottomPadding = 8.adjustedByWidth + } + + struct priceTitleLabel { + static let height = Text.defaultWhite.height + static let rightPadding = 8.adjustedByWidth + static let bottomPadding = 8.adjustedByWidth + } + + struct priceValueLabel { + static let height = Text.defaultRed.height + static let width = 50.adjustedByWidth + static let rightPadding = 8.adjustedByWidth + } + + static var sumHeight: CGFloat { + let height = titleLabel.topPadding + titleLabel.height + titleLabel.bottomPadding + descriptionLabel.bottomPadding + priceTitleLabel.height + priceTitleLabel.bottomPadding + separatorHeight + return CGFloat(height) + } + } + + // MARK: Text + struct Text { + struct defaultWhite { + static let height = 22.adjustedByWidth + static let font = UIFont(fontName: Constants.fonts.medium, height: CGFloat(height)) + static let color = Constants.colors.white.getColor() + } + + struct defaultGray { + static let height = 20.adjustedByWidth + static let font = UIFont(fontName: Constants.fonts.regular, height: CGFloat(height)) + static let color = Constants.colors.gray.getColor() + } + + struct defaultRed { + static let height = 22.adjustedByWidth + static let font = UIFont(fontName: Constants.fonts.medium, height: CGFloat(height)) + static let color = Constants.colors.red.getColor() + } + } +} diff --git a/Nynja/Modules/InterpretationType/View/TableView/Cell/InterpretationTypeCellModel.swift b/Nynja/Modules/InterpretationType/View/TableView/Cell/InterpretationTypeCellModel.swift new file mode 100644 index 0000000000000000000000000000000000000000..2a28e11d31796c94ec1549ad063ea5497144ff9e --- /dev/null +++ b/Nynja/Modules/InterpretationType/View/TableView/Cell/InterpretationTypeCellModel.swift @@ -0,0 +1,25 @@ +// +// InterpretationTypeCellModel.swift +// Nynja +// +// Created by Roman Chopovenko on 7/28/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import NynjaUIKit + +final class InterpretationTypeCellModel: CellViewModel { + var accessibilityIdentifier: String { + return "InterpretationTypeCell" + } + + let type: InterpretationType + + init(type: InterpretationType) { + self.type = type + } + + func setup(cell: InterpretationTypeCell) { + cell.setup(with: type) + } +} diff --git a/Nynja/Modules/InterpretationType/View/TableView/InterpretationTypeTableDataSource.swift b/Nynja/Modules/InterpretationType/View/TableView/InterpretationTypeTableDataSource.swift new file mode 100644 index 0000000000000000000000000000000000000000..3686b39b375295506bd8a0de344426c4ba013724 --- /dev/null +++ b/Nynja/Modules/InterpretationType/View/TableView/InterpretationTypeTableDataSource.swift @@ -0,0 +1,29 @@ +// +// InterpretationTypeTableDataSource.swift +// Nynja +// +// Created by Roman Chopovenko on 7/28/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +class InterpretationTypeTableDataSource: NSObject, UITableViewDataSource { + + var rows: [InterpretationType] = [.general, .technology, .legal, .medical] + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return rows.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + return tableView.dequeueReusableCell(withModel: self.typeModel(at: indexPath), for: indexPath) + + } + + private func typeModel(at indexPath: IndexPath) -> InterpretationTypeCellModel { + let type = self.rows[indexPath.row] + return InterpretationTypeCellModel(type: type) + } + +} diff --git a/Nynja/Modules/InterpretationType/View/TableView/InterpretationTypeTableDelegate.swift b/Nynja/Modules/InterpretationType/View/TableView/InterpretationTypeTableDelegate.swift new file mode 100644 index 0000000000000000000000000000000000000000..d8ec049a6353b171f8fa5e46e15a83b132414a7c --- /dev/null +++ b/Nynja/Modules/InterpretationType/View/TableView/InterpretationTypeTableDelegate.swift @@ -0,0 +1,38 @@ +// +// InterpretationTypeTableDelegate.swift +// Nynja +// +// Created by Roman Chopovenko on 7/28/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +class InterpretationTypeTableDelegate: NSObject, UITableViewDelegate { + typealias SelectAction = (InterpretationType) -> Void + + var selectAction: SelectAction? + + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let item = self.self.getCurrentType(tableView: tableView, indexPath: indexPath) else { return } + selectAction?(item) + } + + func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { + return self.getRowHeight(tableView: tableView, indexPath: indexPath) + } + + func getRowHeight(tableView: UITableView, indexPath: IndexPath) -> CGFloat { + guard let item = self.getCurrentType(tableView: tableView, indexPath: indexPath) else { return 0 } + let width = tableView.frame.size.width - (2 * CGFloat(InterpretationTypeCell.Constarints.sidePadding)) + let heightDescription = item.description.height(withConstrainedWidth: width, font: InterpretationTypeCell.Text.defaultGray.font!) + + return InterpretationTypeCell.Constarints.sumHeight + heightDescription + } + + private func getCurrentType(tableView: UITableView, indexPath: IndexPath) -> InterpretationType? { + guard let ds = tableView.dataSource as? InterpretationTypeTableDataSource else { return nil } + let item = ds.rows[indexPath.row] + return item + } +} diff --git a/Nynja/Modules/InterpretationType/Wireframe/InterpretationTypeWireFrame.swift b/Nynja/Modules/InterpretationType/Wireframe/InterpretationTypeWireFrame.swift new file mode 100644 index 0000000000000000000000000000000000000000..c546098a656ee703fe771943af9c14fddcab0875 --- /dev/null +++ b/Nynja/Modules/InterpretationType/Wireframe/InterpretationTypeWireFrame.swift @@ -0,0 +1,43 @@ +// +// InterpretationTypeWireFrame.swift +// Nynja +// +// Created by Roman Chopovenko on 7/26/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +final class InterpretationTypeWireFrame: InterpretationTypeWireFrameProtocol { + + weak var navigation: UINavigationController? + + func presentInterpretationType(navigation: UINavigationController, delegate: SelectInterpretationTypeDelegate) { + self.navigation = navigation + + let view = InterpretationTypeViewController() + let presenter = InterpretationTypePresenter() + let interactor = InterpretationTypeInteractor() + + // Connecting + let viewDependencies = InterpretationTypeViewController.Dependencies(presenter: presenter, + tableDataSource: InterpretationTypeTableDataSource(), + tableDelegate:InterpretationTypeTableDelegate()) + view.inject(dependencies: viewDependencies) + let presenterDependencies = InterpretationTypePresenter.Dependencies(view: view, + interactor: interactor, + wireFrame: self, + delegate: delegate) + presenter.inject(dependencies: presenterDependencies) + let interactorDependencies = InterpretationTypeInteractor.Dependencies(presenter: presenter) + interactor.inject(dependencies: interactorDependencies) + + view.modalTransitionStyle = .crossDissolve + view.modalPresentationStyle = .overCurrentContext + navigation.pushViewController(view as UIViewController, animated: true) + } + + func dismiss() { + self.navigation?.popViewController(animated: true) + } +} diff --git a/Nynja/Modules/Main/Interactor/MainInteractor.swift b/Nynja/Modules/Main/Interactor/MainInteractor.swift index f587c50d26852e966560197e8e441f5599a829eb..0f24383c634f7ec42272c16b138fac5b5ddaba94 100644 --- a/Nynja/Modules/Main/Interactor/MainInteractor.swift +++ b/Nynja/Modules/Main/Interactor/MainInteractor.swift @@ -8,7 +8,7 @@ import SDWebImage -class MainInteractor: MainInteractorInputProtocol, VoxServiceDelegate, EditPhotoDelegate, MQTTServiceDelegate { +class MainInteractor: MainInteractorInputProtocol, EditPhotoDelegate, MQTTServiceDelegate { func startRinging() { @@ -64,18 +64,10 @@ class MainInteractor: MainInteractorInputProtocol, VoxServiceDelegate, EditPhoto cleanServices() } - func setVideoView(view: UIView?) { - VoxService.sharedInstance.remoteView = view - } - func deleteAccount() { if let phone = StorageService.sharedInstance.phone, phone != "" { MQTTService.sharedInstance.deleteUser(number: phone) } - - VoxService.sharedInstance.voxClient.disconnect() - VoxService.sharedInstance.isCallInProgress = false - cleanServices() } diff --git a/Nynja/Modules/Main/MainProtocols.swift b/Nynja/Modules/Main/MainProtocols.swift index e038bed8e1f76dc3a19de16dc6422f16e4ca5fa3..17dc901021d1a958dae981b5f0984d581d1fdb9f 100644 --- a/Nynja/Modules/Main/MainProtocols.swift +++ b/Nynja/Modules/Main/MainProtocols.swift @@ -7,7 +7,6 @@ // import UIKit -import VoxImplant protocol WheelOutProtocol: class { func setEnableDoubleTap(enabled: Bool) @@ -94,6 +93,7 @@ protocol MainWireFrameProtocol: class { func getRecentsLocation() -> [LocationType] func getStarredLocation() -> [LocationType] func getRecentsMedia() -> [Media] + func openMarketplace() // Group func showAddParticipants() @@ -231,7 +231,6 @@ protocol MainInteractorInputProtocol: class { func call(name: String) func videoCall(name: String) func logout() - func setVideoView(view: UIView?) func deleteAccount() func updateAvatar(url: URL) func saveLogoutState() diff --git a/Nynja/Modules/Main/Presenter/MainPresenter.swift b/Nynja/Modules/Main/Presenter/MainPresenter.swift index 374f50643fddad96b0d8a24041bbadd8670e5ecf..d47d42ab1c7277e432e3d2652c6861a072d8c1bd 100644 --- a/Nynja/Modules/Main/Presenter/MainPresenter.swift +++ b/Nynja/Modules/Main/Presenter/MainPresenter.swift @@ -5,7 +5,6 @@ // Created by Bohdan Paliychuk on 19/07/2017. // Copyright © 2017 TecSynt Solutions. All rights reserved. // -import VoxImplant class MainPresenter: MainPresenterProtocol, MainInteractorOutputProtocol, ScheduleMessageDelegate, EditParticipantsDelegate { @@ -16,7 +15,6 @@ class MainPresenter: MainPresenterProtocol, MainInteractorOutputProtocol, Schedu weak var view: MainViewProtocol! var interactor: MainInteractorInputProtocol! var wireFrame: MainWireFrameProtocol! - weak var call: VICall? func showQRReader() { wireFrame.showQRReader() @@ -149,12 +147,13 @@ class MainPresenter: MainPresenterProtocol, MainInteractorOutputProtocol, Schedu func showMessages(contact: Contact, call: NYNCall, callVC: CallInProgressViewProtocol, isVideo: Bool) { if isVideo { - if VoxService.sharedInstance.isRemoveVideoStream { - view.showPartnerVideoViewWithPhotoURL(url: contact.avatarUrl) - } else { - let prevView = self.view.showPartnerVideoView() - self.interactor.setVideoView(view: prevView) - } + // if VoxService.sharedInstance.isRemoveVideoStream { +// TODO: ASK ANGEL +// view.showPartnerVideoViewWithPhotoURL(url: contact.avatarUrl) +// } else { +// let prevView = self.view.showPartnerVideoView() +// self.interactor.setVideoView(view: prevView) +// } } else { self.view.showReturnToCall(call: call) } diff --git a/Nynja/Modules/Main/View/MainViewController.swift b/Nynja/Modules/Main/View/MainViewController.swift index bf0ea5a49738fffa868d28336fb2a794f5bea710..09f4b1e65a44936d4b3a74ed7e396493e66195d8 100644 --- a/Nynja/Modules/Main/View/MainViewController.swift +++ b/Nynja/Modules/Main/View/MainViewController.swift @@ -9,9 +9,9 @@ import NynjaUIKit import UIKit -import VoxImplant import MobileCoreServices import AssetsLibrary +import AVFoundation class MainViewController: BaseVC, MainViewProtocol, HitTestDelegate, UINavigationControllerDelegate, WheelPreviewProtocol { private var cameraCoordinator: CameraFlowCoordinatorProtocol? diff --git a/Nynja/Modules/Main/WireFrame/MainWireframe.swift b/Nynja/Modules/Main/WireFrame/MainWireframe.swift index cadfc19e38691127e7481afb8837b3e69b653914..aa632f95085eaea0f278855606ddec2c5b1b7c77 100644 --- a/Nynja/Modules/Main/WireFrame/MainWireframe.swift +++ b/Nynja/Modules/Main/WireFrame/MainWireframe.swift @@ -7,19 +7,16 @@ // import UIKit -import VoxImplant import CoreLocation import SnapKit -class MainWireFrame: MainWireFrameProtocol, VoxServiceDelegate, NynjaCommunicatorServiceDelegate { +class MainWireFrame: MainWireFrameProtocol, NynjaCommunicatorServiceDelegate { weak var navigation : UINavigationController? weak var contentNavigation: UINavigationController! weak var view: MainViewController? weak var messageinteractor: MessageInteractor? - weak var call: VICall? - weak var external: EditParticipantsDelegate? = nil func presentMain(navigation: UINavigationController, isRegistered: Bool, checkSession: Bool = false) { @@ -73,7 +70,12 @@ class MainWireFrame: MainWireFrameProtocol, VoxServiceDelegate, NynjaCommunicato } func showContacts() { - ContactsWireFrame().presentContacts(navigation: contentNavigation, mainWireFrame: self) + PermissionManager().requestContactsPermission { [weak self] status in + guard let `self` = self, status == .authorized else { + return + } + ContactsWireFrame().presentContacts(navigation: self.contentNavigation, mainWireFrame: self) + } } func showCreateConferenceCall() { @@ -123,6 +125,12 @@ class MainWireFrame: MainWireFrameProtocol, VoxServiceDelegate, NynjaCommunicato SelectCountryWireFrame().presentSelectCountry(navigation: navigation, main: self, selectCountryDelegate: selectCountryDelegate) } } + + func openMarketplace() { + if let navigation = self.navigation { + MarketplaceWireFrame().presentMarketplace(navigation: navigation, main: self) + } + } func showAddContactByUserName() { if let navigation = self.contentNavigation { @@ -211,28 +219,6 @@ class MainWireFrame: MainWireFrameProtocol, VoxServiceDelegate, NynjaCommunicato messageinteractor?.sendTypingStatus(isTyping) } - func incomingCall(call: VICall, isVideo: Bool) { - } - - func ringing(call: VICall) { - } - - func callClosed(call: VICall, isError: Bool) { - } - - func getNameFrom(call: VICall) -> Contact { - let name = "main_undefined".localized - var contact = Contact() - - if let voxId = call.voxId, let cont = ContactDAO.findContactBy(voxId: voxId) { - contact = cont - } else { - contact.names = name - } - - return contact - } - var callInProgressVC :CallInProgressViewProtocol? var isVideo: Bool = false var isGroup: Bool = false @@ -267,7 +253,6 @@ class MainWireFrame: MainWireFrameProtocol, VoxServiceDelegate, NynjaCommunicato } func viewShowed() { - VoxService.sharedInstance.delegate = self NynjaCommunicatorService.sharedInstance.delegate = self } diff --git a/Nynja/Modules/Marketplace/MarketplaceInteractor.swift b/Nynja/Modules/Marketplace/MarketplaceInteractor.swift new file mode 100644 index 0000000000000000000000000000000000000000..8c338d31e746d3f0d35c516633bb11623c7979df --- /dev/null +++ b/Nynja/Modules/Marketplace/MarketplaceInteractor.swift @@ -0,0 +1,25 @@ +// +// MarketplaceInteractor.swift +// Nynja +// +// Created by Roman Chopovenko on 7/24/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class MarketplaceInteractor: MarketplaceInteractorInputProtocol { + + weak var presenter: MarketplaceInteractorOutputProtocol! +} + +extension MarketplaceInteractor: SetInjectable { + func inject(dependencies: MarketplaceInteractor.Dependencies) { + self.presenter = dependencies.presenter + } + + struct Dependencies { + let presenter: MarketplaceInteractorOutputProtocol + } +} + diff --git a/Nynja/Modules/Marketplace/MarketplacePresenter.swift b/Nynja/Modules/Marketplace/MarketplacePresenter.swift new file mode 100644 index 0000000000000000000000000000000000000000..9d284f264255ec8c29307ecd4d91345ad9367b97 --- /dev/null +++ b/Nynja/Modules/Marketplace/MarketplacePresenter.swift @@ -0,0 +1,42 @@ +// +// MarketplacePresenter.swift +// Nynja +// +// Created by Roman Chopovenko on 7/24/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +final class MarketplacePresenter: BasePresenter, MarketplacePresenterProtocol, MarketplaceInteractorOutputProtocol { + + weak var view: MarketplaceViewProtocol! + var interactor: MarketplaceInteractorInputProtocol! + var wireFrame: MarketplaceWireFrameProtocol! + + func showed() { + + } + + func openMarketplaceMenu(type: CircleMenuItemType) { + self.wireFrame.openMarketplaceMenu(type: type) + } + + func dismissMarketplace() { + self.wireFrame.dismissMarketplace() + } +} + +extension MarketplacePresenter: SetInjectable { + func inject(dependencies: MarketplacePresenter.Dependencies) { + self.view = dependencies.view + self.interactor = dependencies.interactor + self.wireFrame = dependencies.wireFrame + } + + struct Dependencies { + let view: MarketplaceViewProtocol + let interactor: MarketplaceInteractorInputProtocol + let wireFrame: MarketplaceWireFrameProtocol + } +} diff --git a/Nynja/Modules/Marketplace/MarketplaceProtocols.swift b/Nynja/Modules/Marketplace/MarketplaceProtocols.swift new file mode 100644 index 0000000000000000000000000000000000000000..7b44670a3c3d6ab919c18c20ba24abbf5e4af688 --- /dev/null +++ b/Nynja/Modules/Marketplace/MarketplaceProtocols.swift @@ -0,0 +1,60 @@ +// +// MarketplaceProtocols.swift +// Nynja +// +// Created by Roman Chopovenko on 7/24/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +protocol MarketplaceWireFrameProtocol: class { + + func presentMarketplace(navigation: UINavigationController, main: MainWireFrame?) + + /** + * Add here your methods for communication PRESENTER -> WIREFRAME + */ + func openMarketplaceMenu(type: CircleMenuItemType) + func dismissMarketplace() +} + +protocol MarketplaceViewProtocol: class { + + var presenter: MarketplacePresenterProtocol! { get set } + + /** + * Add here your methods for communication PRESENTER -> VIEW + */ +} + +protocol MarketplacePresenterProtocol: BasePresenterProtocol { + + var view: MarketplaceViewProtocol! { get set } + var interactor: MarketplaceInteractorInputProtocol! { get set } + var wireFrame: MarketplaceWireFrameProtocol! { get set } + + /** + * Add here your methods for communication VIEW -> PRESENTER + */ + + func showed() + func openMarketplaceMenu(type: CircleMenuItemType) + func dismissMarketplace() +} + +protocol MarketplaceInteractorOutputProtocol: class { + + /** + * Add here your methods for communication INTERACTOR -> PRESENTER + */ +} + +protocol MarketplaceInteractorInputProtocol: class { + + var presenter: MarketplaceInteractorOutputProtocol! { get set } + + /** + * Add here your methods for communication PRESENTER -> INTERACTOR + */ +} diff --git a/Nynja/Modules/Marketplace/MarketplaceViewController.swift b/Nynja/Modules/Marketplace/MarketplaceViewController.swift new file mode 100644 index 0000000000000000000000000000000000000000..c630d082af795dce95acb7230ba1ea0dcbd79844 --- /dev/null +++ b/Nynja/Modules/Marketplace/MarketplaceViewController.swift @@ -0,0 +1,93 @@ +// +// MarketplaceViewController.swift +// Nynja +// +// Created by Roman Chopovenko on 7/24/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit +import SnapKit + +final class MarketplaceViewController: BaseVC, MarketplaceViewProtocol { + + var presenter: MarketplacePresenterProtocol! { + didSet { + _presenter = presenter + } + } + + private let initialMenuSet: CircleMenuSet = CircleMenuFactory().marketplace + + lazy var circleMenu: CircleMenu = { + let menu = CircleMenu(rect: .zero, delegate: self, menuSet: initialMenuSet) + self.view.addSubview(menu) + + let horizontalPaddingsSum: CGFloat = 2 * 16 + let side = (self.view.bounds.width - horizontalPaddingsSum) + + menu.snp.makeConstraints({ (make) in + make.centerX.centerY.equalToSuperview() + make.width.height.equalTo(side) + }) + return menu + }() + + + // MARK: - Life Cycle + + override func viewDidLoad() { + super.viewDidLoad() + + setupUI() + presenter.showed() + + self.navigationView.configure(config: NavigationView.Config( + isVisibleSeparator: navigationView.isSeparatorVisible ?? false, + isVisibleBackButton: true, + title: title, + navigationHandler: self, + backButtonImage: UIImage(named:"ic_back_navigation"))) + self.screenTitle = self.initialMenuSet.title + } + + + // MARK: - UI Setup + + private func setupUI() { + self.circleMenu.isHidden = false + } +} + +extension MarketplaceViewController: SetInjectable { + func inject(dependencies: MarketplaceViewController.Dependencies) { + self.presenter = dependencies.presenter + } + + struct Dependencies { + let presenter: MarketplacePresenterProtocol + } +} + +extension MarketplaceViewController: CircleMenuDelegate { + func didSelectItem(_ menu: CircleMenu, type: CircleMenuItemType) { + guard let menuSet = CircleMenuFactory().allElementsDictionary[type] else { + self.presenter.openMarketplaceMenu(type: type) + return + } + menu.updateMenu(with: menuSet) + } + + func didChangeCurrentSet(_ menu: CircleMenu, title: String) { + self.screenTitle = title + } +} + +extension MarketplaceViewController: NavigationProtocol { + func back() { + let canNavigateBackInsideControl = self.circleMenu.navigateBackInMenuStack() + if canNavigateBackInsideControl == false { + self.presenter.dismissMarketplace() + } + } +} diff --git a/Nynja/Modules/Marketplace/MarketplaceWireFrame.swift b/Nynja/Modules/Marketplace/MarketplaceWireFrame.swift new file mode 100644 index 0000000000000000000000000000000000000000..b8df785b11a130cacd046261703355ca5aa120d4 --- /dev/null +++ b/Nynja/Modules/Marketplace/MarketplaceWireFrame.swift @@ -0,0 +1,50 @@ +// +// MarketplaceWireFrame.swift +// Nynja +// +// Created by Roman Chopovenko on 7/24/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +final class MarketplaceWireFrame: MarketplaceWireFrameProtocol { + + weak var navigation: UINavigationController? + weak var main: MainWireFrame? + + func presentMarketplace(navigation: UINavigationController, main: MainWireFrame?) { + self.navigation = navigation + self.main = main + + let view = MarketplaceViewController() + let presenter = MarketplacePresenter() + let interactor = MarketplaceInteractor() + + // Connecting + let viewDependencies = MarketplaceViewController.Dependencies(presenter: presenter) + view.inject(dependencies: viewDependencies) + let presenterDependencies = MarketplacePresenter.Dependencies(view: view, interactor: interactor, wireFrame: self) + presenter.inject(dependencies: presenterDependencies) + let interactorDependencies = MarketplaceInteractor.Dependencies(presenter: presenter) + interactor.inject(dependencies: interactorDependencies) + + view.modalTransitionStyle = .crossDissolve + view.modalPresentationStyle = .overCurrentContext + navigation.pushViewController(view as UIViewController, animated: true) + } + + func openMarketplaceMenu(type: CircleMenuItemType) { + guard let navigation = self.navigation else { return } + switch type { + case .interpretation: + InterpretationWireFrame().presentInterpretation(navigation: navigation) + default: + break + } + } + + func dismissMarketplace() { + navigation?.popToRootViewController(animated: true) + } +} diff --git a/Nynja/Modules/Message/Interactor/MessageInteractor+Fetch.swift b/Nynja/Modules/Message/Interactor/MessageInteractor+Fetch.swift index ea2f35beed8e0fe51539a366b49f018bce8aaa67..66b480fc7574265d9bce4aa7add82d1e4f129690 100644 --- a/Nynja/Modules/Message/Interactor/MessageInteractor+Fetch.swift +++ b/Nynja/Modules/Message/Interactor/MessageInteractor+Fetch.swift @@ -30,12 +30,18 @@ extension MessageInteractor { return nil } - func fetchCheckpoint() { + private func fetchCheckpoint() { if let feed = self.feed { self.checkpoint = ChatCheckpointDAO.fetchCheckpoint(for: feed) } } + func fetchPenultimateMessage() { + if let type = fetchType { + penultimateMessage = MessageDAO.fetchPenultimateMessage(of: type) + } + } + // MARK: - Fetch Chat Model func fetchRoomFromStorage() { if let r = room, let id = r.id, let room = RoomDAO.findRoom(by: id) { @@ -50,40 +56,26 @@ extension MessageInteractor { // MARK: - Fetch History func fetchFromStorage() { - if let contact = self.contact { - self.feed = FeedDAO.fetchP2p(for: contact.phoneId) - fetchCheckpoint() - } else if let room = self.room, let roomId = room.id { - self.feed = FeedDAO.fetchMuc(for: roomId) - fetchCheckpoint() - } + prepareFeedAndCheckpoint() guard let type = self.fetchType else { - //TODO: - Error return } - let messages = MessageDAO.fetchMessages(type) - + let messages = fetchMessagesFromStorage(for: type) self.configuration.messages = messages - self.lastMessage = messages.last - self.prepareMessagesToUsing(type) } func fetchNewFromStorage() { - if let contact = self.contact { - self.feed = FeedDAO.fetchP2p(for: contact.phoneId) - fetchCheckpoint() - } else if let room = self.room, let roomId = room.id { - self.feed = FeedDAO.fetchMuc(for: roomId) - fetchCheckpoint() - } + prepareFeedAndCheckpoint() - guard let type = fetchType else { return } + guard let type = fetchType else { + return + } let oldMessages = configuration.messages - var newMessages = MessageDAO.fetchMessages(type) + var newMessages = fetchMessagesFromStorage(for: type) if let startID = newMessages.first?.msg_id, oldMessages.index(where: { $0.msg_id == startID }) != nil { // If all history fetched and the first message already exists in local data source. @@ -107,6 +99,35 @@ extension MessageInteractor { } } + private func prepareFeedAndCheckpoint() { + if let contact = self.contact { + self.feed = FeedDAO.fetchP2p(for: contact.phoneId) + } else if let room = self.room, let roomId = room.id { + self.feed = FeedDAO.fetchMuc(for: roomId) + } + + fetchCheckpoint() + } + + private func fetchMessagesFromStorage(for type: FetchType) -> [Message] { + return MessageDAO + .fetchMessages(type) + .filter { message in + guard let desc = message.files?.first, + desc.mime == SendMessageType.audioCall.rawValue else { + return true + } + + guard desc.data?.first?.key == FeatureKeys.File.Call.users.rawValue, + let ids = desc.data?.first?.value?.splitByComma(), + let phoneId = storageService.phoneId else { + return false + } + + return ids.contains { $0 == phoneId } + } + } + // MARK: - Setup Configuration private func prepareMessagesToUsing(_ type: FetchType, isNew: Bool = false) { // Auto download/upload diff --git a/Nynja/Modules/Message/Interactor/MessageInteractor+StorageSubscriber.swift b/Nynja/Modules/Message/Interactor/MessageInteractor+StorageSubscriber.swift index 759c8aa946b243d2c9b15403e786cb63e42f0caf..530802fb8daeeafcd6a401df7cdb56e33c76a1d6 100644 --- a/Nynja/Modules/Message/Interactor/MessageInteractor+StorageSubscriber.swift +++ b/Nynja/Modules/Message/Interactor/MessageInteractor+StorageSubscriber.swift @@ -16,7 +16,7 @@ extension MessageInteractor { } else if let room = updatedRoom(with: info, type: type) { handleUpdate(room: room) } else if let message = changedMessage(with: info, type: type) { - if info.kind == .delete { + if message.statusString == "deleted" { handleMessageDelete(message) } else { handleMessageInsertOrUpdate(message) @@ -114,6 +114,10 @@ extension MessageInteractor { } private func handleMessageInsertOrUpdate(_ message: Message) { + guard !["delete", "edit"].contains(message.statusString ?? "") else { + return + } + if let contact = self.contact, let phoneId = myContact?.phoneId { let p2pFeed = p2p(firstId: phoneId, secondId: contact.phoneId) @@ -167,7 +171,6 @@ extension MessageInteractor { } private func handleNewMessage(_ message: Message) { - lastMessage = message configuration.messages.append(message) var config: MessageConfiguration diff --git a/Nynja/Modules/Message/Interactor/MessageInteractor+Utils.swift b/Nynja/Modules/Message/Interactor/MessageInteractor+Utils.swift index a6ecf967043af6c6f541af116d58d1fb399d7545..7350d048b0f09717f89faaeb7d305cfabe88f745 100644 --- a/Nynja/Modules/Message/Interactor/MessageInteractor+Utils.swift +++ b/Nynja/Modules/Message/Interactor/MessageInteractor+Utils.swift @@ -8,7 +8,7 @@ extension MessageInteractor { - func nextMessage(from serverId: Int64) -> Message? { + func nextMessage(from serverId: MessageServerId) -> Message? { guard let index = configuration.messages.index(where: { message in if let id = message.id { return id > serverId @@ -29,7 +29,7 @@ extension MessageInteractor { return nil } - func messageBy(serverId: Int64) -> Message? { + func messageBy(serverId: MessageServerId) -> Message? { if let index = indexOfMessage(with: serverId) { return configuration.messages[index] } @@ -42,7 +42,7 @@ extension MessageInteractor { return configuration.messages.index(where: { $0.msg_id == localId }) } - func indexOfMessage(with serverId: Int64) -> Int? { + func indexOfMessage(with serverId: MessageServerId) -> Int? { return configuration.messages.index(where: { $0.id == serverId }) } diff --git a/Nynja/Modules/Message/Interactor/MessageInteractor.swift b/Nynja/Modules/Message/Interactor/MessageInteractor.swift index 7b5cce973e92b4f83a151bc42ab20c6a78d1be18..2fd3df63460cddbd7bde281b26944d7bee8c045f 100644 --- a/Nynja/Modules/Message/Interactor/MessageInteractor.swift +++ b/Nynja/Modules/Message/Interactor/MessageInteractor.swift @@ -11,7 +11,7 @@ import UIKit import CoreLocation final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, HistoryHandlerDelegate, TypingHandlerDelegate, IoHandlerDelegate, ReachabilityServiceObserver, MQTTServiceDelegate, MessageProcessingDelegate, MessageHandlerSubscriber, MessageInteractorCallProtocol { - + private struct Constants { static let messagesPageSize: Int64 = -15 } @@ -51,9 +51,7 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H var room: Room? { return chat as? Room } - - var lastMessage: Message? - + var myContact: Contact? { return _contact } @@ -162,9 +160,9 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H }() var initialMessage: ChatInitialMessage? + var penultimateMessage: Message? // MARK: -- Private - private var prevLastMessageId: Int64? private lazy var _contact: Contact? = { return ContactDAO.currentContact @@ -174,7 +172,7 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H private var presenceTimer: TimerHandler? - private var originMessagesIdOfReply: [Int64] = [] + private var originMessagesIdOfReply: [MessageServerId] = [] let processingQueue = DispatchQueue(label: "\(Bundle.main.bundleIdentifier).message-interactor", qos: .default) @@ -219,15 +217,7 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H // MARK: - BaseInteractor override func loadData() { super.loadData() - - if let room = self.room, let id = room.id, let kind = room.kind { - fetchRoomFromStorage() - mqttService.getRoom(id: id, kind: kind) - fetchData() - } else { - fetchContact() - fetchData() - } + fetchData() } // MARK: - MessageInteractorInputProtocol @@ -270,7 +260,7 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H // MARK: - History - private func requestHistory(_ messageID: Int64?) { + private func requestHistory(_ messageID: MessageServerId?) { processingQueue.async { [weak self] in guard let `self` = self, let request = self.makeHistoryRequest(messageID) else { return @@ -278,8 +268,23 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H self.mqttService.sendHistoryRequest(with: request) } } + + private func requestHistory(from: MessageServerId, to: MessageServerId) { + processingQueue.async { [weak self] in + guard let `self` = self, + let phoneId = self.storageService.phoneId, + let request = try? self.historyRequestFactory.makeHistoryRequestModel(rosterId: phoneId, + chat: self.chat, + from: from, + to: to) else { + return + } + + self.mqttService.sendHistoryRequest(with: request) + } + } - private func makeHistoryRequest(_ messageID: Int64?) -> HistoryRequestModel? { + private func makeHistoryRequest(_ messageID: MessageServerId?) -> HistoryRequestModel? { guard let msgId = messageID ?? chat.last_msg?.id, let rosterId = storageService.phoneId else { return nil @@ -319,7 +324,50 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H } // MARK: - Fetch Data - func fetchData(messageID: Int64? = nil, isNew:Bool = false) { + + func fetchData() { + fetchChatModel() + fetchPenultimateMessage() + + if !hasGapInsideHistory() { + fetchMessages() + } else if let startId = chat.last_msg?.id, let endId = penultimateMessage?.id { + processingQueue.async { [weak self] in + guard let `self` = self else { return } + self.fetchFromStorage() + } + requestHistory(from: startId, to: endId) + } + } + + private func hasGapInsideHistory() -> Bool { + guard chat.last_msg?.statusString != "update" else { + return false + } + + guard let id = penultimateMessage?.id, + let lastId = chat.last_msg?.id, + id != lastId else { + return false + } + + guard let prevId = penultimateMessage?.prev else { + return true + } + + return prevId != lastId + } + + func fetchChatModel() { + if let room = self.room, let id = room.id, let kind = room.kind { + fetchRoomFromStorage() + mqttService.getRoom(id: id, kind: kind) + } else { + fetchContact() + } + } + + func fetchMessages(from messageID: MessageServerId? = nil, isNew: Bool = false) { if !isNew { processingQueue.async { [weak self] in guard let `self` = self else { return } @@ -328,19 +376,19 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H } requestHistory(messageID) } - - + + // MARK: - Sender func sender(for message: Message) -> MessageSender? { let isOwner = message.from == myContact?.phone_id if let _ = (message.feed_id as? muc), let from = message.from, let member = member(for: from) { - return MessageSender(fullname: member.fullName ?? "", nick: member.alias, avatar: member.avatar) + return MessageSender(phoneId: member.phone_id, fullname: member.fullName ?? "", nick: member.alias, avatar: member.avatar) } else if let contact = self.contact { if isOwner, let myContact = myContact { - return MessageSender(fullname: myContact.fullName ?? "", nick: myContact.nick, avatar: myContact.avatar) + return MessageSender(phoneId: myContact.phone_id, fullname: myContact.fullName ?? "", nick: myContact.nick, avatar: myContact.avatar) } else { - return MessageSender(fullname: contact.fullName ?? "", nick: contact.nick, avatar: contact.avatar) + return MessageSender(phoneId: contact.phone_id, fullname: contact.fullName ?? "", nick: contact.nick, avatar: contact.avatar) } } @@ -740,8 +788,10 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H } func deleteMessage(localId: String, forBoth: Bool) { - guard let message = messageBy(localId: localId), let messageId = message.id, let phoneId = myContact?.phoneId else { - return + guard let message = messageBy(localId: localId), + let messageId = message.id, + let phoneId = myContact?.phoneId else { + return } var messageAction: DBMessageAction @@ -771,6 +821,8 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H let messageForDelete = messageFactory.makeMessageForDelete(message: message, seenBy: [seenBy]) mqttService.sendMessage(message: messageForDelete) + + messageForDelete.status = StringAtom(string: "deleted") try? storageService.perform(action: .save, with: messageForDelete) handleMessageDelete(message) @@ -788,7 +840,7 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H repliedMessage = nil } - func processForwardMessageTap(serverId: Int64) { + func processForwardMessageTap(serverId: MessageServerId) { guard let link = MessageDAO.fetchMessage(serverId: serverId)?.link, let linkedMessage = MessageDAO.fetchMessage(serverId: link), let localId = linkedMessage.msg_id else { @@ -908,7 +960,7 @@ final class MessageInteractor: BaseInteractor, MessageInteractorInputProtocol, H } } - private func notifyAboutRead(withOld oldReader: Int64?, new newReader: Int64?) { + private func notifyAboutRead(withOld oldReader: MessageServerId?, new newReader: MessageServerId?) { guard let newReader = newReader, oldReader != newReader, let index = configuration.messages.index(where: { $0.id == newReader }), @@ -1007,7 +1059,7 @@ extension MessageInteractor { } } - private func readMessage(_ messageId: Int64) { + private func readMessage(_ messageId: MessageServerId) { guard let rosterId = storageService.phoneId else { return } @@ -1023,4 +1075,10 @@ extension MessageInteractor { assertionFailure(error.localizedDescription) } } + + func getMessageSenderContact(for sender: MessageSender) -> Contact? { + guard let phoneId = sender.phoneId else { return nil } + guard let contact = ContactDAO.findContactBy(phoneId: phoneId) else { return nil } + return contact + } } diff --git a/Nynja/Modules/Message/Presenter/MessagePresenter+MentionUnreadCounter.swift b/Nynja/Modules/Message/Presenter/MessagePresenter+MentionUnreadCounter.swift index 6741129e497b14588c53edbebe849ae79a5333b0..f15d4013b625a704529b97613935dbe2ae84abb8 100644 --- a/Nynja/Modules/Message/Presenter/MessagePresenter+MentionUnreadCounter.swift +++ b/Nynja/Modules/Message/Presenter/MessagePresenter+MentionUnreadCounter.swift @@ -13,7 +13,7 @@ extension MessagePresenter { // MARK: - Unread Counter func prepareMentionedMessages(in room: Room) { - guard let mentions = room.mentions?.compactMap({ Int64($0) }) else { + guard let mentions = room.mentions?.compactMap({ MessageServerId($0) }) else { return } for mention in mentions { @@ -22,7 +22,7 @@ extension MessagePresenter { unreadMentionIds = mentions } - func mentionsCount(after serverMessageId: Int64) -> Int { + func mentionsCount(after serverMessageId: MessageServerId) -> Int { let lastUnreadId = lastVisibleMessageId.map { max($0, serverMessageId) } ?? serverMessageId defer { lastVisibleMessageId = lastUnreadId @@ -72,7 +72,7 @@ extension MessagePresenter { lastUnreadMentionCount = 0 } - private func nextUnreadMentionIdentifier(after lastVisibleMessageId: Int64?) -> Int64? { + private func nextUnreadMentionIdentifier(after lastVisibleMessageId: MessageServerId?) -> MessageServerId? { if let lastId = lastVisibleMessageId { guard let nextUnreadMentionedMessageIndex = unreadMentionIds.index(where: { $0 > lastId }) else { return nil @@ -87,9 +87,9 @@ extension MessagePresenter { guard let mentions = room.mentions else { return } - var unique: Set = [] + var unique: Set = [] for case let mention in mentions { - guard let id = Int64(mention) else { + guard let id = MessageServerId(mention) else { continue } unique.insert(id) diff --git a/Nynja/Modules/Message/Presenter/MessagePresenter.swift b/Nynja/Modules/Message/Presenter/MessagePresenter.swift index e79f94391149517c4980088cfa2005feabe34407..bb5521ef787192202d6d57ce97ec1e37af4058b1 100644 --- a/Nynja/Modules/Message/Presenter/MessagePresenter.swift +++ b/Nynja/Modules/Message/Presenter/MessagePresenter.swift @@ -28,9 +28,9 @@ class MessagePresenter: BasePresenter, MessagePresenterProtocol, MessageInteract // -- unread mention counter - var uniqueUnreadMentionIds: Set = [] - var unreadMentionIds: [Int64] = [] - var lastVisibleMessageId: Int64? + var uniqueUnreadMentionIds: Set = [] + var unreadMentionIds: [MessageServerId] = [] + var lastVisibleMessageId: MessageServerId? var lastUnreadMentionCount = 0 { didSet { if oldValue != lastUnreadMentionCount, lastUnreadMentionCount == 0 { @@ -402,7 +402,7 @@ class MessagePresenter: BasePresenter, MessagePresenterProtocol, MessageInteract view.scrollToMessage(with: localId) } - func forwardMessageTapped(serverId: Int64) { + func forwardMessageTapped(serverId: MessageServerId) { self.interactor.processForwardMessageTap(serverId: serverId) } @@ -734,7 +734,7 @@ class MessagePresenter: BasePresenter, MessagePresenterProtocol, MessageInteract private func getCellModel(message: Message, repliedModel: RepliedMessageModel?, - readerID: Int64? = nil, + readerID: MessageServerId? = nil, links: [String]? = nil, mentions: [MentionInfo]? = nil, translation: TranslationInfo? = nil, @@ -838,7 +838,7 @@ class MessagePresenter: BasePresenter, MessagePresenterProtocol, MessageInteract return nil } - private func deliveryStatusFrom(readerID: Int64?, messageID: Int64?) -> DeliveryStatus { + private func deliveryStatusFrom(readerID: MessageServerId?, messageID: MessageServerId?) -> DeliveryStatus { switch (readerID, messageID) { case (.none, .some): return .sent case (_, .none): return .unsent @@ -955,7 +955,11 @@ class MessagePresenter: BasePresenter, MessagePresenterProtocol, MessageInteract func startSendingMessage() { view.hideReplyPreview() } - + + func handleOpponentAvatarTap(for sender: MessageSender) { + guard let contact = self.interactor.getMessageSenderContact(for: sender) else { return } + self.openSharedContact(contact: contact) + } // MARK: - Utils private func updateStatus(_ status: String) { if internetStatus == .connected { @@ -963,8 +967,8 @@ class MessagePresenter: BasePresenter, MessagePresenterProtocol, MessageInteract } } - func getPreviousMessages(id: Int64) { - self.interactor.fetchData(messageID: id, isNew: true) + func getPreviousMessages(id: MessageServerId) { + self.interactor.fetchMessages(from: id, isNew: true) } func hasRunningCall() -> Bool { @@ -974,6 +978,10 @@ class MessagePresenter: BasePresenter, MessagePresenterProtocol, MessageInteract func rejoinRunningCall() { interactor.rejoinRunningCall() } + + func openMarketplaceScreen() { + self.wireFrame.openMarketplaceScreen() + } } diff --git a/Nynja/Modules/Message/Protocols/MessageProtocols.swift b/Nynja/Modules/Message/Protocols/MessageProtocols.swift index 4ae1ebb15b659f5ca3c79688876178ba6ba40499..7f267f39a0e8677e4589f37f82a076efb4257d28 100644 --- a/Nynja/Modules/Message/Protocols/MessageProtocols.swift +++ b/Nynja/Modules/Message/Protocols/MessageProtocols.swift @@ -39,6 +39,7 @@ protocol MessageWireframeProtocol: DocumentInteractionInput { func openChat(_ chat: ChatModel, originLocalId: String?) func showLanguageSelector(input: LanguageSelector.Input) func show(alert: UIAlertController) + func openMarketplaceScreen() } //MARK: Presenter - @@ -74,7 +75,7 @@ protocol MessagePresenterProtocol: BasePresenterProtocol, func saveImageToGallery(with fileUrl: URL) func saveVideoToGallery(with fileUrl: URL) - func getPreviousMessages(id: Int64) + func getPreviousMessages(id: MessageServerId) func didCancelTapped(_ id: String) func didContentLoadingRequested(_ id: String) @@ -112,17 +113,18 @@ protocol MessagePresenterProtocol: BasePresenterProtocol, func scrollToSelectedReplyMessage() func scrollToSelectedEditMessage() - func forwardMessageTapped(serverId: Int64) + func forwardMessageTapped(serverId: MessageServerId) func clearEditMessageObject() func declineReply() func openURL(url: URL) + func handleOpponentAvatarTap(for sender: MessageSender) func hasRunningCall() -> Bool func rejoinRunningCall() - + func openMarketplaceScreen() } //MARK: Interactor - @@ -193,7 +195,7 @@ protocol MessageInteractorInputProtocol: BaseInteractorProtocol, MentionFetchInp func configure() func goAway() - func fetchData(messageID: Int64?, isNew: Bool) + func fetchMessages(from messageID: MessageServerId?, isNew: Bool) func sendTyping(_ type: TypingModelType) func sendTypingStatus(_ isTyping: Bool) @@ -224,7 +226,7 @@ protocol MessageInteractorInputProtocol: BaseInteractorProtocol, MentionFetchInp func unstarMessage(localId: String) func deleteMessage(localId: String, forBoth: Bool) func prepareToReply(localId: String) - func processForwardMessageTap(serverId: Int64) + func processForwardMessageTap(serverId: MessageServerId) func declineReply() func member(for phoneId: String) -> Member? @@ -236,6 +238,8 @@ protocol MessageInteractorInputProtocol: BaseInteractorProtocol, MentionFetchInp func editMessage(_ message: InputTextMessage) func clearEditMessageObject() func scheduleInfo(for message: InputScheduleMessage) -> ScheduleInfo? + + func getMessageSenderContact(for sender: MessageSender) -> Contact? func hasRunningCall() -> Bool func rejoinRunningCall() } @@ -263,10 +267,9 @@ protocol MessageViewProtocol: class { func showContextMenu(fromCell cell: BaseChatCell, convertingModel: ConvertionMessageModel?, targetView: UIView?) - func scrollToBottomIfNeeded() func scrollToMessage(with localId: String?) - func scrollToUnreadMessage(with index: Int) - func scrollToMessage(serverId: Int64) + func scrollToMessage(serverId: MessageServerId) + func scrollToBottomIfNeeded() func scrollToBottom() func updateHeaderStatus(_ status: String) diff --git a/Nynja/Modules/Message/View/MessageVC+CellDelegate.swift b/Nynja/Modules/Message/View/MessageVC+CellDelegate.swift index 78ff22af8b59185932c27fc75917fdb76704eb69..f4c5fe1054a1a0cd0188921fdbbe4b8777992105 100644 --- a/Nynja/Modules/Message/View/MessageVC+CellDelegate.swift +++ b/Nynja/Modules/Message/View/MessageVC+CellDelegate.swift @@ -12,11 +12,11 @@ extension MessageVC: BaseChatCellDelegate, AudioManagerDelegate, ProximitySensor // MARK: - BaseChatCellDelegate func didCellTapped(_ cell: BaseChatCell) { - guard let model = cell.model else { + guard let model = cell.model, let type = model.type else { return } - - switch model.type! { + + switch type { case .image: if let url = model.fileUrl { let contentImageView = cell.transitionImageView @@ -184,4 +184,16 @@ extension MessageVC: BaseChatCellDelegate, AudioManagerDelegate, ProximitySensor currentPlayingModel?.notifyAudioHandler() currentPlayingModel = nil } + + func updateCellIfVisible() { + if let model = currentPlayingModel { + reloadIfVisible(models: [model]) + } + } + + func opponentAvatarTapped(_ cell: BaseChatCell) { + inputBar.endEditing(true) + guard let sender = cell.model?.sender else { return } + self.presenter.handleOpponentAvatarTap(for: sender) + } } diff --git a/Nynja/Modules/Message/View/MessageVC+ContextMenuDelegate.swift b/Nynja/Modules/Message/View/MessageVC+ContextMenuDelegate.swift index 5e6be46c7abd7d60870c9c3ed0314bcc44062ffe..90875402bb0c88d5be3eafad9a35847959b96196 100644 --- a/Nynja/Modules/Message/View/MessageVC+ContextMenuDelegate.swift +++ b/Nynja/Modules/Message/View/MessageVC+ContextMenuDelegate.swift @@ -161,6 +161,6 @@ extension MessageVC: NynjaContextMenuDelegate { } private func marketPlace(menu: NynjaContextMenu, userInfo: NynjaContextMenuUserInfo?) { - + self.presenter.openMarketplaceScreen() } } diff --git a/Nynja/Modules/Message/View/MessageVC.swift b/Nynja/Modules/Message/View/MessageVC.swift index ba19708d1417bcdd5a0e7053e68b5719f474d995..db23d18357a445a17b3fac93cbc05e5c57ca79e0 100644 --- a/Nynja/Modules/Message/View/MessageVC.swift +++ b/Nynja/Modules/Message/View/MessageVC.swift @@ -18,7 +18,7 @@ fileprivate enum ButtonState { case none } -final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSwipable { +final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSwipable, CallInfoViewDelegate { var presenter: MessagePresenterProtocol! { didSet { @@ -29,11 +29,9 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw var progressDictionary = [String: [(ProgressModel)->Void]]() var loadingStatus = false - var isHideKeyboard = true - var verticalOffset: CGFloat = 0.0 - var messageDS: MessageDS! - var messageTVDelegate: MessageTableViewDelegate! + var messageDS: MessageCollectionViewDataSource! + var messageTVDelegate: MessageCollectionViewDelegate! var contextMenuPresented = false @@ -67,6 +65,12 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw static let convertionModel = "convertionModel" static let starId = "starID" } + + private let timeFormatter: DateFormatter = { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "YYYY-MM-dd" + return dateFormatter + }() // MARK: - Views private var bottomInset = Constraints.replyPreview.bottomInset @@ -94,33 +98,35 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw make.left.right.equalToSuperview() } } - - private(set) lazy var tableView: UITableView = { - let tv = UITableView() - - tv.separatorStyle = .none - tv.backgroundColor = UIColor.clear - - registerCells(for: tv) - - tv.estimatedRowHeight = 0 // NOTE: It is important for scrolling behavior. - tv.estimatedSectionHeaderHeight = 0 + + private let collectionViewLayout: UICollectionViewFlowLayout = { + let layout = MessageCollectionViewLayout() + layout.minimumLineSpacing = 0 + return layout + }() + + private let defaultCollectionViewInsets = UIEdgeInsets(top: Constraints.tableView.defaultVerticalInset, + left: 0, + bottom: Constraints.tableView.bottomInset, + right: 0).flippedVertically() + + private(set) lazy var collectionView: UICollectionView = { + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) + collectionView.backgroundColor = .clear + collectionView.alwaysBounceVertical = true + collectionView.transform = CGAffineTransform(rotationAngle: .pi) - - tv.contentInset = UIEdgeInsets( - top: Constraints.tableView.defaultVerticalInset, - left: 0, - bottom: Constraints.tableView.bottomInset, - right: 0 - ) - - self.view.addSubview(tv) - tv.snp.makeConstraints { make in + collectionView.contentInset = defaultCollectionViewInsets + + view.addSubview(collectionView) + collectionView.snp.makeConstraints { make in makeDefaultTableConstraint(make) make.bottom.equalTo(staticFooterView.snp.top) } - - return tv + + registerCells(for: collectionView) + + return collectionView }() private(set) lazy var inputBar: InputBar = { @@ -165,11 +171,6 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw self?.presenter.sendTyping(status) } - inputBar.changesHeightHandler = { [weak self] diff in - guard let `self` = self else { return } - self.tableView.contentInset.top -= diff - self.tableView.contentOffset.y += diff - } inputBar.inputTypeChangeHandler = { [weak self] inputType in self?.stickerInputState.performUpdates { state in state.inputType = inputType.toggled() @@ -229,7 +230,7 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw view.snp.makeConstraints({ (make) in make.height.equalTo(gradientHeight) make.left.right.equalToSuperview() - make.bottom.equalTo(tableView.snp.bottom) + make.bottom.equalTo(collectionView.snp.bottom) }) return view @@ -294,7 +295,7 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw view.addSubview(searchView) searchView.snp.makeConstraints { maker in maker.left.right.equalToSuperview() - maker.bottom.equalTo(tableView.snp.bottom) + maker.bottom.equalTo(collectionView.snp.bottom) } searchView.setupContentInset(to: gradientView) @@ -346,7 +347,7 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw view.addSubview(mentionPanelView) mentionPanelView.snp.makeConstraints { maker in - maker.edges.equalTo(tableView) + maker.edges.equalTo(collectionView) } return mentionPanelView @@ -369,6 +370,17 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw return counterView }() + + + // MARK: - Calls + + private weak var callInfoView: CallInfoView? { + didSet { + if callInfoView == nil { + oldValue?.removeFromSuperview() + } + } + } // MARK: - Initialize @@ -381,11 +393,11 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw audioManager.delegate = self proximityManager.delegate = self - messageDS = MessageDS(view: self) - tableView.dataSource = messageDS + messageDS = MessageCollectionViewDataSource(view: self) + collectionView.dataSource = messageDS - messageTVDelegate = MessageTableViewDelegate(view: self) - tableView.delegate = messageTVDelegate + messageTVDelegate = MessageCollectionViewDelegate(view: self) + collectionView.delegate = messageTVDelegate replyPreview.delegate = self gradientView.isHidden = false @@ -407,7 +419,7 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw mentionCounterView.isHidden = true stickerSearchResultView.isHidden = true - messageTVDelegate.rejoinBannerDisplayed = presenter.hasRunningCall() + displayRejoinBanner(display: presenter.hasRunningCall(), count: 0) } deinit { @@ -418,21 +430,22 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw super.viewDidAppear(animated) viewVisible = true - NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: .UIApplicationWillEnterForeground, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(didEnterBackground), name: .UIApplicationDidEnterBackground, object: nil) + let center = NotificationCenter.default + center.addObserver(self, selector: #selector(willEnterForeground), name: .UIApplicationWillEnterForeground, object: nil) + center.addObserver(self, selector: #selector(didEnterBackground), name: .UIApplicationDidEnterBackground, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(self.willResignActive), name: NSNotification.Name.UIApplicationWillResignActive, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(self.didBecomeActive), name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil) + center.addObserver(self, selector: #selector(willResignActive), name: .UIApplicationWillResignActive, object: nil) + center.addObserver(self, selector: #selector(didBecomeActive), name: .UIApplicationDidBecomeActive, object: nil) - tableView.layoutIfNeeded() + collectionView.layoutIfNeeded() presenter.viewDidAppear() } - @objc func willResignActive() { + @objc private func willResignActive() { goAway() } - @objc func didBecomeActive() { + @objc private func didBecomeActive() { goAway() } @@ -458,13 +471,20 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() + + updateScrollIndicatorInsets() - let expandedHeight = self.tableView.bounds.height + CGFloat(FooterViewLayout.collapsedHeight) + let expandedHeight = collectionView.bounds.height + CGFloat(FooterViewLayout.collapsedHeight) footerView?.update(constraintLayout: CollapsedView.ConstraintLayout(availableHeight: [CGFloat(FooterViewLayout.collapsedHeight), CGFloat(FooterViewLayout.middleHeight), expandedHeight])) } + + private func updateScrollIndicatorInsets() { + let indicatorInset = collectionView.bounds.width - collectionView.scrollIndicatorInsets.left - 8 + collectionView.scrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: indicatorInset) + } // MARK: - BaseVC @@ -480,38 +500,18 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw } override func keyboardNotified(endFrame: CGRect) { - let currentTableBounds = tableView.bounds - if endFrame.origin.y >= UIScreen.main.bounds.size.height { - if !isHideKeyboard { - let newTableBounds = currentTableBounds.updating(height: currentTableBounds.height + verticalOffset) - adjustTableTopInset(for: newTableBounds) - - tableView.contentOffset = CGPoint(x: 0, y: tableView.contentOffset.y - verticalOffset) - verticalOffset = 0 - } - isHideKeyboard = true - updateToHide() - + updateToHide(view: inputBar, offset: 0) } else { - if isHideKeyboard { - verticalOffset = CGFloat(endFrame.height) - safeAreaBottomInset() - let newTableBounds = currentTableBounds.updating(height: currentTableBounds.height - verticalOffset) - adjustTableTopInset(for: newTableBounds) - - tableView.contentOffset = CGPoint(x: 0, y: tableView.contentOffset.y + verticalOffset) - } - isHideKeyboard = false - updateToShow(endFrame: endFrame) + updateToShow(view: inputBar, offset: -endFrame.height) } } - + @objc private func wheelDidOpen() { endInputBarInteraction() } - //MARK: - Translation private func handleUpdateForTranslation(in textView: UITextView) { let inputText = textView.text ?? "" @@ -670,9 +670,9 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw // MARK: - Mentions func setupUnreadMentions(after index: Int) { - var serverId = messageDS.cells[index].serverID + var serverId = messageDS.cellModel(at: index).serverID - if serverId == nil, let previousModel = messageDS.cells[.. CGPoint { - return CGPoint(x: tableView.bounds.width / 2, - y: tableView.contentOffset.y + tableView.bounds.height - tableView.contentInset.bottom) - } - func lastVisibleCellIndex() -> Int? { - let point = lastMessagePosition() - return tableView.indexPathForRow(at: point)?.row + let offset = messageDS.isReversed + ? collectionView.contentInset.top + : collectionView.bounds.height - collectionView.contentInset.bottom + + let lastMessagePosition = CGPoint( + x: collectionView.bounds.width / 2, + y: collectionView.contentOffset.y + offset + ) + return collectionView.indexPathForItem(at: lastMessagePosition)?.item } @@ -823,12 +825,9 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw } func updateData(with configuration: DisplayChatConfiguration) { - let isLastMessageVisible = self.isLastMessageVisible - - messageDS.cells = configuration.cells - tableView.reloadData() - adjustTableTopInset() - + messageDS.update(configuration.cells) + collectionView.reloadData() + hasUnread = configuration.isUnreadShown adjustPosition(with: configuration, isLastMessageVisible: isLastMessageVisible) adjustButtonState(configuration) @@ -836,13 +835,11 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw updateUnreadMentions() } - func updateNewData(_ cells: [BaseChatCellModel]) { - var result = cells - + func updateNewData(_ history: [BaseChatCellModel]) { var lastTimeShowed: Date? - for i in (0.. cells.count { - let indexPath = IndexPath(row: cells.count, section: 0) - let rect = tableView.rectForRow(at: indexPath) - tableView.contentOffset.y = rect.minY + initialOffset.y - offsetForDeletedTimeCell + if !messageDS.isReversed, messageDS.count > history.count { + let indexPath = IndexPath(row: history.count, section: 0) + guard let attributes = collectionView.layoutAttributesForItem(at: indexPath) else { + return + } + let rect = attributes.frame + collectionView.contentOffset.y = rect.minY + initialOffset.y - offsetForDeletedTimeCell } loadingStatus = false @@ -904,7 +899,8 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw case .message(let localId): scrollToMessage(with: localId) case .unread(let index): - scrollToUnreadMessage(with: Int(index)) + let index = messageDS.presentationIndex(from: index) + scrollToUnreadMessage(with: index) case .checkpoint(let checkpoint): scrollToCheckpoint(checkpoint) case .lastMessage: @@ -939,12 +935,11 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw presenter.openSharedContact(contact: contact) } - func getPreviousMessages(id: Int64) { + func getPreviousMessages(id: MessageServerId) { presenter.getPreviousMessages(id: id) } func newMessage(_ cell: BaseChatCellModel) { - let isLastMessageVisible = self.isLastMessageVisible if let messageDS = self.messageDS { var unreadCellIndex: Int? @@ -952,33 +947,28 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw hasUnread = false unreadCellIndex = messageDS.unreadCellIndex } - + readMessage(cell) - tableView.performUpdates(animated: false) { + collectionView.performBatchUpdates({ if let unrIndex = unreadCellIndex { - messageDS.cells.remove(at: unrIndex) - tableView.deleteRows(at: [IndexPath(row: unrIndex, section: 0)], with: .none) + messageDS.remove(at: unrIndex) + collectionView.deleteItems(at: [IndexPath(row: unrIndex, section: 0)]) } - - messageDS.cells.append(cell) - tableView.insertRows(at: [IndexPath(row: messageDS.cells.count - 1, section: 0)], with: .none) - } - adjustTableTopInset() - - if isLastMessageVisible || cell.isOwner { - dispatchAsyncMainAfter(0.01, block: { - self.scrollToBottom(animated: false) - }) - } else { - buttonState = .newMessages - } + messageDS.addNewMessage(cell) + collectionView.insertItems(at: [IndexPath(row: messageDS.bottomIndex, section: 0)]) + + }, completion: { _ in + if self.isLastMessageVisible || cell.isOwner { + dispatchAsyncMainAfter(0.01) { + self.scrollToBottom(animated: false) + } + } else { + self.buttonState = .newMessages + } + }) } } - - private var unreadCellIndex: Int? { - return messageDS.cells.index(where: { $0.unread == true }) - } func updateProgress(progressModel: ProgressModel?) { DispatchQueue.main.async { @@ -992,30 +982,38 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw func updateTranslationProgress(progressModel: ConvertionProgressModel?) { DispatchQueue.main.async { let id = progressModel?.id - let list = self.messageDS.cells.filter { $0.id == id } + let list = self.messageDS.filter { $0.id == id } list.forEach { $0.translationProgressModel = progressModel } self.reloadIfVisible(models: list) } } func updateMessage(_ model: BaseChatCellModel) { - if let messageId = model.id, let index = messageDS.cells.index(where: { $0.id == messageId }) { - model.deliveryStatus = messageDS.cells[index].deliveryStatus // TODO: castil - messageDS.cells[index] = model - //TODO: I think we need to do it outside this class - var shouldReload = [model] - messageDS.cells.forEach { (mod) in - switch mod.messageType { - case .reply(let replyModel, _): - if replyModel.id == messageId && replyModel.type.rawValue == "text" { - replyModel.text = model.text ?? "" - shouldReload.append(mod) - } - default: break + guard let messageId = model.id, let index = messageDS.index(where: { $0.id == messageId }) else { + return + } + model.deliveryStatus = messageDS.cellModel(at: index).deliveryStatus // TODO: castil + messageDS.set(model, at: index) + + //TODO: I think we need to do it outside this class + var shouldReload = [model] + + for idx in messageDS.indices { + let cellModel = messageDS.cellModel(at: idx) + + switch cellModel.messageType { + case let .reply(replyModel, _): + if replyModel.id == messageId && replyModel.type == .text { + // TODO: in this case we don'a actually need to reload cell, because height won't be changed. + // It would be better to update only cells layout. + replyModel.text = model.text ?? "" + shouldReload.append(cellModel) } + default: + break } - reloadIfVisible(models: shouldReload) } + reloadIfVisible(models: shouldReload) } func showContextMenu(fromCell cell: BaseChatCell, convertingModel: ConvertionMessageModel? = nil, targetView: UIView? = nil) { @@ -1041,7 +1039,7 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw design: NynjaContextMenuItemsFactory.defaultDesign, userInfo: itemsFactory.userInfo, targetView: targetView, - targetMask: tableView, + targetMask: collectionView, containerViewMaskRect: menuMask) let menu = NynjaContextMenu() @@ -1100,18 +1098,15 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw } func updateDeliveryStatus(_ status: DeliveryStatus, messageId: String) { - let cells = self.messageDS.cells - - guard let index = cells.index(where: { $0.id == messageId }) else { + guard let index = messageDS.index(where: { $0.id == messageId }) else { return } - if status != .read { - let model = cells[index] + let model = messageDS.cellModel(at: index) model.deliveryStatus = status reloadIfVisible(models: [model]) } else { - markMesssagesAsRead(from: index, cells: cells) + markMesssagesAsRead(from: index) } } @@ -1124,35 +1119,30 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw } func updateStar(starID: String?, messageId: String) { - let cells = self.messageDS.cells - - guard let index = cells.index(where: { $0.id == messageId }) else { + guard let model = messageDS.first(where: { $0.id == messageId }) else { return } - let model = cells[index] model.starID = starID reloadIfVisible(models: [model]) } func updateUnreadTitle(_ count: Int) { - let index = min(messageDS.cells.count, count) + let index = messageDS.presentationIndex(from: min(messageDS.count, count)) let unreadIndex = messageDS.unreadCellIndex - - tableView.performUpdates { + + collectionView.performBatchUpdates({ if let unreadIndex = unreadIndex { - messageDS.cells.swapAt(unreadIndex, index) - tableView.moveRow(at: makeIndexPathForUpdate(with: unreadIndex), - to: makeIndexPathForUpdate(with: index)) + messageDS.swapAt(unreadIndex, index) + collectionView.moveItem(at: makeIndexPathForUpdate(with: unreadIndex), + to: makeIndexPathForUpdate(with: index)) } else { - messageDS.cells.insert(BaseChatCellModel.unreadModel(), at: index) - tableView.insertRows(at: [makeIndexPathForUpdate(with: index)], with: .none) + messageDS.insert(.unreadModel(), at: index) + collectionView.insertItems(at: [makeIndexPathForUpdate(with: index)]) } - } - adjustTableTopInset() - scrollToUnreadMessage(with: index + 1) - - let lastIndex = messageDS.cells.count - 1 - readMessage(messageDS.cells[lastIndex]) + }, completion: { _ in + self.scrollToUnreadMessage(with: index + 1) + self.messageDS.bottomModel.map { self.readMessage($0) } + }) } private func makeIndexPathForUpdate(with row: Int) -> IndexPath { @@ -1160,39 +1150,56 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw } // MARK: Scroll to message + func scrollToMessage(with localId: String?) { - if let id = localId, let index = messageDS.cells.index(where: { $0.id == id }) { - scrollToMessage(with: index) - } else if !messageDS.cells.isEmpty { + scrollToMessage(with: localId, scrollPosition: .bottom) + } + + func scrollToMessage(with localId: String?, scrollPosition: UICollectionViewScrollPosition) { + if let id = localId, let index = messageDS.index(where: { $0.id == id }) { + scrollToMessage(with: index, scrollPosition: scrollPosition) + } else if !messageDS.isEmpty { scrollToBottom() } } private func scrollToCheckpoint(_ checkpoint: ChatCheckpoint) { - if canScroll { - scrollToMessage(with: checkpoint.localId) - if checkpoint.topOffset > 0 { - var contentOffset = tableView.contentOffset.y + CGFloat(checkpoint.topOffset) - contentOffset = min(contentOffset, tableView.contentSize.height - tableView.bounds.height) - tableView.contentOffset.y = contentOffset + (gradientHeight - tableView.contentInset.bottom) - } + collectionView.layoutIfNeeded() + guard let index = messageDS.index(where: { $0.id == checkpoint.localId }) else { + return } + let indexPath = IndexPath(item: index, section: 0) + + guard let attributes = collectionView.layoutAttributesForItem(at: indexPath) else { + return + } + + var contentOffset = attributes.frame.minY + contentOffset = contentOffset - CGFloat(checkpoint.topOffset) + contentOffset = max(-collectionView.contentInset.top, + min(contentOffset, collectionView.contentSize.height - collectionView.bounds.height)) + + collectionView.contentOffset.y = contentOffset } - func scrollToMessage(with index: Int) { + private func scrollToMessage(with index: Int, scrollPosition: UICollectionViewScrollPosition = .bottom) { let indexPath = IndexPath(row: index, section: 0) - scroll(to: indexPath, at: .top, animated: false) + scroll(to: indexPath, at: scrollPosition, animated: false) } - func scrollToUnreadMessage(with index: Int) { - let prevInd = index - 1 - let cellModel = messageDS.cells[safe: prevInd] - guard cellModel?.unread == true else { return } + private func scrollToUnreadMessage(with index: Int) { + guard case let prevInd = index - 1, messageDS.indices.contains(prevInd) else { + return + } + let cellModel = messageDS.cellModel(at: prevInd) + guard cellModel.unread == true else { + return + } scrollToMessage(with: prevInd) } - func scrollToMessage(serverId: Int64) { - guard let index = messageDS.cells.index(where: { $0.serverID == serverId }) else { + func scrollToMessage(serverId: MessageServerId) { + guard let index = messageDS.index(where: { $0.serverID == serverId }) else { return } scrollToMessage(with: index) @@ -1252,24 +1259,30 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw } func displayRejoinBanner(display: Bool, count: Int) { - self.messageTVDelegate.rejoinBannerDisplayed = display - self.messageTVDelegate.membersCount = count - self.tableView.reloadData() - adjustTableTopInset() + // Use bottom inset, because collectionView has reversed transform: + // collectionView.transform = CGAffineTransform(rotationAngle: .pi) + var inset = defaultCollectionViewInsets.bottom + + if display { + let callInfoView = self.callInfoView ?? makeRejoinBar() + callInfoView.membersCount = count + callInfoView.delegate = self + self.callInfoView = callInfoView + + inset += CallInfoView.Constraints.baseSizes.height + } else { + callInfoView = nil + } + + if collectionView.contentInset.bottom != inset { + collectionView.contentInset.bottom = inset + } } private func toggleReplyPreview(_ shouldShow: Bool) { let isShown = !replyPreview.isHidden guard shouldShow != isShown else { return } - let replyPreviewHeight = CGFloat(Constraints.replyPreview.height) - if shouldShow { - tableView.contentOffset.y += replyPreviewHeight - tableView.contentInset.top -= replyPreviewHeight - } else { - tableView.contentInset.top += replyPreviewHeight - } - replyPreview.isHidden = !shouldShow if shouldShow { @@ -1356,63 +1369,71 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw }) } - private func registerCells(for tableView: UITableView) { + private func registerCells(for collectionView: UICollectionView) { MessageCellFactory.selfIdentifiers.forEach { - tableView.register(BaseChatCell.self, forCellReuseIdentifier: $0) + collectionView.register(BaseChatCell.self, forCellWithReuseIdentifier: $0) } MessageCellFactory.opponenetIdentifiers.forEach { - tableView.register(OponentChatCell.self, forCellReuseIdentifier: $0) + collectionView.register(OponentChatCell.self, forCellWithReuseIdentifier: $0) } - tableView.register(SystemCell.self, forCellReuseIdentifier: "system") - tableView.register(TimeCell.self, forCellReuseIdentifier: "time") - tableView.register(UnreadCell.self, forCellReuseIdentifier: "unread") + collectionView.register(SystemCell.self, forCellWithReuseIdentifier: "system") + collectionView.register(TimeCell.self, forCellWithReuseIdentifier: "time") + collectionView.register(UnreadCell.self, forCellWithReuseIdentifier: "unread") } func reloadIfVisible(models: [BaseChatCellModel]) { - tableView.performUpdates { + collectionView.performBatchUpdates({ var indexPaths: [IndexPath] = [] - tableView.visibleCells.forEach { (cell) in - if let model = (cell as? BaseChatCell)?.model { - if let index = models.index(where: { (mod) -> Bool in - if mod.id != nil { - return mod.id == model.id - } - - if mod.text != nil { - return mod.text == model.text - } - - return false - }) { - (cell as? BaseChatCell)?.updateData(models[index]) - indexPaths.append(tableView.indexPath(for: cell)!) + collectionView.visibleCells.forEach { (cell) in + guard let cell = cell as? BaseChatCell, let model = cell.model else { + return + } + if models.contains(where: { (mod) -> Bool in + if mod.id != nil { + return mod.id == model.id + } + + if mod.text != nil { + return mod.text == model.text + } + + return false + }) { + if let indexPath = collectionView.indexPath(for: cell) { + indexPaths.append(indexPath) } } } - tableView.reloadRows(at: indexPaths, with: .none) - } - adjustTableTopInset() + collectionView.reloadItems(at: indexPaths) + }, completion: nil) } - private func markMesssagesAsRead(from index: Int, cells: [BaseChatCellModel]) { + private func markMesssagesAsRead(from index: Int) { var updatedCells: [BaseChatCellModel] = [] - for i in (0...index).reversed() where cells[i].isOwner { - let model = cells[i] - if model.deliveryStatus == .read { + + for i in (0...index).reversed() { + let i = messageDS.presentationIndex(from: i) + guard case let model = messageDS.cellModel(at: i), model.isOwner else { + continue + } + + guard model.deliveryStatus != .read else { break - } else { - model.deliveryStatus = .read - updatedCells.append(model) } - reloadIfVisible(models: updatedCells) + + model.deliveryStatus = .read + updatedCells.append(model) } + reloadIfVisible(models: updatedCells) } func scrollToBottomIfNeeded() { - guard let model = messageDS.cells.last, bottomGap < messageTVDelegate.height(for: model) else { return } - dispatchAsyncMainAfter(0.01, block: { + guard let model = messageDS.bottomModel, bottomGap < messageTVDelegate.height(for: model) else { + return + } + dispatchAsyncMainAfter(0.01) { self.scrollToBottom(animated: true) - }) + } } func scrollToBottom() { @@ -1420,58 +1441,56 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw } func scrollToBottom(animated: Bool) { - let indexPath = IndexPath(row: messageDS.cells.count - 1, section: 0) - scroll(to: indexPath, at: .bottom, animated: animated) + guard case let index = messageDS.bottomIndex, messageDS.indices.contains(index) else { + return + } + let indexPath = IndexPath(row: index, section: 0) + scroll(to: indexPath, at: .top, animated: animated) + } + + func scrollToTop() { + guard case let index = messageDS.topIndex, messageDS.indices.contains(index) else { + return + } + let indexPath = IndexPath(row: index, section: 0) + scroll(to: indexPath, at: .bottom, animated: true) } - private func scroll(to indexPath: IndexPath, at position: UITableViewScrollPosition, animated: Bool) { + private func scroll(to indexPath: IndexPath, at position: UICollectionViewScrollPosition, animated: Bool) { guard !contextMenuPresented, canScroll else { return } - tableView.scrollToRow(at: indexPath, at: position, animated: animated) + collectionView.scrollToItem(at: indexPath, at: position, animated: animated) } private var canScroll: Bool { - return canScroll(with: tableView.bounds) + return canScroll(with: collectionView.bounds) } private func canScroll(with bounds: CGRect) -> Bool { - return tableView.contentSize.height > bounds.height + return collectionView.contentSize.height > bounds.height } private var checkpoint: ChatCheckpoint? { - let visibleCells = tableView.visibleCells - guard let index = visibleCells.index(where: { $0 is BaseChatCell }), - let cell = visibleCells[index] as? BaseChatCell, - let model = cell.model, let localId = model.id else { - return nil + let visibleCells = collectionView.visibleCells + let bottomCell = visibleCells + .sorted(by: { $0.frame.minY < $1.frame.minY }) + .first(where: { $0 is BaseChatCell }) + + guard let cell = bottomCell as? BaseChatCell, let model = cell.model, let localId = model.id else { + return nil } - let rect = tableView.convert(cell.frame, to: tableView.superview) - let offset = tableView.frame.origin.y - rect.origin.y + let rect = collectionView.convert(cell.frame, to: collectionView.superview) + let offset = collectionView.frame.maxY - rect.maxY return ChatCheckpoint(localId: localId, topOffset: Double(offset)) } - private var localIdOfFirstVisibleMessage: String? { - var messageCell: BaseChatCell? - - for cell in tableView.visibleCells { - if let chatCell = cell as? BaseChatCell { - messageCell = chatCell - break - } - } - - if let cell = messageCell, let model = cell.model { - return model.id - } - - return nil - } - private var bottomGap: CGFloat { - return tableView.contentSize.height - (tableView.contentOffset.y + tableView.bounds.height) + return messageDS.isReversed + ? collectionView.contentOffset.y + : collectionView.contentSize.height - (collectionView.contentOffset.y + collectionView.bounds.height) } private var isLastMessageVisible: Bool { @@ -1499,22 +1518,18 @@ final class MessageVC: BaseVC, MessageViewProtocol, ReplyPreviewDelegate, BackSw } func removeMessage(_ messageId: String) { - guard let messageDS = self.messageDS, let index = messageDS.cells.index(where: { $0.id == "\(messageId)" }) else { + guard let messageDS = self.messageDS, let index = messageDS.index(where: { $0.id == messageId }) else { return } - tableView.performUpdates { - messageDS.cells.remove(at: index) - tableView.deleteRows(at: [IndexPath(row: index, section: 0)], with: .none) - } - adjustTableTopInset() + collectionView.performBatchUpdates({ + messageDS.remove(at: index) + collectionView.deleteItems(at: [IndexPath(row: index, section: 0)]) + }, completion: nil) } func getNextPage() { - for i in 0.. CallInfoView { + let callInfoView = CallInfoView() + + view.addSubview(callInfoView) + callInfoView.snp.makeConstraints { maker in + maker.top.equalTo(collectionView.snp.top) + maker.left.right.equalToSuperview() + maker.height.equalTo(CallInfoView.Constraints.baseSizes.height) } - } - - //MARK: - Rejoin bar - func rejoinRunningCall() { - presenter.rejoinRunningCall() + + return callInfoView } } -//MARK: - Translation +// MARK: - Translation extension MessageVC { func showTranslationPreview(_ selectedLang: SelectedLang, isAuto: Bool) { guard case .lang(let language) = selectedLang, !language.isNone else { @@ -1612,7 +1620,7 @@ extension MessageVC { make.height.equalTo(0) } - tableView.snp.remakeConstraints { make in + collectionView.snp.remakeConstraints { make in makeDefaultTableConstraint(make) make.bottom.equalTo(staticFooterView.snp.top) } @@ -1625,7 +1633,7 @@ extension MessageVC { staticFooterView.snp.updateConstraints { make in make.height.equalTo(gradientHeight) } - tableView.snp.remakeConstraints { make in + collectionView.snp.remakeConstraints { make in makeDefaultTableConstraint(make) make.bottom.equalTo(staticFooterView.snp.top).offset(-FooterViewLayout.collapsedHeight) } @@ -1647,7 +1655,7 @@ extension MessageVC { } } -//MARK: - UI configuration +// MARK: - Translation UI configuration private extension MessageVC { func makeFooterView(on view: UIView, bottom: UIView) -> CollapsedView { let footerView = CollapsedView() diff --git a/Nynja/Modules/Message/View/Views/CallInfoView/CallInfoView.swift b/Nynja/Modules/Message/View/Views/CallInfoView/CallInfoView.swift index f3e197a0d8186f8fc99c4472d410dddcbe8aafe2..420a2d7875667c79288fcd9203d3b7504abb55db 100644 --- a/Nynja/Modules/Message/View/Views/CallInfoView/CallInfoView.swift +++ b/Nynja/Modules/Message/View/Views/CallInfoView/CallInfoView.swift @@ -16,9 +16,15 @@ class CallInfoView: UIView { weak var delegate: CallInfoViewDelegate? - var membersCount: Int = 0 + var membersCount: Int = 0 { + didSet { + participantsCount.text = text(for: membersCount) + } + } + // MARK: - Views + lazy var headsetImage: UIImageView = { let img = UIImageView() img.contentMode = .scaleAspectFill @@ -78,7 +84,7 @@ class CallInfoView: UIView { pc.backgroundColor = Constants.colors.sectionBackgroundColor.getColor() pc.textAlignment = .left pc.numberOfLines = 1 - pc.text = String(format: "call_info_banner_members".localized, self.membersCount) + pc.text = text(for: membersCount) self.labelsView.addSubview(pc) pc.snp.makeConstraints({ (make) in @@ -119,16 +125,22 @@ class CallInfoView: UIView { baseSetup() } + // MARK: - Setup + private func baseSetup() { durationLabel.isHidden = false self.backgroundColor = Constants.colors.sectionBackgroundColor.getColor() } - //MARK: actions + private func text(for membersCount: Int) -> String { + return String(format: "call_info_banner_members".localized, membersCount) + } - @objc func joinButtonPressed() { - + + // MARK: - Actions + + @objc private func joinButtonPressed() { self.delegate?.didPressButtonJoinIn(callInfoView: self) } } diff --git a/Nynja/Modules/Message/View/Views/CollectionView/MessageCollectionViewDataSource.swift b/Nynja/Modules/Message/View/Views/CollectionView/MessageCollectionViewDataSource.swift new file mode 100644 index 0000000000000000000000000000000000000000..46b415a86292f2a25e3190da0821fe46f9cb20ee --- /dev/null +++ b/Nynja/Modules/Message/View/Views/CollectionView/MessageCollectionViewDataSource.swift @@ -0,0 +1,180 @@ +// +// MessageCollectionViewDataSource.swift +// Nynja +// +// Created by Anton Poltoratskyi on 08.08.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +final class MessageCollectionViewDataSource: NSObject { + + unowned var view: MessageVC + + private var cells = [BaseChatCellModel]() + + init(view: MessageVC) { + self.view = view + super.init() + } + + + // MARK: - Data + + let isReversed = true + + var isEmpty: Bool { + return cells.isEmpty + } + + var count: Int { + return cells.count + } + + var indices: CountableRange { + return 0.. Int { + return isReversed ? count : cells.count - count + } + + func index(where predicate: (BaseChatCellModel) -> Bool) -> Int? { + return isReversed ? cells.reversed().index(where: predicate) : cells.index(where: predicate) + } + + func first(where predicate: (BaseChatCellModel) -> Bool) -> BaseChatCellModel? { + return isReversed ? cells.reversed().first(where: predicate) : cells.first(where: predicate) + } + + func filter(where predicate: (BaseChatCellModel) -> Bool) -> [BaseChatCellModel] { + return isReversed ? cells.reversed().filter(predicate) : cells.filter(predicate) + } + + func cellModel(at indexPath: IndexPath) -> BaseChatCellModel { + return cellModel(at: indexPath.row) + } + + func cellModel(at index: Int) -> BaseChatCellModel { + return cells[dataIndex(from: index)] + } + + func cellModel(before index: Int, where predicate: (BaseChatCellModel) -> Bool) -> BaseChatCellModel? { + let index = dataIndex(from: index) + return cells[.. Int64? { + return cells.first { $0.serverID != nil }.flatMap { $0.serverID } + } + + private func dataIndex(from presentationIndex: Int) -> Int { + return cells.count - presentationIndex - 1 + } + + func presentationIndex(from dataIndex: Int) -> Int { + return cells.count - dataIndex - 1 + } +} + +// MARK: - UICollectionViewDataSource + +extension MessageCollectionViewDataSource: UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let model = cellModel(at: indexPath) + + if model.unread != nil { + if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "unread", for: indexPath) as? UnreadCell { + cell.setup() + cell.accessibilityIdentifier = "chat_cell_unread_\(indexPath.section)" + return cell + } + } + + if model.messageType == .system { + if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "system", for: indexPath) as? SystemCell { + cell.setup(message: model.text!) + cell.accessibilityIdentifier = "system_cell_unread_\(indexPath.section)" + return cell + } + } else if model.time == nil { + var cell: BaseChatCell! + + if let identifier = MessageCellFactory.identifier(for: model) { + cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as? BaseChatCell + cell.accessibilityIdentifier = "message_cell_\(identifier)_\(indexPath.section)" + } + + if view.messageTVDelegate != nil { + cell.delegate = view + } + cell.setup(model: model) + return cell + } else { + if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "time", for: indexPath) as? TimeCell { + cell.setup(date: model.time!) + cell.accessibilityIdentifier = "time_cell_unread_\(indexPath.section)" + return cell + } + } + + return UICollectionViewCell() + } +} diff --git a/Nynja/Modules/Message/View/Views/CollectionView/MessageCollectionViewDelegate.swift b/Nynja/Modules/Message/View/Views/CollectionView/MessageCollectionViewDelegate.swift new file mode 100644 index 0000000000000000000000000000000000000000..10275391e447ad1404cf5fed8741316095d37b9a --- /dev/null +++ b/Nynja/Modules/Message/View/Views/CollectionView/MessageCollectionViewDelegate.swift @@ -0,0 +1,149 @@ +// +// MessageCollectionViewDelegate.swift +// Nynja +// +// Created by Anton Poltoratskyi on 08.08.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +enum ScrollDirection { + case top + case bottom +} + +final class MessageCollectionViewDelegate: NSObject, UICollectionViewDelegateFlowLayout { + + unowned var view: MessageVC + + init(view: MessageVC) { + self.view = view + super.init() + } + + + // MARK: - Collection View + + func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + setupProgress(cell: cell) + setupMentions(cell: cell, indexPath: indexPath) + } + + func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + guard let cell = cell as? BaseChatCell, let url = cell.model?.progressModel?.url.absoluteString else { + return + } + view.progressDictionary.removeValue(forKey: url) + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return CGSize(width: view.view.bounds.width, height: heightForItem(at: indexPath)) + } + + private func heightForItem(at indexPath: IndexPath) -> CGFloat { + let model = view.messageDS.cellModel(at: indexPath) + + if model.messageType == .system { + return 40 + } + + if let _ = model.type { + return height(for: model) + } + + if model.unread != nil { + return 30 + } + + if model.time == nil { + return height(for: model) + } else { + return 30 + } + } + + func height(for model: BaseChatCellModel) -> CGFloat { + return model.isOwner ? BaseChatCell.size(for: model).height : OponentChatCell.size(for: model).height + } + + + // MARK: - Scroll View + + private var scrollOffset: CGFloat = 0 + + var lastVisibleIndex: Int? + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + let offset = view.messageDS.isReversed + ? scrollView.contentSize.height - scrollView.bounds.height - scrollView.contentOffset.y + : scrollView.contentOffset.y + + let absoluteTop: CGFloat = 0 + let absoluteBottom: CGFloat = scrollView.contentSize.height - scrollView.frame.size.height + + let isTop = scrollOffset - offset > 0 && offset > absoluteTop + let isBottom = scrollOffset - offset < 0 && offset < absoluteBottom + + if isTop { + view.isScrollingTo(.top) + } else if isBottom { + view.isScrollingTo(.bottom) + } + + if offset >= absoluteBottom { + view.didReachBottom() + } + + let contentHeight = scrollView.contentSize.height + + if offset < 0 && scrollView.frame.size.height < contentHeight { + loadData() + } + scrollOffset = offset + } + + func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool { + view.scrollToTop() + return false + } + + + // MARK: - Private + + private func setupMentions(cell: UICollectionViewCell, indexPath: IndexPath) { + func setup() { + lastVisibleIndex = indexPath.row + view.setupUnreadMentions(after: indexPath.row) + } + guard let index = lastVisibleIndex ?? view.lastVisibleCellIndex() else { + setup() + return + } + guard view.messageDS.isReversed ? indexPath.row < index : indexPath.row > index else { + return + } + setup() + } + + private func setupProgress(cell: UICollectionViewCell) { + guard let cell = cell as? BaseChatCell, let url = cell.model?.progressModel?.url.absoluteString else { + return + } + let block: (ProgressModel) -> Void = { model in + cell.updateProgressClosure(model: model) + } + if view.progressDictionary[url] == nil { + view.progressDictionary[url] = [block] + } else { + view.progressDictionary[url]?.append(block) + } + } + + private func loadData() { + if !view.loadingStatus { + view.loadingStatus = true + view.getNextPage() + } + } +} diff --git a/Nynja/Modules/Message/View/Views/CollectionView/MessageCollectionViewLayout.swift b/Nynja/Modules/Message/View/Views/CollectionView/MessageCollectionViewLayout.swift new file mode 100644 index 0000000000000000000000000000000000000000..4072e4ca62c7e245be4559e6033338e1b0264ed1 --- /dev/null +++ b/Nynja/Modules/Message/View/Views/CollectionView/MessageCollectionViewLayout.swift @@ -0,0 +1,94 @@ +// +// MessageCollectionViewLayout.swift +// Nynja +// +// Created by Anton Poltoratskyi on 08.08.2018. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import UIKit + +final class MessageCollectionViewLayout: UICollectionViewFlowLayout { + + private var offset: CGFloat = 0 + + override init() { + super.init() + setup() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + setup() + } + + private func setup() {} + + override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { + if let currentBounds = collectionView?.bounds { + return currentBounds.width != newBounds.width + } else { + return false + } + } + + override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { + guard let attributes = super.layoutAttributesForElements(in: rect) else { + return nil + } + for attr in attributes { + attr.transform = CGAffineTransform(rotationAngle: .pi) + } + return attributes + } + + override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { + let attributes = super.layoutAttributesForItem(at: indexPath) + attributes?.transform = CGAffineTransform(rotationAngle: .pi) + return attributes + } + + override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) { + guard let collectionView = collectionView else { return } + + offset = 0 + + super.prepare(forCollectionViewUpdates: updateItems) + + let currentOffset = collectionView.contentOffset.y + collectionView.contentInset.top + + for item in updateItems { + switch item.updateAction { + case .insert: + guard let path = item.indexPathAfterUpdate, let attributes = layoutAttributesForItem(at: path) else { + continue + } + + guard attributes.frame.maxY <= currentOffset else { continue } + + offset += attributes.size.height + minimumLineSpacing + case .delete: + guard let path = item.indexPathBeforeUpdate, let attributes = layoutAttributesForItem(at: path) else { + continue + } + + guard attributes.frame.maxY <= currentOffset else { continue } + + offset -= (attributes.size.height + minimumLineSpacing) + default: + break + } + } + } + + override func finalizeCollectionViewUpdates() { + super.finalizeCollectionViewUpdates() + + guard let collectionView = collectionView else { return } + + guard collectionView.contentSize.height + offset >= collectionView.frame.height - collectionView.contentInset.bottom else { + return + } + collectionView.contentOffset.y += offset + } +} diff --git a/Nynja/Modules/Message/View/Views/TableView/Cells/ChatCells/BaseChatCell/BaseChatCell.swift b/Nynja/Modules/Message/View/Views/TableView/Cells/ChatCells/BaseChatCell/BaseChatCell.swift index 7fd838eba69d926d37e497d39841c7668c4a5c3d..2df83cb89c6fd8076d901265348dd9cd8d4e9a1b 100644 --- a/Nynja/Modules/Message/View/Views/TableView/Cells/ChatCells/BaseChatCell/BaseChatCell.swift +++ b/Nynja/Modules/Message/View/Views/TableView/Cells/ChatCells/BaseChatCell/BaseChatCell.swift @@ -9,7 +9,7 @@ import Foundation import SnapKit -class BaseChatCell: UITableViewCell, MessageBaseImageViewDataSource, MessageContentAppearance, MessageVoiceViewDelegate, ReplyCounterDelegate, MessageRepliedViewDelegate, MessageTextViewDelegate, MessageForwardViewDelegate, MessageStickerRepliedViewDelegate, MessageConvertionViewDelegate { +class BaseChatCell: UICollectionViewCell, MessageBaseImageViewDataSource, MessageContentAppearance, MessageVoiceViewDelegate, ReplyCounterDelegate, MessageRepliedViewDelegate, MessageTextViewDelegate, MessageForwardViewDelegate, MessageStickerRepliedViewDelegate, MessageConvertionViewDelegate { private static let senderFont = UIFont(fontName: Constants.fonts.medium, height: CGFloat(22.0.adjustedByWidth)) private static let translatingFont = UIFont(name: Constants.fonts.regular, size: 12) @@ -233,9 +233,11 @@ class BaseChatCell: UITableViewCell, MessageBaseImageViewDataSource, MessageCont model?.resetHandlers() } + // MARK: - Init - override init(style: UITableViewCellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) + + override init(frame: CGRect) { + super.init(frame: frame) initialSetup() } @@ -246,7 +248,6 @@ class BaseChatCell: UITableViewCell, MessageBaseImageViewDataSource, MessageCont // MARK: - Setup func initialSetup() { - selectionStyle = .none backgroundColor = .clear setupRecognizers() } @@ -294,6 +295,10 @@ class BaseChatCell: UITableViewCell, MessageBaseImageViewDataSource, MessageCont bubble.isUserInteractionEnabled = true bubble.addGestureRecognizer(recognizer) + + let opponentAvaTap = UITapGestureRecognizer(target: self, action: #selector(opponentAvatarTapped)) + self.fromImageView.isUserInteractionEnabled = true + self.fromImageView.addGestureRecognizer(opponentAvaTap) } func setupSenderInfo(_ model: BaseChatCellModel) { @@ -406,6 +411,10 @@ class BaseChatCell: UITableViewCell, MessageBaseImageViewDataSource, MessageCont delegate?.didPauseTapped(self, url: url) } + @objc func opponentAvatarTapped() { + delegate?.opponentAvatarTapped(self) + } + func didChangeProgress(_ progress: Double) { guard let url = model?.fileUrl else { return diff --git a/Nynja/Modules/Message/View/Views/TableView/Cells/ChatCells/BaseChatCell/BaseChatCellDelegate.swift b/Nynja/Modules/Message/View/Views/TableView/Cells/ChatCells/BaseChatCell/BaseChatCellDelegate.swift index 885ebbe9453906199e0ca86588544e82ecd4aece..455ac51e14fa2b25f2aa4578cd3dbd40f70530c7 100644 --- a/Nynja/Modules/Message/View/Views/TableView/Cells/ChatCells/BaseChatCell/BaseChatCellDelegate.swift +++ b/Nynja/Modules/Message/View/Views/TableView/Cells/ChatCells/BaseChatCell/BaseChatCellDelegate.swift @@ -27,6 +27,7 @@ protocol BaseChatCellDelegate: class { func openURL(_ url: URL) func showMenuForURL(_ url: URL) func showMention(_ mention: MentionInfo) + func opponentAvatarTapped(_ cell: BaseChatCell) } extension BaseChatCellDelegate { @@ -49,4 +50,5 @@ extension BaseChatCellDelegate { func openURL(_ url: URL) {} func showMenuForURL(_ url: URL) {} func showMention(_ mention: MentionInfo) {} + func opponentAvatarTapped(_ cell: BaseChatCell) {} } diff --git a/Nynja/Modules/Message/View/Views/TableView/Cells/Models/MessageSender.swift b/Nynja/Modules/Message/View/Views/TableView/Cells/Models/MessageSender.swift index be4c7c0d6c0b865789fef8624f4964574a583722..86d349b4bfe5e81881da4b6aa7c542b50a6522ff 100644 --- a/Nynja/Modules/Message/View/Views/TableView/Cells/Models/MessageSender.swift +++ b/Nynja/Modules/Message/View/Views/TableView/Cells/Models/MessageSender.swift @@ -7,10 +7,11 @@ // struct MessageSender { - static let `deleted` = MessageSender(fullname: "deleted account name".localized, + static let `deleted` = MessageSender(phoneId: nil, + fullname: "deleted account name".localized, nick: "deleted account name".localized, avatar: nil) - + let phoneId: String? let fullname: String let nick: String? let avatar: String? diff --git a/Nynja/Modules/Message/View/Views/TableView/Cells/SystemCell.swift b/Nynja/Modules/Message/View/Views/TableView/Cells/SystemCell.swift index 57e3daf634fed5d86748f565471892d2c41c63e3..b0b0ec9c28f9a67c1f4bf3526cfc5232d46d0952 100644 --- a/Nynja/Modules/Message/View/Views/TableView/Cells/SystemCell.swift +++ b/Nynja/Modules/Message/View/Views/TableView/Cells/SystemCell.swift @@ -6,7 +6,7 @@ // Copyright © 2018 TecSynt Solutions. All rights reserved. // -final class SystemCell: UITableViewCell { +final class SystemCell: UICollectionViewCell { lazy var systemLabel: UILabel = { let label = UILabel(height: Constraints.Label.height, @@ -31,11 +31,9 @@ final class SystemCell: UITableViewCell { // MARK: - Setup func setup(message: String) { - self.selectionStyle = .none self.backgroundColor = UIColor.clear systemLabel.text = message } - } @@ -49,5 +47,4 @@ extension SystemCell { static let horizontalInset = 16.0.adjustedByWidth } } - } diff --git a/Nynja/Modules/Message/View/Views/TableView/Cells/TimeCell.swift b/Nynja/Modules/Message/View/Views/TableView/Cells/TimeCell.swift index 640d4bdb052af29f8e60f4e30471389febddb304..46f9c483d41db47628001cd977a7203addc49e48 100644 --- a/Nynja/Modules/Message/View/Views/TableView/Cells/TimeCell.swift +++ b/Nynja/Modules/Message/View/Views/TableView/Cells/TimeCell.swift @@ -8,9 +8,11 @@ import Foundation -class TimeCell: UITableViewCell { +final class TimeCell: UICollectionViewCell { - lazy var timeStamp: UILabel = { + private lazy var dateFormatter = DateFormatter() + + private lazy var timeStamp: UILabel = { let v = UILabel() v.font = UIFont(name: Constants.fonts.regular, size: 14) v.textColor = Constants.colors.red.getColor() @@ -22,13 +24,11 @@ class TimeCell: UITableViewCell { }() func setup(date: Date) { - self.selectionStyle = .none self.backgroundColor = UIColor.clear - timeStamp.text = self.getText(fromDate: date).uppercased() + timeStamp.text = getText(fromDate: date) } - func getText(fromDate: Date) -> String { - let dateFormatter = DateFormatter() + private func getText(fromDate: Date) -> String { dateFormatter.dateFormat = "MMMM d".localizedDateFormat dateFormatter.locale = Locale(identifier: Language.current.rawValue) return dateFormatter.string(from: fromDate) diff --git a/Nynja/Modules/Message/View/Views/TableView/Cells/UnreadCell.swift b/Nynja/Modules/Message/View/Views/TableView/Cells/UnreadCell.swift index ad379285ebe57e57d6ab9d898d177c95c62f45e4..c048a7cb4c3cf51b92be502bdfbb6cbb9050175f 100644 --- a/Nynja/Modules/Message/View/Views/TableView/Cells/UnreadCell.swift +++ b/Nynja/Modules/Message/View/Views/TableView/Cells/UnreadCell.swift @@ -6,7 +6,9 @@ // Copyright © 2017 TecSynt Solutions. All rights reserved. // -final class UnreadCell: UITableViewCell { +import Foundation + +final class UnreadCell: UICollectionViewCell { lazy var timeStamp: UILabel = { let v = UILabel() @@ -56,8 +58,7 @@ final class UnreadCell: UITableViewCell { }() func setup() { - self.selectionStyle = .none - self.backgroundColor = UIColor.clear + backgroundColor = UIColor.clear timeStamp.isHidden = false line1.isHidden = false line2.isHidden = false diff --git a/Nynja/Modules/Message/View/Views/TableView/MessageDS.swift b/Nynja/Modules/Message/View/Views/TableView/MessageDS.swift deleted file mode 100644 index f4e24b3a41cb0672262b9a833dbcdf4447fe5da8..0000000000000000000000000000000000000000 --- a/Nynja/Modules/Message/View/Views/TableView/MessageDS.swift +++ /dev/null @@ -1,71 +0,0 @@ -// MessageDS.swift -// Nynja -// -// Created by Anton Makarov on 27.08.2017. -// Copyright © 2017 TecSynt Solutions. All rights reserved. -// - -import Foundation - -class MessageDS: NSObject, UITableViewDataSource { - - weak var view: MessageVC! - - var cells = [BaseChatCellModel]() - - var unreadCellIndex: Int? { - return cells.index(where: { $0.unread == true }) - } - - // MARK: Init - init(view: MessageVC) { - self.view = view - } - - // MARK: UITableViewDataSource - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return cells.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let model = cells[indexPath.row] - - if model.unread != nil { - if let cell = tableView.dequeueReusableCell(withIdentifier: "unread", for: indexPath) as? UnreadCell { - cell.setup() - cell.accessibilityIdentifier = "chat_cell_unread_\(indexPath.section)" - return cell - } - } - - if model.messageType == .system { - if let cell = tableView.dequeueReusableCell(withIdentifier: "system", for: indexPath) as? SystemCell { - cell.setup(message: model.text!) - cell.accessibilityIdentifier = "system_cell_unread_\(indexPath.section)" - return cell - } - } else if model.time == nil { - var cell: BaseChatCell! - - if let identifier = MessageCellFactory.identifier(for: model) { - cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? BaseChatCell - cell.accessibilityIdentifier = "message_cell_\(identifier)_\(indexPath.section)" - } - - if view.messageTVDelegate != nil { - cell.delegate = view - } - cell.setup(model: model) - return cell - } else { - if let cell = tableView.dequeueReusableCell(withIdentifier: "time", for: indexPath) as? TimeCell { - cell.setup(date: model.time!) - cell.accessibilityIdentifier = "time_cell_unread_\(indexPath.section)" - - return cell - } - } - - return UITableViewCell() - } -} diff --git a/Nynja/Modules/Message/View/Views/TableView/MessageTableViewDelegate.swift b/Nynja/Modules/Message/View/Views/TableView/MessageTableViewDelegate.swift deleted file mode 100644 index 478d7105ac7f6c56c96fbcccb3f3e1b4ba2d70b6..0000000000000000000000000000000000000000 --- a/Nynja/Modules/Message/View/Views/TableView/MessageTableViewDelegate.swift +++ /dev/null @@ -1,163 +0,0 @@ -// -// MessageTableViewDelegate.swift -// Nynja -// -// Created by Volodymyr Hryhoriev on 10/17/17. -// Copyright © 2017 TecSynt Solutions. All rights reserved. -// - -enum ScrollDirection { - case top - case bottom -} - -class MessageTableViewDelegate: NSObject, UITableViewDelegate, CallInfoViewDelegate { - - weak var view: MessageVC? - - var rejoinBannerDisplayed: Bool = false - var membersCount: Int = 0 - - // MARK: Init - init(view: MessageVC) { - self.view = view - } - - // MARK: UITableViewDelegate - - func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { - - var callInfoView:CallInfoView? = nil - - if rejoinBannerDisplayed { - - callInfoView = CallInfoView() - - if let civ = callInfoView { - civ.membersCount = membersCount - civ.delegate = self - } - } - - return callInfoView - } - - func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { - return rejoinBannerDisplayed ? CallInfoView.Constraints.baseSizes.height : 0 - } - - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - guard let model = view?.messageDS.cells[indexPath.row] else { return 0 } - - if model.messageType == .system { - return 40 - } - - if let _ = model.type { - return height(for: model) - } - - if model.unread != nil { - return 30 - } - - if model.time == nil { - return height(for: model) - } else { - return 30 - } - } - - func height(for model: BaseChatCellModel) -> CGFloat { - return model.isOwner ? BaseChatCell.size(for: model).height : OponentChatCell.size(for: model).height - } - - // MARK: UIScrollViewDelegate - var scrollOffset: CGFloat = 0 - var lastVisibleIndex: Int? - - func scrollViewDidScroll(_ scrollView: UIScrollView) { - let offset = scrollView.contentOffset.y - - let absoluteTop: CGFloat = 0 - let absoluteBottom: CGFloat = scrollView.contentSize.height - scrollView.frame.size.height - - let isTop = scrollOffset - offset > 0 && offset > absoluteTop - let isBottom = scrollOffset - offset < 0 && offset < absoluteBottom - - if isTop { - view?.isScrollingTo(.top) - } else if isBottom { - view?.isScrollingTo(.bottom) - } - - if offset >= absoluteBottom { - view?.didReachBottom() - } - - let contentHeight = scrollView.contentSize.height - - if offset < 0 && scrollView.frame.size.height < contentHeight { - loadData() - } - scrollOffset = offset - } - - func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { - setupProgress(cell: cell) - setupMentions(cell: cell, indexPath: indexPath) - } - - func setupMentions(cell: UITableViewCell, indexPath: IndexPath) { - guard let index = lastVisibleIndex ?? view?.lastVisibleCellIndex() else { - lastVisibleIndex = indexPath.row - view?.setupUnreadMentions(after: indexPath.row) - return - } - guard indexPath.row > index else { - return - } - lastVisibleIndex = indexPath.row - view?.setupUnreadMentions(after: indexPath.row) - } - - func setupProgress(cell: UITableViewCell) { - guard let bcc = cell as? BaseChatCell else { return } - guard let url = bcc.model?.progressModel?.url.absoluteString else { return } - let block : (ProgressModel)-> Void = { model in - (cell as? BaseChatCell)?.updateProgressClosure(model: model) - } - if view?.progressDictionary[url] == nil { - view?.progressDictionary[url] = [block] - } else { - view?.progressDictionary[url]?.append(block) - } - } - - func loadData() { - guard let v = self.view else { return } - if !v.loadingStatus{ - v.loadingStatus = true - v.getNextPage() - } - } - - func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { - guard let bcc = cell as? BaseChatCell else { return } - guard let url = bcc.model?.fileUrl?.absoluteString else { return } - view?.progressDictionary.removeValue(forKey: url) - } - - // MARK: CallInfoVIewDelegate - func didPressButtonJoinIn(callInfoView: CallInfoView) { - - AlertManager.sharedInstance.showAlertWithTwoActions(title: "", - message: "are_u_sure_join_the_call".localized, - firstActionTitle: "no".localized, - secondActionTitle: "yes".localized, - firstAction: nil, - secondAction: { - self.view?.rejoinRunningCall() - }) - } -} diff --git a/Nynja/Modules/Message/WireFrame/ChatScreenAlertFactory/ChatScreenAlertFactory.swift b/Nynja/Modules/Message/WireFrame/ChatScreenAlertFactory/ChatScreenAlertFactory.swift index cb0929915ceeb7ac0b49e9eb6f7c491ead13ce9c..03942e8ac9a3749a8b95203aaf8a1aa499bbfb05 100644 --- a/Nynja/Modules/Message/WireFrame/ChatScreenAlertFactory/ChatScreenAlertFactory.swift +++ b/Nynja/Modules/Message/WireFrame/ChatScreenAlertFactory/ChatScreenAlertFactory.swift @@ -102,8 +102,8 @@ private extension ChatScreenAlertFactory { func makeDeleteForMeAndOthersAndCancelMenuConfig(deleteForOthersTitle: String, delete: @escaping AlertActionWrapper, deleteForOthers: @escaping AlertActionWrapper) -> DeleteMenuConfig { - let deleteForOthersAction = makeAction(title: deleteForOthersTitle, actionBlock: delete) - let deleteAction = makeAction(title: Strings.deleteForMe.localized, actionBlock: deleteForOthers) + let deleteForOthersAction = makeAction(title: deleteForOthersTitle, actionBlock: deleteForOthers) + let deleteAction = makeAction(title: Strings.deleteForMe.localized, actionBlock: delete) let cancelAction = makeCancelAction() return DeleteMenuConfig( title: "", diff --git a/Nynja/Modules/Message/WireFrame/MessageWireframe.swift b/Nynja/Modules/Message/WireFrame/MessageWireframe.swift index a42b2d386a5004f842640ff820975c0fcdd79209..60acf1192a0139cdcc5b283bdbf2decb09862ff2 100644 --- a/Nynja/Modules/Message/WireFrame/MessageWireframe.swift +++ b/Nynja/Modules/Message/WireFrame/MessageWireframe.swift @@ -151,4 +151,9 @@ class MessageWireFrame: MessageWireframeProtocol, DocumentInteractionWireFrame { func show(alert: UIAlertController) { navigation?.present(alert, animated: true, completion: nil) } + + func openMarketplaceScreen() { + main?.openMarketplace() + } + } diff --git a/Nynja/Modules/Participants/Interactor/ParticipantsInteractor.swift b/Nynja/Modules/Participants/Interactor/ParticipantsInteractor.swift index 6b8f30a77142416cc81d3d3719a547eb33135d6b..babd3b0c4482bd136a25a35d649b297d32a50d29 100644 --- a/Nynja/Modules/Participants/Interactor/ParticipantsInteractor.swift +++ b/Nynja/Modules/Participants/Interactor/ParticipantsInteractor.swift @@ -38,7 +38,7 @@ class ParticipantsInteractor: BaseInteractor, ParticipantsInteractorInputProtoco override func update(with changes: [StorageChange], type: SubscribeType) { if case .room = type { guard let dbRoom = changes.first?.entity as? DBRoom else { return } - guard let updatedMembers = Room(room: dbRoom).members else { return } + guard let updatedMembers = Room(room: dbRoom).allMembers else { return } self.includedMembers = updatedMembers self.loadContacts() } diff --git a/Nynja/Modules/Participants/View/ParticipantsViewController.swift b/Nynja/Modules/Participants/View/ParticipantsViewController.swift index 78d398bdac9444e48a88ec2f57306836a1c92339..40ef3ce68d19e5a91a5a8e7949f35e0e6727b16d 100644 --- a/Nynja/Modules/Participants/View/ParticipantsViewController.swift +++ b/Nynja/Modules/Participants/View/ParticipantsViewController.swift @@ -39,11 +39,16 @@ class ParticipantsViewController: BaseVC, ParticipantsViewProtocol { tblView.sectionHeaderHeight = ParticipantsHeaderView.height tblView.estimatedSectionHeaderHeight = tblView.sectionHeaderHeight + tblView.contentInset = UIEdgeInsets(top: CGFloat(Constraints.tableView.topInset.adjustedByWidth), + left: 0, + bottom: 0, + right: 0) + self.view.addSubview(tblView) - tblView.snp.makeConstraints({ (make) in - make.top.equalTo(navigationView.snp.bottom).offset(Constraints.tableView.topInset.adjustedByWidth) + tblView.snp.makeConstraints { make in + make.top.equalTo(navigationView.snp.bottom) make.left.right.equalTo(navigationView) - }) + } return tblView }() diff --git a/Nynja/Modules/Replies/Interactor/RepliesInteractor.swift b/Nynja/Modules/Replies/Interactor/RepliesInteractor.swift index cc4e352b9886a135913e779be36df5b5a289bbaa..7791c2480ec8e54d32f5bbd2ba0868878eaa2bc3 100644 --- a/Nynja/Modules/Replies/Interactor/RepliesInteractor.swift +++ b/Nynja/Modules/Replies/Interactor/RepliesInteractor.swift @@ -30,7 +30,6 @@ final class RepliesInteractor: BaseInteractor, RepliesInteractorInputProtocol, M processingManager = DefaultMessagesProcessingManager.shared super.init() message = MessageDAO.fetchMessage(localId: messageLocalId) - processingManager.delegate = self } //MARK: - BaseInteractor @@ -44,6 +43,7 @@ final class RepliesInteractor: BaseInteractor, RepliesInteractorInputProtocol, M override func loadData() { super.loadData() + processingManager.delegate = self let replyIdentifiers = message.repliedby?.compactMap { Int64($0) } ?? [] loadReplies(replyIdentifiers) presenter?.updateHeaderView(replied: message.repliedby?.count ?? 0) @@ -97,13 +97,13 @@ final class RepliesInteractor: BaseInteractor, RepliesInteractorInputProtocol, M func sender(for message: Message) -> MessageSender? { let isOwner = message.from == StorageService.sharedInstance.phoneId if let _ = (message.feed_id as? muc), let member = member(for: message) { - return MessageSender(fullname: member.fullName ?? "", nick: member.alias, avatar: member.avatar) + return MessageSender(phoneId: member.phone_id, fullname: member.fullName ?? "", nick: member.alias, avatar: member.avatar) } else { if isOwner, let phoneId = StorageService.sharedInstance.phoneId, let myContact = ContactDAO.findContactBy(phoneId: phoneId) { - return MessageSender(fullname: myContact.fullName ?? "", nick: myContact.nick, avatar: myContact.avatar) + return MessageSender(phoneId: myContact.phone_id, fullname: myContact.fullName ?? "", nick: myContact.nick, avatar: myContact.avatar) } else { if let to = (message.feed_id as? p2p)?.to, let contact = ContactDAO.findContactBy(phoneId: to) { - return MessageSender(fullname: contact.fullName ?? "", nick: contact.nick, avatar: contact.avatar) + return MessageSender(phoneId: contact.phone_id, fullname: contact.fullName ?? "", nick: contact.nick, avatar: contact.avatar) } } } @@ -130,7 +130,9 @@ final class RepliesInteractor: BaseInteractor, RepliesInteractorInputProtocol, M // MARK: - MessageProcessingDelegate func updateProgress(_ progress: ProgressModel) { - self.presenter?.updateProgress(progress) + dispatchAsyncMain { [weak presenter] in + presenter?.updateProgress(progress) + } } diff --git a/Nynja/Modules/Replies/View/RepliesCollectionViewDelegate.swift b/Nynja/Modules/Replies/View/RepliesCollectionViewDelegate.swift new file mode 100644 index 0000000000000000000000000000000000000000..fd89cff62fe3629c3d383b00750425a0bef8ddbe --- /dev/null +++ b/Nynja/Modules/Replies/View/RepliesCollectionViewDelegate.swift @@ -0,0 +1,46 @@ +// +// RepliesCollectionViewDelegate.swift +// Nynja +// +// Created by Andrey Reznik on 09.01.18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +class RepliesCollectionViewDelegate: NSObject, UICollectionViewDelegateFlowLayout { + + unowned var collectionView: UICollectionView + weak var dataSource: RepliesDS! + + + // MARK: - Init + + init(collectionView: UICollectionView, dataSource: RepliesDS) { + self.collectionView = collectionView + self.dataSource = dataSource + } + + + // MARK: - UICollectionViewDelegate + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return CGSize(width: collectionView.bounds.width, height: heightForItem(at: indexPath)) + } + + private func heightForItem(at indexPath: IndexPath) -> CGFloat { + let model = dataSource.cells[indexPath.item] + + if model.type != nil { + return height(for: model) + } else if model.unread != nil { + return 30 + } else if model.time == nil { + return height(for: model) + } else { + return 30 + } + } + + private func height(for model: BaseChatCellModel) -> CGFloat { + return model.isOwner ? BaseChatCell.size(for: model).height : OponentChatCell.size(for: model).height + } +} diff --git a/Nynja/Modules/Replies/View/RepliesDS.swift b/Nynja/Modules/Replies/View/RepliesDS.swift index 28ccc1d02314f87ba466f1d45124a459a1205be5..cd65ab844474347b454cc8dcf27160d2c4cdba0c 100644 --- a/Nynja/Modules/Replies/View/RepliesDS.swift +++ b/Nynja/Modules/Replies/View/RepliesDS.swift @@ -6,43 +6,47 @@ // Copyright © 2018 TecSynt Solutions. All rights reserved. // -class RepliesDS: NSObject, UITableViewDataSource { +final class RepliesDS: NSObject, UICollectionViewDataSource { weak var view: RepliesVC! var cells = [BaseChatCellModel]() - // MARK: Init + + // MARK: - Init + init(view: RepliesVC) { self.view = view } - // MARK: UITableViewDataSource - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + + // MARK: - UITableViewDataSource + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return cells.count } - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let model = cells[indexPath.row] if model.time == nil { var cell: BaseChatCell! if let identifier = MessageCellFactory.identifier(for: model) { - cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? BaseChatCell + cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as? BaseChatCell } - if view.tableViewDelegate != nil { + if view.collectionViewDelegate != nil { cell.delegate = view } - + cell.setup(model: model) -// view.cellDisplayed(at: indexPath) + // view.cellDisplayed(at: indexPath) return cell } - return UITableViewCell() + return UICollectionViewCell() } } diff --git a/Nynja/Modules/Replies/View/RepliesTableViewDelegate.swift b/Nynja/Modules/Replies/View/RepliesTableViewDelegate.swift deleted file mode 100644 index a7d4b8334b6a4a5f0ec815e3628e7f692548c1b8..0000000000000000000000000000000000000000 --- a/Nynja/Modules/Replies/View/RepliesTableViewDelegate.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// RepliesTableViewDelegate.swift -// Nynja -// -// Created by Andrey Reznik on 09.01.18. -// Copyright © 2018 TecSynt Solutions. All rights reserved. -// - -class RepliesTableViewDelegate: NSObject, UITableViewDelegate { - - weak var dataSource: RepliesDS! - - // MARK: Init - init(dataSource: RepliesDS) { - self.dataSource = dataSource - } - - // MARK: UITableViewDelegate - func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { - let model = dataSource.cells[indexPath.row] - - if let _ = model.type { - return height(for: model) - } - - if model.unread != nil { - return 30 - } - - if model.time == nil { - return height(for: model) - } else { - return 30 - } - } - - private func height(for model: BaseChatCellModel) -> CGFloat { - return model.isOwner ? BaseChatCell.size(for: model).height : OponentChatCell.size(for: model).height - } -} diff --git a/Nynja/Modules/Replies/View/RepliesVC.swift b/Nynja/Modules/Replies/View/RepliesVC.swift index 1a660f6835a7191e4cbc88b74adf2344c953dd68..179e588d4b8b175349beb5ef73e5bdc096af5db5 100644 --- a/Nynja/Modules/Replies/View/RepliesVC.swift +++ b/Nynja/Modules/Replies/View/RepliesVC.swift @@ -11,7 +11,7 @@ import CoreLocation.CLLocation import Photos -class RepliesVC: BaseVC, RepliesViewProtocol { +final class RepliesVC: BaseVC, RepliesViewProtocol { var presenter: RepliesPresenterProtocol! { didSet { @@ -19,12 +19,13 @@ class RepliesVC: BaseVC, RepliesViewProtocol { } } - var tableViewDataSource: RepliesDS! - var tableViewDelegate: RepliesTableViewDelegate! + var collectionViewDataSource: RepliesDS? + var collectionViewDelegate: RepliesCollectionViewDelegate? + //MARK: - Subviews - lazy var repliesNumberLabel: UILabel = { + private lazy var repliesNumberLabel: UILabel = { let label = UILabel(height: Constraints.repliesNumberLabel.height, color: Constants.colors.white.getColor(), fontName: Constants.fonts.regular, @@ -42,30 +43,29 @@ class RepliesVC: BaseVC, RepliesViewProtocol { return label }() - lazy var tableView: UITableView = { - let tv = UITableView() - - tv.separatorStyle = .none - tv.backgroundColor = UIColor.clear - tv.contentInset = UIEdgeInsetsMake(Constraints.tableView.topInset, 0, 0, 0) - tv.contentOffset = CGPoint(x: 0, y: -Constraints.tableView.topInset) + private lazy var collectionView: UICollectionView = { + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + collectionView.backgroundColor = .clear + collectionView.alwaysBounceVertical = true - registerCells(for: tv) - - tv.estimatedRowHeight = 0 // NOTE: It is important for scrolling behavior. + collectionView.contentInset = UIEdgeInsetsMake(Constraints.tableView.topInset, 0, 0, 0) + collectionView.contentOffset = CGPoint(x: 0, y: -Constraints.tableView.topInset) let bottomPadding = UIScreen.main.bounds.height * 0.11 - self.view.addSubview(tv) - tv.snp.makeConstraints({ (make) in - make.left.right.equalTo(self.view) + view.addSubview(collectionView) + collectionView.snp.makeConstraints { (make) in + make.left.right.equalToSuperview() make.top.equalTo(self.navigationView.snp.bottom) adjustVerticalInset(.bottom, make: make, offset: -bottomPadding) - }) - return tv + } + + registerCells(for: collectionView) + + return collectionView }() - lazy var closeButton: NynjaCloseButton = { + private lazy var closeButton: NynjaCloseButton = { let button = NynjaCloseButton() button.contentMode = .scaleToFill @@ -95,11 +95,12 @@ class RepliesVC: BaseVC, RepliesViewProtocol { closeButton.isHidden = false - tableViewDataSource = RepliesDS(view: self) - tableViewDelegate = RepliesTableViewDelegate(dataSource: tableViewDataSource) + collectionViewDataSource = RepliesDS(view: self) + collectionViewDelegate = RepliesCollectionViewDelegate(collectionView: collectionView, + dataSource: collectionViewDataSource!) - tableView.dataSource = tableViewDataSource - tableView.delegate = tableViewDelegate + collectionView.dataSource = collectionViewDataSource + collectionView.delegate = collectionViewDelegate } //MARK: - RepliesViewProtocol @@ -109,26 +110,30 @@ class RepliesVC: BaseVC, RepliesViewProtocol { } func updateTableViewDataSource(_ cells: [BaseChatCellModel]) { - tableViewDataSource.cells = cells - tableView.reloadData() - tableView.layoutIfNeeded() + collectionViewDataSource?.cells = cells + collectionView.reloadData() + collectionView.layoutIfNeeded() } func removeReplies(_ ids: [String]) { - let filtred = self.tableViewDataSource.cells.filter { model -> Bool in + guard let dataSource = collectionViewDataSource else { + return + } + let filtred = dataSource.cells.filter { model -> Bool in ids.contains(where: { $0 == model.id }) } let indexes = filtred.map { model -> Int in - return self.tableViewDataSource.cells.index(where: { $0.id == model.id })! + return dataSource.cells.index(where: { $0.id == model.id })! } guard !indexes.isEmpty else { return } - indexes.forEach { self.tableViewDataSource.cells.remove(at: $0) } - tableView.beginUpdates() - tableView.deleteRows(at: indexes.map { IndexPath(row: $0, section: 0) }, with: .none) - tableView.endUpdates() + indexes.forEach { dataSource.cells.remove(at: $0) } + + collectionView.performBatchUpdates({ + collectionView.deleteItems(at: indexes.map { IndexPath(row: $0, section: 0) }) + }, completion: nil) } func insertReplies(_ cells: [BaseChatCellModel]) { @@ -136,13 +141,12 @@ class RepliesVC: BaseVC, RepliesViewProtocol { } func updateProgress(progressModel: ProgressModel?) { - let url = progressModel?.url - let list = tableViewDataSource.cells.filter({ (model) -> Bool in - model.progressModel?.url == url - }) - list.forEach { (model) in - model.progressModel = progressModel + guard let dataSource = collectionViewDataSource else { + return } + let url = progressModel?.url + let list = dataSource.cells.filter { $0.progressModel?.url == url } + list.forEach { $0.progressModel = progressModel } reloadIfVisible(models: list) } @@ -156,17 +160,17 @@ class RepliesVC: BaseVC, RepliesViewProtocol { //MARK: - Private methods - private func registerCells(for tableView: UITableView) { + private func registerCells(for collectionView: UICollectionView) { MessageCellFactory.selfIdentifiers.forEach { - tableView.register(BaseChatCell.self, forCellReuseIdentifier: $0) + collectionView.register(BaseChatCell.self, forCellWithReuseIdentifier: $0) } MessageCellFactory.opponenetIdentifiers.forEach { - tableView.register(OponentChatCell.self, forCellReuseIdentifier: $0) + collectionView.register(OponentChatCell.self, forCellWithReuseIdentifier: $0) } } func reloadIfVisible(models: [BaseChatCellModel]) { - tableView.visibleCells.forEach { (cell) in + collectionView.visibleCells.forEach { (cell) in if let model = (cell as? BaseChatCell)?.model { if let index = models.index(where: { (mod) -> Bool in if mod.id != nil { @@ -186,14 +190,16 @@ class RepliesVC: BaseVC, RepliesViewProtocol { } } +// MARK: - Layout + extension RepliesVC { - struct Constraints { - struct tableView { + enum Constraints { + enum tableView { static let topInset = CGFloat(8.0.adjustedByWidth) } - struct closeButton { + enum closeButton { static let height: CGFloat = 44.0 static let width: CGFloat = 80.0 @@ -201,7 +207,7 @@ extension RepliesVC { static let bottomInset: CGFloat = 26.0 } - struct repliesNumberLabel { + enum repliesNumberLabel { static let height = CGFloat(17.adjustedByWidth) static let rightInset = 16.adjustedByWidth static let leftOffset = 8.adjustedByWidth diff --git a/Nynja/Modules/ScheduleMessage/Interactor/ScheduleMessageInteractor.swift b/Nynja/Modules/ScheduleMessage/Interactor/ScheduleMessageInteractor.swift index 27ac6ddc460aec07830ea0c6d3804d4fcc32607b..3a4ded37d702702343f70a5f6befc16f02622c75 100644 --- a/Nynja/Modules/ScheduleMessage/Interactor/ScheduleMessageInteractor.swift +++ b/Nynja/Modules/ScheduleMessage/Interactor/ScheduleMessageInteractor.swift @@ -54,12 +54,12 @@ class ScheduleMessageInteractor: BaseInteractor, ScheduleMessageInteractorInputP func sendScheduledMessage(with timeZone: TimeZoneLocal, date: Date) { guard let phoneId = StorageService.sharedInstance.phoneId, let info = self.info, let (features, timestamp) = prepareDateTimeInfo(with: timeZone, date: date) else { return } - let messages = info.targets.messages(from: info.message, phoneId: phoneId) + let message = info.message + message.created = timestamp as AnyObject - mqttService.scheduleMessage(phoneId: phoneId, - messages: messages, - timestamp: timestamp, - features: features) + let messages = info.targets.messages(from: message, phoneId: phoneId) + + mqttService.scheduleMessage(phoneId: phoneId, messages: messages, timestamp: timestamp, features: features) } func editScheduledMessage(with timeZone: TimeZoneLocal, date: Date) { diff --git a/Nynja/Modules/ScheduleMessage/View/Views/MessageContent/AudioItemView.swift b/Nynja/Modules/ScheduleMessage/View/Views/MessageContent/AudioItemView.swift index f0894559ebc5ac0a60d74b48279062da6a7e120d..eff163027efefae768d5be9674e7addc4d9a421a 100644 --- a/Nynja/Modules/ScheduleMessage/View/Views/MessageContent/AudioItemView.swift +++ b/Nynja/Modules/ScheduleMessage/View/Views/MessageContent/AudioItemView.swift @@ -7,7 +7,6 @@ // import UIKit -import VoxImplant struct AudioItemModel { let url: URL diff --git a/Nynja/Modules/SettingsGroup/Presenter/SettingsGroupPresenter.swift b/Nynja/Modules/SettingsGroup/Presenter/SettingsGroupPresenter.swift index 33ebbc763b8ee539ec1212df84a681f2ecc4b85c..346e13c9e2bbf8ffd701dc3da646aae396ebcfa5 100644 --- a/Nynja/Modules/SettingsGroup/Presenter/SettingsGroupPresenter.swift +++ b/Nynja/Modules/SettingsGroup/Presenter/SettingsGroupPresenter.swift @@ -54,15 +54,18 @@ class SettingsGroupPresenter: BasePresenter, SettingsGroupPresenterProtocol, Cre } } - func changeAvatar() { + func openAvatar(from imageView: UIImageView) { if isAdmin { wireFrame.changeAvatar() - } else { - if let urlStr = room?.data?.first?.payload, !urlStr.isEmpty { - if let url = URL(string: urlStr) { - wireFrame.showAvatar(url) - } + } else if let urlString = room?.data?.first?.payload, let url = URL(string: urlString) { + guard let view = view as? UIViewController else { + return } + let transitionInfo = ImagePreviewTransitionInfo(interactiveDismissalEnabled: true, + startingView: imageView, + endingView: imageView) + + wireFrame.presentAvatarModally(imageURL: url, on: view, with: transitionInfo) } } diff --git a/Nynja/Modules/SettingsGroup/SettingsProtocols.swift b/Nynja/Modules/SettingsGroup/SettingsProtocols.swift index 2617cf66fab6737822b96560d4ade3765fb6b024..bb0ca5f62fc78089a196fe4cba51fd841727c1a3 100644 --- a/Nynja/Modules/SettingsGroup/SettingsProtocols.swift +++ b/Nynja/Modules/SettingsGroup/SettingsProtocols.swift @@ -16,7 +16,7 @@ protocol SettingsGroupWireframeProtocol: class { func present(navigation: UINavigationController, main: MainWireFrame?) func hide() - func showAvatar(_ url: URL) + func presentAvatarModally(imageURL: URL, on view: UIViewController, with transitionInfo: ImagePreviewTransitionInfo) func changeAvatar() func changeGroupName(name: String) func changeAlias(alias: String, nicks: [String]) @@ -43,7 +43,7 @@ protocol SettingsGroupPresenterProtocol: BasePresenterProtocol { */ func hide() - func changeAvatar() + func openAvatar(from imageView: UIImageView) func changeGroupName() func changeAlias() func showRules() diff --git a/Nynja/Modules/SettingsGroup/View/SettingsGroupVC.swift b/Nynja/Modules/SettingsGroup/View/SettingsGroupVC.swift index fe8fe8e8d55ce2b46062b571feb4534adbfdb337..8ceaf243e6e21cef0bb23fb426ff53dacfa7013d 100644 --- a/Nynja/Modules/SettingsGroup/View/SettingsGroupVC.swift +++ b/Nynja/Modules/SettingsGroup/View/SettingsGroupVC.swift @@ -123,8 +123,8 @@ class SettingsGroupViewController: BaseVC, SettingsGroupViewProtocol, SettingCel func didTapSetting(_ type: SettingViewModelType) { switch type { - case .Avatar: - presenter.changeAvatar() + case let .Avatar(imageView): + presenter.openAvatar(from: imageView) case .Name: presenter.changeGroupName() case .Alias: diff --git a/Nynja/Modules/SettingsGroup/WireFrame/SettingsGroupWireFrame.swift b/Nynja/Modules/SettingsGroup/WireFrame/SettingsGroupWireFrame.swift index c6a85a0a6efb39bcecd93b02064e1605802aaa71..887652606040ae4ea8583aab2a314b3b90f48d88 100644 --- a/Nynja/Modules/SettingsGroup/WireFrame/SettingsGroupWireFrame.swift +++ b/Nynja/Modules/SettingsGroup/WireFrame/SettingsGroupWireFrame.swift @@ -55,12 +55,10 @@ class SettingsGroupWireFrame: SettingsGroupWireframeProtocol { navigation?.popViewController(animated: true) } - - func showAvatar(_ url: URL) { - if let mainNavigation = main?.navigation { - ImagePreviewWireFrame().presentImagePreview(navigation: mainNavigation, imageURL: url) - mainNavigation.view.layoutIfNeeded() - } + func presentAvatarModally(imageURL: URL, on view: UIViewController, with transitionInfo: ImagePreviewTransitionInfo) { + ImagePreviewWireFrame().presentImagePreviewModally(parentVC: view, + imageURL: imageURL, + transitionInfo: transitionInfo) } func changeAvatar() { diff --git a/Nynja/Modules/TimeZoneSelector/Interactor/TimeZoneSelectorInteractor.swift b/Nynja/Modules/TimeZoneSelector/Interactor/TimeZoneSelectorInteractor.swift index dd0223add54ec70497941deb3bc25861be454274..b61eef2eb0a999ddcd879a5afd748f93b36b170c 100644 --- a/Nynja/Modules/TimeZoneSelector/Interactor/TimeZoneSelectorInteractor.swift +++ b/Nynja/Modules/TimeZoneSelector/Interactor/TimeZoneSelectorInteractor.swift @@ -11,10 +11,7 @@ class TimeZoneSelectorInteractor: TimeZoneSelectorInteractorInputProtocol { weak var presenter: TimeZoneSelectorInteractorOutputProtocol! func filterTimeZones(text: String) -> [TimeZoneLocal] { - let filtered = TimeZoneManager.shared.timezones.filter { - text.isEmpty ? true : $0.utc.first!.replacingOccurrences(of: "_", with: " ").contains(text) - } - return filtered + let timezones = TimeZoneManager.shared.timezones + return text.isEmpty ? timezones : timezones.filter { text.isIn(string: $0.name, options: .caseInsensitive) } } - } diff --git a/Nynja/Modules/TimeZoneSelector/View/TableView/TimeZoneCellModel.swift b/Nynja/Modules/TimeZoneSelector/View/TableView/TimeZoneCellModel.swift index f83b38c8bde8642f8bb2ffdb0ff9265f97892cc1..fd28944b55e911cddbca68c4d0813a6d7455e165 100644 --- a/Nynja/Modules/TimeZoneSelector/View/TableView/TimeZoneCellModel.swift +++ b/Nynja/Modules/TimeZoneSelector/View/TableView/TimeZoneCellModel.swift @@ -18,7 +18,7 @@ final class TimeZoneCellModel: CellViewModel { } func setup(cell: TimeZoneCell) { - cell.titleLabel.text = timeZone.utc.first!.replacingOccurrences(of: "_", with: " ") + cell.titleLabel.text = timeZone.name cell.descriptionLabel.text = timeZone.text } } diff --git a/Nynja/OptionsItemsFactory.swift b/Nynja/OptionsItemsFactory.swift index 6da1b98a5b40a97d404b2ebdc72fea1ef1f4920e..09089073b07464cd47756b60df48799c20db10d5 100644 --- a/Nynja/OptionsItemsFactory.swift +++ b/Nynja/OptionsItemsFactory.swift @@ -17,9 +17,10 @@ class OptionsItemsFactory: WCBaseItemsFactory { // MARK: - Second lvl override var secondLevelItems: ItemModels { - return [logout, notifications, changeNumber, wheelPosition, buildNumber, support, languageSettings, theme, dataAndStorage, security, privacy, about, deleteAccount] +// return [logout, notifications, changeNumber, wheelPosition, buildNumber, support, languageSettings, theme, dataAndStorage, security, privacy, about, deleteAccount] + return [languageSettings, theme, dataAndStorage, security, privacy, notifications, changeNumber, wheelPosition, buildNumber, support, logout] } - +//Language, Theme, Data and Storage, Security, Privacy, notification, change number, Wheel position, Build number, support // MARK: - Items var logout: ImageActionItemModel { let item = ImageActionItemModel(navItem: .logOut, action: { [weak navigateDelegate] (item, indexPath) in diff --git a/Nynja/ProgressModel.swift b/Nynja/ProgressModel.swift index 2ef1ce78c411c3c6fab1f89a100f3ab2f9e64d9b..a9828c2a521eecba5363b21e1ddbd1cf37f59ade 100644 --- a/Nynja/ProgressModel.swift +++ b/Nynja/ProgressModel.swift @@ -14,22 +14,26 @@ class ProgressModel { case done } - var url: URL - var progress: Float - var speed: Double - public private (set) var speedStringRepresentation: String = "" + var url: URL + var progress: Float = 0 + var speed: Double = 0 var result: URL? var status: ProgressStatus - var fileSize: Int64 + var fileSize: Int64 = 0 - required init(url: URL, status: ProgressStatus, result: URL? = nil, transferInfo: TransferInfo) { + required init(url: URL, status: ProgressStatus, result: URL? = nil, transferInfo: TransferInfo? = nil) { self.url = url - self.progress = transferInfo.progress - self.speed = transferInfo.speed + + if let info = transferInfo { + self.progress = info.progress + self.speed = info.speed + self.fileSize = info.fileSize + } + self.speedStringRepresentation = ProgressModel.getSpeedStringRepresentation(with: self.speed) - self.fileSize = transferInfo.fileSize + self.status = status self.result = result } diff --git a/Nynja/RequestModelFactory/HistoryRequestModelFactory.swift b/Nynja/RequestModelFactory/HistoryRequestModelFactory.swift index 71c4f9715f3c42fd043842c9dcf339d838d046ee..676ab754ff060ff24a820cf4a594f0254853472f 100644 --- a/Nynja/RequestModelFactory/HistoryRequestModelFactory.swift +++ b/Nynja/RequestModelFactory/HistoryRequestModelFactory.swift @@ -19,6 +19,11 @@ protocol HistoryRequestModelFactoryProtocol { lastMessageId: Int64) throws -> HistoryRequestModel func makeHistoryRequestModelStickers(rosterId: String) throws -> HistoryRequestModel + + func makeHistoryRequestModel(rosterId: String, + chat: ChatModel, + from: MessageServerId, + to: MessageServerId) throws -> HistoryRequestModel } enum HistoryRequestModelError: Error { @@ -48,7 +53,7 @@ final class HistoryRequestModelFactory: HistoryRequestModelFactoryProtocol { let historyType = try findModelType(for: chat) let input = HistoryRequestModel.RequestInput(rosterId: rosterId, historyType: historyType, - actionType: .get(lastMessageId: lastMessageId, pageSize: pageSize)) + actionType: .getPage(from: lastMessageId, pageSize: pageSize)) return HistoryRequestModel(requestInput: input) } @@ -77,4 +82,16 @@ final class HistoryRequestModelFactory: HistoryRequestModelFactoryProtocol { actionType: .defaultStickerPack) return HistoryRequestModel(requestInput: input) } + + func makeHistoryRequestModel(rosterId: String, + chat: ChatModel, + from: MessageServerId, + to: MessageServerId) throws -> HistoryRequestModel { + + let historyType = try findModelType(for: chat) + let input = HistoryRequestModel.RequestInput(rosterId: rosterId, + historyType: historyType, + actionType: .getBetween(from: from, to: to)) + return HistoryRequestModel(requestInput: input) + } } diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/Contents.json b/Nynja/Resources/Assets.xcassets/Marketplace/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..da4a164c918651cdd1e11dca5cc62c333f097601 --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Marketplace/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/marketplace_swap_button.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Marketplace/marketplace_swap_button.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..497e4b30d30da927a65b856a5ec24d298d949bb4 --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Marketplace/marketplace_swap_button.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "marketplace_swap_button.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/marketplace_swap_button.imageset/marketplace_swap_button.pdf b/Nynja/Resources/Assets.xcassets/Marketplace/marketplace_swap_button.imageset/marketplace_swap_button.pdf new file mode 100644 index 0000000000000000000000000000000000000000..02beb1afb31dec6cac23b7d2528020e659270423 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Marketplace/marketplace_swap_button.imageset/marketplace_swap_button.pdf differ diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/Contents.json b/Nynja/Resources/Assets.xcassets/Marketplace/menu/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..da4a164c918651cdd1e11dca5cc62c333f097601 --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Marketplace/menu/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/access/Contents.json b/Nynja/Resources/Assets.xcassets/Marketplace/menu/access/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..da4a164c918651cdd1e11dca5cc62c333f097601 --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Marketplace/menu/access/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/ic_close_clear.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Marketplace/menu/access/marketplace_apps.imageset/Contents.json similarity index 72% rename from Nynja/Resources/Assets.xcassets/ic_close_clear.imageset/Contents.json rename to Nynja/Resources/Assets.xcassets/Marketplace/menu/access/marketplace_apps.imageset/Contents.json index cfa020576cbeaf01a9a547a863f80ba30f5bb309..ec684e47e96910cee37ec049063e6281bcbd71ff 100644 --- a/Nynja/Resources/Assets.xcassets/ic_close_clear.imageset/Contents.json +++ b/Nynja/Resources/Assets.xcassets/Marketplace/menu/access/marketplace_apps.imageset/Contents.json @@ -2,7 +2,7 @@ "images" : [ { "idiom" : "universal", - "filename" : "ic_close_wheel_clear.pdf" + "filename" : "marketplace_apps.pdf" } ], "info" : { diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/access/marketplace_apps.imageset/marketplace_apps.pdf b/Nynja/Resources/Assets.xcassets/Marketplace/menu/access/marketplace_apps.imageset/marketplace_apps.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e861a6a1e70b0075b6f3a91e176128ef0c826653 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Marketplace/menu/access/marketplace_apps.imageset/marketplace_apps.pdf differ diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/access/marketplace_bots.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Marketplace/menu/access/marketplace_bots.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..92ea8cf368125d87dd1aabdb58a0bbb35702708f --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Marketplace/menu/access/marketplace_bots.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "marketplace_bots.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/access/marketplace_bots.imageset/marketplace_bots.pdf b/Nynja/Resources/Assets.xcassets/Marketplace/menu/access/marketplace_bots.imageset/marketplace_bots.pdf new file mode 100644 index 0000000000000000000000000000000000000000..775ed007da562d6b007d7255a41a2b4af1f63ad4 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Marketplace/menu/access/marketplace_bots.imageset/marketplace_bots.pdf differ diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/access/marketplace_groups_channels.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Marketplace/menu/access/marketplace_groups_channels.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..c9c3b967726588c6a3b8d211865efcd15a29f79b --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Marketplace/menu/access/marketplace_groups_channels.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "marketplace_groups_channels.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/access/marketplace_groups_channels.imageset/marketplace_groups_channels.pdf b/Nynja/Resources/Assets.xcassets/Marketplace/menu/access/marketplace_groups_channels.imageset/marketplace_groups_channels.pdf new file mode 100644 index 0000000000000000000000000000000000000000..54f5cfc9a5d80854f469bc5485e43a77149a34a1 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Marketplace/menu/access/marketplace_groups_channels.imageset/marketplace_groups_channels.pdf differ diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/Contents.json b/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..da4a164c918651cdd1e11dca5cc62c333f097601 --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/marketplace_design.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/marketplace_design.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..4df94903ba94a7e4732cac24ec504fd82c50bdfd --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/marketplace_design.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "marketplace_design.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/marketplace_design.imageset/marketplace_design.pdf b/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/marketplace_design.imageset/marketplace_design.pdf new file mode 100644 index 0000000000000000000000000000000000000000..56ac37bcf4e0fff09286d6cc6ebeb46207992fb8 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/marketplace_design.imageset/marketplace_design.pdf differ diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/marketplace_interpretation.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/marketplace_interpretation.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..d4e643bea8053c6a573c92457f21e1d83fe713aa --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/marketplace_interpretation.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "marketplace_interpretation.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/marketplace_interpretation.imageset/marketplace_interpretation.pdf b/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/marketplace_interpretation.imageset/marketplace_interpretation.pdf new file mode 100644 index 0000000000000000000000000000000000000000..dc1a3df1f8405acdbcdca5f00f556c8357f7300c Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/marketplace_interpretation.imageset/marketplace_interpretation.pdf differ diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/marketplace_support.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/marketplace_support.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..27daa99ac982d0dfb5706383e7ed933b1e6f676f --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/marketplace_support.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "marketplace_support.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/marketplace_support.imageset/marketplace_support.pdf b/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/marketplace_support.imageset/marketplace_support.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4c10074a6fe0b01dbdf4a70f1e8b4fa61988b138 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Marketplace/menu/freelance/marketplace_support.imageset/marketplace_support.pdf differ diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/Contents.json b/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..da4a164c918651cdd1e11dca5cc62c333f097601 --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/marketplace_access.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/marketplace_access.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..edb120cb996ff7ac04ab02cbd2f801fad61ff826 --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/marketplace_access.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "marketplace_access.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/marketplace_access.imageset/marketplace_access.pdf b/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/marketplace_access.imageset/marketplace_access.pdf new file mode 100644 index 0000000000000000000000000000000000000000..213a0cb865dcda85c10c5259e9f017a083c1a53c Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/marketplace_access.imageset/marketplace_access.pdf differ diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/marketplace_freelance.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/marketplace_freelance.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..ccbe15f4e4c1155e8a1f2c8834121755650ccd7a --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/marketplace_freelance.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "marketplace_freelance.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/marketplace_freelance.imageset/marketplace_freelance.pdf b/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/marketplace_freelance.imageset/marketplace_freelance.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e0b16b4819314344577f7ec26654e8dde64cdf2c Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/marketplace_freelance.imageset/marketplace_freelance.pdf differ diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/marketplace_virtual_goods.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/marketplace_virtual_goods.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..2d94320ac498ef6bf1f77162d96a7e27220f508b --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/marketplace_virtual_goods.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "marketplace_virtual_goods.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/marketplace_virtual_goods.imageset/marketplace_virtual_goods.pdf b/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/marketplace_virtual_goods.imageset/marketplace_virtual_goods.pdf new file mode 100644 index 0000000000000000000000000000000000000000..73dab8cfd122619eca6047fe9bd5b1a9df3f5d44 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Marketplace/menu/main/marketplace_virtual_goods.imageset/marketplace_virtual_goods.pdf differ diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/virtual goods/Contents.json b/Nynja/Resources/Assets.xcassets/Marketplace/menu/virtual goods/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..da4a164c918651cdd1e11dca5cc62c333f097601 --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Marketplace/menu/virtual goods/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/virtual goods/marketplace_media_content.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Marketplace/menu/virtual goods/marketplace_media_content.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..9c778809139f207a44ff52e09100199add701ded --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Marketplace/menu/virtual goods/marketplace_media_content.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "marketplace_media_content.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/virtual goods/marketplace_media_content.imageset/marketplace_media_content.pdf b/Nynja/Resources/Assets.xcassets/Marketplace/menu/virtual goods/marketplace_media_content.imageset/marketplace_media_content.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ce158d51ff5457dbc753b5cc25f5b6a8cb41f506 Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Marketplace/menu/virtual goods/marketplace_media_content.imageset/marketplace_media_content.pdf differ diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/virtual goods/marketplace_sticker.imageset/Contents.json b/Nynja/Resources/Assets.xcassets/Marketplace/menu/virtual goods/marketplace_sticker.imageset/Contents.json new file mode 100644 index 0000000000000000000000000000000000000000..e71566c365403c9a8b35591fefae4b59df1ba9bd --- /dev/null +++ b/Nynja/Resources/Assets.xcassets/Marketplace/menu/virtual goods/marketplace_sticker.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "marketplace_sticker.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Nynja/Resources/Assets.xcassets/Marketplace/menu/virtual goods/marketplace_sticker.imageset/marketplace_sticker.pdf b/Nynja/Resources/Assets.xcassets/Marketplace/menu/virtual goods/marketplace_sticker.imageset/marketplace_sticker.pdf new file mode 100644 index 0000000000000000000000000000000000000000..605b2e85d1518a07d2876595fcefa09c5d47b6da Binary files /dev/null and b/Nynja/Resources/Assets.xcassets/Marketplace/menu/virtual goods/marketplace_sticker.imageset/marketplace_sticker.pdf differ diff --git a/Nynja/Resources/Assets.xcassets/ic_close_clear.imageset/ic_close_wheel_clear.pdf b/Nynja/Resources/Assets.xcassets/ic_close_clear.imageset/ic_close_wheel_clear.pdf deleted file mode 100644 index 583dc73dcaf583016363daf9e2fe23890b36064f..0000000000000000000000000000000000000000 --- a/Nynja/Resources/Assets.xcassets/ic_close_clear.imageset/ic_close_wheel_clear.pdf +++ /dev/null @@ -1,3664 +0,0 @@ -%PDF-1.5 % -1 0 obj <>/OCGs[5 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream - - - - - application/pdf - - - ic_close_wheel - - - 2018-05-23T12:33:26+03:00 - 2018-05-23T12:33:26+03:00 - 2018-05-23T12:33:26+03:00 - Adobe Illustrator CC 2014 (Windows) - - - - 256 - 256 - JPEG - /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX//2Q== - - - - uuid:32433c3f-d46d-4542-b0db-d7a2e136ea7c - xmp.did:94a44799-036f-e744-828f-48fad356141f - uuid:5D20892493BFDB11914A8590D31508C8 - proof:pdf - - uuid:d1c078a0-2746-42b2-b0d1-25aedff8fb1e - xmp.did:1b6690ed-28a8-c141-9479-b6a9cf6be651 - uuid:5D20892493BFDB11914A8590D31508C8 - proof:pdf - - - - - saved - xmp.iid:94a44799-036f-e744-828f-48fad356141f - 2018-05-23T12:33:23+03:00 - Adobe Illustrator CC 2014 (Windows) - / - - - - Print - False - False - 1 - - 34.000000 - 34.000000 - Pixels - - - - - Default Swatch Group - 0 - - - - White - CMYK - PROCESS - 0.000000 - 0.000000 - 0.000000 - 0.000000 - - - Black - CMYK - PROCESS - 0.000000 - 0.000000 - 0.000000 - 100.000000 - - - CMYK Red - CMYK - PROCESS - 0.000000 - 100.000000 - 100.000000 - 0.000000 - - - CMYK Yellow - CMYK - PROCESS - 0.000000 - 0.000000 - 100.000000 - 0.000000 - - - CMYK Green - CMYK - PROCESS - 100.000000 - 0.000000 - 100.000000 - 0.000000 - - - CMYK Cyan - CMYK - PROCESS - 100.000000 - 0.000000 - 0.000000 - 0.000000 - - - CMYK Blue - CMYK - PROCESS - 100.000000 - 100.000000 - 0.000000 - 0.000000 - - - CMYK Magenta - CMYK - PROCESS - 0.000000 - 100.000000 - 0.000000 - 0.000000 - - - C=15 M=100 Y=90 K=10 - CMYK - PROCESS - 15.000000 - 100.000000 - 90.000000 - 10.000000 - - - C=0 M=90 Y=85 K=0 - CMYK - PROCESS - 0.000000 - 90.000000 - 85.000000 - 0.000000 - - - C=0 M=80 Y=95 K=0 - CMYK - PROCESS - 0.000000 - 80.000000 - 95.000000 - 0.000000 - - - C=0 M=50 Y=100 K=0 - CMYK - PROCESS - 0.000000 - 50.000000 - 100.000000 - 0.000000 - - - C=0 M=35 Y=85 K=0 - CMYK - PROCESS - 0.000000 - 35.000000 - 85.000000 - 0.000000 - - - C=5 M=0 Y=90 K=0 - CMYK - PROCESS - 5.000000 - 0.000000 - 90.000000 - 0.000000 - - - C=20 M=0 Y=100 K=0 - CMYK - PROCESS - 20.000000 - 0.000000 - 100.000000 - 0.000000 - - - C=50 M=0 Y=100 K=0 - CMYK - PROCESS - 50.000000 - 0.000000 - 100.000000 - 0.000000 - - - C=75 M=0 Y=100 K=0 - CMYK - PROCESS - 75.000000 - 0.000000 - 100.000000 - 0.000000 - - - C=85 M=10 Y=100 K=10 - CMYK - PROCESS - 85.000000 - 10.000000 - 100.000000 - 10.000000 - - - C=90 M=30 Y=95 K=30 - CMYK - PROCESS - 90.000000 - 30.000000 - 95.000000 - 30.000000 - - - C=75 M=0 Y=75 K=0 - CMYK - PROCESS - 75.000000 - 0.000000 - 75.000000 - 0.000000 - - - C=80 M=10 Y=45 K=0 - CMYK - PROCESS - 80.000000 - 10.000000 - 45.000000 - 0.000000 - - - C=70 M=15 Y=0 K=0 - CMYK - PROCESS - 70.000000 - 15.000000 - 0.000000 - 0.000000 - - - C=85 M=50 Y=0 K=0 - CMYK - PROCESS - 85.000000 - 50.000000 - 0.000000 - 0.000000 - - - C=100 M=95 Y=5 K=0 - CMYK - PROCESS - 100.000000 - 95.000000 - 5.000000 - 0.000000 - - - C=100 M=100 Y=25 K=25 - CMYK - PROCESS - 100.000000 - 100.000000 - 25.000000 - 25.000000 - - - C=75 M=100 Y=0 K=0 - CMYK - PROCESS - 75.000000 - 100.000000 - 0.000000 - 0.000000 - - - C=50 M=100 Y=0 K=0 - CMYK - PROCESS - 50.000000 - 100.000000 - 0.000000 - 0.000000 - - - C=35 M=100 Y=35 K=10 - CMYK - PROCESS - 35.000000 - 100.000000 - 35.000000 - 10.000000 - - - C=10 M=100 Y=50 K=0 - CMYK - PROCESS - 10.000000 - 100.000000 - 50.000000 - 0.000000 - - - C=0 M=95 Y=20 K=0 - CMYK - PROCESS - 0.000000 - 95.000000 - 20.000000 - 0.000000 - - - C=25 M=25 Y=40 K=0 - CMYK - PROCESS - 25.000000 - 25.000000 - 40.000000 - 0.000000 - - - C=40 M=45 Y=50 K=5 - CMYK - PROCESS - 40.000000 - 45.000000 - 50.000000 - 5.000000 - - - C=50 M=50 Y=60 K=25 - CMYK - PROCESS - 50.000000 - 50.000000 - 60.000000 - 25.000000 - - - C=55 M=60 Y=65 K=40 - CMYK - PROCESS - 55.000000 - 60.000000 - 65.000000 - 40.000000 - - - C=25 M=40 Y=65 K=0 - CMYK - PROCESS - 25.000000 - 40.000000 - 65.000000 - 0.000000 - - - C=30 M=50 Y=75 K=10 - CMYK - PROCESS - 30.000000 - 50.000000 - 75.000000 - 10.000000 - - - C=35 M=60 Y=80 K=25 - CMYK - PROCESS - 35.000000 - 60.000000 - 80.000000 - 25.000000 - - - C=40 M=65 Y=90 K=35 - CMYK - PROCESS - 40.000000 - 65.000000 - 90.000000 - 35.000000 - - - C=40 M=70 Y=100 K=50 - CMYK - PROCESS - 40.000000 - 70.000000 - 100.000000 - 50.000000 - - - C=50 M=70 Y=80 K=70 - CMYK - PROCESS - 50.000000 - 70.000000 - 80.000000 - 70.000000 - - - - - - Grays - 1 - - - - C=0 M=0 Y=0 K=100 - CMYK - PROCESS - 0.000000 - 0.000000 - 0.000000 - 100.000000 - - - C=0 M=0 Y=0 K=90 - CMYK - PROCESS - 0.000000 - 0.000000 - 0.000000 - 89.999400 - - - C=0 M=0 Y=0 K=80 - CMYK - PROCESS - 0.000000 - 0.000000 - 0.000000 - 79.998800 - - - C=0 M=0 Y=0 K=70 - CMYK - PROCESS - 0.000000 - 0.000000 - 0.000000 - 69.999700 - - - C=0 M=0 Y=0 K=60 - CMYK - PROCESS - 0.000000 - 0.000000 - 0.000000 - 59.999100 - - - C=0 M=0 Y=0 K=50 - CMYK - PROCESS - 0.000000 - 0.000000 - 0.000000 - 50.000000 - - - C=0 M=0 Y=0 K=40 - CMYK - PROCESS - 0.000000 - 0.000000 - 0.000000 - 39.999400 - - - C=0 M=0 Y=0 K=30 - CMYK - PROCESS - 0.000000 - 0.000000 - 0.000000 - 29.998800 - - - C=0 M=0 Y=0 K=20 - CMYK - PROCESS - 0.000000 - 0.000000 - 0.000000 - 19.999700 - - - C=0 M=0 Y=0 K=10 - CMYK - PROCESS - 0.000000 - 0.000000 - 0.000000 - 9.999100 - - - C=0 M=0 Y=0 K=5 - CMYK - PROCESS - 0.000000 - 0.000000 - 0.000000 - 4.998800 - - - - - - Brights - 1 - - - - C=0 M=100 Y=100 K=0 - CMYK - PROCESS - 0.000000 - 100.000000 - 100.000000 - 0.000000 - - - C=0 M=75 Y=100 K=0 - CMYK - PROCESS - 0.000000 - 75.000000 - 100.000000 - 0.000000 - - - C=0 M=10 Y=95 K=0 - CMYK - PROCESS - 0.000000 - 10.000000 - 95.000000 - 0.000000 - - - C=85 M=10 Y=100 K=0 - CMYK - PROCESS - 85.000000 - 10.000000 - 100.000000 - 0.000000 - - - C=100 M=90 Y=0 K=0 - CMYK - PROCESS - 100.000000 - 90.000000 - 0.000000 - 0.000000 - - - C=60 M=90 Y=0 K=0 - CMYK - PROCESS - 60.000000 - 90.000000 - 0.003100 - 0.003100 - - - - - - - Adobe PDF library 11.00 - - - - - - - - - - - - - - - - - - - - - - - - - -endstream endobj 3 0 obj <> endobj 7 0 obj <>/Resources<>/Properties<>>>/Thumb 11 0 R/TrimBox[0.0 0.0 34.0 34.0]/Type/Page>> endobj 8 0 obj <>stream -HlMj0 :N؉mӡ.zvQwa -c:D>Kz<ɞWΫÇf#E >X@%[@&qD1 ,p DJqni@NOg!buTzJ UMNcSh;UznPe>tnSY__F/EunH=YȻ - mT6pdLIPox}<=,Jp:קW\? -endstream endobj 11 0 obj <>stream -8;Xp,*>JPW(]\SI%<2~> -endstream endobj 12 0 obj [/Indexed/DeviceRGB 255 13 0 R] endobj 13 0 obj <>stream -8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 -b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` -E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn -6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( -l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> -endstream endobj 5 0 obj <> endobj 14 0 obj [/View/Design] endobj 15 0 obj <>>> endobj 10 0 obj <> endobj 9 0 obj <> endobj 16 0 obj <> endobj 17 0 obj <>stream -%!PS-Adobe-3.0 -%%Creator: Adobe Illustrator(R) 17.0 -%%AI8_CreatorVersion: 18.0.0 -%%For: (Desi Karavelikova) () -%%Title: (Untitled-1) -%%CreationDate: 5/23/2018 12:33 PM -%%Canvassize: 16383 -%%BoundingBox: 6 -28 28 -6 -%%HiResBoundingBox: 6.91000258922577 -27.0899973277592 27.0899974107742 -6.91000274462749 -%%DocumentProcessColors: -%AI5_FileFormat 13.0 -%AI12_BuildNumber: 18 -%AI3_ColorUsage: Color -%AI7_ImageSettings: 0 -%%CMYKProcessColor: 1 1 1 1 ([Registration]) -%AI3_Cropmarks: 0 -34 34 0 -%AI3_TemplateBox: 17.5 -17.5 17.5 -17.5 -%AI3_TileBox: -265 -395 305 355 -%AI3_DocumentPreview: None -%AI5_ArtSize: 14400 14400 -%AI5_RulerUnits: 6 -%AI9_ColorModel: 2 -%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 -%AI5_TargetResolution: 800 -%AI5_NumLayers: 1 -%AI9_OpenToView: -15 1 25.56 1626 923 18 0 0 46 112 0 0 0 1 1 0 1 1 0 1 -%AI5_OpenViewLayers: 7 -%%PageOrigin:-289 -413 -%AI7_GridSettings: 72 8 72 8 1 0 0.800000011920929 0.800000011920929 0.800000011920929 0.899999976158142 0.899999976158142 0.899999976158142 -%AI9_Flatten: 1 -%AI12_CMSettings: 00.MS -%%EndComments - -endstream endobj 18 0 obj <>stream -%%BoundingBox: 6 -28 28 -6 -%%HiResBoundingBox: 6.91000258922577 -27.0899973277592 27.0899974107742 -6.91000274462749 -%AI7_Thumbnail: 128 128 8 -%%BeginData: 2039 Hex Bytes -%0000330000660000990000CC0033000033330033660033990033CC0033FF -%0066000066330066660066990066CC0066FF009900009933009966009999 -%0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 -%00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 -%3333663333993333CC3333FF3366003366333366663366993366CC3366FF -%3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 -%33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 -%6600666600996600CC6600FF6633006633336633666633996633CC6633FF -%6666006666336666666666996666CC6666FF669900669933669966669999 -%6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 -%66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF -%9933009933339933669933999933CC9933FF996600996633996666996699 -%9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 -%99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF -%CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 -%CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 -%CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF -%CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC -%FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 -%FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 -%FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 -%000011111111220000002200000022222222440000004400000044444444 -%550000005500000055555555770000007700000077777777880000008800 -%000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB -%DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF -%00FF0000FFFFFF0000FF00FFFFFF00FFFFFF -%524C45FDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFF -%FDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFF -%FDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFF -%FDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFF -%FDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFF -%FDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFF -%FDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFFFFFFFF -%%EndData - -endstream endobj 19 0 obj <>stream -HOfGIwuuu&+EЕAeI`܁l$޹\gspAFno{kĻ:;L,gqekpplf9ʃuzdoՇsо/ǙT؅/#ל6gVTW[q`xS9}>#Ie}ZBF -A1&fLmjf&m|L)EK,FXћ keU! Fͺݦp ȓsH3lbm4Bܗ[J*gHДuȫtom5!*m9"s?G<-+d6>{о'ͼ=/b]1$eĚ}ósXM>sװ?kk}+G_ۗj=]ŋiT8h2=b6&>jNcdޛi533An gk]{P^#]=Fؔ0BCѨ; 1p#^#|eiӯW0HmQ;0Hc~9CAtEcN[p6lR %O WCƃ.ohɝ!=<1L\̏E@4d~7tkR'Ԑz4/fA4"38аc s' @ДɷaDY|~jaa*Qf? DC PB&88& b -Ѷ02)*BVTE;Qՠ:l3 -yQd)')׌ K{V@á% <,~c:N5};hTCo>t<1&No -z]@.ZpKcc`i6"QNhhZu"xQ1Νc>Rb1e#)эeک{iPabfq.ia%װjxDSJu3@' 3=H`gC-h' m%ʸM -ahV!)˂}EU/Ӌ% 09{+n3=/te~7!QOI* /z dFR#K)YQ8¥t kڣ]R1GLt N]l['d< ڲ,g˪Mi!C7X!C bBlt6igBu`֔zHȎoJѨkV #^4)hD#p7Ge=$J-tK'#+D [A +|y0 K;|ަzGSM0 C>[΃ .!X+}c* 6f#f̀ਃ6`STBߎ3HB /Q ux!^AavC$\^S gL\3| }Zu-yOZT-Yi zYS' UKLe=ɞ$4&eDP6e#%i]ުվ/L`\QP4Ԧ$Dqk98FEaȹjXxb iY`HFC@V[pcٵGS p;x3hMW &]'lȵA;~bL!.M+tƱTr9|f)P*!фȑv}y)u*-/dh&ծJznqqRWWU" A"> UHZZ|t\Y\+Smn4dwݡJTaܥuE\*~K@Gy$SHl>ES-ؒUR:H>Iï|?=g_b/__yo;AGGMmn!6g<) 0o툈.'La q8JԨD3)Օ x.\W6IFJ-%@t. ~צ`V.DL{1?Y:kq2\}Ԓ6{(Qpsc4R;rK7@mQ"`R*)\0(R~{!.fo(=U]uCkFϸ@̸=5K1|YΜui:R A*+(nN&'sn3j XÒ !WPf:H.:F8]<8,qd"jJynTC+f󹙖M,HF7 c C&)u-FO7MU],9~H]҈|N yca 3! )](`(JX^n96ʜ` <;"ճ?c,bAA#'B),pMB S-G~6K2eV#ayL9 HDB= Le3N.[VbGi9~~xqCoH7@(\"x[ $P)[8LZn92O}qjP܆[l[L Cc=P@Ê7+7$B;oǸCQu<an,_*3do -jDX &6oaɔNΚB:NHU .OP.N7a=`< njAq]n2x2b9$[PX~$Q I w[mbcj|;o'yT# o蕺D#|x>~SZpߓo0NT*m - /_'73U"4~#&[@6BF; "DWN]9Vȵ"9脞{<ODxAG9 Z}rA@CxسslἬeYevr;qc iK*geGb( Tt 7l:t5|RX?ޝl^ͥ2wBt'i[Aׇȭe2xHUEȜaU0lv/Э>l}F%=jWVI%_Ӡ #~ܮn޸#Rl.)Sxh*s'G 2uq!y5%l×$mZ¸;=H:[eHZW۱RPkp3 z,ŲzgXD FŽxFNв5(bR@U|d((tVFxvrf0,+ q60~?sK7pAݼ!pau_3B> ~cog^"`W>4xK Nk,fn Pvo8h}H5Bra&);W8uXahL7DLtMSܓeD[oЉ"ӟ=FDP9i uQ4auY,r֍oWWZ;AtP3ު|ly8\7ƙZ،̍BdE.PqN:B`N -D <#%p+#ϮVe12A[Oidw]&XF5#QZ\X3rts4>D.1^*4l1G# s +HفiUpx=˧yh -ɼ)ٖ!I7LM[ۺ~rgr՜&1nHqLcYvq<O3D2uɴ{T7l]\%JXwޣvN {Syu9jTi6[Cb:&lD)1m ]8ەc~&FYJ۷{4y"0 */oru =^~"#*ݚ2`|2}I8 دS5KAw +Y (MUL -يȇN/)&##Wv"?/x{|%=*ߠ5-$acBLi81_+cFUÂ${`;TΈv]B@m5b$avh(n=-b"C=THv`c\(q*&yl8,yJǮgQ!!nK[Ŗ(\8>98IDɫ&_0+\~) -4DUΞsrVs+ʠqi\0xwsS/[c]5a03ue%)Fж-!1"F][D:egRH_AJjs:jZ$VTB1 - G2;1d愐lrbǧ+hSlR '1O vD6D マ?xP?@/Mg"\Qt'gFGު#E6N%'HK !r!3s\ܴn -u@}=*e ',"Ыj%*5\RDAE΃ ~Gz,†IbUPG 6DH2?L葎#RkD]E0ʦ(DkaW j b݅dfO&$7Z?4/pp^u@nwg3RCM -AqN9B6)׻Lm1cC A0ʩ}AA.]HN, 'P|BA/ YWo@^&=rY+Ԕc+,{B%e2 }s#^X"(ەq3;| $zk:z\/A0gq"xUZD1SDluB0=:ɬDfy]oox=hȰ%4rQit|7]ÀVnmN'Bʹ;JonY˞lSIana94Phmq|H.ݙ>lOAdcJ#lf̬F JD\lO.8B{iX jcui^f:Jia㺿]jO#BIոQh FPI$@,mA-͍CFTF3̦7_*98Ye,vNYl:ʻ",=P˵.UgK6b^a:dF h-փ+bSJ̀ -c~. kPUȥ3(Ӎ!w|[m}h=6j7jX+"C ۮ#X2Gv:$)F\(M u tBޯFNt2*"FtՊ\&gzf]͵ZzJEWS#}RL3U"X OKlIEU %DWHʋh|I\H6;YN|ӓ:AbNV\b[MD`:0cDliŬ*0C -tښCҒdʼnRx\YBlx)u_>hE4:{eBӂQ<EMؾg^nCa%ERWŃBiq,' 8iapQ{Uk%,u -L't/dzM+ cɔ|} Y/"A?D q{Re%(}~,pqr{R,>52b,DЬ쯪s;T~gARaqr`G~N@6AQGAc\a'Hyl[$wt& ő88neèI$i)i\Xb9N[J.g ?٣.lYC0%q@ `N ?LbԨ:JJf(X(Bcm;sL3F?&m@I'K5_뤁TcCHBAFyO95?E%X:\e3)Z.tv@]")H_O`vDZy -?,aML2Tqk*#dw"UJDj=vDt(pw`R߳;ˆH9щAb!}# -"T03Aou+$dBF\>f#%"L :*5GMu (`As6<*H-aIޙYnt9sm|CNڢ -_B1-*G'b0i 6r .3\"0B%uZp(seͰ,"7m УF+m=S7I -Bޕcz jAlVEީ0e[cK2Ǥ3c-6E?{igkVⴲ2+ǝsp8|Hd &uO9kBRH(A5gl~3qb -A"@}Թy'M#B}1)k4>gߴz'@Eh}b'UA$865;Gy/sps05dK7S"5A MH[ -,[{)# C<'P= &$MA?4g 7Kɴ$lOPqœ612)#8EP,[%?; \E3fS@!Iv4I\p9iRH겴3dЮoTrݫ4X <HmcPh9@ -? ')I82Z"Me  UT> m3iCJvZ9ΎfTJUwFgd8V|281<1(*|"SP0>l eNV(sRwe܋`0\ -\TwIa*CVYuoӊrx -f -_OCSΈp}ҋ k2ԧe2fBvj$TdC"hy場,w\lKOW {( H ă{~l;ܟCe7V"ϓs -a('`ƒo1=fCA4퇏_o?/eo}|Wo߽o~?aß/;wK?|~X,FtB~6?B9*XMBҽJX?Sraay=>2l)Cz'HY$7R'Hw[w=JL1NLphtV e;C{H/ Քlo7ωr -<(2E~\Mα9"ppZNH&\Dc4' 6"&SHBreq}rg0 '咻C 9^ R9,[o* -QX/z'#&2ymDFnJ6.3(~ɜH ZD?#2@kF\s)\LT:ŅW-jgGޡSYVԔe8a Uf| ?-TN$=Aq\Uv<PM,`['5QB [d{Gr>xHAݡ|\ -, -֒}߈t%-xS5 -Sm߀%F6j8&+t&]@p2L-5Gnh3Ώki6fKofpe[Aa*ReZϴuarnTX4>ד@ݨ 'MDw:C]zwG_1Y|Pg N[Ouph7b=KUSщzqn!z}d X:y9H3ѓQULӋFltC1Q6Aoktt{,2)s`Kk#:0rDt 1Wv0wG9ZJL<1y&ߗ{Nĉjg "_:2Os3x_#23И@]i -'q]MI7":hDG^j]-+>Z˦ -?Cea~ l/WLqE4ㆡ2јɘ9?,+ltp2?ٵݞLZ@5`ʺ{P/$́*d;|:jRd.5<pY译0ہ+P_'as$vpOIn^N`FcQ+𹷆rC+f6)iq"eo[ϝ`c߆LUV=+ByD0iaQrDMaO, :pozC9U 0A3̍gp\n)Ẉ1f"Llf>+qP0ucV~*Ӗ$/^ApGLnݧQk<ȕFEhb`dLgt&NfRݱmΪ#SP/=^@Hd'`EL=O6|nC0 `H8APY!׊WY!Kaוeiu/D@"+kTs0Yv~9󨩞[}'yU3M__}a-S\ƯXiIS("u k!׹:[,*~`0*KaF8XԹ"(j=>Xq -s:_ϧdZ//Ծ ,S++K\Ƿ5v&t>1סI &b8+-4i?+"B5˭t_X㘧6"HE-Y Z?KqȬ70QDr)ѧyUr%8pk|zFydP\ird^:Z2X6ij1|"h?IЊOcFUqit4,{Ŕt[(o .U~{0ɹf0/vB]PaaFfPTiJn}W[h=x:)5t4LJ*`$$-[.0F-0GBg1QUA@56Tb "Gcu^*0^4Avbc^E Xa.ґ'U|<o yZx>A&X3πb-}PdT$ -%kɸo}@p{z8NrU-ƇI&2?7_m e8EsTƑ = A-ДCubK(&zD+!R x H5+X\("[c8 ՙ]24˅б70 fL1麃79s= qنd [;as/Ew2[qT=&erdg0 ]U%ot:U eig*I>Յ(4h~Z= ]gr#&)BAG "ܴ-F c'ypbhbȃ($l:10}-' (\SjV?Hf) -6[FSmpG|2gtm_}b+ye].MYr}XV?JmĮFl¨dpCS|6fpXD+k@ !m -ѝضXӹr?W.t7uyˡb ]K\3w2H p|}FSbF3dk;y=FDphVh [:)QD;uFx斁4֞ypZFE4 ^ZF`K|˄ :2#X>"k>"}BydN8d"ѠW70a֙[aȅ؞ˆЕ 5b}1FL <F\-zR)˙a)c:Bu{{T[\}sM;QT?PTaP-96^_awGz[wܾÝ\)T%taulS߀ >K `/D AcPrv; J y]8>\cNE: -߅ K2,Bi_Ycbfp؜ub+4bVcaD:H67A LU%!3b rG'3y@} 5'qYysq=;|ս^VVq.ߛq Bd9SZ/t]KQ+F%mƱ= 72v;p@Dn=;G>*^ -m/f)n9 Xp d(BoZ,7x";$=EL>^BF d K+xzimvԯ1I} (1GM݂5AGpBb)n1'B 5E-7Vw}߸*}09L&GM}AU 7%,vEzsHZ *Ԑ%[rYm!LgKKL9n}Eɏ#l6]v(WƮ9łV@`R.MjBD)rGICC[uh#n0Ucc!"Zq\z%0P瀰<Cv¦;7 @D --I٥g<:@QEkAG܂a`@hƈA)IlZMmof -v uF`6tp<v pK.#Or$Յ-À5ݲL S[E,57/({nn9|c(4m-v?];۾hr^ -EmU(#l%rDqKsm(HŽpU#mrnP \¸66;DG!zxh[%faf @#_m)mdKa=!("߹7`FRaK䂥ރޓO0,1 -tۦY/Eɞ@np*'HЭΠ6ke=XvcN8*4| U0"J2/QH8zo]̏T -@0*@ψ?T+)1_=i0 H<"DQAx=zOqĭNOŲj]EܚbN Ͷ;#Eu.x { ]]HX4y5s4y{ Ge*/J0R w4?, -R@ob80<07 @=Z~v@UYr2 ~2Q5o.ޥ3ߣf<1E 8LP7Sto!#C/%q2gz80Wd>N}vYJȖrB#"Dyg%+_Ec{Ef/t˅AӖTh)-} bEXgB˚9gm`5m=qy&ĥy"5*#uYNT' #MiŕJf#'7f>W|63@=2SX/bZ߷fa[o -i߫XWֳHN(92(,{szfO1%+=xnaq{8x'qݣa"Eϲ" -O*~ XE[3naqgmAf#<6\ZAjݮd z!uoп)gt!thd r LBY# S) (hm4î&QU04Шj0QUJDp΋F[ Y>[3ai~Bn}v4^<4P*4CkI)rʵC('5ֶmY] <RZR1'Ul2 -ޅdhNpX k$H4!G@2Rl'[Al*< -ފjtoBc -*\y\Ìy; :h3z. nl|3*S$3p'[!6䝡6Z1k,бTssKDpRy/ -{[ahmhh@VCe^lCh;/C0%jϳnx͇W,:j<5?]|݇,O^x?ܾ2;S~r]chyƟohZ}u{Ӈ}ۏo_ݾ}sǒ'ջ|r}޼㧛rȺ<H;!9N  4GჍDpyyDyG- /#ED@U(rFg76uq[#&r ŤZjAvƆ߈]fs\ȇsm9V@$?vm\cuDkE|H - C Hw-bkT.Kpx #V/ZIzl` Bw[,DD;D1 {ʎ7 Fe!7?ޝn;PH`߽{;#sʈna䰨PƠճ9 Q7șxz=+f?-^Oz)x>Iˣ`o$|a`4}]YD>Dxq0tz`]QY&| -)iG,)H9bH|o{?Ur@bO/;'] 7 .5M]" i-QPf*!&J3#`"ZM/evHpDAW MLbw`L2s%NĆ ^x@@"ڳ5ZX@򲣍}rbC[)q)l'[f $dNL< Q~hҀ9 8cPjH䊢a"G|aD- -Tw)"Em.6Q²,t= J%.m -jR\ r -rM^*4DLE1RefV9nN"0:K -ܳYoTA]jp#!b`'u7^u xoz^h!\6ۛjfb G[>)\NHڗ^ U|kܔ)ѹ#`+8F> @9?asbw\bz x*OW tFwvnNю y4(}SϕOg -%씭iLyEhE(|QcH4ixx>l8$aIU0$0mFp mb*hI. j} L ZdnBPm_Lt!ܥFe1$'pa̪8K ќ -n(ýIO9̦y&h<gWŢz|A0>5 m)薣p(%AfrD^H00?| ' 6r ^˴3&=q&SQVkx!(ؖ'œ_25Ï ڼTBQl haKU7̣rnڣ\7=nM$^IݥDz! `V0|.w&AP3  -x!@soq}V(l<"T9GBtSPיb+Wl^^ -KՖңTwLsƩ!?IP~0I"3 {Vo],E2_FT*^Q"*щ=ܠz2X{煔7Ĥ F\IU`<1 8:IxBw%{(,P%j08Wd+{B(t.2LҵΊ͔:U!YQG"m FR?Whb0CuS3`Y lv~ږ96 R>Kq.b5g)iau굣kI( -L}i2_n S"n 7K^Z;BB@!+"m ͳXU"Aۜ [Da VKgL!d5`L8#Mk/2{ɟ<& J -1_2 @b`{yZ)֪~^!4#!ҨH3zȃ@ZR -=Z$|KZwe}~UR_B- N&iHԤRig[42(5<)GXjҭxR -~= -KnOPp0k<&*YX M0ꦯӍ fir3Yx/y -Z - *+X.C`+D}xƘY" Ƹq'?K1VZ"vDtIQU~j 0ʴJ -mgdXPp_KaT5\ǵq/+Sa_͉6RiYs`ax ㅞA_JZK5$idЮbȷ}c:m&hkyÌm dj!\7;PJ}l-1x -HC׾e*ac6HP 4YLb f"d^L[izٷz[`sW=P֣!fTVZ'X41+w~]ce:?ƁŅ6t9 -%$~m!O$1UUg=|F7ե+t5`. -dzO֍o`T~0Z4rnp]ev #@V$4Y~dJ3<ې)#^ǘ+FPifTL4m -$xP8"FDP g:b CoCiuK Gj|&A$:稹Feû-sT}=`)+xxDɁ0$n@T^;كIB"u"w(X!09a-_(z tDP.X FųaS.lٌr`%!n6|"pFn -JfXd/DjSJ衉v$P{[Guz0\itROTc*58r1EU ш1*3vle[IȐe3OcXHk"HG7"bqx,NB>6^7"R8}7I䋞DJp(HJB6F^?/|lX.>@Phr xlT -P;S]B/oc?n$ZpFh֮<0i}Nߥp#C+=RLJלReP8 +Op5. -x[ EXFlΫ\36JSDiD|N$G -EmKkvw\:DTkdqCtOe XBS?ae42R D iBymze8)}i8h7zo~-cHD'w:~SJ#` "@`#+R)QBWNIm#4VL9lD^F M}}~_`㫿}~|7_{w#eKPD)e!`+4҅!,l$j=8V%ixدZq!RJguqDوK## [_5ND1I97_?:IV#ʻu^y2~ζX}"RE!x_ mD)F>)g+5>C ;:ayUQcM,Z,/K3/sj65VSˆp R=g,ЗQ.<|{MJO>HK`zHrP*u$j*z ШV|K|^0!XY *JbT,U\,uCNJ-<˱C}ۖQɴjM6i^Cp h k>R@yar$`.Pf շwEsz>{*d!"fЀͬ2";Jץkͻzo/V9/.y"kA$ FXBY(Sr19,DKNB|hpEukLno ER*-4ˆVU>T=ȼ48+A-WYWiA4EwLOU `39#VזxBx a0 qw۹Pݭ8]q5 -gډ@o6)ӰWֻSc.x8GrD.'hqMe2.\rhUM?VoG^qvǷM8ʤNݠ)S)5]MZAx -࣫ېT -~ne֔BgS9T.2#]UI<$jXD6x2x~Ъ,ՠgaOtOE&>0ٰ -(xW9 UeK7[ MP>Pʕ xsC5γ%#:lI:sbJ@diǯV_|5gW:Fa#z%x5`&: -ݘߖ(d1tmW:OGY)⟦W3nCS|aG?Jק24$`BB]vJIvNR$< L$l-$7z@mϨ LH>o d.W%uB|* -b6OjYAaTՄݻP-p@O!`p.+&JJ=%sKajR+~SIæ|@$yS =h3ц*)|K:Ga#uJ&R{pڴ -`#IJL -܀H/q*cU8Pc*] 2r]8Z*yбxCP]H< $S9 ū9-P8מ#qQ5P?B;bO͸LqScޭ20ߎ>ȅ鈞 x8o}u;7ekGuѿrM22bȈ)"+A$ Yk"f_ծkP-˔4iD;} -[ TN " %$Yߋx[V Xkx S %B#J>TF4A?/L/Ա ‚ojjg.L0Y`P_- Hx ̴{DKEhK^3ьz5$. EuLF^f/31O7Kb;!)2|qv8[bB=JVD=Һ!8¸azW 5J,1Fpѽ] @UYP-c+fSy,,Rk{+I؃yʆ)W"5[ZNQР,T0WbѴ65PRE^(LD3 1N>vӡ IuFB'AnT7-L$PIGxce80AZϵI(RMcuaU&cz4}㠈#穵jhT/FD>g_",Qj7cz/Xg@[9DDRP#B8j>mlȎQխYºŴx[Dg5'H$ۛ4Mz(3%y*7=РVX7Tb>Z#|KenaV`]!pأFPn,%)m[/w)E0N Ȣ2 !(Mg|(}$!~9qW9;mXY-K11+uMzW)&¤|̸vs?kdRMD#z{BCB+d;/8 mX9NDH}m#aF"#"dy"v g+Ѯ} ;A&M\фMI)l5()I%։4cv$B$-`8a A>IHqFsUıUO`5(AEڭ[5%5O?x'`K Rn1'jH4Յ[ 8hCaX@8X*.𴻉 bŊ&z²H:3ϱnpFX9G ̆Qt$חQM+ Ln<%X}m1åBt )#؉u zcd@``g'Dqfflڱ!&u﷬0o[׉t^Ppv(f"JiQS~ m k9ϕy瀕9{P%猠D27줁<(N`zX.-m 1$:%|ๆ-ILޡb<bUm p!FвeL.(#ḇy^-Px.CS藤_ 0F(w\.PPNLK$ltɟ0e!=7T8,-y} :měXp 56 ل:},LʴkIh 0;Mqb|ge>stream -H͊e$*2^I'YHal{A!Rwɬʊ?h#}k,z}{\}y[Z<ەaò hce52dhlsl{l!  s5!fOs'Pu/jkĊKD>9?uu+NDvæq^s56I=9j+du$gv{ϋ37jR}AKȽDo=T$`@kE6霁Mm]x_+[ll3J8b(Dz]f.Qk`sj5|Z͹:AaV!rx!V])s*!o^3(ѳbP -`۟I# 0 N#Vl(ʳt?V]˨A(zGT3{/U6mSb\a3'mt}cD"1 E'Qt:[2¿ئT]hV ""@rNj=kwK}ސn%qD'tOWB@o 'kڏ` j@h0U"R d9Z5٠Vmjs_^A,l,M v;CAy*kDҪ?֡>H 56DNt3!9jAn *3&>{U-2[g k(O](Gl'.Z6 -ıYN W -naȃb?/_O=}q _??/? ςM?jۏ?d./f /$'?XHmFE:$j4Uuc0Ӡ[jP:~Ai҅C5嬍v$=V{FZx}(Q -'N`|u!-r\ЁZ9*(q2fwhc!NMb=Pr ⪖0YZDKפ"rZ(4M_ 4 &·dKbN  /ÓJ*XɸZ_h%!%@[J{ E0#,`؀G3XqΨc>ƝYr?8;n5-}pTS@ :ˀ2wB8 O-}yEډc͔(~Z^Lkv)eA@xT_1Kxӓ Md(XHV!PG( Xvu!JՑf̀c Y -Xtu"'c 71JTxZ@#Pnt>,dI!;8|4ΩDZ!q2AZ(%|A$Ӡ2cokoN̦vq5uf=wl*Xt'鯽PB2BߛUU[**ˬCY1[XTw!ۋ{X@H/uՍ\zAQ7 Ss]aJ+[<3pag2wH4؋q^*l/z=lL W - R \φsTH+cВO[Yޙ&1Jory HPҠ(1|) z}?j1ez`bB](g.L̈́>*_6UYLk+iԷT(649I*kCB0`JF!0Bؒ>1TzBP%DļS-T v0N/怊 .C pč[F.wR\;rMdDpiWY;Ixf\ L~u-@e?Ⱄo<.3P5Xu6Vska w}AdJ -3fb[ 8 Mb.ěfGuZ D]uk:Vhǹ4,;3 oݼzRͽ nEVWbj;tr=$_;!bZ*iuЩjk4Hxr&bAX/ʴ6>% % Q&`:ۺvlP;&wXAic*&UzFX (?Ⴃ -YTGd27axwI(Ͱ"`l3A/Ќ O[@mfqh&w*yCJ2 fypC/=FN$Ҝr `Gm!(Ւ=S'KEƘ4lUa`PAR].wo7^pq˜`^"nmC۽|>FpQ\Y(P9#irc^ѫBiorb^ Е5iI>ntkZ 3h/wQQzja[tz7hI~hw 0N=Zg^cТz,:ΰJ``SB%?(_Wȋ$XF0%v{o)C+lA62%D鐅0a-$7p (MwQbqtfn:uӬp-8IP&*]=1;=L1-_w+> =Jrҫg Ciazih@i؛z_oA= wםB&=22wB=RYIUZO;D:MC-+ -,~c_/6p2bӼ#钯 U}o PYy0K.UuCH!Qn[.lכA 9z3ꊤ @q,oMlT@^:7ĉ.4 7u0DA@曍-BO= -q*ߤ=%7i^ܨrM: -W-5j3u[4PB `K -N]yoCq_~ƒc5XG/egPzDNoSDHM0B_f|+yy\OЉY#/>̡kF4M;sH闃[]/]p&P)O'zcʅ -?3,c ȮPjq5 ç/ZTs GbvyҀ;(EpMDdYL<|WUlX8% 7)Ã\z>~/^jK 5M=Xjz~ a -$e,~n$v_łգI4ά8+to_ 4lloRI~~F`q z+@ q7)/s)wA[(A'ṤA9;;(B˽YҊ|n}&t׊]2q-C I ƋDv H3CylgzJ;>BL$OƐwHEȱp= A]zfyl8umB\X8axjX ȠIrW@{)}I lrP, n QEx-&cj`!9M˾ZFo15HAb£"[{S Dޜ-@u/Π]wvۮ/V8!WVzumyzqq˧]/8/K4YYruz^i}w<iA )l${VtI}ۅ1/^X}(ߖnq!hw܈"EZd”T|R }8YVAB?K5Fr(^JthP$l  g_[AG+̨۩|XwB3f UӤ+$X[}Aߖn\:BVM2k\Ap9s F̓˪,\nH(?w h7HG -}x3 b8foG!,g`Sh!2;R,Ԁ3rU<1X2wBuE}(!tVAa>$?JR'_Ug} &hpȰEkf?gp\Bf -kg.X-1úGrB5u.kgF)[F?m6l%taQw]$mU!V@Q]rݳ%tDлf51@ -*0āl;>tR|╊S]C.zF$f荱`pY92fdp]FgVkhD"N1zuy޻tHp04U;;DCgR=;K`@q} U5 VΠG &(R.{)j#`opG6nuqL~~I8yz -&ށ:*Rt6(;WtЅ;e)r.RTXY=UC҆_jIQ,zjƪUIIV"n9F TRf_+lt^ml Zjmj̵$=1XBَ vG 2jZ} >׭qȣ #BA1xۻ=ܭEZ^4Vu!+BSj{ R.)b!R@DHg5neMcӄ%J`%j ~ @ -}q+ؑ;gfq-xD^-Xu^VCvaIe[;8$8+\jFxvrL;v3ݸ]gt@!-ًPjDzq~Jؑ"HGn9Z)~J'4i(j\@qemV?sf,ovrcqQ0wwoԾ ǡ@Vnfu6#^#glzIs;glev~tX&G8(̰V|9?ڠ |끃8u=r˱^Ô]B<Y8רkZOmjB8^Ɛqipc}{H`NpPQ*j(?W9`[ C"3\τ9JURg qۂ8t/_Ru\ ٥E^p?6P>ru&ێb`xݘγ)\h$>|s}Q38 FxX - AmxZB#̴P-9a&sp -9@}лAqT{ߩOMfx'E9m꣥ i5,iw}CVC`\{ -ߥJ܇~O{JdGK?uB7&59tpˋ莻+7/ ݠXLZp7>I1!bZ.w½7[ кEh1`,/zxx5@^gun00}(P$)1yToSQaLj*h[}*c&H֫.r ՘ -D#9nd^'T@mt -]U5C=Q1plvHsgP XN]HRiϴ+Q y:۴9yEB"8>Udjo4koBJw:br8P0ڱX;Ö -Hs?A b ҮBk;)VvWA;}?V.LQ\sգe>kW \M^\=N;AO*h@y^&] -qۏ]X-: -{ZW6yQ֬;k"vR}Z!4:\Ms;]@?g͞&Xά! [=F8L  -̼3]6I(|Vv}05ϏK>%'^ -͗%+ 5HlW >GYk~OhWh/޴XZ B8B |YPVrzVۧ}&p -OY mWR -1|̶(}k٪$hL˴`޽x0&Fz]XLFī]=f_gWu? :ކKA%~(d?3W`DL-Ӗ ǡ|fIWc߽ƾuӕ.%N74XSaw3XDԣ{}MƝIYrg5ՓݼT+}qvX5Уífٹ ʕofx댠J;vjdރUfnwqc)A]<EJG?|* ~ 7/#^6X/ÆfuF+f_-l{;_$y)7·Sh)[7')U nx[YTAK1i:96sb%?.+YVݼ]A+h ڷ5QS71UnYAAL H Gjddc=hȍv ?{9qgԀWWx+ݿ>7+saի;ks -`av@KCηEF׺mbEgrqZ()*~*cUIᬠLp6$):j 1{>j1'p+> :9tV\*I rMpCEkSFV/pH$n33^ݷjq>x@bp{@tj9EVq Z{PO_%(޲8g qDzG\ps'΢ ,&T[LT4 q”"C@$/5=õMCʺLZvZ*MńL!+*N L;_$ZSTVn8¿|g_qƒNPѾxKFUy y]oxQMʮl 7܂/6bv&*sSq4[{pT|L9G_-Iܭѯ9TOe *r }?!:j#:<=b2 >L\>H4AF\ -(کeJGB YaV@+ U mʰ4p| -ܛ[N]awk' uD'U]n =o5˻WW hܱVӘ>@ -: KE~GӐ ~-1)4/n}>`6YX,~x詯;/k$:Z/GѓfC!p )y½O kS-fm cUg[EU62Θ?{pZheOʞݙ^Q Cg@yƵZΙP2٧Vu]Ҭ~"IvrHQ+ciKx{S9"ܚ Ht|>Src:&Qa@t$k0S \:IS~m9\Zj.{ϡQ}۹՜:RJOts^Ȍ[lLP\qB:D_9/-n -gg; S`r^PD*<&pfZ=B'[nǥ0+u8lqkYn`n{PlF_Jgb3z:<]N{NvLD<|j=mlbrr%w x{ϔf,Gq Q_\5{*Oڙ(:8D!X*z&Mvrzo(^q%b:32p  31Xܣf#`]ۭh|0,?Sk"\3keECJ ("5_<%_̳sHc"x`\OImv6) ][96x}Mw^W,o$}V%x=.0`,U8V5o`Q˽UMSl爸'6?Z_EY -ڭAo7Wxj(Gv+qǸxhnާTxlp^8Xsl~Tz/%m8Avva|-4pLK,nL<.qc2<~;wr;B+ܲymF cx^{&c |'"ij_b񩹳rq={b%|CYy3uG'z 7Z߬|Z'G8C8`y5dz˹ --Ilsȍ+zMj 5G+~ '`&WVq`d>~tX*\cV&Y[\6 ߋ'º_;Zͳg)Pa^V?Kj0<^[+W:.#+aR%s"H<+SNnxKrY.3YW۹18"`BcXAJa^dk ^, {Kt -`ho ۞!l&c*. e9='qCȠ4΢m3gOorS!vܗ"&j̢Mf+"kd. CS"}BEuW:KwvF0ǯۍC_`'.yيo'7XيUl/ewhIOp.gLxKCk¾V!NjBĘtrY-6zc֏QN뒏!$Ƥ} -/zvYNs~H`V6 `;Oҷbz&~*۱X hGB ZT0x yGl߿~VEx/imWat̩!aen!? n*Nᅣo!G{4i 6V]?k{?M5zw&0` &tp UH kNѓ~?~Alu-G&~%T(9 .b1 N}?zx|75*]q &ŏ~?@P_J&`f(ycf -]4Fi[7ϒGtp*ՕW{z+dVlOQjv.,GGU5;=[>l<m3'㮲ƂDɣG8tM*OLJu-Z}VOH]+p/C|-,pil"{t%@ -a7$[ȷin5xoo3&UA^ Gg=4=QBZ'w RZo啖5'bhI3_KO&D{%US( 8l !!t !a Nfzv:nsusIa&۔SǮIwJi3ao_=<cG\0liZ t3iKZiT6VNΊ r8Zbah:<>̲z-Sz8ʽRߤxn#Y^+bJҔޙX-BNiniHI7 [ǵTMeB(<%'q:ȓ4NJ vu9A?0 >̰Z{D )cUD (`&_A6p0 `LV= -!ʉūBFfKY$×BĎ2,c"-F<E\ZK$V2.†z3$'_3E=âT0ۦ(_# +ǻ,%YLc8Q+gZ+|`Hs:5")L h% 9Jߏ8JDZgyமM(,hݮ -'MU7c1/<ǐA5,d  W^(9K(2.ΩXp"Yi9D(LV"h\%BJv&mn#~6p<8drO%m x;ʂ* -(8R-zJI4T@n5pǸHi8 ".ANkZq9MhbZZN귈9ت8w1714 X9Aqڶ#)H #a(Z +qx9ArqêcāFixn m~FOۆr. *LNPkI)[m8zR鎠O4XHGCH `8G@C3N:h[+p֝2)G&dh&ߥHRp0(SOP`5V hy$V"MyEd8h, OT<HJנV0H"0Z&R1JM_huJBE %+V959I\Y+5RCg b 90h*[m0%fʃ`!#(!hbrR@4MK0 l-[fyF̺B8'2\a -%3n[[hU<=aZ%FMs+6H ь"NP^@#ˢpAhVaI3i|"%IGf)*=0KB3N?9o @, ѱjkN4BN>[[s ]VaHAH?GovJT6T ,AKkkj2Krrҕ]t/n=٤`uQNؚdl'FbKϿ>`ig{6,$#rbғ] [K@~":~Ϲfo|~ް?h>p41gSf<$~sc??&g}[w=Mrny0}O<71PL^}y㋱\"з=g}WW[Nd-UWWv^_rw*+B::{ W/?~s?ٹ) B,b/&##Oopⱍi/Ο޳ԁťk۞Y(':=۞ټݺ-̀=eOaz 5vfF[3;޶쒯KYv_ ?۸MG;^WkdP=uu?<̓ՙ -+B:<\B>-@Y𞗓a+kQo+k~k.8'ᖽG}uk5>RR9[m'(+vwbJj=ߍyoِܵIlO;P,_嗃ƽy{xﷷm9u"]^!akTC }gs~7ϭLG2e'=y -7kS]`6|ZVsEk>y0PoxKN8rtD^ԤG -I8 _w~i ˬ3`.I&IUPTI"`7dKeyZRk_,c@!0ƀ6lmj][C\t>9dqO8-լM.3`P ć˦^: Sxٮc;x:?9Մ߫-/| oԊ\U/o6 NF-G8i-Iǂ逼(wUA`:2[\ª%65`Aϧ}|{< K~C7bPt=F05e04,nײ1&|S$Vi)RS̤6:mՓq2a |Trxw،g+FtYw)eOχp)utL'^>C{ "ӿVӏ88 ;We Y#k{ v6+ɟ,y[eL-(EE$3lр7s y]La F>eV31E_v !#e!#ޖzo1xdwIb߉%ѩBPՖMsc\ՃEYcO66>aBML̢U.[%i70ylwj]^> !a`q'\m|"bxa<^O*w SPՋ萢۹SԭEԩZдn<~",讒kѳ/cˡo!Vǯp -{)|=-1 -uȈyF5=$ǨVHJ6ШZr6tIbt^ӑn ]ELӸ?nϩS+}|dǞ1a ܿrcH:S]0x0 4VMy<դQحtT'$AnJ!01WkzkI>1֒_;M,")UV{O $fD@`6%:5$Iǭ{nqY$rEl922oxnfʆwOMg0]oqMZGz :n@%\R7\L =%^\7.Y!K!]2§]n&eՍG|c2>+V\\UIcl -V}?j?X3 m/W}Sݰ>1BdTGD̒JL'm4g!lwQpT7|T L+&Po!]YK|?ox;;mnAqD$8 -{!9s8Z_uw -X.$fאJ -&eML֎0iI} y26cmm|ffA~jS∬n,ZW3Et\P6U)rMWmZQn9 ꅅBG'|n`cVr -_9y{_9'uåܸ¡JF=߼4wgZjN}|9$#\ʖSx}wRrQ݄{I/W';wMr\[\M- .*+«3oտN~M-[ѼWncYӑ棻3ˉYaI/JJV RNqC| Je]ֲ#afc~:~T8kzIg'|^245UE+owz:c+e`w_6lPa1)2h Ч*Ґ^EiQ&W#ɠARl90Jq@q]ec0!EL>Ǧ胹BlAqh`Z%g -Bݴ_>yP, y͠Kgn^9Tf-t\H=šmaa6ch;z].(<|p:L6ceN~X Z=dVSr2N*Ir.Z@Ǿ?ffmʭ>|[G7YqLϽ#6r^%0Gs -t1~wvUn4e{{so`Z|eẙc H/iہ~<19Vke; ʆ=(W/+K`[9&꼰[/d=^Z|%T]9pyM?P|gˆr[.=/~w_T$U} \z)κeNvj+.)y^ErطU} 95ɥeIUzI|mk\Yq5 p&~&̀v}?v"s#C@uwqCfj&SQPv| -uV̸eu!-0oڍ4$p7r'Wd̀'>~Ćlthja)a4ɲqDد7FXR@2Q Cn2~ ?C0'Ll)>:A:7d-G.u'_rxUme=H-[IMvEIt?eę_03US37TA0 -@X80`ɻek_[j^0$,ƀ-[byxZ&#s nT}KVPsj8ҟ*-h+Y3Gzg -x[ - lҵlTLPٸ9y> ~T,߿87j̿Q+\xs9~OO;~8&7u(Cu,61 -8 溕#w:?I%(;.F Do}[KK!d_-eQ - ͽ@BQ!b 7%]3T:vF\׳aLQ`}lB[26`cT ޾?YXBz4^ѶTD)a0*>[j­%RqPH灔ȣZϵY-^- b婒v3x@~V㚛RѰv!l,Ő(|7-&ĴI%2!IS4)v+ RD\IJӹ@)0@ w)D.@q4=pf[zOֻa<.'f |'WqG*UJhN -YbچT6mD_d'^\c-όމRBw{gNyf%r5FU21N uRRV r8]X…U9`ǃ;(hT)Δ|lhtYp Vtefi|{4aZʁq,۟ͫw n[\Ȯa266uw38Ѯ͗]'KAŹ+=j33y]4\bVe% ]pb)wPipv }  7fdoMwG$DbU¦F -z.qImwtꞀmy}sZRSn?_fcp鰈MMKLKyb9toiQ)@mbbn 6j hm=V*#dU=!iVsu"Nceɮb#N>[kC[}eaYBgoKne%aM3lW*.iYz\$cHrUM+ջ6bN`K@#Ոݐ[@9ei(GӻUKZT22Y" OCqu$8U08.uwLMV{BfkI~ ߪ؏m_;Ɇ&/xK6E8XOQBb:&jUH9.4_TMuskMMNwXmtV "Ȗ@}9Nv)6: -*k儐 ,gɊ%f.{>8¢pTB5|:r{⻕^MꭕmT~O`c>dǙEqI'bg[*̹[sϤ܆k[ϧ - 7Ő,&t.iSw̿{SMըV")t] =SrGq6g1nP1^a Mg8| گ+OطN"cLhY-f¸ .ݜi?SJzz&*qY>WRAAoYNyO-kl4fy5USY1 -sez[併\D{+G~33إzQ~߰?'qeGH9qJ[N5 *Lոi}"o۳zpތW{k:+ߊ7}}\˒⦄XWi[/bclgF^[Kjβ!1'R¬ҦZ€AW6OR~)k4G)L1I\ɃE! >~u.퀾_0Sc®b\. SaQg!j}v%;b4Vl =ĵhm{4[s#S*l(-c}ٖr aI֫KLhbǢuD)IWNXgqTHFn⎬ -IE`9N4#EdTu[?ZVJ[9i7!0->e=;oS+J4t,{J6,nøcu5d}vpG9)Y9:x_7/YrUP/yo/|q.o,4b*e+Q]jMޓ\@{|>vÿ;ajw%fJDP[i啈lN֥g+HS1!?^0-R~Av!0^EeʥafxwI!FivUG)zs.3sA7e٘|hEɅ+8bDܣWc]ppkFViY^N^!\\9;j]-FGPAdQ02@eRT).noM}WNmL-Bc3 XVMU3&$ɺ%-WO4¦UI@P^n}jU^Mf*(dŠjK1bГp>Cа{=S&ގ\%0ۍfbC-ʅc^rSwL&j#q8ZtZsaIy^)2]me (*s\8 -z>9o7l/g~"<60Q=/h-ABuOz*jG/ 13:"06/:e5nL +s !R\8噮4ђjG~[؜\*wB NfP3d4iNq0OSz]HAv||;+ŬdiK΍t;o2֯ uKqtf+{q%Fa/ʦ~oCOa}.4I_2Q; rޅvٍBެS!w" Clmk&vЛK9-l2ae#6ș͐YQݍVf`7tvѴN4% da#`OAvRvL(zk> ~#BA"YD%u#\Ju7oS\J5x\kD]v"Kb3þi$Bڮ -oɾd2.IJ4[4*)> -}&xO&ؤyO$vp]~æ"󄸢;B[8UH, O۠NwWȻ=lQtKnwPs9zZZVHoJu A>ȡ;b'ZET[':[߶wOUR%>t-&\x'(dz4NԢ')-y.-k#n -7kSCf`}4g-Mʓ \ӃlLLlW~WYKCRy^7㶵cFIj۪-܇k.jS7xm[wbeW0?L^6+ B0و+4vC\ܩ35L^0qsmJl pEu6Uq%_MheuruE{ML8Mʉ0q1D83$L 60sq#ӽ+u,tKWB>y nɺ !gi xL)ޭg|>Pz?l%DKGeg7%7N+`-P m*Vp#:W޴HSn0q@lfķP]K;DFSFؔ].=feҏ -+'@!!{?8~[]x=wH;m,0L n޳G3fY+.qLu[7LZgo3cS\x&C/(f-rH~S -<rW@v< -V;C.yp^uInѢ(z'Gn$qOo{Ox-t*30S$ 7LD-}w:݌7&?OU7d$S% m&\N`S`?trXu܌,=mym ъy^Q7uř6@ ͘S {p>_xO! V_i+/}xBq Sv5)kmOl(ve_OvOg3Z-:LlRwMhĔU^ǿsoۨZeM7QW?J&2.ȚO'}CW Ǻmʍ}̸았Z-ʃfVm{sѠf:x]Zxp:i$`vC;g5gn+L{܏"n ު}9 -*Tm@@ {bx,B BHw-Nhc{ƳzlüF3wsxM*\ZiZmm{/H j/ȓn I>9vA-'Ucl:WP*NtJ8֝FjY)k"nLd j9P-i@ ^f!ihu?Mc 6 ꀧB50B:GE]!F KV,v<&=!^zi B`UTCJփb1$e-Cbt=}EIQ6iHk -8sC̵mgE:}rb{]MKN^ > MbL{1BcLc+>z$5օn9/BtD/yڄ:x"f0{kEGaP#䣻o~9]8TH72%$>Щ㔵8>:7,WpmQ5L*4nK;/f.4}ȑQ5[Wʾ)2L/}X/q'Kܚ@G9ՄĂ#p>,_c"7d1qE؆N R?ݶ Dd6D"+ndM/UjQ)8UȘP`:*5'\4>vtYF6 u>^/b6N~1!B_xEd"4h_\-y|p!vmtHHB0K >l6dC੷&m{ktml$2N!RWY¡\N}MްRr<6"F;AWa&s7dзKoN6A9qj}+L7)g3_ڇOeՒ^S _=jf|x vSœz6a1g{O6; -d{%#&4kb -ksmCnq.TK&-LpUE%^OM]' !LtKc\6!2oZ12m.'/Xߤ|ÍIk$6jϰ@PbcSGԦЦ>>c\ϙz>stream -HUiWio93siiAAE@@JTR@(*vGە-aR*E k$swL~y{{[tLqI+TKcv.pҤkət5U%<3lr^0 _V7mfvk-sz6s5 -Փ16:F!-M"|9C.p -ROª*ԕ_]))ܦ뮗V4-ƄY~Q߱VspH-e%hTBF 7ˏ6l.mC&RӼf_u]W% zI#@I;󺛥T`F59{2&C}KM=L=JZ71gGiMEz0ɘ~7 ->ufws/x}G*Ak󄶗{x%tX5$w1rh;&l b0qO2qABC?0Gt4`?3 V6-7rLNDzO D%DY!w-Rtcw|0\}sDa&j7rə_19hB\{]Aಈhs56sOE{Ry~YQ&(\ria>Gb4ʙQV 29B<"(rnMOTYҬdHSvV#R>\./V%IN?7Sq! \ -qGt5YL=S(|eVK2>O4&E=eT6V}nlնg߻ldPD1M* -~AAbj1bo؄UWKZ彪/OQ|hUy*hJ]~Yݴj/z&|0ఔ"JP7oJ*01Q#Uc2Ry^^+@kޫxρݞ]ٝWխ%JYu9/E~>A>joA*_y)2vuppc䤫FXuL aq㭢\XTU^`PQ6K5d|еj߈c\ _9ʲ 9\̉ - 9SsA]_7~Z>}i}QC3Ր]+y]̱s6&9ߢ@G rV#r!1fbDuyJ@ᦡ/)FmSi;8p=GE%D6lU'M$jb6 e1{D l }맔IG%ql i¬`M]qV1!Чh*Y[ȋ8/j -;J& ҂؟CqTI,#օO"FsײvK9ơ[U1b -!tGl}OXZƬKa`?-#[P;sO>VHD˧qncumڒ^{jiNa>jQ}@+Y\-&)D&gS3Ojb͹(è͏z=~3yM\ҁe0Ar&+K&rNvSՙxeLqI}vh6sA ,z5͕y(si`^ۼN{1`]qԿ7mzG~lt;<~_w_*Rګ4Y?xдSAp l{ ]&It8kk e{6EyTe"y)  $Uu iﴗhu eC+;OΫl.4Ta†`=a]06$8')hg(ct>]!!螸@Ů J%׫O{N!n9 ]]CDJM* !$.Bˬgpݻ+<ダ!-HOgP.v`SLC~ZsI,~]}#c?ML*nR> y%!xܓu>DI[ }x -Y;Bz5@&Ǚ&XC - 靥dh'Уl}!%1E/A5 doSPS]brex}%y-o`?C ɱQF9!!<7+*E[[)q[c[†g?*Ҽ":V O~ֺZ2M1ir[NAxa;(O‹N`c13v>Q"MB]w0ˮvc ::$S0GGIYGK`HTs 6j-*km!.;^vF ik 䬼v!#zK"l-Z{u)%Xϋ.GPCjfN[݁y;aB˿b6()AEG\v咏׹ -ڷ@D&PQU?ońD{= dcU1wvW/ -d|ro"^хі0%2FOamb"M> eW1#xxw48+M#$8˚絗֬U|m^4H o5F؆>F:1J[UXC ,qxUF!.Zy0!A%).wD=y1fc#K`e#B EJIB$ۄAVB g0{\rs]sǟVA EJ NPRJ^Gیu|Dxq%F]CRqV+YpɝNw֙XV|̴N5YЯC$r$t8 p)) !pׯlo Vj)糬·>y(zp +odIw* -syi̩AL<Ѷf*Ы%AB=zy-t+MǍx=Mۄx+*0aN}0 -+fǹ\+ߴΰȁ;ݙ.jS0CT3/UH>&Bp*:Jc_T҄sGFk+Y۝X !i|lx1c)$9N2|;v{_QJ2\ѵ@ ;iSȊ;}~p:N :/6!m8V,όc#r?aFa 5b^׋yu9'J%I:]dbD'h!&)%0@ׅY.bn~"ٜrAhhzI{ F0-t_L ׋iQ-Kh1c^1{*Y{^Cuҽ"ݵ6=6VG\ Ex`CD }ٰP[9.Wrԍv˅Þo|N_k:ҢD̗.vU@rbe,r Wr# blrU< -5IYƈ@2l b-9 gJ+$͘Xb%&DDPDzaFQ "]`M29{#{?y6;]Er|ԭy&' 7V~/ۣs]TNO|mSξYR.krĴ* Fu 9oq$E9|߭>V}o¶>Bmy*HӈVp Bovhug|1!ʟ*ATUx̓6n/JCCs;:TaĶ]xbnuQ25՟Ob+-TaKBOSGYzwUuqV:ۭ<jV=%+@El/Drq̲~?M=>%O mzQjRGԅ v`PC;{6@Rԥ'l;4Moȉ_ 5l0ߌyearVPE8%\IB=HSP0ѱt Ӓhۨ!wu AXW]=7%`Q>suǻҧ7Ԕ=#W'+ah'Fk},ea@Srf,;J6ICvюe.2F;x{4B8ҁ|}0BN>QuPGs<$0PGDn()ٯ]n4BsCݾ|GCchExx|O3M4-9,GYh?]jbN83Qڊ Iag8fXp$X#x$FGyt =܄K5+b֍%JYi#wQ\ >BŅhp<b"6{aE^8-TŁVYj*vz[p?g3'$} 'rRsi_ZK"y}xx6Bg';_%OV Z69SSSOI6Wou)Ǔ1RuSx'`}SZ3`fU\kl/~vn8fS2dd u W%*sKl^/T^3oYwZNsGMж`^\͋P%;J3xyhh,8t6? oZ2+1Y B6Ͼ8ã׺q!Ǔ 6P'>Z]V&uArv|u(7朥#ͮC)RPMv].raijF/:pt"27uoU+y؈}#XJnqNSm0 -!J[v,ԡD /-Jb pB͟qAk`)h䅬'w6%1je†Ԅ:12¡f*1bc}c 5/ <(Ou1 %t" V<_ w$ tW?2>jo lUQLɻ4wK"$-tum)+s -blj('3ltl^jbۧWj26YVK@g]Rxﶴ c1nTW|OFWTgw"+\͝ ~%ZbNX{B{|l*ٴgM'&CS}zcdt֮jd|b@HѿZ|O&Zf x뉎fu>KXp)F[>R$}6Q _=6Bڦ89{Gsܵն4 SȻFMn]Puj7p C-֒8fr&doF-y+k:χg]/Փ'p dBn  \lo?RW; g S\Oiniٙ{w]S4g$hMtEB% -(E^D`Q M@TDEPLrʾ̜9soN ZYȰ:(HNXCFa녉x NC >uë;;_]*TW{M[tGǺbm–>x&fVN_K-wʚ2,`h-)%5>uBP\xQ1*`푸Mk/!LFc bʩ_y[,Eϩk!_?q[]@I?<=^h@׵ s[P Nwi@?-o9 \հ!\aj/*%3y0Ns[ -CVs" hNpfH+>/Eۛl u,gVGM`_ ^m[@-.%u!7 -tr{[5Z|L2Fw)ةLKW- .ۯ"WGZt6@.t"*`›GA=:C.*d_凣Z"Xt}W.Fkds<6>RnN=֑a /'.c۽1wqF62jw6EC%P(l|GWK`8gWB6`n a+`G=[=*1u=,Rdx{|*hK -hĔI|}8>NC&vs I.|w1gOh{GZx8)ٟY1.P}MJ{&rb͕u1-3Yn~b9-@ꙺTLᶷ!S ΅I ?#oHJUܚs\lk Aj&jK GbkZ#FDHB {ՈV j{DAK88{~9{\u}9ކȓnaPk|DVp(kK^bqwY?ESɚ F5<}kE@V-_8hF$][ N|:(>FGPy4 jݑt=-:N 2^QI4<{7GC,2oUǃTb{ -RYktIHAY>Ř0s[݃9G-{ueYX[.DwMI튨R7}STץ=i?ZshPGćជ%W7^-yy9$?v)d׫Rv3i}XMF}JvZ%z*i[Rp1T-ӿ r⾿:w;ߧة5An&wXh!5cԨ{Uw?9 PMv~xgӬ:FMR|Teއ{4D'+ -C[kV:횼TZ=^{%x9Y:GMR8Za[*}R].T&x?^۟!< F꣺` ^䇎+EfL#!N": "= X X_>[9)Y)oFYܠ.Ϣ6rQ*,%$cS`]\CqN ->5F.7F?1>zGtXQ1&3,4Q458Iw-rfWfUt=fe xҰ0QJ\Gᯃ` ^ mpSPI6s2rۢv0ƍ݅|4f7tc -.OɤKH2|-pCYsWiA1z&{}{P}d_Q]*%W Jfۦ[JVͅ9pFrA${iw֯M!fUmC}b$hݔt *9GowՇ-IB曫}jI.D+ -.ؠ,>J/ xY#Asgxk;\K~CMfS̼|>';oT"^ ZU-lHw;!;m`%2 MI6'pcg&"8wGX.6AOřw!tJ^/{)E zze7\=BCצZ? ѐjҷ71vг6Ew+ P1G,dN&^cOǍw,_e?EԳ9av_E3\r &LBEad`?ߘaU3K+ɂ zVË>xڣ x- -# h,l*%/'L3V%Im<>f,!ρS`~h.  -B@+6 L r(d[(*slcx4'"|KS1Ř { BBA;@#K;;vNPFPݦ @IXY -L65 HK|{C qMWYH100 `< @ U=o[ɲh J% alǯQnKSbaT\$GD_W 8C| zwJ}.xly;5(;|^N{_iUE6e&H {+eV(BKcνppr_E/|r~缸u}7zQƨӴts*9TN$G҆ ~-fNc-}Y7b -# D~ĥ2L"Ŝp5݄wɠ HrP`~^w49i)n^72vj(]5Qf^A $Wrt"y-ȇ P97Eiyշ5`BPv\x8E sA""F DC^{$C_Q enR_J7beש]ih8^R1_2-["bt ! a av n{A}CA_,OU_㣣썶-ȝ9=TҸa1!EJgH̊.wFb.&[Nt,s^tF휑W؆V3# pq$RL%Hy5ur9lO0IjN *;un"ضԌ -#dDg|$c$j@+1_ˈG`_H /%`&P;7uOU?]t(kٟ=IͨWK<E8mXXAR No(4xIsŧeVW˞ܒ(2f3j]#XWeT?o;: |kH-*>7i]GNj[~|2x)90F-DHMH^1p(tlrbEvh0}a欏(K]nf8J\+ y<Ү?ahP[ƪl (JfK!lZ)}gs?[2f: -֦$RA ? oezS+|Ҳnf& PЕ+3]pQbX/K!Kv1%ˆNǰm aUख़7;l86*Dǻ7JʼJ0aW`R7AcT??r%n({^d: 7 R]h, 'D,6*R]JCCY5]cKʺz-S |es۟ RرsuTvˊSsP?-[6u7CkvM9!~^Wޯ*N`Yy=ըxhUMiOV hL8]{UN z[#5}YҮkźtPq>:u34Sv2~|0elL?ۚV:'' y9wL$7P>+mY iwZ rrޯWU\7Y~VѦXOF Uα'uAd*\V3B.j}To'O鴏VfWKg/rUr(9\.k}`^#ͱKYYL4k_S󆅡s'9,5gMkGRe`Or2lU}2JL?0gfi?̴/sҙM:$m'V5F!l}7Ġ ˢIә)>y׃R9]!^F97i!;P9Y`ON~? - -]-<_s/a^}jlv Tys@Y@.2nȁXVD6Y/Hp0֡q-밻nA0I`P?FҺMo'H&H.B'ImR:S] |Y M 6f}'jF9/-xMlGYz v [G @9@ܴR@HM9UvJv]e]yZEtLgڣ:󀈞 z̭ {ÉR sTó%BsKgP7쐻*Ŀg?j>>5k!Zvdw]PV2ř=PzXkiguLrqY'up vl~qd*>잃˞͌ԂTDI|~f!ʾ9MaW)]"r-]}RvBotI7' ? {>6&OV -v⫋D -k*e@?MɂZy7VI[ABwDJupGfR{Ll٨/;䞼7Y_ʁP -2Wn*ul^=w]ŲO@ʻrX-FfWs>Jʣ[IT&=n& -|Ł&q[X4c^RKcR%Q}9 P-Lxu(0?&iaFe#`]ḝ KSՐ׌mգ2uOE%P=X{P#Pڃ>!d֟XA(XrPxNxhG>4L ޶'L\+dfRJHBLڮGd]O/Z'N1KvPV\ETȭ8Č ݋f_^q%<|iE˺XIe 4i)<JM=v MWС=32VǮ!ݣ䑫 qp0 ^Νg7z$^4kit/5hAz9)nU˘\W ϯ`%LfbwL ?r#ET_ zn~_7 -Zfs7c;n0XKPXM@@P q8o: -zzH9{˘;WKUBUJXe762VFD$I3ySp0K.&0ę.zP>52kJ=1ONmbڠ<61Љ˘X͘-,7+92 -Eg} F ̾O~Q٬??sa=;C;h\_hqwPWCć΋g̞q -؄ x:סn/baNy>Hܔ[rVJg@ -w&FW8] FSF !${݄ -!u vTcX'r -,)Kk贙;1OwW\SHo mQ*",a-K9dV*,M9:cꨞZ"ݍ&YYlŦSF2Լ+Wώם#K9l.e&Lcu x'(+<N VՆp z8\%܏%2zyT1 I-q(Na%IAm:zoOAi$jCJ.!%'].}ٟ\| ZmGvWF7Gڜ?~1;gR&~j,vPA!n -;.fɮoBR -IpRłgv9u{z/rFvvh4sOr_yRr3 )#,lbN: ew✛A+/y9HtN.9nap0Yv'Tv~C7[lgeo^W񞲛;TJ:pM)xKMAZ8lCxٝym&-|l-X::fF>,aTsjz[ZI~r)T1rܽWT7BKkX}G -ܝʞyp/_bс+~OK !ؑ*J}E/ =!Ώ 5z9LyNInIwPU\xj~ו'jPL/ <\maa姗*tu' BI# lcix7x3cSQdv THnO8Au#Fln{;x+cd៣ 5撕.YZPry۬_u յ:61a F tNh>g@8}ۑ":ZsXM,coZyOlZOS,`R8Ee&`Gu֐32K.׀Ld+]:m"ڑ; -SQݍI ) #IO衮,-aS ^%I;RGeoNUH^l\Hjk~^* -HIk;V1By R5Bߵocq ˷7PNsesQSV;QZLqK4,%dgG͈~sic_zQ=%ζpo$wI6ħ҈Q=+Խ-MZy`}L!2zmLp2ݚ^jMӶeTJn^:u׶Itk< -6& -PSC,j.zyTDkvycڽU5%njalp߿I*rz X5?_83U/ڱ-sormUWQao)-'W, -$>[NwxVoчKvc_|22rJRDOښ7>;^Mia $TD>w!\zQ]oE%M[ =s6ױܖ<T2@g #3zCBȵM j~ -Ag+NIKv yg6~}0pi~̫}(aelg A~% !Uz홾5 xqCsJ0oe;S!cPC{|l - 0-dV #c'r&\7ʱedԲמut1'evqGֹ\3Ңf2]mόxld$f`|.-묶M~jmJqKasj6(e60q M7µN,2#b|oV+'K:Yg2rї)Ry)g4 .FŇ=wU+@e{~Z\zbNS*M% -4q^ykNmRh]' 栿y4ʾ_eÓQ[}H]c;-i WZ䜁|\P C OlmyYoyгiq3a_ӏefP b@JZ< 45 ǂ.:a]'+K/P8֒肠X'!F -S -n՞))'l`cI6| Wz$FeF !d"Ⱦ`*$)@oŞm$h`v9 # fTs} -jP7 -{l}$eW^i Q|&V ݳM6Vڲ;[7߿afȣל|S9У U>ktFO<8],b3gER NsX&JHE'6%$tsZ@j?PJq8@y谳 46ۘ,!}Ut?"C>#m0PzNY/5{d pl{lDҾzļ`!fRJ?!7䝕w˴8$L 9Y8|)2ј,nxh"č=h"Y`S\GqK;LldCV(zmx)n#s˸XreQBLZw513++igRM%MIl&N4j64}Ce@@ّE%6h5FqTPDDqa-99s7߽syBus4G袊QT\TnJ _>>t Nhe3EÖpġ aNɽަKuBF -zGJ\]ֶe=ۖOGoō# <ؘ[| g6:""TԟC:>`ꕮHUIRRgɢ{f{0i.YmkߞXuD(Rɽ('}9tiӟ] -t'_6G 3cr}#ٯ瑶LĆղCF.DCa`mKO.P0ax,hY {PדjgӴa" )wh`. &5n96Ao舭LCGQyU䦊/;0Gٔ~T܆qGfz -ƠM92~KޒqRS_S΄Bs|gj4#iJWJjWqsVP핱kweܕWȸ=Eßlccح=D[&, Voh[!.ekKJ/?<hh亭)ZSJ3>G)!lKKu 7=R Q/Pa\Ow"qRx~ 2L=%Wz1rMْ=Z}׫$W`K+1ń<ƱU$z6Ϋ"WnC -g+M<_Ghys4XbŚ7C /i4m5r!7A||GML2괠J(0[Kȣz 2n^_Q,*sp.Y3A-^tqbƭO+,rcypORÕ|dsC`>su _a~_>\tukLJM\O' -Uj2=LR~^Iv5$.~hڞXRLV g<~` -1Pz􊵿4ʧk( (u9&k #Cn鉃aヽ]p`b:zm gVG-B~)j[AHw âG s,[JLwJ~1"@ߛ8 -2`'} !]SE^lyY3+a`0mEcA\9+Q/DD9lă!\fg=5}WޥIo6%u)'&1)'9M{*~sPC j/qdZį"ўXl!iGA(Mz&SUĂq\c-'eƻb8D-_Q~а~bI9dm OVI3ܒ(Z/"8D(4t/< o\q!:QdSk{{&(~-6ĻSAx3>mhkIwlx=u[[Lu\}w_󥚑 G -hk9&6l>;O-ܶZ%4w|*F%:zopk&*>Ut烉Ip+ 2e ր»TƦMy_K'/uK¦ήcP7YZծ9{&!3LZ8, #eaG^?eVFIL;I'=Nj$f;,Ƙ%.q bYE@,&qMĸ QAV "lj줦?Oy})gfb{Á&l vv;JXc=Ω8YTHl;zc(ȲG[i^y㝠7CWW慵W= -VWӅny6dk9*E^i&Zミ1uQ_9ȨVR4gWޜ><ݑs&ǖ }u߆xee*p.ph(`f0oaﻥ{.)I@ZR{7ⳁ2P榴-qˠ4Axv{~DE̡r| -xZHfܑE(kH 6"dļ ۻWT4 & ]>LR!#+DC0ER}]M+1 -ys,ؾ= -{l{vz}2l- h!½]W&,0߃;GߩXJ΅-ZPW?d[ɉ`~^U]+ 'eP$n1k-ݒ##y5Dh0 ȝEã?6ACw϶GEv`{jb6{ N= %6rY'͒ ]M}s hm,s0Q5=PrR"@q(Za%<CRes;@"W U'"g"ZBs)=H1@)*Tl'$d<$j k-.*c]Rxhbc.[vM@7/olM7fYoÂz@ƃdHh)ǎޏ -nɲ$xdY_]qSZ;t(wN)> N8XyZ;*jdf)ւmrcf\q V%.򞞷W%GqwxhKV\~-/-4ԱȪ5 1;7sb|u bq@*3 tad^qyg ے3ʴROiy?m4W0*Lw ձ)|ia.fw^0FٚZ7D{7Oׁ0p0_##*{F75kU۪vXHk|dVhx_آK#? aטsvy&14QF_pJ鵻jQwpkpo?H + LܶM;HL}#ՁTg9m^)RS`k<=Ukٶ1FVɂ̳.)3E-\Ťb6 -}GM*H[>=kͨSb<ȯeI&<^.Pm sϘRvi"!|f,.xdҧѷԊ0w&eVֆȥI/, \xUrMz;O//#6y` ?{ziӞcib6I\1 ^e q8D ы=DP4ioO|ޟwN..*ߣvz);3sq34qh6o'.}$"=Ӱ"Vf$LEw+ 0hWt<*tv!k/#oܛ>}Mx;C -눵 Bm@ AXyL&Yh\+:jǩ3*5(ffݒ*Ux%qcԷx+ݘ=06(u5$jzv׾г:-ȼm%0L!-p;pg3 RIHNP -9'4a!iW[pbJbz:H#6}g -Mv5y!w1ܚKj&άoydUyn bieq9Ai;)Df6aTM y= pIeحĭؚK>`OHӜkݹ񎬃7)#Z"VhuQ=֢_4]Eex^R|E',p0*N(<(uf"죃qn>dO5iGF4'_w*_w>O[E$m-ǂs82{ e簚P0[ [qLB-mР@`%6ţg^6fgS:Vg`UʋGV\YDZaq@ojCMXY@w$ koHwMW)W59fAQ _xGllu4؝](nJFh_5xIHvfD,ܥdm)_"UFR}bLOꎬc*V:lOClkjB -V2{5Bup ]vKM!-sΰj7' 烧_ؠ}b&XYbW&bM"qTU -dp2L ՁgʫI}sR4{ t^'(2-K.8o.{Q,(jP}Z̡%UZߞA-bAk⊫Q5z'bU ->/\ژ]}Z%9%?WW1Ulk&(*^K#o.U͢QXs'6u$ĩ״ܜcj>7a?ϓC5llq5Tl@-9W׼wQLt=IdYt⦛N*G/Cfm)ADדjlyD&-- e'L^D K:w%X \hw5+EFRkT -n|?nt~J3M\M䢗6լčb -ދł E)bAJL)3m$9!/fK{(,sNF,z1!{z6(A= $/\D۔ v{WeP['L$:2J =&]U@.ี\UF>SC }_}f F?#w{@S7// rF}`C>Yc74ۆ բǛy\1[QaޥaO4\!OJv!bZGacI5I?< }iM0oE}[Ơ,R -]3ԃ X}VXFTz?+7H}"ha6 -O8</QJ>ۗQZN\`0,csޠ,; 3 obov/#) -&!𯞓jOnQtH(=MS[+ÿ<5#Vȋҷ&RP[ -FlEP.:^z*rOuQ=?3 _f~5FVm.a"T<`gA%A¿/G$~u^UygCFm; ϳOrJRWłxJj7C:IopCieC6չ:vG?f\jl1V+T4s ߃LkThL3/=ϴ"β~Pz4;8dj4>=lrwCA^ZKOnP%KLy4] J hl -r*sϴVr~qj ,`\m'g&YBQ?.+jjŮ7>]O٦/hf~ [ED9Nҽzq}V!y7= ~rBuQrql΋+z ȎTOs4R@sB..-0Jnm&ݷ![%a[Wz'yF鑂0G+5]aexWOYȐCo1Zڭ1d_: ǻit˩%N&m|`|myYZK@#FaeDqaࡃnA)n/m^ܐ7>E/]Y)1<)`{EXj>'8Ea?aw|3,d5MȽ)|t0ٖV:&t4DJXMUbujgc\AhͧZ%`->C?){5G ܎9Yr2Zg9(*GD]IKR2"("ҹB:|)@4ih 1y3=Iz*ɩ&g9k[ϣi/տ8F'ϱh(df.[y$G9Z@iek.y-s"@[^ysU?*1rv,|]K>SK*bT]+(ԜizS<~n]]6.%d"K-˥В8ɰ*njy|8I %8u18 >1G+@;rQ@|jlW[wO31MkbLjj82FLT4q -.D-{bT $1w~߯92 -b,}av>Y.H -:UE6ʤǀ~{ Te97KtR^le}[i?7EL؉VTQr#.T\suh 8B˻y4!t׺ɯچZWeEϾs@Ͱ0Sc >åeT4| ?7Q^:`VXؑR@XiSQGϏ c3Bks] tjS}+}hj̻kwJ5e ȌKRXn#щ0rN@e(͠2Z.(%>d_Ǧ9V\-ƽ 7i[CUW^=l7Z{p/)Bk3_<&&t<5` c7K/ӷUzͽ b Kmnh7v`k!iW`ZK|smJ!jAAiv1cJb=0K`yZZpv9xmwi(fcu.\3=L*;SۉMw/0;dS6G2zk"½@+wČCU=`i[h&/L}t\5p~l}eĢ,*xi)vZ 01I.+.\ -vd+o,Z=&Hïr*N51}񞖴',7"&kEDz)잵:Ҋi\]IJ۝&38,]A*Deaw޵}5 !(poNLة^1fvd䎜 Zz.՟-Q.-d{C#uN(9FrŦv\9wy _KuvA5FN eQqQJtG~b2po 䜩(N5%ߩfոXH(m(]ſ;S YINV|ұ+=:|QZ@SF76v6NV:4a&4PcSU$얃i{7[[|jlt^%%9w?aͤ.GYCՉtt_]\TKURJ {|ߝ&n}=YDՔ'1Io+l!TjgeU}-;֒4VԮ&ar›qr}:nW^3X[D/$t@ A[d`~YVOc4_! 5ʲ$7>Z_c:j,HA榼E.g"ї5YgxM%cl`uUx8]WԊ|ԈP ݨU'3 -$:U"8.ggu0ǫvKFb(~]`8 6@>9n ;C%"֮y.(dx "֧ڒyQɼ;o [+#갍Ab86/+uVPpwx< yIqЃO 2ۑ)6SdAZR5Pw0M8ϢL1 Y), -ra# g˸w^3>ȉ6S64wybPm=!ֶ̟pj/ߍc}?٣b3>^nbP9&*3Ml]'./ׄ7Ů !~Z; WVI׹/,i93,O),}2Z5R6P <55mNKBJ(#:SENjOf$:莂bS?ZZj -nJ#(nT3/l}0ܜ\'S#ZNԣm9>aw Z@Go 8G_oCve1GFr։nwtȡ~ipSx%r]-N fto{Y dz*2v`LU=\ơh/S<š7;+޻ʞں([Bug_bd팊PrҾ;Y[܄} \܍?4rfbs%gstR7ꍾ([Ю[ %):aaby)4@D4RBܧ[p B'I%MSBW{rM這 _s2jZkIn0Yӟ --ƅM|e2fk -..jpv->gGǪWЋ*lu&yJTC5bnB] 맪ƒpWVvK/6UΈu#Pb=և0v\ qnl) I:cJH;wJc?>^/|&EzB_譠nɽks" E8G {1z`kG7  3\jB6x*ѩ۟ &MP3N'&i[,psa/²Vxb&y`1gTfT4 >}^"k =#ls{p ࢎf'm :HQRݒ &j=io2Lk20gK̥|$rW&qjZ8Z!Y]~-7ԣ;QԍhD->F>K.MBkmN - ?*sNv%g;rj^J0ʨS[ >nqxcVtť\6Ɓ޻\0)?W8ZR yPG97sIz>) _-nV^,J֦ežs1o -LRގ]d驊z4^%YE^}׈a޶LzV -~1ۘ7BI%4|%zWi~B̈<{0.aוԔ*u {HMZ^d~X5`{j*AvAf8%kYb:j,qcgߘmBM nL@^.Tc/ϰ+&&$ wGRCYmTJ3K>I63褘Ic `'4KEQ,D"" (v[syP3|[b8T_ -9Np3Oɦx&y8GZ+|+=Sa>&i:Z3jN L]xb|3]#P׺Ǚn 5;i!BܾUq:P=|[v5,k7)F͆*i>RG/ޛC5 -kvf:zKKaw * 3رE:&IάPȰxQO&%A튊5+pq;:ȥe|v=`NHM0g*X D0 ;NVB4 W:լ\<$Z$s[JS׹E?5 YTaö4|07J°AQ|6-(e஽2rc5RkUyndSH_(~dntosdWʡ&$nH3k}r}7/lj BRXy'qn44 CU;rZ:D 9 a'& 9J.B 4"/L6#%)l砏n ?MkNRҊ6B,=/7mxMFY[~i!ƾSuR_P FfOQ'GqaWByr<"=}ﱽ,bSQԖ{61ɕ}j@N{ :#3n W{5 4Iw(X";2s)v}DӂF̳0:&XsVګc"ٲXxb0Mu4TmI(<+Ҧ"Wx gl@=ܟa ui6R;wYVчyBG1M2Tke_Rd#m]l(hc)wࣶp4)ce$x}h`Ox| $BsjmSZ[;L[[ 9*&3BNI2Rʌz~Ez/o %EI|>zRcl!DG-xchk1Σa}o<Ӌ=Jk9E>N=H64^T%ԅGN̋(%#뾚7Ȁ~ Yun^bɿt %uƀX_~RJC%ʼnJVvO5=Xmҵ>J?fumQNL&g<܍M}r,n93E5#=~:+C|tt"N[{yQ܉%>T 疥%?ió]M |k?NH+KQsG\q3b$T2sNwaG|.kRv_ESz;nf$'Ogou4"C击D΄X$6|llG4\['6y]@(՞] `x5 F ,x:.iZZ5BɲɥfTK7 9%"z(9=6Xvb/]*Q|`0-voO3:gE+U*❮ -ץ8]fɅM\5Iˉ(eg]W>bSsf*M\S-ݟi{բn3k寈DddǮuT3cjxwv3VgsAWosNS[d9MMif.()*"erI1sEYM@@EvPlng̟r9s>y*o[y!/Clo>OJc s5ݕ+E5K)wHp9F~eo\1vf 3>Ҏ%}o]cݓج߬MA3qavZZC# }+F@׉XSpsf:tIlEMQگ.Qpwu"l-kmcR59U) -<쏾mΐ^z e_זrƖpMTgaFp͠|G*Z|{ُK -)ȯvUkؼTӎ9&{xx[* T] - -~U3N׎RPQ?q3ǵDw78T]r =МQ\0wNfaLOzzkp$i~_!79,ramO3*&v_yu’ Yc T|Hwhh аxmSGF,#ž\({g4WRIhX?][m&U$1l#01xhL|:r(Mٷ1i#j*9+$_]䘮5+9GH; W{BwkosHE*h%iT-ՀqZDgP;+:AflaSҚ~ldbbX<0{WzM8XaBZB*^[T%,l[NV1jW_O5dU 봁o -}䢚{E,q]B|T[hsh^ >GZf.k"ncY?A]T!&,hH6/B*ɩ> F9qzc_;jRB˽2i6Yxfc?u!oASk="75C3{#:<'CBK +^& XXGv/^{Jdƴޒ @kL b*&b`A.fFEl" @><2>ğ݂N.?c -_Eo<ӌ 7S-ZM[xJ_gfiw`$¾ lȢ -|a.}sX G;hqh%! 3gLWNpPm쀒Vw`z.>B kXAu(+9meq>W)@Go,BEDB`Kl -ȐŞT_c̈JH-)jᎌq-PS 69NV?L.1+܀O[W?[5Ut~Q:V4&KȐBH $@Llc^cVx_{all\` i5s둎t;N̬vgrʄ(ȩ /Mv1V0l,51 tKU9f=iy\J~+$d<Ci^9(3 c=V'lFL!'fy -[xi}-Ӻ3h( \1]H%?3M'xUr)nxJtI+?;I@zѓަtMĮh$oȝ7-9ø&:fWE|4e낈ic싱5J.F5Kw&`˟j@Z,|c=|Mp{ޞ*Knw?QÜrX#U -<*YAr-$]KT3U;}QV 49kG-8+u/8ۊh8" l'%7$au ncsD8׍/V'.93;=%+@*9唈eT=ujnUMD8z-ʽn`5 Du[~6/Z/Oiߎ,>j#d%ʝDx6ۊQQv3vΜXgYvwQϳ*cQAUvf<Jn%tSjQ՗\ @ܠϘnE'Hʿ ⱼC0VpI ->493~`'Ӗ2 -q_cE# @uFM+T턄&]dzTDicR>'ArML*!"iZ!=kP&x#0N#f*m)u0v71B M{_0~GW?5|N4oKLԎbH^7uY^#=z)71[/:Vj1p׬uwI1:=ޒm'o#ܪUc&E0iD '{3.&p:aLE@7XrC尜YB?Ik~)h0xaF) -?(Xi~+oiJ!^+0e%햟HµZD^1XHlka':ᬉwj?;G!UjSoynΓ2"2n}ZPsSߛf*n/zdp -ws${ij=T'c4"kd ?:w YW9fpV {$LQjIr?kom;qBTrIͪI̤}7E'Ꮧ]L-dT(lMQ.Kn95M"-8 n -3zet\Gg$܍+WDP=,M]D3m7]U8? A  E vԱ㦵ص'VdyIBڲD˲$RCĽ#Hqo)KAQ= L}^LWٓ{H !#5 [ -A7bS@m[GſF\k>ɠl:-hmM@;̉X'EyZ&[h(oJmM-j$ymy;~ޚda'&,}U"%uTŧ۫Zwn '؃ O9>Y@Gy-Y,>'NrjIDOsͺX%I}-짅u@rZV,:-p^6V=A6|6nrBMI/;\P"0Y4b)Ze#6b;P{3υE<4j@1hFˆ˧N!koQLGp~ͺ*at0ؕ>Ke^ θ3*wC;{7}{iyfł(ZfԣrV`qLA F_wj$/7$l]e/>y0ufCfP|ͦP `37rr}%͂BC4.KX :Fҫ6cu,jn6fa6 ;ƿ(ˌ1c02e/Y -,V"QB%KYR:f 39uιϫ}߫s$dޜ_ ?7jYNd:L~dDi^k a(U|"-IlK15 o#i4WkjH[zzv6 jTnB^$|X-8PcxahZ6h|Mi4cCL (!Rpym}7n+zs']>kP|a퇑&c PG5"P`sxPJ %d:}gFCŝ[8[FVᩕM䍉0ROTXTPǷs'9S߂R$~(d,%:TeHu~а`^qP͊]Y8RoyG,MN)j4*[ V|:8k%g *ۆ1YT nBPT54o"BAdǡ -q]sÙnہ|aYk;b}A0"_S<%1AF*Aqu}ȉQkUmik*/֤k5ӐarxZs,R^F\(j»g/JJl偄82Hqd@ P,[Xq䋸Ķʪ(fBs;3čCul^ܳMK/2_3Wn0w3^ :N9Lf0#2^c奝o%eNR9\_^y^:(%{*~pbOIPY>j`KI;XyamviVԁo84g;{A;A,b1? -&d]gԯ,E۽S83+.Ib##}Jm*!;OCYOl|B{0GF}8un`ll7w5CrmHu_> ¶ojf*\wp|- -b0ϾI`ƙW\v۳e{p) YX[,_e|G9&>Q7,Mzf/ oA436bJi3$cUO Ə١]y׮YVn6i:gJwEO. gcM-8 O/ -M=8`LsumtW/5&ΰۭ6yX*O kpY\;^2哜͇pgPdB=H+Ci%2)Q|ǧ':ٵ=uWc ;.kٶJY7&Ocwtk+޵Xհ -c@B - @hrJ -^ߠ~1#\g϶Ӷs4meYY3vhyH!*:*x rrxODʲ&?WDDd[!֯ /pT-vt4lvj =_I66M0sM z3RcW S7+j[&h`#%/Uqy3x\\3QS!>)9MHͨ8P)Wnn'Zsid=xmYV[|a4;:6TT2pMWC?Jx>^wZZt?1w^x<\x*,HV -x+(e鿱ftLAEZO>*{&^cdOG_v|q/6|v_>l Oujnʡ%qhŐz2ԯs!(=T"E`5)M Sg!NqD yDLQ޼wֱm{)= _h gr3C&gKT7R2)/YN4lz8q:`zSH햮bߵ?$m&61Ȩ L1"r{\cvЁNM .TFJ&,,bZiՙM5!%`] @ۼhr'>V aȜBn2G:7xЀ7i/+'PS|~SAsu>Jf%mOƱ&RRvA؎z_KyKq= )hKbH`Hҕ#ďCXYU_n9} #n֏RQgyO!k6/ÔAΓOԳQsnIPa5k4{Oz:G4 2yc8C灗pʼn1Y)abQϺP-cA\@m͢jl--.0 hH #[^ [ 6rݨk 鱍Vdj$i] -wYl5&}A[g?aRlNꞲ ɈeRkY+ܷ /!͖W(e1WVg>ns8Iz XkHV*urj[xcG4ڈX`\Mz|ywd+l|ޫ.`Gݗ IFCh#>rHA֮'@hurj뤐bl L)#0=6ia ,^ĩHxԤD[ CPwSӜy}^>69_;K@?ua7>H>V*RHOr[7|VFχ궨2pb@/ۋτS -qܛ`i3tZ@Կf74x$ -˙g-JM5rYgu)kҎnL)s3jͧEӢD4@@<a;#cVPrXcи2|~rMTל0pQ$XoxF}JMA!zl.X/n,,K!Pٷ1]ݛURO _iSA,z??Axg=}$? LTF_Tڽ9j7Lwӂ(F۹V>ZR-x^DyGډ7Cxʻ9 3;)nzXГGFbq!^PWsf$P9>ӳV2J!"+U -*4{7>ۊXqØ.+Řs:c!nـU()'(t~aTI _,+T2EʩZw -HՠDNCQҷ}|bIwY4B%Ģ_E8NA}No4/b7KHW!FsxmgLL0i?mRSS3&N2ds5QW1VxP.AA3$( %9Sx9RIv?Y>wWW{ozЍ_m\|Ll ޑrQ ӏ-oSH;Ga! Y@nH.9 |lNЈX <J% -`2mA*YȤ^;fwfg11A׽lrIusGcGbsU%w%pK԰ҿ8ؠ^XjhHٝɑӽYɩmqNglcQг=w\l6EvհB;`-e_|(lPw+P gٛ;tϵ]wuW_?g]@> L^xf«+i-}f"q Ǔ6ј_뎘z;o'c°Sjlhxwn)=?3H+De|꩝8rH9;?5}%Ƃw#+MngLFKUT`4ki81化?%-or[Ԏ:Mՙ0t^h=QYqET2 8+"C/zglQZ#JpaF*(՟p9 2^5 -Ľ{(LP];{=(yM -8-uk5[ޠ|M1eLNm,aJbꬎ\֒%𥄊uavC1P]t֤N8}e\b7U}i../zDMTNEd,LX2mMӤ(n%7,iBէ#ĮWd 0wng;njTUJ̙B QZzs2 -n;lO&6';#c$;c,#C)#7b"8&XGk305CZ)1EiOheob3ƿ1X~$'5"^[bBA -<P+#(a#'c$7}spVyz?XQ }ٚ`\8EUL^O\7{7 IGRtW Z+~p"ŔV%5\l"%Wԩ|ܱ}~Jƈ(=&ʹUZ&X'" _301aI!MIw"k<$I%4 ~ 4cbcW+}aqZj3jDud VSk #EDHWVj܁w(+~zuýks+zGuEjccOq%qi*\hSڪYz$ q\EPDEQ̭4Xqe@E -!i3|xsOf[bɹX~}8X/!'MS8Ё:\&)%jP3$1XNN-e..!b-yҴO욳e*HUp圧f.7m!^#U3"7 8f ˜Wηf[G/w{O m Hkj%Jp`di]VpuomH̐uD/;[b`NS LBL 2P;Pŭ>4uʈO-):6Uk>”=耭qrk˯?e,K(Y -rޥ -=WR-J8!\ь?[`Z!nŶ$ϯ y&r|]L/ KTILNޛw#0pOEuI>)~GDYHP:8y6X -3ä+TϥVԝw<ӣY2&f^<>nuW󎈒{Tr7ٷNP?uͱ,aW[(NE5*x+ /^D$&.%ecaWx6 t!n4%-y~kTANh3a(ybG^jtOfgy_#e#$ DCD4d -z$(sBuWTZᩲ6"@>6dݵOE Y -:U+lp8?[p`໬BR`~٠=F6O&*U`2*K2c?*t1! Dx>ϥG9ߧ #-l:۩Ǫs/#]ˬ0nnj_OsgxUU,sicuDIͿ\cܚ:[>Tp2vпj, -cs|89y]ꁔ'.fIw(}_=e7:pubR!|«KPwnmM$&^c}kcB䔋vґU`#RFCFIޗ@r\EXqα0H3+vȈ/pHyGI4=8eiC_C=,؇c(~s*ɛJB\-h"n?_љts%g6B>ܐ3>bb&=* qI^S @ju4)L왦+-`c(/*l<4|FUv@ -^~] 1^쳅iLPq(9{:Jg6}lwl3Yi: -3Sޔza~UޒcBݓ@ENՁ bfG􊦻&S{~94^/!:FMB8ŏeޕ>iU[4mМ25wbc <#**C*( -8SknOr~}}.F4;kWHIWavatui{3 B |Xi()i-!-WWx4z >)3ʼnVIA!Es`5m-Vdiľ"aF"B'>6"R,sMŪB/du׊:3GݝAx"_s -ls,Q ->y ; Sw<6FʻHMOÚs%`|s.޵CSF9 pn. -?ߨ=~%qyR™a[B) -t~gv 㙼 -ko/}ꐳIIARudRvd4Y纏Wښpg;I\)6`G"ԼL,-/Mׇٛ,Svc]qQx݅zyb'n^v^lGlf?,sPLSz47GF#E wwKRpK -e!N(Y -` L)pM/S8C܈9rzZRBŸҨ{L۱da}GQ~hWZKU%'GzIvr!gQ2J5+z?k+qo[ VG=^DUEQթNdLgIN+ V==8E[S= CEzf'r8/NRV"l\iM "$?K*>V/f{F5p4D-cCx{O.U5EHim$-CXƈaqby n/ua$3Qъz -Rc#XucgK۟ۖ ]E[g=QtžJѡ?w9%)R)PJ8^lc_63eIұ<%raj>SVQ>ȱkƆoڊyxo `F\(ku#˾Xt$BQC̳VW&2u8-F_'ɧ&Ҟ~0S"op>Q\z>慪h)5~Hs3iΚd'CH {y:SUUTûIN&tߚvc9 FJ;;chfb̘)XbƀĂFDQA)&jL* -""XAE(`l̟sϖ)Ѓ)bsU -F>3˄8 .18O}w 2Է;th`̓Idr(6' -.zO샵zޯyLmbw&.Eث:+~7#{Pqq\$%/+{qk=ѧLر]┒_H~|R y_$Jq;Ra}a%Za'n |*znȫ>a_NNlfOmb+춒usҵѼv$ A##6YUE[wy*)'̪t]- HU 㴬漈q.vm(]JNY?/ է/v4]hYUNYct#"75I8UsAq$b ҽ{m_P{c5 70MkyJ`(kiFگH'.tScۇ7|p쥡pF}G:F8>Mg?{U\T璢~oVFL% z9_pɿxg(3 zج D[QFji w^pn u- F#ɩ[Up<*.Ps(^keܜbGC^km6)4TJ>.aV{D - '8ȴGw:FN4`ޚ({: -ۖ]&|BS"mӼj -+Ǧo3כEѶQ*ƿpͲ Nq+@@i>kFt浩ŧ -xQҡ' lıWv@׾VEL '~[=[ 7Fiԋ'jNo{~aΖ(Qԁ|[rjo6tL̈́^A_.7b#)&7C*8ajM eb3`ʇ2:觠[c;i(W{ [}S=skB9;6 1s$TK/6Uj#qNu*7XǨ`S'7څ~zfG᱖ 4BM3_GSgSibn\7^ۄ7Eh -1R{aP} :(ڭ8X<qfl]}ʉYϻ{B:_bw!C#Lq'+Ba0PFHů؛"tãZ U <[M/t'u|CLtUǬtߝfZzVuXehY5@lxy:25qHKEa0܂S"xL#op!vG)3lm9++¯@\Z#cxM&^Z)@Vl$%8@@][ꗶ! - &m^3i]r:r{!sWaS$cZOA$&1dK.8WMrb ݯ+ǖ# rJ4\{ pn=ocx "H*S!O[5OJ}ä~$RN5C c 'yHY.6`"bkHgd*)8AdЅ `z %e8%&̧kaE@(ֵ#WEĴ}Ym0*n sn(=5'di-.$_M/:Ja[Ռܜ el3Nt~}WDž]׮HtL-qIIc^jU5׎T>aQ~-gzedqI9N1HM~4}`ZŴb7\鍹cι\"ئ\QALF&hj鈹NiUzegCpOˮnGo/eڔVO3]S52tD2MLeUTQӉfqTEvpE% ![͇f>Suys?ujyD %BX[tO>]Џ |duкԞ3>(,Xk`*9$;d0;wOtFnxӒgmbh_ < [Dd~ۤ z\Kt,zȖꢶz) VWeM."|\pjw=d=~^vQï~~at5B_w.v$>} 8p\eim| bۣ}&>ߘj-HpL^>J<Ҟ6ҹL+l +dPk{汖$Xͯ{U Eg't3kV.YkCг:~Qm~۵O!ɵS)6% dg9#$8ZvxKel1 5beX-4Nݿ̪fC8|r|a@Dƈ#nbkL)?ٯ TF42=2rIPIFF.9Jw*f>߫(F?vΣ!Vr %nx ; -G!ۅ @Qv(HǠ]MkӨL,0p/" TL퍽pdKV ՖCrQ S?S<,%X]13 Tmˉmv#ZLGRuwWqJRr63bҏt򉞆 Ȱy.aJ#?`౗x -Xc-vхdbw9%yӨw14wM*Iɥw_FuwVSjM-wLMa=M}3ن^0 DM<鏼X -_\֥8n5i5"6vкt@;<]Adv}Z4j}ub1mHSs9YX@ޛiH5 4D(gʈE^!:_f%r< pP0Ga# 2PNQ;Ĺ}QR切!>?bZ{xDW~ZݴMB/;ZVw] .)ҾL+>Tp5%F@{VyW'7_|+??k)J*Em~{8 nhHxŨ'NQӿBTWF*(؍Qt+䳟RbW*&|s ݟNNC=\j|沂/ӎgDm6+mˋorl -_v%"AZ+怾!<:ʢJc|E=R0 D%V񶹶 hI-rӽzCI1KB7%kxE熪D:7 nTL`Õ TFX $䰁I `yUӭ1lLiˠ|{b3^+d[YjV`a[}_s_Cˑ6ۭ!WŝE~_铉!68[s5<7Q+u=N2A}/ٯJZjRdFTGܳ͞Tze'n1[IT:`*J[L-g϶<ݷ K C6:h[tHy3w"y}zn7%+Q%,ڗ0&,`^8:;Y2}*rMϲ.PJ}n' oP?k'cf&ѣ,Lc_N*q$ie 難8>}L[3[3MSdjkvyejy!r~QDT0FMJPQ9E9$KnҲ'|ޟ~>qkF4`酭)4>wGJ-u 1N8D{(q]dCTv\ 1Y\G0p2TrثV1P6,~LD ˈb +/vU8:]K^7 *ww!=0 -Ů)BavyAP};<1+64YF,bDndP}hȇV>?SI6쾎RR*=5ȭh54 [aË5Z}<\_ɬ<0 -: ?=3YU㋿l2!a7̱7tȘkcZ1$W&|N$X[Rr^gLs -}ˣUDHTnLlԆd -[.5q5"/ HZfmxW׎YgYbA)mlQ+E op%Nw -?q7䔾gTTP^Xv` \0$9?(؅?XGaWBz~utO4 ¯E5Pf~i =dmzoTf|xaK[YMXy|AbCH NFZ u%ϟto 1ٛojMn|7A_]IS'#0~aV+>ܓcne,5.ٿC: cy -PJ-pAU#B -n. KNYF#>nD5¯7'[Ls*NcHE g?CnvcDC)ۛ#؍|8!1+܌iЍx}2TfxX[ eΉK' فEVU000HC˝|#@Jnp%%m>Iwl{@]Tߎ)vBpZcr]Y}Qt)f4}-LfH"JC@ q=˪-#?-4f[Tz>Yyf}퍛:H)K;eT7G|r|}v Bu?4=2 +ƌxUbO }h΅Q7|tRYvdnTtrKSrN 2R͸HZb:#Fg\\/H}|Z!7D;=shda:;:2$e' jx]zeĭCGڴ:\۱Kg^7OnAH176FjY^V ̓jпT4m[] wP)7V2Cˣ#dGW~M{ZzMX+4 _wΞ={dwf60f4NL4$+ Ui *M " -*QK,1ٝc?_>u^H,NU7 -{j~Omt^6u}muC?zs# - wħ4(9#]fUre=t2tP%7j] "/5@/onxR{S\'T9fEX EPgt: ?MPs'db2,BlhZ@_KPoh*!G n16g%;ƧmQc_+ _qKAF@M -*oe} vr{&v5TEFov2bZFmάA^*jnk2&̑oOw _}Tr v̶5M5a/$~?pI9Bd&g˶g+A=/w]o/Ŵu;?/1ʲҡ!-| lzЫ$yqq+u= yQ{ {x^ӳ !3^mkPȆ,>%ÿdgBG'c# s-L8 ^x=GiR.| -5SܸEDV-s{ -ƽiV4vwF  1 =b -⍭7+Fк7˸r41Y2 -=?G/H"39;Kw`>;/y?v-D!v/% ;[^jٞ$.0WO%1PDhư.a6:2NHxBO.KX@f׸}ALjVЕvP8Лq>&?++%qm_1R6lCfL/J] .mbcMRA{ȫ䴜gidSJ"@9LV Ö] kkQ1 T#*}FXc=Rћx%4**HXȣ-ٿk#CT34=G;\Qc..w_dֱѰQŌbBi,*BRyzMZ *m.&z&!=,%. -;\S6&!>hFZ}bS3wvW90,\X^5e!ѝ!.y-/qkbH|fdQ $=@v&\(h=Pyd'.ϣ17_|zTeuPD ;Өk:y!UGaž҄]LOET:ZRqLG8(Iq=I%ȊOXi#+g b:JOU;Ӟ?n oHG9.;?Mg:ΤN/)NK}’ZVvK0}E+BHHBH}btFy8眙sp9  -"~ u9~!Z*U3!\br/(%rg鴾_\~HϮU|*u-eM\\t~F+`d{Y gB#I3`laR&|RT?/F"rROo#~GhE[5)?QA!]臰L1OH]$߶IħM`.{ ǰDn~6܈``i[%QyBZUh,y fm"b,ė=rA#&M]h n/ -7g9EeW'gXׂ uyb}ނ693kIe-J_ -*@/(Fz^L8~2xrll}[J9#E@9\L둾 /蛎'd;i6)7gPS@b Z⥄x).ۥކǔS/Me[-d1ȟE7YFyU7ܔ΢:z̻%P ]<(ڸt'=F Jln匪{RuwsT3 -*^EyZnmY%;<9 -"윍[6֕ʚSqPț{ _cO^+_䫟po%ok)Z9-:J1szK a֬mϑ/2Q.!.W~_~ؒ:2Xe]NZ䌝Z"}ZONʽG@W[.̻KɧAdcrfFW\%u;{>0xjC୐DZ1<|'i0m?m]BmÃTvMԆVm y3>g5WžS8R>yCRYq˹4_|2HsF?oy0=ic 952k7얌۽r?l:P+nt,ozs&Ъ)|$cXMY%$CIۑy5?%Rn2l6H6p[5oFc 4\N ׼JE1Ώk9m%ݴ9-i, -i$Jy%.5e;s =B5xyLbx)Ãe'w@ daU=g\IGS(޿_;_ҍYS˄;TuJ9Iȫ^'W]f+/z>k/y-V"`ҕKHiE~Z[bTlRѷ=_o(~8}+SMr?##/L ž2M)R>zK# U d()F%/uJkn  =lG@'^R~_@!cmEzCåL;oVfg)$bBR-^<2;pba|{G~`M} }J7Ya5 @JCL{K %jeQ\0AJڙl;`N^;{Y`h?oS/YKFNWFm-2w$ -膄D52b`NN rOdҚR!|_Jerf\Ǩ 6a {/=B8UۚG貜ZE\@E˖5A%d|=󲀒2w²҆_d -zɿ6FU|lZ:Kx[BK9D1<~t?lcx]6%V7E ˇ`ÎWԢ-:jI,G `tڴUz _viim/$!N pqgQA4IOI?e1& - ̚,&ju|2ȫӪд$V`8W.J¡46 S1 lvz6wNUo>oĸqkٯ}EҾ=sc-YxU}xv+eqsn FQhR|7fPbFMN =I͓W7gx Ɠ1MHښ jL9ؘEДIQW5˪ȯr~2 ;Z9ͰߔvA=T>00Я=+ -j9#rM vm||/n VZ]. ".;Ѩ/dIʇ==p(h -jQ/)=>\lAw{`4:k.l @2vX{goU﫦  8-P n@{n>>1ZdTzL(>j.YLty"v.`]ܕvIȡ^T$ ƽ%9 ;^d=|\b[X7e%TE1.Q=sn)pCL #IKʦCZ ^{g㠓>Q֥ɹ:iV=mH9ĘE EXĢZX5KϰgO:{̄_X5Ħ6!9b܉H"e3 `Iut('''S4w -endstream endobj 22 0 obj <>stream -HgS[ff%oLv370K3)Bh@%[vm\S$QA !l*GGQdg?~;{?VD{9S`ir[/9bf#&@ q#9i 2O sns{12v^'jQ7(r|n]d{t6 䋼oD8}UF˸F9H-ȻNHqp MRo)q)L-I-c,[v5%$j~y.jon%u !i$q)s.9-igvfžK2T n9\ 4{tVyKi0(]y"10m2k|v7c` WܐRI鈓ߓP - }d{,@.C@.BjgtM<9kPJkUۅVèU:[`ݏYFI^fԛt_(꿊-q󠒙#܇UZm 3r.))ib0}縁p-!&=SQNeSͿ+\8Ic]J}5 :vWgt$Qq.q׬. =ΣBr*FAk0qlӉ=3g MSqWr^T - -ώE-)mH -rҪ PO$mM:X -MΖCTxJz*:e#v*P*M97 q)hd[td莭Z+ObNQcwClZ{Bl@wCYL`M叜W;Rؘ8u -KMhomWv kht9Wqu;_&L]u!Bxk3Lwa(5flL1}#ʲOI:D8=]W'RiH>V -^p)"@a_& }12NABrR)J^9u:c+q3eW-)zⒷ§K#UNo~حiJX]_?gyZ_}w]UZQ>BO{i=9g5](nyXd¦vNLN 1wz&jK0|7n ݉W( 1P͂#gD^ -Q#a{uR[<8G٘#:nݢf}G_A6P1&)noQxˮ.F*{Lrm^ " -5<8f`/e0R'i5}3-\ǾEECYVyeX6f!'[=ёVth#ax~/j ݈ĭ\ɄӜW!@=Bbާr2l$^ۨMuҽ*n?[ [X`n%K:kӭ_$mSqN@};6v1-ØٕoeѧDlQ=zHNIoEVo<2G*npK 춣zY,{d5).&?eTz`N{9ǐ؊hA($d!뽹 EpiG=TEI’}ONKBO?xy==QuHX}^{Q;RWtڰY_6؞wa="Lk=uC9wՔ)s(DqEyg7"gm{3 -kgˈ0e3Ҙ[XuHiY fB|i iSvFsvU nRq q"lH~0Kpe}(aKq+.뀞 K2KƄA% -'hUү;COǟTHŭ" x#~LO^$ݓ[F!5rڑzeIyH.9^ݎXuip{I'gUGt -3RRYdDP+8\B*i/I^f =}5>ʻ!J8t̪ưt{kPvR+jϚ@LLDr/{[3䊔 mETGK̊QB:\;lȗB̚OʯC/ĉ =sIql zX7Mٙ*0+1rk"N)$i)ĀVp^q[2>_oǢvXo$\r! po૒&Ma<1{e[lp -v* >D&Nl_RfV˯U! -~ܪ1O2pEݒgkR Vؒ0KC;9eoT %:ƤyHjU k|ěI]/?(w')k=:D/>J1 d雍 UF 6)2/;Vg;wv["k孼o"䝐 vvv oL C^B^7/ўuN|2{n9sK-rM -asyac7+8~>{>zY=xoBc!j ;`D};ͼfI XV|6IwL2CqMǽig9MpګEe\MX-# }&Y}L]zh2itY ~AHѵ[ƃ5=Ug9^`|̢VPߌaAE\4hϾvhmҮIҶkprY,H*0ͯ[geIj\o4JsgW8iw*>zɋ4¹C죁sEPUF/bm+Qz]?W&y r?>/}&)<X$юX_r_q?U霨:2Ppsy2Yy ʱު$B'3ZV%/b[6P|a=ʸ\M*6)}F.ǘVNK+'DBkP3|oA(Z0Kf~@~tv+瓈Eu̱_S΃yFoW[•!z(glc,6_KsKCm0_%MAJYmz|0VU=JX{O%znHG5/z9ؒx'}Qk?ݵcJ\.Qpڷo`δ>FfM΀nv\x8u]l6zoCkļ@GuEdfO{Ogkib/ Ȋ>kZUfIWsR|kxjmrZq12{hgV)3*y&p)j_/v#>mڃ\wF8%`Y17bɠ%sw)':A.߭ ֦߬͜Y0N $H6l @"D` $H6l @"D` $H6l @"D` $H6l @"D` $H6l @"D` $H6l @"D` $H6ڶ?ʰ_&%y1%1JbBRvaZ+TQDbPBٸ8Cs| -2cөg*{0qj8{!IL>#v\"bWPĠht|?~ϧt0X<8v"1NLHdm6#1AkATZ &H)X)LNcCUb© RY,JM;~m}]# Db  [12j}ߕaR[X57c╎ݹgI02BbSw3 RF7)!R䳊)cy+WI[̬AYN*f*&s^Zzo^PcrZ`BX4(!ִŻuIZNYk *CSJ[/LI 4Le:u1iֈ%hjA>TpHh>3-LuzAwJXt,=ru!5dVZeC)̨&kC"Y-&ls[cAB / _weĕ? jn&SYB&0`llް-!˒ekkeI1vZ{SK1xޛ%S39ũ{=wkYK{rq-kt}.}`Q>cUҘL"ʂw^:0[2g=ڦ?9ӘmjA^9⡢3$ɯ)v*2iȨo2nK!jYP6,iZ˘C=PXz`AlUmŰ5fm($4N#P>2r~&mL|co!jh]:es_;x&"k`..-7gYSIh~uӎ{J'U&eaF=zO,51[C%!*0H\,:^?l!s)jVL>BrKc=R?v\ tjpД[^ڟyHkj]Q-n9^8k-KO1Qe U5,5ӨSE"v9 {Mpop@.-`gCS|6xAA尹Z3qEġ!7 q [I/$uTDcġR.}s^%B -Qiioc3^uPE%ٛn=|onW\몢IwSIE O(H.ҋ|c1Ω$ Ӻ~0 TKүɘ]Qæ~0)v+f*ToN<uWvmmcjzj&n?,bNm -ImW Jjڮ3TrD0A{̵eKش,VaV\vY Hcl -dˢO?_-4iaؙwlOq֏~#ѩҲ'q0)(;4`w:sn3EB>ro.MvT{K-u<*F᫙ .FlbG5C%ة[ԂfQ 5 hEz~{fJ wW~}7]Sw@exhVUFՌB9 _ -$ǮrIB'L4 `ᷦx}F*i3A(L- 6ŘK[r GĬ߇?K?h9N]~wdyާ/;DmS~Fs^Ӵ M?_g}}$>&\ -pX'sEY k"h$΁~,@y7N5P 7,߲H:$m#us^iU?#!!O -ŠF877'L!%$/KM^=Lp .1~wM~zdd -v=>3-ȞE$, -E% Ku *H&:9\ =򏝹IT#&*CQAr8HfFx5߸JʘSǤ4.e!y5(LytEûܮ7/-: -5 IǸ,&c`/*.3&|y#F@"IݵW?xἹk{ZUnQuGBY"}*>dP=J*dIYGv+z?-Z$gŬńz1<& CuS^{=fw^s䗠dOE &at4bS>eCδc]j/i.ͫJK%CӒ@>'&@{Y\5шE7| -8 ڏA_o$h+Fd 嘪L p{9 ¶eswnr-~s0slnV~[vI?y<_0>3\*Z^>8|"}r^ ;+sݫ}rZ܍ӹ\XQN@Bcd*1Kt :Ʀ  Y>ZC]eeTz===}szz:֎ǙRO, "B}!I*-"lIHn,Q@n$_^7s<~|{V,5}QOֶXٕEx Zɂ3NU76H0 !C.YCJz4Ɛ0\LAN ZUŘR tD?)F'!%pS6Q}Ze!Q"Tvq#Lvʀo4}[/{R-g&U!o>!<#foP|:q֭'dt` -tT֛vq-V򒈴ms^pukARMEbD! -| N݅v(V\ΐ\h"xg*< -g|spwB$AE,ӇKu`uiS.FW)]5aqh0'F1fWgmˬKeUkʩm/ jBO|IBnФ[OYmu..i"\˜ vYvMyi1iԢ뒆aނzM%m_}]4q5#/,Opv߷^3DgKf5鹿s1"8;ZuY;V%sww8QP poJtu"ƭښc]x7V;or*㐷cgz"2>)&PTStY 9 +jNКkv)xVX(Gʮ(yK!tX-\ -gs=HHڏc:u!7̽5w~8&aH%t, - λ9 t<Ʀ Od@)uI[ -kԆ^Y2i+ƍ#tT-{ԦvuY?t:eLBJ=,<Axi=t.Q06 & Y6xMڤC |<:9h_hI!J}C !IDCy%m)[O\߷Odn返^5Cl]QMo7gAxgIѼtťu1+Tݗ^;oW6ݔ<woJ'/juvЗ|/m@Ԋ], S6nm!ĨM\]q wMZ9Yev?#n8*!%G YEuMtUX{8i_Kϥm0vi ʯy&>v`W}oCˏ+̇[SU -,<|x¤UTZTci`OnS!תH̃>ɀ (pM Eic>dĂrvί`|nVq 2谢>Iމi kT!6ѭSҔY9t`M-Q8\t40[>bLt¬ o'g$tb`m:GFx5)'.[Q(,;LEdmW)i ҫHM&:f4`FD#z:bP!p9 dՁaq18CXna ^'H_vF| Z ɯqZp݅6АͰkÄ2\ʨ"YoCR<@Dv&A OBoW*Q8sdI3c2|BAԽ4gRV[BF8ref=7sI$6Ҕ~ڤsF=F>M)pDq X!&;C`*ʄZ+cc ]llfzі*\K~syOf&mz(r$MpxKîd,6G ȭUbih&`>GrHu쨓v@9gC:^򷼲HhEૂ{rէbL{wLg|uC}G/1/荽50{:x֓ʾv:H!3z{^ -(;v_J?{.KًJJ>mw.';/&=&qW^c -/_䟋O<s5`l`"؝WV\CK\dm&Q}^յJDMd_3eTr uߡTٯGl[} vů^@&Ղ[o<^8 [4)!LFLi.k6F5ybs\-49%/z[GLJ51YfsM[FjڂӱUbjXrJ*2Ⱥ {LN͉͌@ oSWNV~S%ȏ_U#;&5M!eG0jBF߬rE>mP0IKkAF' coAg nM :4]Av,o+5}2)795&M\an=j Y8MquWB-c6i3k$,>t饣5.3XM Co ;OMN\xg5bC-^&K?WF)Mj&eiFFG v<Ƨh{V]f˜Gq1 d~{ 7`vWħذhg Zʆ14].: e#35pۚfV50IA?/k%OGF0e=ɫť՛~Yڧ梸N@L;_~P]9QNo<ΚL|JĵZH/$N7` ̴xqSp tscY=Սy1 aJԂԢv]u5Wi =c߶-|ք!ZdL iT{bֈ۰RXYXFp_(`u(onkB? -i3ڈ|AGyĝFj&\nsIș-bpJ NG~PH-7VsW&I~vg@}Q\&Kky⁧D.>'!癸tNw&tQBGf}r1Q#:8ŐV%ꯗanX҆ -pW0sSL YzעdhgfG- +Lc;O>6 7Σ]*f< [CV\zޛT<yxfԤ9wW{O8Qы;| KC*}1|U\P])LxfR|Hl=(k$nL-|:{$mF>b }_\sˮB&Ί.k0 rjA^Nt̞V8|v:gJoNpʙ)S6guB!!ձ]e; -&p]cىC Y K\ht%}{A6`a6<9F#_՟wO'bê.r$"$UۮZlQJ4QŅN]5i*a.ƶ,aP! ZmU[fkMZG^`>c0RRR S*!Rt}f.JID:&j"ݙVsX jEl(FՂ5}lȈqQ,RF5T"*(oP\s1Xi򨔴䀕[|&uk;KNG3uTe8J }!Q9- ->W=\uG덲TNPiƏ='鐢7VtSaG1Ԋ ([ިf{5U5H!1Eh\VʧzS']_66cV6snͭ'AÇqI&l2Q!@@zẬi@rhT׎4VIV"i>8ںy&4s٦?1iKukt)Fj_={7O%fWl?WGzi7_4!hòlۉrPuYR1ń=kK -~0M4LblgOƋ - ๎Jbe1>#Ÿ~-K1/Ga# -h7.alG$nc0fSaT,i؜/cR z?B*Vjl_ۃ,1eP|X- >Fyy*퀾ٰ?e3ĝń\#4>91a1a&[9GK9oNNXn3)N'Ÿ}o_."zs6b1\ౢ$7˛cxd]k#W Ai{)Ad]e"V 9 hGy8˽Wّy96xWBrseSѡ/I|__wY̊f)ATyYZ5ewr'nS+7x q7S?+qt: m.SV6 6ʻvtN;yg_P!FҾC%!EgƯLs!$E?,]3Kxj-5"(Ϯ{5UOΥy]zYѩW)N\9R{p{5zʭa":4Ӊ~\I -bXWW'<#e"܆ZD~&HÐ×^/l=YILӋLW٪*RtgݲfګcZ!vIDCmxVRJd6ƭ-n<8ܾO~'kmy'7ߎ q0ۍf*Jr!%%\;\tf&Kaظq#rU_RyU"8\:۴61_"1 6¡wsJ!j{"m`I-?EABq&M[1!Ì_1,dT|.T{]-č֌W7kbߎ~<ETy -2!ipy20Ocz]zvgN=SM;97y@޿]̬[mho[E`@(y1lԁ2.'吷f\%X4TLK?ov{ N`0`rl(M8ަ\-;WVO3–ߤ l4?e}3Lg$di'g1ƱlcK:VVJd6C(L 0ƗniW0ؒ%hj&~}ӭ8Y- ,XOɛ3.}A3P#o(í"jsZ\}QcXq/%To.UBs!ҦwE_U%/d7EhʸG,{tR;8@oH7pi|Cumds#￰ 6Pk$M7kk{ϯa#j> ,¦QD9ݜEբ,.p$=o!W~>~4\֞{6vl v<{؈q`s~5quo~YImVH!\8mJv+n/XqzY;kӶN4j؄]NGB -YL,a3!' 2&C4t0L4S2|:qeFC-xlY/ݲmjԏ7ݢqW@z(܌R/uK$f}3 )0Dҡ0v8f[6(F4W\4$R)!ksT$lƦI!c1ޖͤyLz6];>Lk7fde]"c&2I6!?Uې׿>1v0">/Զfj R;bp߯QLJbq-LJ[SQ`=Ԉ{\-p7(:{e;؜;`)=tK&xLNq tꀉS(B'M#\ơm<.FHeFŒEŗ _/ntބ~x E9)[ B֬he_~=Y0imYrwʡGR9D(ڸEcM0۩'@vKv9~\ ]*ȃ 9{%n0g+AGʦsV@[Nv}+b ×ʿ$AtL?l1!CY_Kfؘ;٦tc=p1/.K"IkB6n9.4;!d:L3i4uֲ]Gv-봭[$=X`qHƶ,]'NTXV%[7. @r*@.{//}yk١Շ~GJ̍%ef9qزI) ㅺx쓱uГM.նS>!=sEHdzrv;/Gewm?w$u6q$j462('sj"X7b3݇k |X!:ګcVia)褿f۶hx\J-sn$1s ̴ʈI - Je"OB5kysE)wW?[ŇO;@r34in_؅'<x=פQ PG23{mjfpD/ EL5]mdMB`"].xkM-k[mk?(T#KE S\|+ž65gY?W]$6ݯ_}&zJEe7H ߀{,ai$2JI:ޯ&z -qSB5azYD/x WY(wex1=Oݝ=$.3!^ .8|7%-yz6L!$b:,$}ܶ邜S|?Ow(MD34̢Z>Qy -޳bmg" f:0uY h^tTbG1 -=$!C93经|G?Փ2. -!eƆ蠉:d=X%,Gm_t9|tՏ*&&otϸ81eㄭ89L;^nAm[G,:%ӄ]/vl/Bnl#T-GqlRj!FUzjPac}/KzO;MoJԂ l uz)&Wx6p 2')qk=za8ۆLm 2ʧ}5Н'8ԗj6]( C^{qE4 /=>Q!XVH6'1>CZ1 lRN#Cbcw6n؁m1,јI;̐9re7ժxr泸FD^Et]@4nz6yeyw+#~.q4fc(iz3M}!ƫ|rP~DFpXz`| $86f?æ"gOn2mRGʒ69ŵpmDb RB*U!9>[9c%~S)b[l7ef]!^a=ԡ*Y[e*Q=]3hU}ߒZ&)!O؏,A mn Ʌ2|4`r!P!w5icB|&}&wa;(Gknil={?Qny! \$d - Oa tInnOmOv5S7^/@B-R!ѢH9Z 0ֲh) tPmQXt\}6記0_0=ݘhъ'L_XdmS\}03]xnjC=2 JcUIJpn}'[Z]iun\2_>ݱ\=\7nx'Ư-m[ZYJSuL/ QJ[oq?5ar*wEwDFN rtҹ!:L 7I}I䆄$餓Njf~]}lWbƃcU3Pn4= -uP6EKG.`jN|.| L z.5Aoq$ښY| 9b 8K|UR6 @SXIe!/x.l2ˠ{8kTI ʮ&GG '|+,:E7rFH= -^3Z1GE=1L,`U9ϭG9#?DdpwղO0r+\o/fg7k࠷0^Ask6 01DCVh=vc9j X;dsaBĮ Ok$znϬ_gBZ RSIcg'Y'1xG!Y  ->|-?˅ǟ1Q#4(a.Q6VSC $lPvؒ{8[ؾߓu NƏeHew:wfuFF%ǟASጙ hWXB;1lH˴UD[Z -ŖdeS3q"͜Oz4,䇤kdx3!:9vj)YqewT&iޔ"1FtIkԢiݛSI$j!i9b^6z~pwƭl]n!)Y>-n[U yIhuC] 3R_\K%3^tMԠ+ Rn@܂7`Wクlrhͺ mc.8?}Y3] -e\a=?'kcO_B]VDfeuGOUaM-ʮ('R3#Yrؘ d0- z̒z)h9&hc֣S을;4-m̬ yӐNkz3^ XۨfULMP!dTe|zaҩq/_/FO((W55uc-qTִhꑃuc8ؓ!Eŏ@jO kp73!eOngV̕g!I?x^{n]׵O-%G]^x sc2cO3_6O̳~<5J)'~pG)aI,24ia)IH#b} -mUegwJKbXޤ$(+O5iy=?>}7}6aW$b.2eFjX= uM9p'? euZ3!)U8W7L/1ʹmڟ\ޝ$lM)c\X"V}8W^q<˺ kNDiKߥBEzIt5B%/TlM':$_;OZ#,d2ESƾؗqEEM[Y'eÙ r/zU碬_c:0A)TWL֫QѯرM{ja iA8Gsp1n RV,r` Ӣ3ǐK(%aNMR'9B9Df,G Cr~weESj]=3Uu2%coe0]Od86yM.3v/qi9HP A )bNkL8p>UU:)/WuƝ򛻋k}r+ŭjelX-Q8)KمXQnorR>Y& 8i75+>eSE a\ 14T a~Fkcca띦6l.{TIR];w!n_Y׋#;9rTwF#_+ х GSX{umdF߲NUzw&$rpښR:s4`Z}^AXr0)-=+ӊV|s[Y7YŸZxnMUp_~g{_[c37)ݫ\l1 Ms#.)2OsJkO5 +ˍ@i!9& ;輸 SMYG9X%h{8L8SF>5.ϲ]ᕃ~6EVA]aDž?ν.,37N] |Zax0Vd8Jq.uZ*NV;e8K\21~"!y#YRqi[`@\3. rVaWj_($.f /#nNhץ&P!l6.m]vslQwRd5~K=Hc֦:^gH2IfQ}O%O!bE2D7 -x_GMc C5ovY$:dS.вY(-ʏm5:d3-ywʆ^0Fn#,#>:=*. -Qf!h `'aQCdzN+ZF#k0ЍW?^{SkcwFHBvqCR[M"og2*>4[r8RzgcTUO]F24 -;(ڛ3L2Dzo~s ^])8MKpA;@9RA?u! bb2n觨3UozO.|>$=uXz=yé0MC.eSEﲈKPǢꞂw>E>ȇ"_U!Umy触S R*MAd1lpK[j<,^? ,-.2 Ktv[i n33Gkloo*ݑvܲK'|5*}uqZ -v-:$ "-2wg]S-H;}@&*:VA!n?…oq`koa~H[ub+*b&1ao?9P>Y5&kz.(l,:Q{g6nKW=.",E;!,Bb<2䛒^rAuJv[OõCjN=*»g?~4\d}ƴL(*Ir)h [B3BQn~۞w#w|UQc%Z' \I ჿqߴϲ^wrA۝HOMK Q!XRQuϮddJ |\]]ZsCUm$3y^{{+z y=ZjD4Ѿ2r$/[/is>>LX#MtpwRyo-|([(zK 0KI6 2V/6Pv~#bQ!}nJO2mcTUApA9 [/c?70PhJ;$iis߲wuoncfLd;V1}5g^7t?#|e-i 끳bN' Y^ j>* -w#ݗsVZ A5'Uhl?JRuz>˄ԊĿTUAmħ& *&-_>o3%%XNYeƁb r&V-Ć$؏"G8_ {ݤͼ_Z_R'͢D;8ViC{ެr1JnWM{>VGE_/4Q 2f A*nIB ]j'+|o_/|spXi;i[nw.staҋfJ #-acٛ_[s-p4Dׁ^uerXk)cA;.6^ )AD`'>\weԐ[rLR!|Sx `6" xUAxe]%Bzɪr!,!2Fyz<ƈ*1J2QAs\ip c:b]/Zg{2@>|N)'X2I/%m##93ncuN8|5EfhEoKX~%=:LXH>/A3'S?6fx0T`* -*(28#"#ٔn217ʡsįRp=^GXAݬV*R?Q8g{#t;ѻ.+~rqD?o |[ϗl+hTf;{)U/F>8>TD'z)'lh !jڰ`t0}l`~#gcbm &yn*R^H;#Emu!kdGzEI=Jz}dm)㠏r>4!ܭrZD]343;uj9jajco2:`؄Ϳ<4JJՌ.ϯ5 -5kܙ13Nb,IbLL$ K,AEAEiREPCh"PƆ 2}8_:{w?𧜢.|eaae: -s -ëfzT{U .oZ)iI㉩kqČq68 ܦhշ!9Ŗ?a!rVqݚ{} _H7#Ѳ]@;C>ʽ{|cUuPCGyf>㻹ꚧlvv+"G?ބn}$Ũf{2BE>W>o3o[%q571 -VOG48e5Pros~\=KN%N v:9TF5/ݙ%;Gv"mS/Jæ(_6;P*LF1ߩ)z*b~gMXbL4a#u`p'Yg yvsP3sG=4D Rᇃ,=H3uę mE`LKt}.En'{avQgjӂK^LjǞLTDmb2^im -t*q(Iqs Lͽwư`Ys~Fwe5TBtlrk ]':JG<|8("`gg2|"O;ØX&8R9!b]Dk ƳHȿZa^1<ǁ8ÿZb]*ZMBL\nJnJB4p&snC'ʘ3-#(C[A.'#;_/OeVI42'gv|k[zTNNr/t:>)'GJ.黉G-sPy۝1frOW/b84GELvȫ/W1iZ\;#i%qӇAۃUO' \M/&vN6!mv*PjN*DF~( sH`vWD"}49E𩸕CK -"${𓱽UJ{uh8+-x"u(YUME{#H '䄃AB̅KE~呎9fն (Y?ސ hܯoe}FqIe1Pq e -صxv4Cl Zf+czJWNYS[Ci[.wշhpsiK%1ŻK?-˕$COq<!DJ7깊_ysooPr;piAZqm}{pUX&ߴL  ~(n'\EC=+F? (=TxSCe~u-1|0Z^zEYszh(h_-p/qa_s@E) -.o`]jFmfSǑh1=g0j%,5׭iwj W}idc̍: 2+/ >D_^5pffPbx;\Yֹ_=C_M]Ws#r![b|OIPz*?@v{oۡkDZf >q驴,ƿJ}6](gnE-B%g~J;];sݽlfw2$hlLւDA)"K"(&  -(6DHi3<)`V{_OA7 >*<ؘ/T'5aU@uwBFM䚄YJI~ r@+ȮeT\~/='jqk*^aKѕSqj0:{PBLAr>YS#t<.c^5򄉋hՎ[ȼVS@iuq6؀+Kl N&+sG֞X79_wk_u9LC  -U=psW֚uZVs$!G]2?8tV&͸C%Zs/藈WzŬL (iR+PU@\q]`"%)#&m6QoZF= 5yK*$u *H9Vrݣ\2D ?K(+u)]QPC(kO,ڈ_}Ƨg&=ם8$!vQImPOmx}]E)x" z'mؙ#o<@?JICHЩe.*һsл -vdِٜJqԈ -,{䐜{/[Lo?2`[E1Σ]Yxު|]4+o?4  -uY#(e?؅m-$܎;2AyFbvʄ7y/{̖PՒ*/a%!OU뻪BFteDh9P=^DoNCVBUXG9 Vк3/P2s#g;yK4lvevC\ɮ燋=5*?l{X~;Sh)vW%<O5u%'m -+oF֞"RNJ?H9֋:m[W "x&"?@nH^ˡ:vtos0|7~OIQALp1ّIQ[t1R!-:s3& ƈ^E߻1Kr _5Zm5궐V_w0>T!È˓N!QHi?[ǵn2W -nKڞ^ VFu - ^cwV$-qK~`ҫC}5 -4:0^`h;E-[óV[#1\$kCST̎@p`p>d$5{TJη$AVB$b  !vžf'I AіbjmZ:մܹO߷gy<{ZR>Z|vljI0%F\ y< K9Xjy40/;ڒ҈[RƮ֡,u\nI~4,*<Yr=EnRW8CV2f5<骹 -97S]$B3Tc3!-<hD`< ؓ -,O" H@j."Z?  ڤ?nLY2: -g*/g^A2  -Qa xDCP+3]O7u;C̍1s]詮wQRR ?*Eph//He1 N @t"u!NGCS Į97JϖWoi>[U US $"dQ ϛTpDEt/;n)_?rN'i}ii}6k] W7w`)(0ia߇a4O \2|~!tXl6ݚ*|!jk \pr5nŪo"4WlLcZ2!+ <$"pD  -8}OGpx Vߵ:SK}K7r\wӉZM;DDJ2 L!AP\]_$_ƒLX[̫S?mX/74K'9s=}X^LLPp(Kd |xX,0~dOc ByFroި,lsͿq:xaŒ`]1"g/ -h}9Q#@DQ!H+SHC$!BHiOu;ۜa)d<7 D(]*kbFNQD #aBFX06I'veO곕k FF%ؐ+vPQ-YbTqr> @^jhޜL 2xtG]S@_1Wwu#mQCDoݞfƄNej99I.E铃xf'$9:OD?6N6Wigf{ V, JPSps-~XZr+ -C ӨAT:x{EA ,Eƪc{m؇վʝ=?D۴ :ρHy| ;#R@v#@pf*dUt]z-|xZ_u /etFU m5L6%5dD72D1Ed"Ä́RPS뜳nEԉb}=QNMcSUYQ^px0=f5 X;]n@UJoEeSk,5 rJ-{A@6%w1BG{@\h ux _5 ->'+}|!bοV9pF@4wrטHPSy }%7*-Y4M t&n ~ײ0W}d>iZU<3E䴨)+nD.LkwhA=M'R,3UQyPTGbCfyB_7}˨bocx;K -*&ن"cNx尢X\"-`L.SyRZXXD{)%t]GwFo {bֶM\7ʩKmZl|ت -i^TS+k y<_EDO|=@$yҊ?uۄ˽-&=;.","ʟ6HDȚaj¤|?"w$?wGĿq/I/vG#_ay])Ǔb8}b/B9tF*ܝNTBjcȧ2+1"6 9){ZrxJZP0-m-#ٛ7O뿍!d @-UY,p^!Q@qj"^7m} 쫀wf*X&8PS}S -?۷>bߴīFqdLO7tI<Ru|u`7YxvŌ|%5.otƚ{ĒkԫJo#<*+yHOGCˣM_[,&$~8VN {rɆM4wRMۤwW3_Ĺ!ʴ,}VGvBg@0)ΝPt #}qH1gN.+OmooqeTRY3JA}TՁ𶾨#gvc<T{mN -)V>ecWe6t7'c,X5\H/Y­ Bu[r/ dZ>1?qN!ىE;7 )7-BweP8>ѻ3էv~(F^&3B^bIɆ^E=d6tj `Wr1+:\UӜ}n`WtOnyW^f49f2k\&aʷcjJ2&j72ƾol^l(?+ H[31 -ҩqjq旎NML -B-WbJI]Rb c^,9PͶuΪݿbfwZ7Q,g.FN!iV~N.ϏTQpo?_NŪrt^ͷrf8m;j@˝Eϴ!YҨV6gtx; R?)sZ&ĤB?KB?|ɥ!f멈-Xk2My-%0- ]-0-bLh;c]ܩQ5sתN -6ٓ\-=:"(?c6U7+aiL`wr'4 -f#=iqi9kQGM]$>[+Z>]9)8l,JL,I97ScjB퍑w#̪QqI^Iα˹sPT;#Xe.=(9{Xg_sbC&8˩icv;shk/VvƷYdd6sB -5Y] hڱ{f1ZП8'60n:4QqpOFP&LEt~yĴBD5x>",b\FԚiI(Ooʉq!xEmC37K e? g>V깜jt_9,gas*!wyl]B|%rczݜM/GwG͉OJ9e[%\u_?4ǑLکԺcl{~=5@fN_)/zX&.E6nŖ:n<_PA7M ~B}]vJN@]5`t/Ed⌋JYm)չo8ܾL.C-ș#w3F.tm HK%J9;%Rw =fr0hȭn-7gVr jA[0U?yYjड+~%zWrVJnv*E;{j LϪ!Uɾd̹E4![ QA3^/])gƱ'[G<zR." -%2c,T:s^60r6eZY=C4VIհL'a/)h)9jD*|\~>|IߣBVꋭ~x?\ O;r-^ի=/b7KA:TVC8Tc [ -vfG<6[ev~J'j}-e=\y \)2zzE  r\}= ||rs-d%u\j5<Hڑ7QY =El9Y!0U?њ>F/|uz2|uUvWI#^xͷ)EH H"\94P -O-ketq_Rw2&BE&&FW[;Jy02#?I} ?ZP983#c87QO7q#w $lqQh`pSU̞[tqѧFrաz2r~ӛZT0N4\RdgKkgkK\ZrHH9p5 ;_t˸I_Exv'kD6t;@; _fL'i#ṕubDhOfބJEL:Fc[]]&< _:`)g60KC6j{I wmwW}vI$ 2h\M)$ȣji.@9/9M5"(Y*׼Nʛ&^`nFfZhag U}k0cbvBRUTHu }ʜwqƒ2اpEٌDщ]vw5<"2V%ּFu d^Dͬ.JNX!_u&jb3/.hw 3B(0]~oZ]m` -wD>xu%bfMBJL e5Ȩ͐u׸0 ڢj>2jZVK[kRBUjšY bI%E:13;3&ByBg XcۦIBex4fa kHGKܑ/{id&}a=9'!-ɩC$ũdQESxpEU, 2c. -JVk::[/i BDC9AkV.S9G<.%o rv.)'?Z硼 -z'dK2ypDbؼԝ3דY-|U"Mn8 SaYFk..Sag&⊰^Ry%bw̮1 Gj. OGgjZtU4JBvڇ+pKu,Ič-(ag2v3n_:0pE5j~9s*ƄuZАyE1r$MݘS>:n%ʴL_Z`+l{OAy%Wiն 5%r5^ R  V3BJs%5rjѩĖX*}*j,涱22$&AFvwHE- )#b]PjO#_R=xvGؗ1Q#t,w "BjzCp\^c6uV#CƮ69ԐuPl_ElHGi5G^=JJőV{dyIy_cnJBjzss/Nm;u^d} nfc ̖7]z+b[C#qd2O&tTԈy/CܴO\Y8mpN\fDd<~$4߽jZnsBPN CI#g#z6"F솔^HˉKꔆq0y;י똆X,9xo22c{`{U}":VQ5_gqUЇW.Y?ыks_uǐΐ*g65g}Fa?%VFѭ Tݹ -)>-}x5?'.W ]P +52 _^m^?h<Jl{0fck<WW'kxpQ5ݿ0OMƱv&V8dżU<ڗ2Ha_!{pe!cSxo\ {rfneg4="-pP Ĉ-kQlzn o1NR]˸ NLR,>> spyXSy闑KR -ع@-}?H yL6]ΨK;H#{"ӷ6g8,I׀&>'oΊ_GLMYxu^9zH,q<·ƔEFƴoBȭZ7O"x{Jv(l$(§\0n1? - *aLCLL\ `L::"j6;Z"khYxc29]Łr.1pb2 }UIIxg\<ѲQ1C+A3ca_stCk,QRKwf`mOդ(БڵАͿj>I_z'w~eW^F+*F -Y/Arܣڧc 50'!BCp[H?1p9wd{d{"lyV7xU|*2tqSZ~;\{iNtVݶMNv(UdS֒uA3PWދys{Sp ?rqgZ1,vpӃ] sg^}M+|_` [U}~*$s{%{ -.[r/žشc;.:zqNtH{%i[}wawz'JL!_Vؔ}AX@6= 9*F 4l<"ْЫ]r"[XA3\'OfC9XlٝnI3 -K/K -&K/S0.Pq@q 3笴RsA6ơ'FH%~f): 1rqsO+!DtW}Q1GߧMW[_{#=6tl*vޕ1F!?/j=Rr}9Y+`\cꮊ_#z}js]*N(rq yEÝ1`{ee -*߻$]Oȉ`@ ‹ -Jr)zW-N -JY>)4< nI6;Ie$d h\d}r"Q=bQ hR"'%JԘV?=?t[]#):I;ux'zۆ;K;kv4gVQW}v<=pačakK7ZV[A/=%f`'[-Ċ[<~L$孍+}Y>uC^vu{q=LEdW 怎z }nsa5vB}^w-wtpn -N?^0Jk swJޝn.2v bT2J~MtmyM -kqÕוC A "be7TM+u*] aS(>sIK{C%Q*DtƧ&g{o{`*(nQ@C+ (wԘ)`X#ޥeZ.;'F--TŶ$y87@͙9 -b x{qoy !x853㻹KS9dv5NVQJw-~Y^ |_Ϟ֜GтTɥ)ZpR=ArSvt.գf;'+bdh!ԂKʶN9C4"A(K\zx pb{K%dUr=JqO`n+õMBa#U!3)fjk#HW."o4e[\> BYK2/zv!6l_cFf8O&:qff]H-YItEA%cv ס댴rv֩COFMF愴Vm)Ls}e삎^YxߐÒtC{?,h#rdWǃ\o!=8U5"Cxr;u=2ys<^mM_VNI_#v<ʞcYs N OQ`:6d Z? -qE8-q!3v)c l=DUolda^%9Tu#RW|°ߺf m- hN޷ >=hV%vU,TLˀUӷ!v&k[ɿd}-ѷa=.N b7 MY*ԧƤncgY*ZV(Kptg}1rN@Cپ̳қgkn .ly訞 hpKC'?)ؙ#:_YFM9&ek o-y+~lG(,͑Y(kɮZgTmiOg_! 梃6UuӋ܋C5QzrRO -I=+!fߌ[AuE46s[O OiOSn -endstream endobj 23 0 obj <>stream -HSwvgLӤI昍9L8cI?>n*Pz7*֢irbSvng&$nDWg%7,J7ĽKV",0ޝZ:dq@?3qFSPZ}t0-aϑCQpC0B: -دy9$IMwkB֮Z[a|Z5Ef]C;.Ym<@C5N\{@#k# 1ڔ7bw5p=,T8'0IRMv -‹29fvMՙ'F&5jrD8(9k{9_6ȄOa3y2JUF̉E]ezQ]uɫ_^h+[8cM{c >ؒKBLtcMtxB%@FRr;4_P^Gu^9E)u/RFL4;_7K:u j#&khx-3J5~kY|ms/N$Y:nk7Wcw*15I,ڟEd/ д:*gy,ll0P9^cuL|TIJ)\CNn4˰uG#-ΣD -_;SPgrJIrzi@/ ΈQ, JJC CZ -]n`^l :RD p]X3|+Eo[rOM5@[]ӒAALxfBީYBLg3 _2b@*y_娒Klp[nOßJS¥>rOc jJ$ю4J).n_ lҚfASf7"5L,61p>\scg[c.7$7G kɵ-RmDO hպ!xyM'=&Χ&W37;1c#H` \*g%ۓdoj_I(]ݝe:0Chu 4jQ:n灊 8((:׫z`wHh5䘆 *Lvx=\vȯfaΜ3'uT4+ș_C_*%v9u`9S:m&哇=|랄= 0#J^۱VH:u,yNm225%sHmDÏA%m{4:/F5TlQT7_ruy`e1#ݢZ)TwGb&.cHq=xn1?:A@WڏVDȝqN嶤_s-$DNar96$*Nr^]T93Pښq̡U -spxE2~'۹t -1Ø-@+<MYx*ܓP;9Ɓt &ϿNoM9 9 x2b#f.e@$Qt(h4ݐ^,HiDA{n`Rik^Wu&P; ˲>TvBGjvf7H lّQ^m ip5'kSk&?-Ɵ[YSRA\t|\`ИQ,]-+NuAWHʰZXzme֧hE@;ˬ8P*srhTlD>vAQӰ'&#F>rxwmH*nEUd؉o$) R zg Q7Ycjnz}T."jbExW^Ǘ-! -5qƳG {K I 6; ECAY MW O.DϏ,5rAIYG*,:{`4ָ8i/{H{ KZo?gӞN3e214KL'%(QPeA@Qp{D EETAQYI6Jx^7y{{=J+ kM[\ġᢼ@m=`5ipb2LȖ'hhX^Sq8 p^mþjx8<55}aaEh+̸߉鿹牠lև"n1/P > hz2>`wk?θWzsdSȷLrmS `}t{fw)eXΪ*\#]O]H<1c=x()ٕYZzmӌc )>n_CiWc@23ʼn<7h>5*ggj8߬m[B:V7}T jzuk]>F˱ z+y1^^E&]m/~4Z>&=:IB&Я -rW (%w %t -aEM]V¹<^rqDԸt|G>Yh&4N yrm 1*\Sp%;j*`g9W)nݮSf$n|?ۤ,)pKOQtW% -\:'<6[%4_IɚrS >%Z+/Nc*s2a'dgyN|o]$մD|[sx]fZEH.!ӼV"gCr? -ytBuۨwUF[٧T|ΥKr6[MD'PIz-,@RӱʦmAyhˮxcx ͨCz#*TCP'x92ZSMDnBc%HT}v̟Os\MG5u/%Lp*žm,Q\nFOM&-m2wVpⰦhd,C?6z0O;Z"5ͬ2]w2tmd@d| #g81;ӈDuvB'$vLόcM3hXg/6xe|kDG .ɡ|.6T<ݾ}GƩz+dYk%Q*6g`,jSN@{㴒Iwg/9J<&xd.ٿĮݙh,ZplA87pg&uRc'Sq5r{xCՏ5{WIJSLpʉ3҄F@a &%dI9f.Ģ 6~Ye!O~勭SLvy9ld"}|_C4J}9}.94SOE|rv~>6S1)yGP82;,b@+9!a@~X[y*"p25%׆ѹi{HƮۛ- rI1Y -JA@Ez{&{C9xCB˞dT?r'Am&{=Ƈ_/dbLLO.ql!Nǩan"ޝ_C=r>5udknwpYR,e̕VnA, -}JI[SAq}m )hD pXK-ŎilPC;@,uZ߸/2O ^Gy+º=WI{gnp^@ aA5l0Rj6c xOdvȥ:4A*?3QW5n%Dtcu'h iYr *jL(~oɲt:̡Sji9"∌ 2ˌS搊83*bZNsgztcY"-}%tСk0@/]6¥ԬQi; -Tw 9.=ǣK:a '?>86k 77^>6Tkn cn½$g>E\SxLt qls !43I-pD-NH92駋\9 7l`BaS.b/Y:*MCF VooO=$wDfz(E>>k:KÖ0oO[<=43OG.2vEuN6@'lOՀGaGvgVLSлNfOmAXxo倮9_Z?cS*f%w'iY륮`Kg铥΂^LkSxg`C7sG "@K<&dWVO 6JŽLX<6V mFZxZXz(}7Aٔ!ud@lr -xm#07MQ&Zw::H-I -Zn^Ss7%, ЋI@>Ғ'5ŷXN%kj=&'tbw/*`zt"B)Is c.:Rn?#5lSL;.q}uvnv_t"@OTʏFˎ2f44u@2;%?/D-,{ޡd*DKY.5R3 Ĕ0f"Q5BꙨK z.c3HM[˪j_IFi)QNl?):Zdh'3@SdP: -l,[䌝Ifѱ@˧ZSjshdF_( @}R7!+`ۏej@׾b/D%7W%hDOUbĠA$Zt!yk DGA1L-5j^wCgyflKsjaNznc%!kwRcU%Zb7_Mª 8p3 hpM&钢6$'և뭈_c ;ǫy`<zO8~|puKE)ಭci.5#;22:A#ɾ-~* rAGz>u}-} BKILS£mrpR7#_pFͣ2ǙŇ3Bጔkemcf[*5ʧVdZ!fk i(u ~<C3pK7:ЅYAqzAZ4׌EaF3 kF wR -!YH' D<.kgXo?gAc}{ksVUli-~8I΃ԦrnU+IC(UM!1~rZ cSQ{â&/&j8[NMC#G7< @?4/\^Jnksj!9>6zmSnsζU9d$~3" `45QR@axPe>tRp:DHq(9;`#-2R *f1ﴔD_r6n ^e6:ߍIl(L 8b+, -[?J~%$_boq Q>wg -Ju=;jsÕnJuQً S3nYTǭU!!~C ]IOZt94bSS]pGNN-_-aaҮ -!1LԌ?ʹ=tաiU !x"+DԖZ"" b]HDT9oy뾮՝͉L<0+utOeaW`w B 03x[E/&Cc(9=DǙ Ǩ~¢g^%Y,xpb쓍ؼGނg&FݿB"hE<ߐsOU%_4wtZrI ӍV9=Fx=ׅLy%ç|)}kj=I RRn:=-9æFoI> 4÷e -f̮a0?Ɵa۷5dZ}tD%صĜ][Etȝ}>i}2%* p4 ¼tؓOUa-e`>|S 2 Yq1NB̵6tҹQ@?i(IɖcJXw8/`yb_toSF-EFp֒3@y85ϵ@\/17PM,_lݵ|maR5{oM|KIvgOTݲ)eV$n#P%jj3ZcUfEB)8f.@OȱkO1~Yq2H:621ZL{9D2MaZ._'({ceȺx#T_=N/}Ё~҅}f¿u)wr޺:խa@*J vD&^@AT -LbbVE֥_^'$̊Q!ԤZ>b-]M7; -̶ 7¼k4|xڍPM&Q^s_k*>$4 -<֯jB"t9$IADjoV:hXu #/PdL -n.qUBNZb}XYWg[&!Ы'>M0!kݘ)աȰq3 LP@[r` :Uq_ QN%5r+A!՟Z=:&o cw<%cc$c\Y 2*Ƒ 3: ǥV^ZOV Tu3 nᷜ-r\Լ}T4MIPuukψ=:AC-):#,r鍂{{o -nGA7)MwNգj wC3fySN ƺ#gSu>ؚ̲8<]f, -:.]&!@()^"ZB  !@ "0YOps9;::Ҕ4CЪ:|_G*8YїL*~Gy&{sbC~q>"}s~xqo0Оn[(2]m#Htn;_n=f>oĉ .͕PRPe< Cgx-8#G~bb:lO_wլ .j.1w|xvj0j\M*,9 <⶞/iKR񡩣Ͽ(nN˽r9UA -V%Y7w!:Nɣ+JBҕ^L 2"#A -\T+2_v]hA<}tE|yQ3KGCCFIQo^M6ծUElQvI_ë++C/PY5kc *eOyO~|0@L8<|Ǯ@U=9a|u Y'SGyD)w'8קaGI53 Ef$:yg=2EkÄTpFq["@M׶G^m[P WFolI -leiA#hu.q7k3 / SQf*䟘x#S3yc^zdYeq_6l@gv'{1(~t -3afU]{AF:^daO!QE.{qN0*Ʃ@m ' \H ՑYg;m5q~#8+NmS[ gfYV5FΜe85x#1MEm3%D'11c--|} h$5^lVx fl8!{W*vZQf5І2}lL:H_!+}*j $ Dg؊Z4YzGK `徹DɌDD_B`b!$PTbD]CIrm7%%ޜ$|KC/5ʱ} O/3Pm]9;$=Ѝ,>YdiBDEEq=䔟F쪌UO?ۛh*T&1{4Gkzv>Q4XM-/t \r96{ $B/4(jc Y|h4;Uֳq3bc1"'TڪS=Dr=P|[Etbc*t™7 jvW#Pr{OJ/w{)%+w%nfwFL4.H LK l}gh7IZ#AR|GHz3sw@sxfSxsɚo'h 5ˡ&TN6*=u5/frn@cs:rSKw m2Tkgԣ{U=# Rζj`3 -V'b̊ ;oR>FGjH1MtLT UJCxE[0`mGq%q6g4֜iۜnƲլ)+!E@Ee߿,⊸Ȏ -_Yk:sf9<~ޝ~q=H, G9s?X8 瓤7~8!3/WsK&|A_,K Ϗ|χS1'Plĝb`獮#/>#bւu)“~%_uE˯-o -^YCڡ#|r#w`65@l ֔=i;aRDۋF*,a ^ħ.?b35ℑ@YJ" b* ?K,s,y`$\ܖ1Q6[(8^ &ŤC 7]nさ2*&ؒ?ӚHAr%X$)ce--0#FoBj*4.G9&"k?%'_ky}hP9ҧ`D,ăVPws>6!j"C)h/I z^MG%@kH ֈz(CJRs+ -zmxAxE'\)%kjvy~7.N W;ˬ㝥N A-dCIQ3QwpUVw6Tw/o].0VxҁoX, 3H {w7ޒ1_Ō{6nsR|C'Xn/ݘAAPl-NELe\QQ`ͯ MTpQ|ދJ bC 4$! `r.AUYƒ-☄G*+eNf^3Ń,Zss<WS4^?unw%9aD5@5;'wH~튌(b4|樆S -1Xu]/IIk(ȯ ~{C4fJx&Cf6p]mGoݥn)YK =/}}Kޮ\gg¶L"D y4QTsRE;NNzK#ljIKzIspAޜeyF)x7i}\zG9mDW#|̟z{a1j΍gUM)'}<.ZaٜkbG -.9@\y.yӅ3jۊ]y?eΎSCu-!b5]=cO|>"nʽAJEwyk8qpf0OCjcPr+.L%$vP~+en" -u,Pa({, Q#Q|ڈl.~ʫ~oy>Ne+T ԍԱl&c͖0|jճ'Eʃ|`tM? ^{vU|9VRlӾ.Ҋ^:Ѧ7wᒬuapG;~#3B') Vb[h!Fdr6MA^gğ.`zDOJn>̿ſOʠvDm%ms(HT[خCBqۘn֥inV5_yӟO`p~HR~L_a^a\:̝ye?d7i)T J%7\#v~ o}0Q``!\vmzn\ox6_,!=?-ۖwGb]u# -[S=(7 zD)ڸ.Hy:E;zΫ0?B;T4XE/U0k!I/9pcc뭨(z匴SEOU^Mlێ)Rt8A I+"ec2U~IvRvrnAm,(NЊLy@$;"#<v\N%7Q1࿩EjvǤifcoM;,b2r }aj/Z%us;HY}VL.'eʫ갛P!Z~xF\.{/.ҿnMRy ^'m%:?Z%&L.ED!Q/,b4\¥M۳Eȟ}٥ϸNRQ B̩p5EtLj I /Z]>b2%q|&^̚Q9FůKhŝOʘZ)+YťdtW+;sp`ɒvwpDjcmfۡkpɵ N/\W 6a֯1{fPD.YVT' \GxnX+n cnu|3 h%V\包3ؼT(ѭDCJ`u=BjUɡd{$#g[Jٷi5[2PJܝ9Ս9%{+s+G:4J}kFnˢiBOG.q0au杻pyV榾 V;11絮~^en!6SY;0׎CAG%7~$):qj@8]uvVjm]UDN J $x%y{/y9}_ )ܺv3e~&FF4`uZUJ+Eq#,>g5o191#E d$Fe54\6slmB_^?>k}5-NتQ6UΙʲWx2%&{:lvà2'F]eA(H {78\*_Iy,nQX';j*9fk*č9ļTiNbLdB_뺮N=F\?$T:)wQ&>|K)./7mXg;)vG/rjԉM\W &Ith!@bi L6Q_;jaJ]T[sςc'W\=ބo*b!GNC)Vgk `!hR6j+'8q-ڠ򊡚C'XELYvE/pQa$0xq"3͈EByiAogI?Cy2r|oM5.L&0(^m5}^03>^mr;^{lKdkёz;t)c'B~D_悘`qJ-؈ʅbߞ Lc@;$u?%cGcw)[xߵױ/6T lm91y oZ6̍;7%9%(v^H鬥 -bg(قKwz+ivA겠YY4r%o7E`r%#/BZXJs"yrbxbKC룇W>ih! ڡƏ]7U3$}j3.Y,Q_csb01;,w VcpЙu O45q{[IKxHUd0ЉFfϭcOޠ>u՟/Cex/#ˌXH̠le߲%T$$%_e,Ң%[Ju0,y\:~yP R qsk@U fEnM7*EXXV UҝBh@1'ШTB"UͣՍD=}OV@1'+Ͼ{d(X6_tƫ?OтV Ih-Xgq*#M"*ɒ|{ -B瀔d*'HArĠ(T='nx]?R<5P?v5kyAUrQTUB ʪĈzU@AdgI43 ơwG uIۆnw{enש0V['/[8T7dL+ - UPzL%̮͘">Lnó@"7:T5t-ĝGͻZϝuuEڠr\+{*?_gnO[$U>ҥI-MXm΅ĚV9MfȽYĵaf\&i%=Ӗl9jho5Zw7?V=0MSQv5 !JFO@PihTnIUt*00fQ_6aM?IDư![pًqRJJ|6TzXZ,9) -f"646>„ύM@giLwti]l4Uxh(zMI~+daDt9& κ;}C2ޢsWIT -ސ 5YChy\8 ~3PpDD[>$[ -gsnKp)wq^uW293O _꿨cS_oPt5?vNχY 78å`l'B x1-6WA 1POn ?W+Sߞ|rjԝGϰ31ۥz;ht xs'yV -j# +L~!Xݭk倫AK <^20t{^1Kl -ݞRK(-s;oj&c#h@3l /f_DMnW &"2vxz<']Rы\S m֚g reTQ"A<> LwYP K7ޟwuvwW0o._y#/"G/tByY ׀vf Jaֈ>U[ST+G|HT/iKS9 vz.!JU91I\bO=&fSygJojoE80SܝoSvOYO}[:rpf(7A]/ D 9p/'Y|(X~1|c=擵N~B]W)r˔ #g[anFHA^FB)N"Φ"FEPUdkُlae=Tٲ>>:0v)O4IUwuuWdӸ%i%q5&&⚤\҉&("(@@e]DI%dxn:ܽQImb1c0A#y l@0h (ܚشw(ĥbÛCuAn lxRnB6Yc?z+֝[YȄID=s؇dQ.IXL:7:"0 ЁMR_CL[E쑨?'ՙ\˨5ܓ6PZ!5 }4h}7ņMWCiRR#cce}[ч_k>N$i5m8;a8[.36g~_sh"A~u-6WP_cf[# ^> -MWsQ@`[q(U -^{`j޻;=S:)ѷ9M"f"䜙Օ1q gq3:ITYSsg¹H(\K1 #4Xg5 5.տzI[7` -;~7^(bZZm ƁanP#@gQ-v;QY nS˽ -Vپ2mawdTY9fwNd'Qm_}W?U0j ڰQZ!;&[~:~~="8㕳붤/bb:1x<`yE5rERKMlk ՗jnG2"M(a &i-)fT'|yXl^V쩉{*Rۄg-TehDƾΤQid{^W[H8})ǭ>I5 }/?MahhM“ pA%.~3c*wnZ64V[o>·c4qn_J| :!WzF㩖ZTzV~fr OI-q>hn-V烑 +Z#V0H9RJZ5$UjRw _S N`nZLho: ]3[h_^p!5:2h=*FXH𰶷9f`۵-.B2dA|\?*•Edu{5%AR3-M#kw s/#V-wzygͱ8-4Tu2x-J{hń~ǿIi>hYfEt_,QK-4p:[ڐP -lr?7B 1oMSegduu=SZp ViT ?5_m޵DϑLiE@ͅbơg?}w0ٙS:OVMu"ؤe2ȿBKΒ.Ͽ2z+WmTqfޘ6` -#a5FRکVJb? 'u]fk}p};"DjAwzZ[G֟ws~iPգE\I˚&A4JgIXIibw. S9`rǍޓUwQMOE$<;J }oI} -ԯ@z$H.I}1CM,dJρ' dInr߶^ٟ5O,:UOqy%qqnʬSj+5 (*,+P"ܲIEPp%to_rz>?lw&(<Rܾ5M &Qe F?.""Ebo ᎋ=g|ryza}1%y's۲WyJ6RRZ[W91`{ixU1wؗ'\3T:@Lͱ|brBߓ\5m `gJZuP7F4?79FMQ 5 -sBv(~Y'Qϣ$ M{T}+/WnFa|'x .4KCEqar-'m ѓM.ZrSթ"zRbLU 9|[cÅkMT Z:(DG2X& -B(u?Ԇy](?v8[50``Oz,'B8X{VsȟM̿VY%fqI SN/[ǀn6t5H+R퇴G|Z2lm 3Ug*Mˬjwx4tr/V;ؕV]KǞis%5fА^A Rør&lAx)ٶFu?%PM}x@&Ehq>wR= -!F>-^݆3R|8A;V~ծT7p:MNJ:Q1*cY唤#)!XZlobBߤqPml[f"rkG|a$Hϧ s;܄t6*gd~n$YvYDiNt`n-:VBTo]&Iq@_gϝ-F^-Ҿ6m/=Dx}d}H<Ą\*ܾ^-3e3ўzxөׇSVos9:E^ dA)wjoSHG9]]mn<8NZmͻ,Y ꑪ$Zb_ǝ H$3lK9(.?N[F{A@Z=U+w>ud|GS>[0RO)q[Xt7E]!=F^Yx\lEmtO0]uGC4=$' -)3`7 B$ mw L13͓D`rg zW&8嗃Ʃ wVޕF;֏C^Vӻhnߧ]7¤"}-H]A%XWIޗ~D‚; 4UZ$ z..HJ['uH~, -."%L2s|:WW^/"\豸$lwmyV%=E;kNeYxƪb1^'l n?G"<qVZ㙚 -c$[B>h:"cWXZ_g2&{JwAJg`fۦj_F1~;E_KA\ɡGEtG|Uphsid<@D3 -Oqyw`aM9&DS4c\XPa JK+B@D[d&s&g? ?}4y3TyheZ*aL6Dȿx%ѷ@>xh -IIh@rYsJ(Hh1sppѐpQ4t"sW_S5N]0:ur^W- -C)@G{svG=wT_{`Erv7F񷾹z 8@YL0".w{f.՘qľDL8R2+(4o@hW?KkP핼бJOuJY0,Q+xyOUBOwA@Q "=fwqwr ÌQF۵i~4^fzn0<]g_o Z6;✀s#L!qal&h| }|2Hs~'y!dMp[$ޔMPuVwJEJSB;meȧ}ets+z*F;_dG &XTQqKU^^Lcs`ޫ9W](/`>k5 y߻i\7Zv39Ѡ1E?<)MVQ@C{emi>aSxJS鱈5[+s?J<&@k.95ٕ`䓣 nEEZ|EH ĹH>&(o{(GlhyZ}dt{{˧g,uW3qAOcua(j8S2NV>2-KU犲qI9jah9WԐ%ʂJ26t u0 WoTX,#zu~.+V4ӯ -N?!vG o-F򙴙x N?V@t#.HP/"-*D;Tmǃ~Ww 8O7>%?>$J:pґ롟H9dZ$UɿJ3̨_+#oS; 9V +۶T63bO3Z5{ݏ}i3,@ѩ`yoej;9Tƈ!' e?)X;Jdt2Wg5f9q -0#5qHN2:5 }uYzDMi%l#Uݓt?2Od>Ҿ)҈b /uǠs7xu7b|wKv!{r˳-4 qw&3#ɌtiXb# DtDn4ibAT@D E(ؒMv}wsuyr=ܲ&h$?$/*bhf-aX'`F.=2ssSKUkl_uO3 f` f|6/,w!QӒ냘Gz>|||CALL*Lor>s}fll,AhMEu:,06PCu S{lbUlܴΊv~(9N@%h( Gis"‰*KbG2~nͷctoj޹0.ş;+n?W[XF74T ˡK/My_EuqtzoɭwK+7 RǨO?r/ç⟢CZheo ӽ̰63J,π/OMր?_R*x4ʧcw5 Is`m{Mi)%né]'捊Aդv-7\ -lX7~-2?IQw~ib]EDY>prUq zrNT2:?ˈOvpCo;{qA3)Y%%ԧ@eg,wBЅ/1-#;A}W1A=$ ruI3hň#ꛋo -%97!1^S&c蠱"  N0rS{/k :]FVWs (Ϸqlb`U ‹C<43NN`IH{[;w|o] ̰\$>_zKZĥ}n_'Ą- 6x;&H}|wH%9%3+o(nS w_%(]-\Zj<84ʈ( dl_ǁ:P2E%;{#ߖ?+ób| -s8Gw!ORF@/SJnx'=eC&>wDÛ8{W+y@tvWƻq/#&!-dsUF3a>B[# k솾8IؑIjXXj ƺg8k{)~%ϭLٚain4ʀ -vbr\U/9fɜԉQd,N, ;,>3gCjs8hNۙl(5b>TxTm%!y};6 jwT,𖒑5B\G ;Lz԰Jgh UKPC~Zց)hlo=~l r&eQ*j a`L DC_I`%׭"gih/,Cy _{:O.ά=3PΪknlV,&lG qdjh9IӬ:(*sS&M<ɴjizg?.۲qj "MJߟ"3ɺ'~QbEIVJ{r^ n%)9d')beu﫯b2Uv.phYn)-8G26WFϿ* zzٱO .I=|MRC6,M,kChz=mE|t}y>8_'9g2mJ+M𧙮LLҙtN,ih -( ( I4;.,QAQ&Ȫt+f>SuyuU?"3؆&Owoz&ɺ*g9SME ku7BzOj_ꆹVG20B³r&;呴dDseDd-r>N7^9.m1$ݫ p|"T=]J9𐔃 I8h˺0xpAFMp͠#ZNdselHgdXDE( -PS Wٰ \ґX30'Zf㉺8S*/s (H U,ZRR'F!B9JQ^|_Q{ZecJz홖K -*w{ qL6[:1 ͠  -bnp3P,|{F?ӻp/"Ssu'Jmۜ .U1|G5/b ̦_dsO )e 6 0>+}m1p'Z|OҐM*) ۻS;P6<J; -g|R৛ ¹pI: -vfi#xsd/gM)?RˁVt]L]44J[%_?4>`mG+'LR9lPP)bWiZ~E;Fzb[G, NА5,HK,ͽ}w48׾Oq Xl0d !Sl ؟EDGF.@D}i'C+"pAՀ# "F:"l!\ ECrXOO -baXՃJ*Z]tkPܽk^s-4? KyrwgPĵ˲MSȻ+?ຘqL-7}hN2¯-P f_^~p߿L &7igL- -csm#[3*.5]ͱ^u)2^v(g%- m{Uw [J9Vݧf5[„u~u%'a>OYZ>g@ޅKylߋ5[̎%m=6rֹT`Ene$1ĕh;W= w -~16%B%ĦebӨeƼ/%}^oKPlMmg).;`)P~]CւV;U_ Q! jSPYd1MWcv`Q5JQqL84k(<q)g17@%kx%FjBɾ?ՔYa)lfD ȢF ,j"Gt6 ~= UZ190vOȩ_i5!lMgOm| /|ur8^ !,9Z x# P&Z.\mu[^F6w!A_o^8IcԀ^4XM -H11.fn򭵿)z@F1Ov&ʒ 52jVo}g!zVl|ؾ>>vagGta5Z?Ŕg\$)7 EyKRgs,<~7-zh,ٶ#e/ti~[l~AәE{H>ӹ$ T 3Cr} ?mvH@G_PkIUQ &@<0yNqZ+|̢+SsOW?-5溚S35Mu՜:LآeK"( -ZjZV. )̜QVRJ>E'Z֧䴪]1\7앵UD>9>#æJΘ -Ϲ\uUn}-8PwӘd5G13:AImoYWrPC$Uo}r\j/SEgY?BYFZnm&b 6\xԨG;!6ݔnR!>8.s"|W5(`1EtAPRCfYXpUmy :=,+aޡn舨HEq=&#Vƫf.=;9C3U]>0@Xp_ן=Cz:ZYDB~t `k`~Y~''J_&5&> V -)Bj5jZ}@F~rik0ɡᣝoрhOM{p@syze\GήtI^.kϪGѿ~?r]R)J_>>pL#zdMp2wU7vN?ZRz8wJ"_ djhEy:s=9ڿ̩Ҡ6A|KAlM z]q{|3\Kļ Y~Iя>ښUs 1F&@O)WM˘Y)9S[gſn>l22:&`j *r{U!ec0%!  T!! ի.I7ߩ;Tl9mRFN*ˌpڸޜ02&/ѢZz}HM )̷@?W64o sXIl]31׋B -KkZ:LF:ޓq4VH|/B:^c?>4]Jhs6y? 0CUm{Dr{2&jspݩDĴM.G{NFq=؟ZRuXK 1&>6eaC*ju `k+xE9P=[hsZ -Wjztomeχk) 'fQzɠglݞvU20Z|5M22j+J/ka37r"'?d?2_'g[l{WukPwhU"gk:NhWv0_Ud%:/إ l0%;/:V$U~ ;J4\eMhBڝwZyWaewYҤMDOۄcT{bҽ\s[ǖBF%{Ix5'uoЛ1ZZʃ|wқ>omII0*y;t d l]S 1LGI# )uvDM·/~*'q in[&Oo9 =ӿWyhH|%jvn=#"V'lfuff{T?ܞF|r+y@R.YpoD\xD: >?{ţ5jEIYD=o>0N\3;ϽB?y__u6r0ې{BNd`+ ~͞YDZ\W66U`hEҨhTqgccQ/!#?d/0s<{ or&RaI)., ☬/j 2FҲ1ͯ ixQ>ũ\KRf,hڦd-2Z(TE +V%Y @̀뫿QNz~!\z&<=nQy_\R"9Y.e\Lt<@|/zt"DCw)nR/"&RJB/nmrFpc\阎z9^Nhw -vo?Kݜk 97w5y71nf@hHgK.aop!ү'Z7,LMnjG s'v>M91*sZUwka \6)Y%GνiZ)f^p*EӸE%jrG+] Q -@Y;p~{{'aQQh$e{4*4 K)eoI!8<Njt(cu (Ӎ&@(J9i#> -Ir&UFv)sYсҁn%#b*}0;Pށ_5uWkE?|o#sw>*]kaI.\ ->`46t+;~Fb5\|{?eVo8U?M Fуw~S3O;KI9h!4˞ t[.F@Ҫ12a5^n끉~a'wM!%02(9Ij!G2C)o?Foe<-t1 75BXWYί7#Ðiv%.CA@vG ?$7>VT!TrnqO'8u;/H'桪 bf~IYZ2T}`;.^ ϶yMa뻐u2jM@߄%03a~F - +i WRf!4K9Ĩ=RHڏCkc`eOOݧfNK~cܢ^o.,(EA=г/z5kqvM]XW]:3$e w8fޘ]Iv6ŵ2'3NfN3Q0H۳V 1svC;55CO)bVv}ƪM[Vcznm>Bݓc1&DjߘZKoW-5PH;=o1 -KY <%LFnĝ,BĬ Rʍտ>]$x< !.+SFv#N>!atcʌ|NkJHbSYVD_n>%+n^,D'A^ /2+v pϒޘm+;xm>`..G3{λ_ - L.sj͓xS|IX1K>sCEHY |D@>U 7粞gSTiNlYV\ݑWPP2Mht%(8 Ё9@p:d~8~B š?߃\YUMBGiSαFw9E3ЎU^F i'a_sζ^tqHVpX?JZmTVؐȾ>= F.SDr(Q jST7Ԟ aa&zt'X][zJ|mjLYL<>,G9W t|1 ]ZYI ˄[! -T}ÕsMWN}RB{t"&3Yllx.mI>$fs}`'̵V0sPgbٖDwhIpg@_0.)?.O`2#`Q 1i`4,THE|&=ؘ'Ü>\/|tB:VB'Un2hwtjvJ!-.H&fPCys#Fj -)w -"W6QQ+!*Jo}X01@GmG*rUPn`\k~)ei;t?e5ebf -(S9\ g6,R*T(qA>h6Ɠ_.1'b3n1;nZnOHlATuŭrg+ZvGH`bCu2|տLn'q5n\*$Ǎ5eEӬI66 !K}?n)9\nR[tpy Y7kV㜓 :)ODĀ - \ғ I`0n!JB-}U -v $tckw_svɆ|*nx[w{f ;HP9XrM$]L|b&f"j1tmMEĄax[PA:Jmt/itǴa NPis7Yu)]eA[ 0+DuUkmOFo-PIHɍ!5oaG=|'mŤϴ{{ ʴ֐11"[ -ReB};,Ou\pi1>lL."'/s}|gth ]Qu61eӒa"L0ڿ/GV q 9|϶c+:0008-v-WTo:idvI5WH'ϵqtooJhGL2.tɋ[[–(1 qW&iqj|uqS"n$xؔ7E̘I -f A - !#B~UzDC;!;GF8ED&f-h܂a9R1* W݌[(:'ZNWB7ԜW1v޴`7_q=q-.[p>辚1S.Q&@D'AO{jddRA{T5F CR8c:Vow52j2=՞9S8:J *`a $! $ovuSTV IY!!$ K}!~U7OֿhNF昀Wiٶ|wWF48n2R7C p17{rRnqU9(NTk\ Ȼ@*8بƄ'[)OQJÀoQ=,(Շf.#k5Uyۛs^]<%5fwΒz*etxzҦYlwL$CÀ{ܼ*Ä z;zrsd 8K.2 a´ړPJJtg -`uׄLߓJK: uU̖vR0m1-,sΡr" -fƫfm6+a&JK}=h -q^ڷ+5B|vis8;`OE̖ZnP4]- KWpБ;39C%]FBnDGmLPrG /.n<= ǼF@ w0!J})gP]fl^m$= -5fg؅ -\"N∁ޖV!a춄 -ǛUk?łFHߙw}h5ޑ"ٞ -1> & n,"kfɃ3ĻH $no#+%HN[B?IŢ*#' kY!z;)Ud{k]0JERDVlRs# +ᜨ4"x  \[ -)~ޔ$p7oHe1ppΜC  l|~${s=K,Al͚ _lt̗-:>f) m h~K-+L2nnBOvh'{2&g6,f'eGv.qn:|1/#໐ТT -JO;}E>aXBڟ#SG/gSV̴3/9[9ÐUݗ2q##z]2|}W7'w&[*iz)8M(ؙ$|2A3 Iޣj-< r.7k fͻH(|8lUБi28fS L,ÿlKz1p+ﻗЋU,jqQ#~?JXm%VU&cbb>uDz m Sf7t=)(oSzVox̏M?άYr+pA 0czFKPMݚD]vL\Ju3UcoVTrPQ5ܕЃ֓B~fc$f!/zw¯栣.!.%ww$ʨ%jf;oG:޷_IY]4cf#&k9>+9")y̴-ܗcnE"FIT_A8 JE>UWvd5}+?MJм$+,&{CֿN?8g)=\NrUO~1'|7 5W|'+ e]\ya`WB6pM(lS+WV 9֒{ISR~ӓ# 2@]`֑kpmVJlA?s@ 0M N8S4iDӈM#F4h4iDӈM#F4h4iDӈM#F4h4iDӈM#F4h4iDӈM#F4h4iDӈ`QA -endstream endobj 24 0 obj <>stream -HTs3tvNX–6|KWjV۲l Ф&Ѐ\Y1s/@g+|ɇ̔hvy{?gȻkȻkȻ_H4!qV5R#BE,Vatɴ(Щ*Q9!&ve>W\-ajD`6b r\$;"8>Ǟɿ -Xl'0Ns"N))..(asNq+xeyl!-:]Vq9ҒQ!uYWe)cNɏviwTVl<*S!CMPw5hFa]*`r1p-`t/W_y Ee<[asyyMe?$؛7 Na%|m1laywTKr$|Vʣ#(v;:឴FnvɥWٖ"DؤN5; Ҫز707)EvDiڗj{>bgQlV;Iδ&$I/j rΐ *[k9YɅϋ*m %pkʉ\!|I6dՓax=٭ޢ?:BL?x@%a:9Nq)1BpSڣ"t۞p逢 h%3yHZ;QYu1!j&nAPn=Ex @ʇJPkvd:v^CTaTJhir%#Zca* L=4 )SQTuј̭X-dPǜVD22.yCR#C -2rըצ+f߃uZ}F0FE1/ҟYG׈k_fWo͆ǯR!ܚVB`P'dLZI9vo+LFHnl<شND ZL֑HNYޢy/rIZɠfp>{Audr:JjGHLّqKI/r95ws f%K6x$0(&KxmƒDpbb/|@-x.U{܍fB6Sm=ݺtoZXXU16 H#gfQCO?ܛmtR3HH'*hɠI4g1M?!Ebb%Q:ƗO7D0-ݚl+\[cĦ,`acOjww 74i<O,,fAbŠo.z^HtKk(殡0NL,p3ܰF JQ=<۰}A?6m|NvbC Zq?PHskdpܾWx!fA3wNڧ$P{nUљ(^iނ"o^޼Gq/ *oܼWfvM<ظz^9ID> `:6`c26&Wu$MPa(6֐SP -@: pDi6 ,mvvkDE; W.Hxɞ#Ǐ0"&Mvì'WTX/g"2j F^ڕ>=>>£` ОN̔1g^Mv#ЏGE,̣$}V}mN5tntK#Oipla"̬ k^\ɼ7#IIg=S̫{ɽYN-w*~' ZD*7ɀParW^M%`B~C 㐔]0 --ɮ Ԗzql3:D Jܯ~^3KqxF'Љ1;5iȨNnCtaRcLUvSY&jSP~9;%|uojmW a$~UwSAa3AFcM繂C-OϹnaKH?21(IJjr?xmT/!Cbi+ǐR⨠K|T[&2>$x@rPU -n9{gDbϺ,/b ;mm{/LEe˯ߥWWKLPC.Kq+U>Rw.]_S;FnmζI88˭y$9WQ#2k >Up̻Eq= ɼEZ1;e,#,%w zYۯܳ+s+ku[Ea]feT^|__$OzD=9 -ᗶQ=&0Hjbsڂ[؀y$,p hUy7"_JoW.:0|p:߽E}fYFnAݲ;ynk'm"kVѨE.wꇪalq1xyw7Υׇ邞a[j.tbDƠ5TZ>C(v00 -*i-GH.: L;_$ -Z.zT=(ŽF5 ̶0jIOFmdH+A q2[)I4%O59ZKw4W:Y5C*q2sIZ`G|y5(Cxy7hT[ɺ9׸ 2!K0^@@H/j;Lh*;%h TQ#Wֱ($m`"YV qS "V;2*PMZFO-jҠ :& ]Z):4=\*VR&#)))MxX- <ȥKpK|Zty?뒶9xZNW-yI%D\Eo4TҠBpE&2љZiSN;,>$@ƌ:!+ISp -眢Ƣw sq*2/]vK|l-b4[ޑS*~>^v䝽|U`pNxy G6tu]Gq\OMyV;5묃.:xK $BguQ !`@@I;}ue_tQt?|~= 2ږ8 -j%rHz{b}O3⣄O-Y7zq 2gї'~`m=?%b@vZYTܜ޼Bv>I}!O$T\ȯl OްR|&Y+ز pSn~41I]GF4dH g֭A1'l.ߔV>ܗ -z%0? -JluU`?kARgW<nwnT~I/|`tnw*/{~_P_}\^y$:Bq~?cl{]Zژ=v:=OO]_;C64A7:2V`}Ҿ:v bZ?l}xmmL}Lφusg?_jϐ"T(a6n(Al|u(~;L#B|u *r٩w,BLw3C.Ә]K=[w.Bz2{(7 lfLA vƬj<\  kIfy3t 碂A,,VFgVcS -fmRs\F¦@{"%gn>aUÆ?t:-65>Ms^ !t3^8CT'T .:pq3 -ndңStҦg)Oi#׋oLznŨg3f qi׭XT5C\y Ғ_7ȕr-cV_+y6c26l~eN_-؏_AV#Fϲ $ukM{͍J1$T4W0lv!G'̚bI7%}o1 -z׼uT_{G-_)E}/4#E( -?|:f{g RZKSs֬S s9 ^PqQz -5=dr(7=*`ڶWͩ U6~u+ Ǧn& -8f3t&t9\w0 k(wINp!E6"=@V>D@GzIBUF%D7~8m9TQĔ_I/ihȤk}+>4#tخy%dQA`IˊallV烪/!+f&~BWɈIɾXeΦح$dJD\@& uLMMbfe짾_L6}RM_n͎&ޝ9oĤ=pV%f7#3xG '"lPq+JN&-mTm"J 3_Y7!tآdft+?Ox Bs>hkhYeFz_ʍoRIM%;eWdq<onB1$H[-vefl<6o $о/ X nIIrҍZ9lpN4`+vmh7fg2;@\--][;L-e軘~ݷegs燭?&$}@qm{gw~{cj!遬q,8Eɻ[ho=UtuR T o9ۚg%ŪբtD1@GNɋUcPkZg&QK8tLTVPa0T/WC%ZR^:\Տ2QspuO :$(a=4HJFF,v5/E+j!OKeE%)EC:m)șQ@:uD9B`91cgjy%22ņ[z?#ʁZXMhx -#C9UY)vak -P[{KڪPj:UKTk -.woQX 9*^PrH\pbؾgikaV7[Wwu:aR'Kͦe,D;ar]Hq)IWgȌCx=oG֬!o0AB_M4,^)O/A9J=lX),m;aLa^3RpoVࡌOYu/LNo-pϮi?#Zn9@-F 5b#?iSt߿/*yU陁 1juπ^-%kw;L07Yr^O~l SJzyqiaɣ!eS -BPK)GiV)5 3Hh0֒..G4*أ~)?ZmiNF"ʫS{` eRIm߭/ _/\E%+I -:f&TR -ji+j)]}9TϞPv[؈JPjEq&baЙ0ҚRT ,ɢ2RR&fTVM+)kXJj)Fwsot|l1O#q6+pߵ9cRx(K{G [Fٶ #0&%$SDqOAYW)$Cn2#d@=\XF3KzGZh;H(T%.*z%AyHoΜp'jgi?>\XYawcNq4:F bPٗ*^y"Ҕ~wnZp{c;3ƃ8Qe) }Z - |8Գ?R.Ip֯=etcCEpsqIx;3snE=J.KN8WQG˻$-W@n=t\6Id{ߥ.}0| }OxKΩg`.k5~5_jʹ]tka#yȹ\)xw7{5Ѱ30^ލʡr2ڊ9nމ\Fu$ IO)D~EpOn΍ 6 k61:ml4Q6tH2!}#lǵa!BE[Q] aO + \a #,TZmp ~I7 Ks>\*yR aFq']}N?~T6뀹ڦh)16K(dXN\lx坢dZ9 ^]Νq\fMdgk^RyJUJ&!)(& SC x_VkoI% 8 ltka6^ZKi[;uC/^;Ql)hp>o7NGP|k]+]Yڟ.'NY3n tBh߯DtYFaAEՐu.pc_2uD/>%w7*<RYOX4L,/?w6+Dau8n1Lrn-1/]hfKf 0.):[9V}2iu y~X|m癍Đ S|¢.fO1E_-a'B&:I2^Lq]ߑOJv3 ˰'BV3ggEƒ6s%bSU5K^\]X<ů-1B,̰~A5BڊWr1zW+ngKkĵ{C,m居OT^&lfc9L42&q}Rzil.7׌pQz -)ACۍ"ʎ/JT&;C0EvJՄAtPwVgOoڞeh³emu.5'o/Aa!'07OX+'jI6}'JKknH'1dOg&VY6eqi@O}dYY\h7vfr&D-6q 4hNi3d4[$%gCyd ;~|&>?2\Jۺj;F.5Gdͬ~gamҖff t jj%sLn)'s_/uIMeU:2Jik3qDB'o -%*mRҤnȬ\͝c0[&aӳN1>5=rv7밸@\B=Q,/F~ibO-{_=giI"|"X!*Yo)\Bu#@kcRD"Pc;fٸv#MJulB7%W1ꢣvmv7t] 4 yJꤤzgou#L;!cQSI"$VZ''up$]=53m"{ۚr@zgFńT=LXMC /ŵ˿-=;ZFaٰ ӌb.}WV;~w9 gC1HGFU$m=1Qo׎֓6<h: -Du2SB'8B/dTTL?A'0Sۻ`䕀ULV`9;_'QR$LB^.nQY,ay V#4Ӌ[MT79w:IBBw -k9ta\sK?vkhe so[9aV=^#JیB^Ңq6I~@tILv~4+6#9-f*9wOjƹaOߧ<7c! "6wnM_zb>Mߍ'> yFZmc3V} _{LVFЭ&9 Z%QQ` I r$sd:틺g:m8uَ=E ;5vXB,Dz%$$e/X}7 g|ﴒ#}h8W^0^RMeqW>OYJz0H)N[DzKd:6]g-%ْOO=-og5_uS*wr*Yk톐D͜7.>%oYssyM[b"hQB߅~RްZ!3Nm+Kx(2^)m4&2ퟆNLsu5 %~)|A)Ρ*cR rW?A. wBA%煽̥v#hbMÐF$ƥ={i|:s؀sd݈E9~}ae>Xk? 0S vN, j2ƞ6ÇEJ -5)nȑͬ£|}Tz0R'ûk5{ް>ýSAO[tFwѝ!a&S{@''hozB7=~sw)h00[M96b3Tp5]Y<~Hyi31Iv[{on ̶f1OA'ǪIT{[MK(L{Rt ̴6f`Vf!4nd-aװB,͌kA) 7l-`u/H P8yŹό'H:ڿ*qS/y`Z8OfD60)g&:?#rpĜ!2U9ՔPh퉹f~l sklTj><"ܗ:u/{iVZv:3[|X]L8,H=%?Dɇ~B";x3J׀%"oiLwt?DhnzILjwy*ӫl3~L&T`BNxQ @Ąm@-=xz:(wy0}z.sw _[%KKs{KŀQSG՜[/~7J!;䐜a*dFW[4t*'~rJST%2솔Mn4QeBCMb  }yzr5{5x|cgz"%ptwy%ѽw/$\/ۮ-ȣJ - Ps3;o/YȁiX0.D 1bOB4C7!>!傄 a^fi(C ofܸ׶(_};#(O9cDQOzW3no*Y}lß?q^ -w+h1`J~i>4ҩD/=g^!߭h8rnE[Ѻs@@5bj 7Q`렵S5h5Uc^!E%55+MzQU638_| t킶I |L{Ee|\H~%IC!vUUX'vo619%0QlYI믲IKrRTӓ 9ߴY&̆^Os;tO9G@oA xm AqJ) Է|sʎ^m@ow*[n_u7S"3LS6691)eo?` 2PwU4LKfnAW6(h~o(C1}M}-Uy6x.p=U?UJ\1\҆v[\-z [ _<"^^ -"'|n)篝_b#[9D)4{wVl|VXE[ &DS̃]^M#Đj|o}*}G)}r5 ras{%j!Ӧv1oaR2d _ [ӏ)3gBR, ->B%*V׃d)%^.2 )Pw ]2F/+4>@f4a}zlw$Xg2g|pxm߬:nLW9e8L7- x׷'=DOWk.4 -2t5Ȁ䤰9cQ-`m" 6&0M)Z!3bv;]+0H%^Sf瞿A[J~;űAqŬ <*=jJfBډƤ$S)ML[8/I=M5NFzIk/x֭iy<]QaŠͳti?kHKy½²鷟Fw$njrx !knqwn-؄XѲ"缼nm)/V u9\,W縠24\)ù.-+ Gg]])#!a[/^( ↵,'EUg!}"VKV_1kqe]Jġ)Z:Cnk" |T>KyaK:r8{sXى2!wLp.6='SkXכH a0B' {x -XҪr5?eVG_ꚭg:L:vĴ{pGx(&L:SI&wEP-csIOO!s(Ks>.i>]6)tpjggrVbUΌXysYjԖ6ΐgPrK.xtUs. 5.=/WbG*wL2v _&o,| pjS ]\\G͢vq*ya{ Ds TTŭ 5{GEL -{ZPf>jea6~pui!q3T]$ܨ*N8M ׳ (;OtYneǵ"*etLp̪&S7qɡ袴;$%t\#!$x1ʈ!7aQ1K??}:.jf՜nIزO&<7#6EH ^ɮNhU~[ͱSr08фxť?~L 'fW&ƀ߸7+?PEA;(0O6d7l !1- ,^NM1 .@+ 9!31]^n>T5o`&lT.@9G[>5RVjZikZPK]jAyW> ;N5ykQ1qA-*VH꿻/ק 9;8<%2]d -FN眆 j:OWԠQZ5 &5ߧtO|9qص>_gDMzU x ɍd]0fz -->}I廭+c̽Tx*}g#&^cz]LNoHQ۳-4&-BjkgI~y~g[{4,-1xHuPϩ -#վYzg?Jic_w )!@'q)m[73bSwf:Y>9tBhemoWkc{1-j0aťC&5xdkP̀ ]XCIuhPȩ [?9nL_|udx ~|] |dHهe)ɥf\z}-/E@vuH=fa7Qp}0^{;k OoȐ֠0*q+ -#d4yu;2>CَMK;CFzyjLo:G7UP=׫fסzf9f5",뜢c QѸ=;1}sB\H{WfU+b&/zQK@'lrʎnu\3<0xY)$ȃ{J6xUx0d7"Pե5PEqs]zIRVt^3*.;gENky<֣$ ̒@(yV3Nc|d bS]bV`*qAkTϫ - 5 `~Pc_y8+ϸ3L|԰FAw.Iep E~cH-'Y>Wސ0cvAWzuݙ;o:.[1FY@TE!;zg:y{}Ee]Z-2spYxY֐?쏐2_>ۜw/گO 4Wm?_nz<  ĿۿRLЀ5⠖SPtdyt<,]?v>ؽ-[ss1sʩ$f9,R EsDư繯_y~u~>tv8--"!ER,oSwb_E^׎uff%K\[OHi63wKlguUڭ>F>RL20*\ę9QlWG\6JHNS Ѣ<a0 ) uyOM.UxT` o s@' -JavBj\WĐB.(h*`Y"?\s$c&n IUD0 -Iṡ -V9s[%أ j &@EddM,fF&a} gDNee1^ꦲKM -`cg# v ݑ:h`C75*I50)PJ -1|u-m!DAKIJ/WZ¹jW0^=SMKr@OYUʹ>Մ9W0n[]h|wMSs v -lYTi V)!,* -2a램#-?/KuYۃps?PRaa, -J<}dT^w,-ynZ~dž }g\j?X{o#tُx_=5ig -CC](gd8Jӵ0QfMzc#]j+.k-dIbsCf7[c)/T96 %|Sv[Ü |@PSX˓uسY62N  -S2䋊KsGJN][]YSх~f5Sqoک"_b%:r1@岷 T{seI?u,5rW^ٻÕ/CASi#B~ta#PQ.Ȅ_-qj9:doYGߖC̄' -:FebotC] -҄]v-eg(`7[]jB>TUݛ*}WݒJrdz{хX@+Gso/4!=ĕ"R'Kúh,ŤEٚl1Feqm(ձz0'ly;WG3K8-z0F6S#"r?i=@w쓜t4+>E=@lRRy֑ ^?7 WN^R!Bzͷ`YpmiLI`cWQSR/0I"뜫ʿYr&'znV[}&JبgMe=ٜ|"g&OEL a^ "n 5DX䠷$XFɲL:Qb]L#֣b2`~݅(QVFMr"MP&?4iN~3.D/FƅcSG*_mh\ۑ~;ܼ>uR't4@m3%hJP2.hݔ|ciUJېNrJ:*BfVGqA1d po%G0Wa~Ýn/SK,52Vac|F _j(6u >ّ_o~7˹pY5}w7Eͼ2Or;Bʡ\/\MN,~b-m<[(z!j#S71-`hd/4j.|vk-s -l44!A@m2N5yMKuSuup4Ys4N8UmբByK:b=cA"m)#!ZuZ+(xpx0像F -W $B&@o|~u[wˈ N-tmZ{qLbOS=rՅJJbwuNȯ#4~l4vErγTܜ9:l6*][o(JF\jV)rt ;1jsA?W -Pȳ)b9pq*1Ȼڦ%W X+V{/*>Z('&y?\'6j4؝~J쉒Z$ZFslҚqf%/mC:f@nĴ+b]Fu K)ab%1G#xkq`;tC /E6<[k-xljx;d1Ȁˡ6pk^D,JKF+ZYzE УFX /ӡ`Ma35oK"ADmB."(qe -qd踹_k`&?^\kk {r x%zs}2`SA}il^K]])I6HQ菵F>=~Ζ6QߤUwIݨ{A2^~'%sVkߨk -E;Ռ,&- .𱌐lXps̔tyu4]=ZQޛ]C);xZ+"[fXBJ{p+\_/5TX_Zoq@?="4 ׇ2f96L]JNt(*Үio|UơBf2,iߖE*N=n x܂4LRH+!լΔ]ä m"[^7:kI?$ -+(7T'hpɫ&nIܚtS1s6EW.mY,"**e^K jN)9os=*9f&,#B*.Ug?٭3?!wD 4ck}iY/}f+L\Chx%k/̝-) SK*jX\}DG/"@zp9זjg+$y;8 ,׎cqHKߜ@T{eL)/F3O]ʦL)TCmE$91kЧcV72ݔ ~[b-y>GE6B]ۙkʏ1Z*ܳDzQL$x9ݭn[M*䰊5:"&Xj85-qĨqIS2lǧ'*ÙQu%j" q"7G ;ߒG';oo -fs4B^[h+~vk)Y'pKÁ](MX  pgbYjT}Oȿe&8и)bO*v lrK˅uKI5r 'tRvwD"{peL^|5m+5t˚{HHԌ""~kU1*,jbnNԤFxx}t1l簯쟫*HSB*'?ZzK ›aP3q KA(;:QOɬrHinYYH-I_fPɫ%]>- YL,>;lB&~W -=Fק/Խг &<{%!FXZ]x[ّ䃁&E>6j󩖎pMR$Ё -۟kptuR$CҜV&Jɩħ'ڸi^Eqͫ`_GND3Ts8P -ʶPEE/{vo}-3[ߨ(`_O&-T=ޝ,I<+_nu(d]n֞3f6Ⓟv-wnI ˆ^y l W0JftG:/OA.6fB:6&jˬڠVQPrt[ɭ#a5$MPRFapUSg; -6z1i*ԫվf1:eRs pN$l]xý3c7DJ,gU<QЙz)J>i "-,7.5^-6Zj7[gSV=ZhU3 %I)xS3ccVP_"ޙhycW(‡vawQ99n9 ^f681%ÁDEȴ6={t8Ґꞧ\X:1GZ|fņX|i“E ԋS\^_ifm1QI,Dc ` E)v(Ď1FIfO7u^~ӔٝĈ_ L!,sKC&EfaTo8Jh( km^ĿW}8̾k+A^2tݥޓXE!F?<K$ⓑAv&Qlغ4gۛ!w'߇*;mSoFŜ#ef-j -t p{yKa#0O㓎gȹs4ZϨ*_A{*I) ͵gFḠ`VEY&Aãzjx4) i׃AZM꘣>N7)_?F*=f(8bpu^Zv'#pWƉuD/>#M9VD4q_-⸏ UV]*aW&ھ\TVE\hiA}vAT*I{nF{'89;=+*l*x~Ui/;od2y"!OGB+jN0cƅ96JQ xf)޺vv)LiDS6A_7CbM!#bL\7 Uďev՝eV2`?6>ྐj\mu`Z.}Xpyx%f:֛8Nq6Q?'U#s`ݦmf/yRN'( W5ze {3i/qlç`Cթ'S5X]-FK 37{)Ap_4bsM+L>CxhSaFh "^`/ x#I=qg~)mU7FWI6q:ks(AXcL^olw&OhVB877Z6jp<*\7xMpg=+}5ŽQQE"$)'#Ҕjv7 -zLCс-6R!Wk9 -jam{4+djX -Q$ܳ$.y5D:@;GH[%\ ]ˌj>P:ӓ*vU@P=ŰEq\_XWj -` txYEL591+5 -6p} Hxژ5 3R.UBZ6HPE;]|QdԵ c!x^A2`^>y؃ -> -B`'E?/bt&yoU%u `L0ϔIKlw ¹&\8Ź&3β>? )>Ī r:nN"e4L<=C놻}&@-0u[8e /Xd8ՠtHnX.֖g{{ uY<` m2K:]Ep_Q5pp$QYTD@AEeqijhZgikS>=3g9ä( 9^P ³5rR>RR+ -qIPMd&&;H7ɾKST7|SQxy'Ogwk6mR_'w`2A^P0HI`t8&aJ&>ݚyC]v+xwԒ␵(|hL@Yb 'BH2 8L=)cmq0j?fV7IД%nl}24Q!;4ш;B$`XyUIl0Li߷J>mپ9ƿl[3tŮ 3'Hb)gAB RϏ@P4#>wҠ`5⩳)]Y֏=`yb=45]n|ܛY5wƸ k"(C3B'rQRi]A^SzQQxhT2[%U]^RZL`d a0B( -^S@Qtof/S[Xg#W'Kk#vth˱rQ SN(ʏp;bBҤDL_fƝY g9?;hjvljsِJPB@?yӇYa1Й/)>;/?;TU>d3':='em>N_uUˁ(zD q4ȊhbR;PI2XwŶb֥-%^n6O~54UjzaAZDH`0cOss(*($ dBIr:hϫ_6UM{&h^u\k'|ɨI-LՕ4%'C L xBJ }P_A4Wv 7|9jϛ/o&ه>TGִ6.$ڔTd',ӁLq@9d @@qA!J 3 r4U%'S}w-u7~{a;^֕Z rAݕ|^b$2 &KF A"H`1B kT^+==ar86asӾqЪmT9. KpeLi"IvH(x܀Al0")W0 u$[՘׷;OgW:[sX Y*vp^(VV2ilk允Y͉ޯv9y=YT]v=giȵu˙.cVma Li,)hꉚJP]ͼT$O\=boqf8[wY=uMu˫HN[YhzN5OIA=!= 1d@d>AW˜ShxOguG%7̭Ϗƕ]qy5eDRaQ%lXx{!B'/ħF'@8N& [r8XAj6+h0ˤr|׽ #۶Yjbǡya9g^MH[}W.+OAJ>ćٛ)yPiFrkf|"{]<|<~m\޴zoaLjHTߠ6WX>7e<y$O? aCMSN_Ѝo7]:/ߟϬ"Noh$\J쨮!f&@/2s"~AzӺ3H7'.5S-`#GHCN-f<y"ZA'72 RB1X_."[12zɬ(6G;ylC/cOv9cONS]kt/Jal.󋡹X/$p#*՞v,e?eV`j?TWWuL'dҝ6Q;1qkD\.pY dMvAe7LM[ -ڹ(TiT`N}18Toؿ$V5S̨^ؤ",f; B -N;_6E=7fP%3-zW b(_eU#V`| s\vަa1."%=uP|S>^W{w,W :tz5M(zUJBR*"&2ƫc뼙ꗸ\?) ᱕n/wwFfм/ -nz[no& eLUBBQ),r^^>Q^$-zs*A{f#XT1Hi)/TY =H=*߬IJz\anv!ⱋlla (]>4jcD oKƣfoNm `=OX^&$Tkኚ^l(Kzxfsv1)-faĻ'; ](~|&M -ܛ7Gf%#T -.>t%ZRAІK>>DBsC`EeS/5}xK>q|ʅ3.vD eDy~vքszR :A -szAfM~-u;HzԬIv:iU gBXA2(6⥜3uRwqmORƯB2:pO:/dճ*,+0D!<%G.Z(;g[δE=COrOK/-Yʛm㻰Pv/7{eMs+815Y8EoJNT@d32:NĎ2O;&׉F [hP3X1$3r  %jP))ʩG~Ү2vt3B#IFI Bx -)Q覾.8JT|u e.\W!|Y#ܷD? pC6ḻxedy+ ^H#"\iW͘t-bA hQ:D{ac>1OKcn`ꗭo4#I's>33^{iH¡ UMni/Zg?3ε]ϩb4Mz̈́O?%uCw~,d1{YEjV@$XiSQN֋%M9v c~IH/Ð=3qbƴ4CG7Fo\|$3dw x0_ƫRI!ctg˟RuçڪkdeUTA͙]Q ϯ}5-vm^*9z^LM]+[A7.|ι:ueħNaw~qhAf%f䞜iֹy>EMq#wVf ȴ5RWq+Eǵ;3]BsH^a`QX!]2mQ?Wıf@@C݂ yܺW*k*œ:3we0E2L7%9U5 EAܝs#cSj~:.ms3׆G81!::*w̔b]Leo-[%m1 |RYO䝔 VMx$ ? Ǯc׭h&={ic(]B5c} -Z~gS.`BRb1dfOq>QTMԤ{j:L:,SI&VW'&1ʘ% oAvE\b4m4*eD"eu'ӽu?uz󘡞W2'6~—}elOy=㢡r.٫[6Fd=2Ж,ڹx}= 95v 2{Ba@7:bP[ӍW?B`o%SrAKL.*66:5rNw\5|RLnLN{~OlZ<<1PJS5z -QwEŃ M!5iI}"Q\OoʚXIiPy("[<ͺf3;V5#cZ을㵾[a ӡM'xyCL%̄Xuշn5Oh?{Xޖ3&`lkPF~|hx5٭F45Iqy8X05تNTOk:@-EȖx(3#iȈ -Wz獄l-:g3f #nMPWHKI(|M\n;DTyO (h%dan&u):;&3a4B+p2e}j+ia nt<] Tˢ}iH7{jL'lDpf"" 줦ޱ©?^D4YDi(q .dcW@wKa9%*;7¸'ǂ2m1Vu = Qsn"2+w=KÍ#5>|zliUB'}UOt)딑 -KiU+'kE Lٙ] 3q&%Sce}5)E96)a„5a-FVHU5UtN̗ܢ" -~/b)ugbkVAO#'AYW+=[KIx!'}=~9䎃K[uuJ5l8ec9d -H)12x_aw\p9{Ỉ]WZo]54r\3y5C1-[lVc*a4 ]"igOq5&`/zjj&tW:==I;$:QI$QqMP%jbܷMpCTdw52_͹9yŕ' ]t -=]ʍjԈݲOBwMq 3!ׇuZT BL։UTmYTl. ^BINȤůyi5 -)3׷/KM+i04ȚMYDMZynï45 dP9+#,Trj-}ثU{T2&W?/ $@'̞J-I=S`nޯa NŒ dD.z=O=rtWO@A+@s <[P+G?ԦGKkۄ$Sv&|lm}5qyq0mRV@@\8k0:]RL|#VwJ7Oꙩn\0I-2;}rjEHlLjޏ⠔K],ojHīQp/- k?m=^`iy; %?{Y&[꾕q7g0_i^rC  o:j du4HjOG#"ݑZ뛶ɖMLE ->@db$S+ȨU>)ҹ_qݱHIg3<>Gj˰qncԣ?(E4n6Qy 3I <@P @.^ǫ^gu{BO>b\e_7 s7 N:26X7td3o{-?G.ʌDjqo_sKxg 1kcSƆ;V(~xP!TXT+da`{lBgп&*iL !vV` kk!T<}q[ -T\%;L_lmKƀ -]r,R`7Ԙ2:p Q}SCH88IʫĖHP9q:1ED#dP0 xCO+?uv|tc==3E{j:!KBDYeDV[튪BB$"D$ABl'ޟ9IBGDvOV֤٥L%+H6D]R26C/u? / AZX8%;Ъ$9L/Kbj,9J-jrr&v,lvi^y f3*.*&5w7]37Bח{\*6zELJY2(lQ -#o`wscA2^u*Cv)-j̼&8gCG-U<1 lI8pm[F,bb$$lpO"hYtus$&v`R/w'~p< -J\ <М.ziݕogWW]UM o>`Ca_VSlw7\XK&$?TǍ1 ˉ*&,P+ ¬ՙk2,7Aaд6llHhj)CDszw6D}> rh'}8I-J=uFTZTU⾺3p^cdQ„$|YAbGY5|jD2R϶q#gW*[+6NE).!̓_Xo䨬nIݻE 967so.,tCC 䘨ER@. gio&2Xb_'pyJ4k:ghp |m'dytB38&kU {`]SsG؅v)9uvJa|hjYz),1vF1|MUW"&>mpQu,1iyBUV)'j9ey5؅Ivp; -!w$cw(~R\),%elIi%W{K÷U-ŒKK=#C}}]lXSUxk_SoIIone)ؓk zRȶ!!wDe`_`MwZIX\6 , SM7kegi7G8 "\dplP<2jZ:@ƺT/?]l<0ri-iE1bSy8l}mK6^* W%8dBLuRڏ˜/ m/ aW%{TLFP&:ٕSҺrE[r|}*y 0cCI5 -01ENwM|=b[u>`).yyum`M #AH -Q&=ɭrX%}ɿ s"(-?+4ĩYpdpX~RZbd̞Jm|KE~rN -h06\W_\|\7wCٻzJ隬KO.YKW_x_-fYmIy5~ rBm8fbF?k&ӣ"SDȑ1 -SU%3C*h 9kfu?leO:c/۠S"]C^Jߔ8['DȩlDl@6g5uI/K -JFkYQ(Z|!#f:'8mzw;Yh)m˺a':D b+rB[QQNU36 [rCFz iS,Ì ;IVi~q)b|؇AeߑRW:qǀN lܾC4 i!' |ɒ"bc),?6x6z4rQԴ&.ƒ'ة@wCkР򄐳K)_lOKhmcFP+>$ f? -;ɧͶ_ cvյh}pb{5).ӧWO>Lk&ft2;vbƌ% -QTdQVY [Q11 -. ȦA\6535Uݪ[sT7 ΫwHE -Vs t:u~9\0߈/KoI{ZMHBFuZ\ s-dF73KτO~9RGlX-1SE&pU}`,ЏLyHӏ-sq(o;2)d6A?5Cz;"X-ĭ}%|d[XG!p xpgQ+X3#ޯǶm Q 3,jdAih& -¨ S䌦Nߛ#kLC@M,пb/9j|Z 6?8M7~x$glI1A 3ݐtT <)ww-6A2ӗ5?Y&Яg++[[C3̷sѯef Y.Kz'HفؗÓ6}EnEZVnObFo˂j1TzR3Mɇ9jƒ-TtjQw/7f]`F%( Akz7LoȐV镵dZF/ϲ.t~T2dӿRTZPCX!ndEr, } -hg -`RqU9橯D9ϸ6XͽbIjq:1 o^>;0b6/ܲdu.fwy$`B"mpMq9[ w, .X_0YiT:璷#kltS˻;ڟ5?)^,]`}j6foB_gKؗSPh# .ӳ]u -ݫx_RJg1O7ȵ1Ck{f.a);d퉩{,:N7x+0}Y߷E޽e1d5b5 ~L͜&I*\<Њ89ԫa6{W9hGbܥ8<9Vy+#+ikCXmWF.pȯw C5g!y/Y'QיSޢ g VYe#ֺ ~aD'Yĕ׶G럸g[ -SڍOIPOgzA 6ck.9g4xu -.ҿ*jZHE[!Y -j>6y̘Кcke^ayc噺HV3Jvj޷\R{0洍򛞙Qx,5T}9O-D|vcfXb{?X@ʡlD7`A~ צpL6gy_F)u#0|.`ڣh-N6i_-t/v+|wRץ׏U&%*?xHoa b})cz""!e"mEIdc -`{'$glu{J:-E)wȂoF lz)Kљ;ӰDPlSV;i0h >'a'-b鱙C+|&W"Uʳ@/YZR' 𛣘7?r5yy zqw*~ϨPIRSꐺ7G|}̊!}ʆGҘ]e'ҩ5Qc|yQ"֙#3W 92^\PAg=E;LCԢ[㋻SW rlw6Q{gw_~$G15=5i`?$S7 2]n9\ƾiu69LyrH5naU7Tp#O@v Xm~_j@_gp҉ӝ%G`~G &` W{M|X57f -?eImk ? YxuJþ;povnW."꿊rKrbrA=D̐ameG 3ouz-"ׁD; Vm^w r}ӻT yQPr=XdMrT_&P33tn8x:<M.*83 /+9 -rWj6^.2 AU@Ϳgk]UYvȲUb vy* EuBjPC**O}*bgW]B}2$ 4 z'&zl[B^%X _xb= 2lONz&>WwZ $pcʿ^\ZFIEvzܳPǦow({3d2aML̑C;upgNx.[Ž1i|坰+uQ: Q*f4=X}:Pq=5IMZtdMҒ+ʰ\גw#p2שp\1.Π>P -OF6-~ 5-?tyE%tΞXߜfz_G;P5"b(>mYqZMC8sF!3yAfg.J)Ҫ ?L"VZG1x﯆kVp%BvO݅;ɦNmtb҂) j*)XT3"^|pwVy+:unPer1)sv"my%FUX 4G҉g"}_fAZ8+ScշJ+`sc02+l*~\aþj_O>ʸ?G,O) ;cj]d|b6<-S+Hq=sS@䫸 7$ /(:*~, -8s*$OV؝m1shr .JsD/`mJe5:w]n}4$kȽly86k rWϡJO؞*#I5g[c3>fƒ{Q2P!#JI夭}n(nO"YN9^  -r|Q\#GA(1|+1Dm`4=ߒydKi9U?9{.sU`㨗M7W:a16)"eAubၖ~-16s-f:5dh֨*ױOfB'g>/cZR >_pO'iHsM lrRA+*[İMZLlkq@/G:v} ]E:wH^u -=1=ېPˀlD\?&e]C٦qP69mON<td"Z hhk -䐥]_mM@8 p>| s[ά[8%NJgQ| )ﶽpz/,mgQ7ELRda+}F~QZq, mFKF?УMDsixh/97EvTY&4l_96Nlcs*) -X;?ypu'ⱡ[&1*&%n>vWEk[r.kwFkn>|ۣ N7?!bC;Қgi5D >Dϸ_"AG;Vxm9_ؐ h{$lvCs2~ԍJ^ -䎪2e[)GfTņ9W -t׸TФm98qg62>DC_l4TĄ+kp'߃+,[ј#$&X-C<;J+ߛUJɈb<íJwM>:eR1)l2&pjϬ,Bۭ@UN遦Qo?7dz&>-*Meѣ4=K)RRRξYYJLΓ<˴ʅ. E+j$.j0?po![o -GY~BW”K5IPcrB%/*ɍ(K3'ljɥ.swneMhkp*^a\ґ^(< BN߷8c3]%qv–SS4rjmbx lK -lF%gpUs+=rh_{k]ĎraQNы5%7WC1;8 -|k2\^}{Aӏ,͟&l-wlsdBsU5Ko}QHî>iޟ`7O97M܃N07]rLCZtˉ #،)drKFz$iu }8BsHM%=N٘8*>߻X6ttmˉ;Qd@#M g).6A˸E1Z1$n_I*fM3J)7nSv3+qffQ ldV2ӑ>3rBL}q -DI\]F¯֦S\_iY߳g2kqlI4NL21NQbC" HA4vaX@i""*bC@ M&gg}y#qJFjbs^ 2PE/91fEeR[s!k*a_ͨpiAȵN3+&wQs9ugcJ&ԩ"~ohrJ/*z'"gq;]]Ģ$bz>@VVӊ̲ڸ|kQ(vSIyJgYߝg!Z6bwAܷFlG{u Fb~ %$xu9DX] 8i~m6w!M~2AOg\sY[iVv讬t0C~UHNmOM?t5ֻ${hLϼj[n2<8ˮِT޷ccG:6YT|s>usͯNօJu堀~*\`/쬰!:2.ǁd„KŠovE"ԓaڶ0H3Oa=,JkHp4Oj)^%)>}XVCR~|⣭\zkRbv-S#^x:6XwЪ|*J^vj${`QXG'J$hs"_"n6^UcN";P\sA=ck-8OM)WTFdF^^k]Ǜd gjrdBq{oKxCV`pkSO{S%JPMcpAcUK._^lE< ;hcL4đU^ jj,h_DQ+tJ`A@?]Rhظcac};䎡jY`~UHweUP1ֱBM34r_;PL>5GTr 63hfQ||cHcЩ'[E^h: "wG.Q c.W,Љ9[n<* -أQ]AHwLg -F]NLhucgyZlSɱi::@-ŮZR7MF?^j:Yl@4ҝ1B2vBl7ɆNc|7NJ -_|׋Mm-ag7,92|m.ç;?aA[Cmem?ֿ Ϻ8TxNȸeCGz5IᚦGJ R"/f؍ϑ -FQOohgxxնZgkَ$;^.ǼwEoi P+dXۡ`Q,y:ɏ'2]ǧ_ϐ/,7OMUOEntҥ1[T]ڂ>/)xUOLIJUZ$ģ'&qK lX>%=*6u Z1J~)2㒤&JsC7WE\u^%>ͱM\jnw00gPI0?ꇲ 8S}rQ}3pA@*o܊vyCGN g<^0~ohuc+M-ԌE8 -!K@ʙTāNߟ>2D,qhe|(;di3*혐~"hc_bnBhj.4x *ȡ 8]6EV}BCU[WE贬,pNZÀvpq39zQѻZv3K+⦟6aŬsh5P0k‡>Z^WokpN16U$yX:A{:E#CzV-6ϫg8+a3l1pAG -bg5J:h;}dl r 3X9ڐ[^3ʜn&T/K]uDM֏iɥ g_O(?;M/'vΥJJH,`=z.EfaOe̕ӓo{FY1 X%ա㴜[S[ynnZ-qL͡ڸצFfx[a=]lmty $))gGS_$;W l mt=8ڒ'81w-ϼ*_T?nkhzIgz/4l[Yo&ecs#g{g&Zّٛv&ȐvWG/`]C9†-}ńMn3}vz&$#N`߸ -rMYIϕ|m/A1fyȾgTHɻ*bʶ^ h5* 5s\|=c @-tU16a+M:fPmZ?1GNӋK_eܛiYZޟUaV\{K3h'S×sߟL h'm`WSq-$dz@~NEUs|$ -P3 0]JN_^D~0X[#ₐmKgGfʥb^\U$Lhg,s;MI۶Ik)6+\Z b2Os;u4C-XO8[\:|Ζ]9_`?Ӌ)]-/(v:)=Y Ty SqU^9dR܁XZ*O"t=fSEHv'Ŵ"ΖQPc'(OWT(aiUCfia޶w~㌼VI_Ң'TP]mB8:ᇣ ( 9Ւ[vyS5:9j.Fw%mT`=݉ J恑_{0- :Yp(]\>ۆPb~'^WLb99)g]4ŖHkKYw|2k9" 9֎H8 ½ߟb -N5&ޔ~{Ev0ՀfeO p9Z1[aͶ]Ѷrubԋ=ԇʐ.xK+njŸ2"-66Ί91z^Vl[ {ɜ5=KAm(>.P0Qf9`?LμSo$d_Klu C'ށOs:"\jnS\=#d[^-2~;[T]9{C{v+0o?J륀+䜈j$ nH fUQnT3d^7ҵqJR|LFx4--)ӭc{͌_벾?;ls3la3#˜PP$NroFBB*w -D)-r3lvq=| e\ xo > 3[)4a㭓 -"6B=ª}pwu).~فħhXAVŝKYwmUI%{nXR[C9)H}zUxm|B=7ՋoI_{Yg_BƇ\i?MLfᇁUw!:GPqViq|;:Rz[;5ȿ$`̭UAwV΍_jG=Fqa1Č77Y($RVߝ@%'1P@[[$B]*\0>aOEL[&UjӁS|j?PN$(kA B`"*"hg e!_zЀGs2v\ ĭ؇KOߺVD)4  naGI]~i^ iD Pn[{+-}e$dܜf"*~fNI(ink -,!6p_X5r1/_ձUq'|ԣ~Ԭě뒺 mOi?&H!W/C/>s4zÛ'~~֏;CHcw :( 59IS0r]%oN0 ~8K DNs@-nXFAYGp &]}%ش朐̫vGrJOu{ 2ÃjXI75>->&-F)ho Tsjb~Xu&c<0 -B<$LRҚ(>a{v+v(Rsk.g(`B6Hqj5LJjUMEoŽ>ͫ\gjfYXU{l6G`C\̰DMŀ&)pO36Lj'&NÅ {5h] `Mc}[Ҁ+.0NVyj͉Yp*c#윥#7Px&n/30>-VRdoHз.P}t}7!~k*%FoablX<*6,*6K[Ϭɦ⭑x@Ys&Bs̵h2$xpky^ '΁3K9@85]P\dȬ*!Te^*o4?6?5g7N|sҰK$Yj\*fL+,{dl$1˫a{;0 jc]Le9A??eFY瞺V ҬҴ-q SDyFFpeRAP tG, HUs -`P~9p[+Y{ וr@< pCS"W/*m%͝S -|֣e9 -@ K|t0|xٚa5Dq|cs]_YdgEG{z`RS5<Kl65r1a ԯfUF!t;3%.[I{m_ѱeE5lH}s i5xo_] 6)hH Sڸ %gYVs ƧzbR}[ΞYZUhȱ Aڄ<rwC?]%+|'k7ͬ2i{F gKîixQ!0Lƿp+إ!Vppg{b`\=,hkGGx U%6( -S⛵)VVԴ2 .lE*BCxU8pOzW}Əm򈠆nGLYg(QcOoiWek]w/;5G"$d.'zZSaAׁYpn:bޖaC˫s RCz!ftiZgpJ @g݈}β,,?qpY.iu]T1z 79ގژn x{ZjiXRL6 Ģ8x 5*ԏHy@˺ <0 Yzѣ ]⋃M̦mL̆9o00Xglg!cMovLyNc5MۜF*R<xOO.[db78E9>qtj.?F/3LC:?Lȋp~ļ7Wŀ5 1Cyknqlw,B>6t]`!~5̫ gZ&wš1-(mJp \#x~@TR2lr+ϧ!A~5, 6gĠILeQdtvW7ďWGpM|حiV͔{Y/Hޚ#B2{{2D9?7 wfrUmj'nĆͅ+J#7pkWSRBP:%ƍ+ni0 zܚsϿl:j$Gӎݐ"3nUػQ􋐞Tԓ! hC,(ggR]?<4%|K]QARQiBob,;DG%8pt^S~rZBb{S kys~zspQhgbAo|iU1@^Չj8KIl41ߧ}`K+{ю1ԫO0J%@jpoV&yr^'&R'*nMO?eƕyTj2TML%c$8v;cclA !$6jZ7YQkG -LHIĭp"&d\%yaC1yݲbbzڏ%;/eҁ`s'>X(&m2r.[AbR*.]At ͰJ -+_fe?t_6Q%R -f]/e= m  !̧$MW"N޺ HK69B !y3s{YX.: KxUKTP@yb;"Q93bg5TL |}|y8/^Pvֹ.41Ĵ[E8Yr -HۗYxhK܈ YWƯXߓ I'Q>7xOyz*[nQ'\nΛ+TӵZhg׻FW9P*>Qsm#"W0ߣ!0?jUB| -p"hYN,Sɥ8ٌYtktSsӰk1r?ftӄj)͈L VQV="+aF:3+(,(ځ: @,#E҈6kGjX+xVDюKVAߙpaO2Ϋvc>t0 7*Iy BoWySٓ36Vog?`EՃ@!j+85%ZuINphf/1n%܇%th_af@)lhU]NPă- &[+1"/lRx̿ue6ăMKAzkiN|O4\#l#50C@*_!䜔yy,9S5VZYP b2F qq V ?6#iBMbF/3Հ)'Uz@BoH̺1;`ML٪PqzрA8yV c~=&@Z^!s~|6 6cOJq\*yn<+ki*V#߃ѬqMTĜsƩ<=lI? ,'6\8GY5@d^ūaṬ?ŵ09.5`k購 ׃5% -93X[c%#PA9i1"6 !*f?smayͯՃJ,ip4RfVg9ɪwOr9p$fbFoEZA"QmIci8d;aIi$%RwuGK\!HQI,5 Kx->~WDL?gq漮j3 iӖŻ9g{دQ>"/Q;VA,]t ):c~ƾD͈os -S+0E@j9IM9MGApXsT2h_Y-yAv+p1k<.8{4$U\MCvO?%xYoFT"-1`>yǡ_ N -TIw^1TПJ'EÿV< IX;IG^L*JH/Y}tBlxAJ=$[KN9UY' !gT J>aށ7[v{pFӃ5QZ!Z<&&|f_ā~o3~v䎞ysҊfA`><+ '~%7of=L)`2p`-R&L-!⬞7GpQ**93yʁ]tg$ anT>yN0F[1|/޾ \L8x)U?sCcaW!tn@D ޅfJ?fW;RK>Їi -]hTD5RB^/70L@<{ٜ:܃9QS czT*xA;Ha=2o9gGN!NFuRVNv+Hwcrx}s?k|1\ 6IJ#heEQjTzRWT"^kJqXnK`y% -[OvFeogH'#VT+۔v˺:Oltr*:9b7cԣj8_`N_[ۑ㏭l_2(R}_kMlYH/@I)*vu20^IN=ihpKү@w]Dމp+ɚՓ8:% #}dg6sR7I4NxeVZl"d2̥vr7ju( ςzƾ=b&Ϸ95znط>}2*vh]V((ˋU!yt~r0a*nYF0[:`ym7g7D&$o\}BFPJ;/"DzSC!g|G,Kg"F[+J5 M $ h2Or~&B5ܢL|޴ݞھ65~C??/"r2Y[H+ɘx)8]4Z>9D1f3i]ðYHuTޒY_ZW6Q-* |YՖ@TFL%bÈ-`QTQ^* -=آ1lYo{w󞹻!,wZjˮlk%qM)5voV:54tD:Qk ^wR\ j'M_]c`ɅCֻoL%3kx4E+&sEҦDc6G"^8Z@n$4a uO[q#QT` V4ުMv}o\ꥇ=YQx|ew\j|E,&gC0рI 9~%DMs 6愷[RUNDž_б)nlvTNNPNѓ; []Ŧ;SyTB=obc-|l䚳pw\_'Zw^SSҿ/o]pӡuY68.v4ey̪V;s~ ֿ ;Y}:oٙU<^ͱ+X% -sbmw=jn6>&ԍݫ(7`#Au=4/ڊ?5:V'Zٚݾ8]yhk@ib#~O1 ˄sHzu[\dkOsy/McZѐZҧ5ruoYFBR$sHr^G=3z -MOA?/mxVq\h琢# 5Ͳ1eS6ㄏOQnSd6#)#PD&<Qq8Ctc! >wϷ1\lLzLEެ|(.1=#W%220t3h_h -.|χ$QAn _)D'u8Ik=}1=>okqj"W01t)U⤼o{AuEw9AiHԋ"YG\PQk:vU@q0}6cӴԣ}ɵo0[?K_R`&9; f:YG?=TIǏu%z{rxeUTEKzU=Iw# h[3[@ؓLh>HΡ(8T1`q{]".juc\Ϻ!}gA Ƀ -+ Iܱ^$6abARsdSjifkM#ATVKCbƴ?=$t"эx^r$ bkȯCPJ(PEOF uդ^njflŻ>ä\sӻ$l%zUHJZ\S@aAXd.Ml)I9yY|7tcpy20ܮ~Ri}ȮxGcBV\1ƩCPF!^V2YL-$-M<^06 z,U52$3DpS@+1FHV.CdH+eCk0\!OZ2 }?9<%m@ pzOސDȫtHDvL,fdC -\qtsӂ~XZ>vW~e))2=[2sD?:j$AS|HBtx*nJPlޠ1h9ƶl):p9-|]UTQ;;w@?o ?2^S'1j7ك # .B=CX"1`K318y^K3ML4]+Qq {xyE暋I^_~wJaYξ6>m|QQ?Q|7d kg˸x9*+ԛL+F l~'^DA]MKNs#:p$T'ko.#2K/>9sTѪ=_XQ+tf'x- ||38M:$u@dpDbj.Fɬ6ZAғb湪.ɷCq2&_!&|W#Xu@!2Q=4}N OKO -Ipu -*ͧC맏ʯV*'.`Cq-艩w2$=_%BSX{Ŷm#YHZ҈\~fV7$ݜ~;—c she*ݻCCt9'HrsD_$t Kv*zǑ]x-܈0 Wk|5%]~ -V߯a -X͞i/.:-Cz+uشI Wtts{Y уn}0RJEDs%2qJ^MԽN~Fc'ruى1f$J!rQ6iɥ3Rk6-ɇK`=N*\N}oߍkМ mWTAZ*6?qBOi:Zy\]RQɳ3oѪ UXV*bYG%xⓏbtQ9eEݘױܠh4֫]S">.^(φYfџoɸՁzQFxU;q#C!;rd\edل8͛.RQ~? ?*iˁ>_dÃFk|` -w!*קZLw1"@S↺^7XIɚ=vU xHV!A/g`NANn9'/3@-62=S k:ta*0@/Yu'>>%!#!2{ʗNٮE0V 1sP -lď> ڙ_U/Iql9'I8%ۚt<2̜_ؕ@@r{QI)5(d}JͰOVeHN7ucÏmUS@[S`֫yRdcvU{gY -;V|9x>IZ#͎S ?,$oci+GnTz*b0\9\Av1ٚ,T.S{Z7B.3)β{KԿ鼿%1R2}7>ZS09))X84H2C¢q3*y  B\}ǬX9 S5/G'wa} C[3ri~*d+Ά%] s`OiCy38v@EԄDZ5LͶe2ӱ*9fQ~0kV%M7e3BV14}S#Pl_bP?ϺJ໭l"(=Sk#VgUdQc䩔k ZI04OԃUniGz;~K4S=.y,ʶNINGO'*>D^Ivoԑ[4쎋9L7 Ws- P:N&6egf`.{siV wGYs[=O'>!xc;"< ,ZGҽ mQ~|h1QTBjN"=[Ȱ>d TB)[pI?A%Y@AW۞y=oj+BҌKiiBzDu|B8QtLo>~n`=V,r9!.E@Bސy3lj^#Mrtq=gԾ+-$sZbkJBn:y'зsR+{]C7+Ħ0o#{؂Jlz9gٔ\]^5|2ցs_ZL:t iGa{Ap}#s"2>_X05(]4a';ځ꠼WYSOǝ'.(4N79: d{Ƒr݈"u|%gBQlݛq^Bd(2?aa u'~pU]prH6+Y}N* //etwLWrδԐ5P[!.k:jKݫF.m:+?SgXynr_Jh̞~=kk/HCi2 ~~8p4#5*ڑИ 4Ė07I -bũG",98цޟ3QZb}ʄK1saO3XS`fZ`uCM 7g/ -endstream endobj 25 0 obj <>stream -H W -?=smަ{r,-͵R44eM@vdElRsgQew"좨N3==y{~45 [JF˷ͲR -R󐚍pHqOAEK^pd_ͬϰYZwYuh3>%+Ëf6nװ trPń<إ =%%'l[c 舵}-7O'^Y{ئ 3Ґ (Yjq`XL2wdwPqaEDoL=^>ߥڦHaM;.l$( 5~ A[yzekiI(<2ZЅ9dED,pkK~Qo/w4m5.Z>ŵE,oBJ~dK29z!҂ %L-*ֆ8#QݝI}+ap6x-^_Z3P+.[F|ꘂwLBZ+o"r6qNشx4:6Zq>啡ʶ.K3B'""f(8#mM+"4L$'8'R`UP~eέ\V$lMcȼQb}e': oH }D]O啾ҋoFT3 -,PK\"\g -KW9gmc-e}}:X` -yUE5 HH*증v1OIB!diHIj3 D;3o#C ;EÓ$ -FCgNp9XdNWГ5zCD/tJ->N~1.Z -b<)%9Eϰ[iq{ !k lYugn9ơ R<k=F.J_vŔgkj[{[tA+(Su9L?XXP)3l~Pb؎ -M1~/ Ώs9FSB*p5Y6'f9e܇ 싛 -ӴVipcf>ůi:eซjRFn@g^mcgqM<HXgTb儗xxT(Gd .7[&0Y]r&(A jۡI#ӻn1,'iN /Ll}SS@O8%'AmCHSc+,9zDM o ΓܒxLRryx+]a`{rQY:.𲜚sN<=䴬}%YGa'-@VwcխŬLJpMOS*!5}&}&yİݱ$ے?{G"k_֨80!= RO X@kL|n] -X|st#WUj?m6dfuqccN9&9JJ逄Bsj%0!"gIbfv]}?|~x_"A; OB? '-sۖ0x4֪*5 R\WR}4ᇮew%35/LÎ?UQCds3e1mO4 B崰3 #h+0#1+q[D4|UELdq_*}EO -dMT-E ͵ N?Է>?#XnNp4izm -y393Nc;KG˩ uډ p5c?+HnyneMPnPoWC%1П'.w`ƹgJCe31Z`iҢ)UYf!d SV%:U9p*X{2)XFN;E؟uk}Iћ1$[Lq?Vs3 ُ(nu5s59,JPBΚz$h_AO.wc|yR#?Ql|Ī'y\Wd((1ƾ̧ -Jȑy(g@?R&?-[-ۦK fb]hVԣ/萿w?<*zw}[׏/8@a45jlMhH~ān&~%ojTK 6DG{6]ݓ Ƹ/ϵkJcjK-J<؋a,x=QM -)+A+ӝ4u}97iGƆn~f `2a+_ߖi,O;1S8@P5 k8Su[~d@Hr{|]֚3@pQPYJga8YydbV%8uR"oO֢\h?M#eBׄy޳n!=k*G*b[qxd$Xg2m{*EY5d4`9ض!f1LuYBz;DNzeUPu_3H|mÃeôkv5mdTJ|v>&˒TxxAغ b`}%Tƫ2ZomNUő{y;㽿-j>ݢmb, -)ަ_{B0WsVz%adUM[}>n@M[}c<ҕ$"^5rtm9T77v(gKQV[~ph*<71%ꎆaCLa;Z?onc~T]ن׹L|C*諎#9*NgIGj`N P*/62Y|9ǧD S?>h?/p x~zOLϿ\9(۪'ܬv nV:;~\ t衚F\.qPE]> jⱜo]i CW|sZWmIm{(F7=7 ]KsSĴz87c&ER|Cn<^(3;[&7f2hQT5֨X"4 -hlQcJJDb Xbl>g;;93Io P!!rG ܜ3$/c_a}>yic;^Muwi+zԖB7P3;1?W5S(z4Љ><4yr?6:`vX;).mfWfq)؃11&@0T"HFH2~zNk |⩚Y{{D-bdeА/gJpə0`#4]:Q#ϗ[R`2:CKs*Zp"v־:ikWx0C;^+JIC%~1 {Ј'E;r;qc1zT?O;=0D0{ +햄 =E5njjȸsJvJr}FCAh 6=|D?[G[V`o l)]]xW2}Fn >vU%0+2}\2kgm}Oq(zgeTt^6{VN)9>]MV <5!g(ߣPyH?ך_j$uCVz_ =q3&̵D0~&Nݏm&ѭ&y_RZD$Eg~r^K˨8f\_[o BS-z0Uk&8kr<\쑄3rD@`#= -j慊]r -fjR<E@OTgF&ʂӼWR}$5 5"/Uxu4GN )ɺ|=&r_lVChMwu[>qlm잫-<RNc9E@ߟJ 5=۟QW򾨾`s>i)jyړ*bz]nE30B]GȺ0ôܛhkҐi3dI48DEĉ2e{ 7Lڊ 5˃~{h<+UUgߖ:[.En9x 7둪 @!D Sp!Wj[ <`u=.}v(hy gc=UGakPR>!8S?@Ołt)%HI23Ue2CLϹPWey׫ F}EHt!GRF֞:0֦& wcY̳;edR&|_י}Oِ|WOU3K.:6/WhZ[ŀ;eĴER6i9jS܂֏2h+mLEF_i6aUdYMPrB6t}̧%VAQ\oigis]f2']I4L5"( EzFͨ)+bJ "Jd&a/89}}3􄖏*=HÂ(urr*k#*7a#j. ]PAL"<2)^޷tq'f&1 չ[2.!_(L HlFo0I[0lwȅ?I۟g|]@mq l1}jy-4簆\3U@;4Ԩ;r@B1SGĢ]9~1=EΑK1s;&s?.9}wT ^cw-ܷK9r39ҟVUBf |(dʐ׼3 -hD_aX{DRӰ.*gEmo./׺)7-@rߑY1l~D_Z3V@tIP9A5ZjMRˆ p)l^XFxp/'q(k^7WggH9c>)CGAżaퟧWJHR<Ļ+ܗL] 42*o.9jܤ$W)0WW@) u ';הU`Ogrǀo(nb1hYۿ)9kRPN(> -9Gf.9aZԫ`Tl/9ab@;a|Go_EVxȨR` _wɊX-!ayx5B< }[e#FROya= y MC+U)lSFXקy|kUmG9/.zoD_\_GS+ @ bb'ToƎBF.ŵ5Oy-罓5N6IXՇ.{[blKB2BqBK,;3gj_JzwGQ &!Wcs⸪rjD~%1le=5\GQ/傦\ -`iJh,nL/9P!sx ]ӈq`/7\BPIH脞ےжyf[׆.RQj bn@ -FuDMM9-RBgQ_F?[ v\? |myI5D/iv}s0f>:Fꋸ\WG@7xW$!UXtOܜjLފn[PC5J3Ł^!ES]S]qSzDE>'oL -zMdO RxSU}E߶D}~5:ƉI92rq#VK}x~DVS¦bO pAD |쑾&=)tc3J6Qˤ ĴF*>Ȉ98)sAH{^@EHظ,|BHC.K[\Bӽ2V zY; ܆yA/!Jb7bwUz p@.vAd⠞Rtvwvso`7w[93'_X_Dy5՜=HX谹3O.W*&fsrCޝg2J:Ow{f)έ)3m\3A@dE,q2dQA ɖ9oy|}JTQe ȣ`ٮ4z;FDWH%Q 3CfS^޴OW]r͡2*.6*{;3C ʣ&]rC4:`~S?(;.'-*-P7Ks3'/`wź+jv = :;溤${ejЍ=T|'/PP6ܰ!?n{"_ɑY>.7eTOOM<;̫!^uKU UdxTdHB@aV8+kKϵ,u[nwZLާKVȡ%wLUkN>'j2;=8nsֆWp$#bJС'#|h?04sOո'/b~C*On'%lCs".׫;tppuD@޵~0;;SV萴=mZNm# J^kph&9=DNdұ=JB㍐ -JM cҿD,zhU`r޲U8؟󸑍 k^:8c`{z!Ɣxd-!S%%wCU疘X)1F$ 3E̫KfYO "v|~ -}oYܣVc^,Х뢊 شo':~ C:wxs='V)۲C{K↴ u !.@GrSJ/{f+<6alװa_,@s-W^ꎘ]1O/nWv1W >1~P*H+}#^LyaU rIoe~9>_,QKt떗M-b(=5I;|O? 1ڸ&nh0ENE[w=}ۚ -O@«$77>7Ze -~=S{=;Vnw|D-KΫԒd8bxH@mx{cs„^IZE"QHO -k(1 :."TF?ݧ$Ϣnk_S¼3Id >[( 1d{zGufcY /hvR\`,2/GrBcն8L+D.9ɹ@;3fFn;'Q]C11ǥ"vTdCfRA?@nl1KZc#f:n,cor+=8d#|"qhD72Z#0a! p -ugo - GauU7tc*|WH ]ó#< jN-}%xY:~9#ҳAOn8 WGAXVQ 8u^D{NY32pff?ӣZ%ɣ&M\JTKiͷtXyW:Xy 𬒠Id}˨҈)EdFfXXM{HQbMrH2J@ΜN;dɘfb6cjL\" -"(Ӭm(; "\d1{=]~z {@?DtD`]|d8 u*wIVdO!|])BxU*;Vq+Ғ|ұGszKXp~"&:ҧTF $,a5 - p|+T ӀM3+0=cKfo5%BoVٴ -6T׈k„GHĘޔt2Oz?zzi'0Q)\Ab>^d$5q&$Ҙ%JNռB7#a?ؚl+J-`}-4 {;, -!喯K<#I%*WE2zx9z=wb$ȯ)M[㜘0|qb~XCDMb]mS{"Hh5)+3Rj½]]O(}Lco&L\ЍD=1붧W=*a" EQdTMi:23`+64]) 1 v`yƐ9xem9dځy` -NWĒCwoF,$h`ꆼ9J=ܞܜ>9 0);w{9IԐJV -0qPq=N.1by 8ZR2ڒnrK8^f-}#e٥UgF+BV\j臝/({.&yπ_ߚjp -)bJ9'K\*'ncȼqYUo451s/%b=WA@) -iMԒaXw -j܋ %j I}Z{wsv% A_Jh)cmɶK5J(kbZя}4g?'݉.Vb6.Su -.:ch{?.8IL%m3ep,M>¨;Y'2t'oE)03%@Z{׽1isyև&:d;3zPa"[dǜ^mhg&(/^%Rg^5U14Fh?Cy(Y3'S-Cw+͂Rݙ]SM&jU@# -/ -p^,b=ƫ5?m!pDz!5d];EۙvW^;%MrZdƃ72FV%]{z9qߠ`d -~ҊL2"68˨:βnMi%(FEwrMdW:Z'pʜבkwޑ+ho # ,[δY3ϔB|iO3*v?pnm2 QJV)%純,2nM,똆V]pCԄ_jbB%;=GE5Zm75ТqS{GOL,܇eͲ+ )Uv3L~h;PJ˵-`825þTWOx}LY+ѕW!X!ҕ_nz j(b]*yTcE1 61~>*y -sr.DpW93n Wvg+[\(I )=sެۚ6"[=d]9~ø*{g\&^=Z(d=p~В.*E0[|c|$B%yR;IN;O*u3"Lݡ51hl)a/lQ84nwglΌב0 wOSSMHSzV}#a^1srrQ MC-؞nˤNJ6uBrN6!iu"%ix!8cny2)3 -M`r,!M2Ҿ~zIpcHûw97yr5fR6(Iҟ2%^*΂]w^JؙM:;]S*6ʢxG^% )cT09.ӧ,OS55LMմev7=j*@  rsBQ{fl[U%B=!$֐; 5ȼo%XmJ5)6c֜Y9voywsz"989ϣ - Krq(;bw5wNݳ6!|䓱N`։lM.XǚD_*xac įgoG"ru~c*펚<+/+uS}j% I>%6Rt5$<ݰ⪘1&לJ$qS?A& -GJ(§Y1sZZVC!'{╂Z\RS99?,gwf/xأpVX Dp@X csV9m1)iF&'W=ro -6&k㋴GL*HiyϣFA&gVRcbw?a/& 2&q;u0nπt._v=#g}QM &npA6$alXf9A0BRxIĔS,h"F1Ϻ I׾*e'kej~L2n$^ڞ^709u5)oh6:v'kUrPr2V+@^d}j[(E3@opɎENiţ`ևTEoy %<3"Zf]DBr.a*gZ.$V=ƞT=0` -vڽt߷%<2TY{3OH5@ָCX%~5^Z2Be1C/;j6'5Ӽ+|B:{/͗?Fr(іz6ԚJP$ KuQH'0wE ͜K"Eiߕ] -FuUV w*YH<̧qj:#ڔE DnH),̋Xy m?>b\; ,G]wvᖥpk'NonNK&mtVZ[EAfe1 .Q2NnIYtH=2zn^N8e )*PR"gg"BQc .Y֧)x2o'\C&}Z´ EzMFN:xՁgӁ_C<=:.4Hq^4Pv3"|Bt_ۀe3 mYUtsLkfwBV}ҝ v:7f=DM!nbF8e)N6';cO'O}_vR RCi1hKNٚ_ >]Lg]z%#TנWB L뵤KTĽj^!-qA@z{1 ܟSJٜJY-s}~$w6>(I،۔Ir?qg' 4Y]-Ѩ }OqA$.Yne`"6Qh!+M>hxүK%J&IWǬ88SBЗdPϳL@N|P? :qmYZ/N[&Qsʥ?XE:l5’ЈVGA>j./UEڸ=%,ū[rN Om7c .=j?Sj4ӻҙh匝Eu3T0uQ3"6q-acW== B@P+#cvꄛv sv+v,=bܡixld6:'kHdC: wdpʤ]G2^FÝJv.Fgяjքriu;IWB`,Pr@>5'­I7&O!]x8jFNӊx tkڴX+[^zLSN>^~l+j39uS}죦褨< 8 m5.- jnƫds"(/okۙ];ᔯLj9U$Ȳ<ЕW؂Pʒs,JҺ/fIMoEroXgM~AޒFpwf5]ϋzḣG6Ź=R{ =L* =ҪrA'{p>,elL*P#uC=/PLAʶBd7ݏ(efƝPSQޭYd"W- PkԤ}]ݐVj&4[t@ۂ\1n!PclNyu;4By2o_;|N8cnrAKݶ(Iׂ!ڹqNqpi>>]@Tl /kΥʂ[cޏ &'܄QVulOw3+a=Jޯd|0sn2qJ3?N;;vfwn&ijFM<7j4(7/ k61A<&vg{|_p#:REM"܇YSOOoP6'F9`~]QBzrCj 90T~Sƫ7 R֧% -z a#k1r>W>wY PD"jHh\԰lOf}j~G8]J`V.%%(F}3sq3F>]%U֨ ;5+As+DO,Vy7ŭ#ݥmYSx`v <3ZŘ*ncqw;o8sCymyou!= -^!6n %82!*lLĜ2z˻8x = -ad%!6<9&G3ў}22aൢ6vK#&|rN-6#9|I;=lϸ !D~ʡ`}Gy2|U0](3.9%Czz }U:QwH/æ,ӣ!5u9Ok_>j. -o%(&E(#5VPy4' iOFAI=BP*רwvK^.LٍQ5ڀc@}:x eg7:Yӗ{ޣ"Ǭ#'zvpjw׎IEy;J+ U.2fԐnu)xQ|J3ױ q6z[F11VRIUZy&TS-7%M=Rk31Y!!5*6iu|⯇rbO9sI6~Wʣ{{K{Ks3oH79M"Ꞛ^>ٜt3.aEVׁ3udrާ/r>PM8]c'2a+aMڰ|7L:3J;Ɠ%Ҏb)g޴ӓpKaqEwe_KCB&Vl}?j[KyK!.f󈒓sq)#h~%Tt˨y " anG/lq{["J<ٔY<P5+iɵ}>S?:MZyy+g|%T_?*]紕<Upp̢A=q&36 z&% Iq;|#b0;#߸^=UH!-:l8t}F#- @ZR*۰-.\y{z;QV`eǣ7gƳ - -^)s> -W5Zrn9#i a M(i7;:v+QBMO -z!N8FI|i!11e紃?2[d~l( lji1b_:lp2b"d\R|9"ןɵYW̽9<1;J - vnab/;mݟZ8yˈZ~c_gbB@k? TjQ7)mҀ{BߢVOˆ&9?zeOl*D9Ӥ"m 8QR[|"<8R1I._Q=o)pxa:%ヅwʴ~rNˊv%BP#>duS a ΨEu+a/(^>DCsLk ƤL -JT͛=X'<|y%3bv%i?eH(Q;acr+ޠzM+*^[8(D௘渤Um~)f2Ls3uM(cY9dV\PzNS$}}+࿭3t;mjUL!u卼M$$ܐ;kk]]WAy%yHyAu= u:vwg|2swI{=|o[ -yW&՛ZhTԺy]hٳkt><4-mHncS +Z#%罴4fѳZ ]ݒzoOoVY/x6o*ExZod}J6=ZA,0KRyȦ'TgܔǷ^jg*?_8&)8zo79u$h2\fK; 3gU~c49GBU̶~z?ܶI^mVoK[榲-+7֝ L =ܤn%K]FTݴԉEvC&x.uʑX"* H//4/_`' -ڥ}/}n{'|Y$ܚVK7,򺥁ƬgS?ܭ)G:G޵5[6>M]-us3ėV04ǯCY½bl}s.hJ3V?`>]ݚ\r!J%N\Äu@7YdoQi6`Vz+c{$S? Y·\P|q`q9ES9=9 $r5pNNh -Lct2-.6>J)T:[b)D-N%`h`"D&(؛n NǓ[  eDN$\ ǰrkKz$WĔ?FS9 KoǏ8Ƣa 1 -%DRWS8D$a9ĔcU ˋ:x,zO'KK?]4"rtDYr~S CYp*8iX9iUNF'| *u^c B"Ʉ: M7g:@GGE:Pc< IJCMpIfQ3ߩ KVGDb&,8.}}xc0ꦍQq7emX>$*)6jԌ{)1Y(p4AE/WS{yQ~<nӵ]n8ma%c!;.=>szUMЬ?/Yn*򚤌5=+ye6a[_9),|c2愦gxFyaEwm--A2|&5i^|!}2y>y_T0j=#ZP3ypE>qXfMiHd*T%B L*2X 6x"d-},c$! C2YcZ{SK16޴޲~|[}}ӟL ~c̪yT\mHϾOl>9Qׁo^}9bꬬZKţ'Ɗ%yE}F(8(`- ;dSYյUB^[r!.u -NM^=S7A-Toр#յh`Gk'4]#KYIUy<硯Sv\ڦ&4ȨIi[$tuGM,nWИYBbI)k$ ],ac9!!1bsSàNAT۲Q5)M[G2n˿C_r I7t -kt=>sgQŜZ!kSјL"ʅƂw*n:20*z VhӘ -[KƽEMGn9J)6`l|XY0obPlOgl f!c&;k1fޞSo/hT0rsVudbo`n,ETWQxcN!1 Q%jT6ldWȄ5vcv 3$PаcAZ6O  &!r -0U"njV4#2bR> uSQMO1p -Y1C4YɹLTmo@fInƧO$gзطܚ? Ԟ3>7Z%RP YMO/$dAc. 242[z_Md_qMt|Ȥbj_U^8)/Q)h}]s@Mܺ_\P:*)dZqU< -x^s-k}*d 6 E*WPDV_ݦp|wQšӥ975T71zꝧ Kk?vY <~dҶwjvCe=Ld??׃|kgN+#F[/6Bw7{&1:Փb>6t&.5e94b WdOOT"&Q /Ne)nU -HC.642 7&~n?c -1imj9Zjُi[W +nxo~(4@n:U R5 -tQى4Γ1HT[Jwu~V7 -ĽYsl~^}Q@CpE,"8|,Ġ{՘m9%I#еջL88tӗmLIqҴ4v<1F t[s$㶞8s - VBBZ{hWdNj}#<3|gu|Zw>E#U1>սnpa1u|U07'sHc֭k%=ڧnˬ(^Hmƭ7ȳw'e׷&'7ҋ9YѴdV^hZHL{/6)({t鰶Se3T A sAacv4Մ4" 4h{z'ѼxV{}<+,;x(TokՓڏSJ⭾~{'ebVی[)YX\s>C?mO6|Ea[q;C'DU}ٰR g0DŽbҏ30DeyeW4#[4S~<9xG3#69',/_mXg0+YOE,J PYG0QȬu>62+nZMOqc;rQ -!3!Q[l%z -*S{㨊 h&dqmw&i#F:8lm.dVdf  !Y ,&O .*$\Ϲ uOzUR=np/C$ILۚ="#?ƭbܬ"At T4S+3/`&E!|DrQ#:g66/jt(bʖšEm:O++wX-efr>G:ܲ=3jOZWۚo/.NKt-)*ٙ>o#>L`&fijYKԦW _}pATx),ɺM}a34>y};PC(*f7bSM~=Cv&"VIfAUZU쁾oDǗyu#蚖{Y`h )KߤۿI- K-_ecFr8qՉoNDRY-~+{WOھʭuU འ$1ċ x'Eòl>WK4卐 ZiᲴī3 -"mRrkz1Bs?Ӹ0A}l=4 q[Wu7k"-tlSAa wgIr vER5\ ">60Ӕ_HU䚮T:sqEFlhqf?۲9"e6ccO|lx9h6LZj -ພnVA@f}Rd@Kze5TfJ=I>gIa*&܆^ATll2Qd}l̘9rXlq!64JPo]@+;iLO{$7 @u$hOnd&c/6&o=:ϫ$~`nWR-ޙnrü/4!rڭnJz Ģ: I7m<P$&Rޮ+ۯ?oec/B5{ݔ4{0w5Rӻ=^ՒbvқS!f\yjQVC,ʪgIze)&hʅ -£;/@/5eYAѢc'["H1) вhqXU{ _8-sXV*1xFRFxo?t VHbA -gZ耾4\WYȲB!䲼."A 0;Ϯ IDŽL5dtB2 C$gUchVPlX/dutXb$v98dB܊<[I! r*7N -|N7Cj2slm>;2˲~ՐNbb?1a ӄF4&N$9o>lCO 2,ԝbjҞT\ճ1lܦV}\`7?{D>||h, *l5 pB97r!+B5mO }~cvn## &YE8!]U4z{̟ڸ8Wmd&mfvZ;H$KZHe>2v&LAFHhu)`uvW+/~y -'E\R'>]꿐[w:]"NVyv߮Χ-bwN~(e1onurcdn +_ԕ YT{YDqW -!mk3h(dl]Q4I#R0pP`)<@U[ Pvv[51<&F&p֝tg]9IB96'dTDAE]tf/FQVMq|E TT/1YMjj@r ^bokqpT`CP IBՒFWux{^[rKNLyuL*lIG:hRYV %5Zm3`cTÕM믤K aSc?PĐ2)7f3`6LʊUV[xbPtHq̭e/2Ï(|Ɵ֒?2C&]Kw դsB؆s  UݪDTǐ\ʤ]o>h̃M~UY69:UMdtB+dSZ1' *a R$l\ ]w1n- "ps}E0{ߍd=,3+IxfKzIpi5=S \ 2|OY|Ǖ Q40Z1­5Y ,JĢ."j̃y6=2Ukzw7LmeIL.V*!2/Šqb*:i3)[Q! -8+a;OvegEV\fCUnINenm -{0K/u{ z&h{\bpڨLCnP[ ͊FِeB#%pے~DAP ^ܘi޼$$|h\¿1!4K>ٖOnht<Sy_ښZVY{S,T;* "$$N;tNYOPDq :79 @@@CtNd̋J:<4LH-/NCONM¬*48Z)iO9S$ -HY'+>)p '|<YKs{sܳ*H{cGw'Ah8= L-񖣤у_Kˋ/( ʃbdt:V깒uo/p~h23Xƅ {Q>`Gx -M;4$kQD-fuH aaGܓvawf^g56{SXJQӽzRvdڭSBxF?Wㄅ3[)29b.1)#A6?IzdmҴW$IU%n(i E]'S6USx -f 1ɹ\*_%͏.ޅnL/j UI\RħVnE/Cʺ6Q'g3My%9UkU ϧl_Vاf:C-5I@#Ej%gڃ@&p~8I -Zvf?RvB]. ؑw9^YDzsK*V֎=ʮYj^1S#OIÙV ZXf,z\Ρ,ֈ2B`*bx]|99j}ʧ(":짃rN9HJa\i9~{Mf^B,gFjg_ޓqH(G{ՂbXS8}D,O(}lrQQ OUCUsnm[)72v\ޫCe6]o̅0q֯`܆hW}g2h>`cՏ#Zm%aO7[-*Y&Kz9.&;Lgi: i!N(!!c,߇vV pRp `%KֵS/ɒ^]]~y>:0֓èv]Hv#8E,{* ",q:v3Yߏ\6o!Xf9Ogon0@OuYYYO7\hnmhg3WY`s@5Tv:qR5miNT̃g(h6fW } <Ǜ3o͞yxtUoi;hv9ZeqF&[հ<CA.0tY6s2*?Bڸ3;"͔}n*l UfGzE(6?[>'sḩ]BuQ[4XKlփ*vO?Оŗ7PKڍ@c{؝<82s9K("aUҸqܖ1;MS{? \rN1(Cgd'x6 =ʯ{D__h@lq ʙY\v172E{6 ]@Ty~* -FV9 'sj瘬^MK<ϐ6M549hnkQ% }h|&(h+]2&Cf]2)% so*mvWO;,tR3J9F#^%n=sww?f5Q]Mݞ¤0Hv$k&i8ுC\c9@ հ»ա/F`Nc">&JE#u'8~9v1eO٨nYPDŽ}ZLs+Sp74fڢlv=2n0h~ YI.Iht"' f&=^Hu5bV7J%tʃu G] ;dtPSږ <ɓKb -&LQ5>itY,apu Z 뇡Gj*PO{<:21eAF>!t]tY\$4׍wrqǔ=3nRJ$Fo>ig:@ - CaY&t35ҦN` vE/ aO[nt඘w1Eʦcȡbn&'U&pXn Y)Cײ#bhtUːj dƁK#'𖹞0 i |̀GS+70 tw`t}ˡl1&>u)9g6prV; ^,w%bTUB3_c^1 wy)@quld3 p}+3nG? 1my4^\blM%Ӈ#VadfށyK/p1DvܔeGۚtÜnV`gd`~_0H;yD):!'dw}\¦nbS tmK773Z`L^x+:}_e!)7V~TYSIY~5)̧6&nW"gԏqqS!_B%nJ'v mXH9P Ecӭ"4we!rLJ7hߗ,j*&=w|r? _*{BH-435EƏa4tu -wُtK:uBaUnObz*a`qT,:vX4vPeyERZ4Q/#/6/(spH7i[ƉcՉˊ%k ZY+v?z~Ass9O9!-Cn٤ޯ1m;ûKE%U>eѵtvXcD-e7v^=^3gUù&Oj3}.|!H5YcirW`>%`e ,%煽mܸz xq Au^ڛULѝ\jRpIg(ߵF|M9"yHH?NdHӉf>O{H'sK Fqu節'k,ׄh -`c$9ίӔȹ`D&e&솼n*Zn.o_qKY̺k~) a}0{;\q9Fv U!mR"Z2yW#}įΖ'~gqc bn-(i\.IbjUܞhυ6#ږjdl 9* _*r8"1|s?BDxm#uor&|l;7+7n(x} 5`Ldra18^~"Ps3]L=R8yU,j35*@U8;d7c_(pyN0MBRIt5[OZz޾}*f;Zo`!64Or:[H'&AdV)B_Kzw2EiZ݄a" /fУ֖"x9dӊ$("?V͵ -ѧ|pZ5:,fBX7]OdFIuo& c f.svW^@,:#%MW,Š['j%Q,|pUjL B[_--?DKѴʺc )ZxD)9/ބs6> #3y|ѝFO'f:!&og=ihdqa1pqg[{++mmxTaz!Zptrao:rCet:`3O`D-FJ)َ)7;KFNkJ*`Nsh.ۻ'"q!v!FB`Z-#͙EɐB:p6xG}Ssndiw%<^Hg<33/pVlhF(x=BGkdzؤ\<ݰIlEjm+8UnL <wOH̢cfu>^/NEF -?Z}e}8Ph@ǔr`J.+<>̧plbMd2: 6mqYbp"vPvi[]ES3D`v֋]EDrݐZl }i8[ȟ?ߒ^ǍWjk@m5j&qKd`?jGv -tSKfOyvJK+9?lF|uJܖi04W$tu[נG87"!Uͅg|xUOK8RK.{&s~F>6V~8sΧ[,Pٻ\FUq~Q=9gOf&tIh\1j٪((R5dtұOI}A(( dN/TQ[ϽwSa+zmr7e;3Uz[eŸe ̈́~s Os,~Қ{>~Noki| 2,~y5c>F4.!NLX'ɐ<}6+lXoczyfkI('AF Wߦʚg|&IjExv^aMhz,Żr> fFza U y*6hRnM0H\eȦKݖ`O6uCnUnQVoG'^qkqzi%u7e=E9<9%94iFh?& 5Vڭic|XÏ -~A9-.!0MgI[$") W7mdI,!^uWy#/n0z< Y}!ńVWx6=(„u-V躐ɘ - 8H ~XIY#aH-o>S9+ͣ%Ս_& #&4s#>S7}0r[:K\҄lY&[3/^zQOkf.KKYjǾ4dNf)mkG-&>K0.l|Fѧ.6>}~gՈ|bʅFǰ-!9`~&jF(x0pUdY!89QJ<b3OhDH0d -ph=.+؋sS-'64t`IMOa>*yZO#xW̨ޔM^zowQUm$uhZc5Eۡi{+>jƯsr`I֡\c2:0cBV6 dq&sub-6eS^?Zwd׷.dsnE-Vea^uMq#Cί^۪ -^]H9/3A#ڎWMEE} -]OfzEόMv `'q#S5}FIڎ5=ڎ2=v.ln o|86!D~˧qU(ew4aTӏo\V.*E_6R\'šG՘j9rrLˀ3I΄՝)j΃4s~]oE;(z`۲> m'Q\~X؛jZb2VZ6hy"302ɸlCy^YvlR܀`yxN!6} A΢@GkJYɱq6H^? #-B3ȒGB¨*& M -=i`Onc -@ݧ7fx/# ၟ+r!-GI ܦe}[8Ç#W4~UČ4,$Es.D( BNyK:MVF -8q9_LP-:թUeUK*ZfBfUq#cӋ)~'ؔ9ఔׂn^l:Ư\g%`Zƛ~V]ɬ:VКGݚy\?yEzU#-{Cv ]8[Y-ǫhv3L%u5 2^5^.WgפWY/\pkD+y鹜({#뮯svy v5KY[Gub $X/!M=YIiQ }gjʧlhvKZ;ȨNذ -Q9=0#zi%+EЦS Ԕg(Rcix]'}ơO9Tu6wmy'z ri2v{>F ,zͧ +.ŬS%|Ȍ.]/nɯW߂ٰƬC{55nc2ZiQv%T Ҋ ->Yyʓ8Le: s\Oif{uw식ݛl$KAzPTLrc&""b wP ry<9[>?R&=1aop`mA El.QJ3Ĭ#M;O~lsڎ6tA8Ze4٘&[|(0J0 - V#-\7,PhYF2昍@D4l O}3 vF˽ٓCSaU[Q.Cɬ%BKI."=NkT5|"'x9 bQHsLb2zG%,^dd/l!,KZ|^h $$-)zK֖TW0/SM{ӹb½sbtT`QJ*3#zf[Lbryz`2NM_W8x흡T -<4>!AD[ǛA8d{f9X}|@-4vʐq`M!ju"L/9\|kR_^{vK(Q](: :$h'(e6lA }R{.ߧj[ \Iv)bBu~dÂ.NLpgrbv1t]<ZȑޮAY&]?"Tw;rZi@M@ǞjR`vڇ"2b[I)8]Nu=퇫\GŪ}xU*^/$E ed%(t -b%7f#A7ks2rSJL2#^#LŽB(#58H).i[ /ʎj!m1wſz*5ҽ 8%Uχ߰jo >y [:9Vy}΄*HA+y0),8E[MqI1=d`{oqů.Z\K1xe&f 7GI/Jmy^ySՋ31GY{ۃp{-2kR(DÅݡdjӀ`+9]g5LmKP"`a;o~@e\S"\GW,O@ oo ¬bjZoO:@?[2L(.q\fsMcCܚs02c7ÖyU/j/n#L"(}ߚA^iCqhLTkCILI?ds`aAَ!gQwV^]ov $&~CD 9K]YMɖ]>:j-.v-煮RUe>A[aX}}]o#%o_(zIv9F/DoW~A*D2dqsWyIitoE8:$BDXzRn)&%gy:J>VV܊]KlvL۲9%igjNV{!t^d{)^8Qnkn~s3/ܗq,{ ( t2۪s`εk"#8rs;F ]CA8+B@9GӢΕ%<_uuG/vMfr RXeD؁P[Ē~Ɉ)?6Ļ G3d4)џ >%}[)j69N85j'ٍ[i]ʔ]q7*f}n?xin-hkgX0 ص<;Ü.t]JpwkQ7Zб\ s<#щ(χb"]5'"Ar.,?J~{v>"ʱ >Xܳ<{x8IGeюaRz'&}o< $NRFYi4Vafݜ}:de򳶻a+XטZmN4DM[6dF l1 oo;nRA Y>ui 1!N $$M!\cTTğ#k#'kҴ;sLs? `v#@Ί?t0a e)qGS5ցbv_ܩeh( ץ|jEX{Vs荎 `y+f KPsgYK%luPI[2ft1cW[=Ns3 X? ?sEY:P`iGmE5~;/LI0PiB$) -UԼ\b}HSKE}DͱPҎvŮ1bb /81p!rQ0F],V75U].rQ*ړ# q(S/@,a"^"\pK K{E$㓮NWh1N,łs.skxd?_2"{h!>C '~FNo'zquu(S+Ⱥ.tD{V>]8&_߄}XjDy%uuc-gpm՟iI{c.r>gZ?-"@RYN3R|CZUV#=UNO>X%'gQGS<-@6bl@8gs(FAyh9/d 38G*&O\ʪ>Z|Cxvcp6T#NVrS/V׸Xh| {DB;i$8g 0"b -endstream endobj 26 0 obj <>stream -H S۽DИ`XMlػ"" -"( U@D![PAD@bÈؒ'|^}&D&i IMb[)S2Ã^Z6; -v@7c%|MHth_e+ J?N;@ Sp|j -1^qC7~[K-`ϳ)r⩌h>c1X09NR݄ICh0:HwU>Ј9`!` 1{ģP 1.\\ aǸu*koyo.¡&$=7V[s~ '͸\>\)")3Ɋa+%/02ǚ))1hoYǰ!6=a{~|;]#F[Wv*JwFNNrgC#~ٟ$f+Yy{~ S)@STq:ͱ h]+*gM6Qˢ_w OGN;.ZDNd m!MJBF ]MɺM1K\ D8$v"MZ -">%f85f7v'*` yׅ2Z'J8n/ 9hiǙ䈥RocwUTɍ+,cu`!BUL9;ev6G5=?߸^eV5v_xYB'<[&i,AZ^]^Wb;QѪ\zw&>,nas"fN55n]OMwa}MͅRJics t.I>ÅE0/T\lE8T빚ma*rizkѢd Xh?;Bn5& Fj(xuEyCHUmi2o赊 6˃MKu32P`b$OOM#nTy9^ O / 0PgW -Tw$ 85%oY Z^+p x0cWV"@?Ϸb*lSVGn xwԲ g}:P28?0/"H+zzDuSօ<˳J -NfgV&` 8nD?VDt:9_T Í(%QFT#Af''m^r䙱m9!Zq|gH;eNUZ繥r8H^i-hC߮muaQF]ɶ˙iY>}e /~fWagg,B5[;[Tח)OWך&Z&TIyږ~"C[xj WV3z2RMLo'?6=W+7'ܼm1h<Jj%<7za/+.bMĽUK wi96UR[s4`o}.JǃەȣqRe2[3Fnlԡr2ζ+JB,#S߉Pob FK*\NS0q>:c!EhS2!ߗ]E5dQ2/ I*V45nl -4m3lCO% js{=p>b[NK%t9tY[kӶ~[j9I=c\ig‹iJ)., -Ku=Tҙ4I%fhc1& ADQ@EfQsAATphJ^]뮳<.M!jf *Z45<1 (% |esK۰9 z/˫SdUP`T%y<%mnuFi -w[o &b쪋$ &P&Vxk>p 3^Yg[r}w&ѿL-N0rqkG3LLݴ|ݺ6P@yW\ڏHQs'/L]- S!쫩9Q;\o]U3#ZVEJbs ցV #uIp^Pqx㐆9)+Tʛ_ w7f랺͙.YsƩ)KIr+b>O˨ޛhze9ULJL_kZ"vg}h<3ߜ[c45"2G{0Sewk}@mSwa馜i0/Abm!&=*J.*c^Z;Kc6ӫfFD~#%iJK1Ȫc3bC5%&X.@l_w59Jf4t+h ~%nk9YpK.9Xx4,~;2JID؇䴜ZNRs+zve~aboҐ_4DHG,۟-{W]gX(}_Ѱlh4!'%+$&uuKEk*52OԨt0b錘 2l9:Bj E4my\[+'CBZڧXa[cT^UR &.6faMlKEm LǖF6x&aގ!%|򣹯,A+x #ԂEWuP+l)1&-%9+(a1,2. Xavpa2Z6 2d!x@m0~ yd3 5!ǒצۊ_5L  V>0s6QO$ V[N54kbA&Fd6GwO@C.sN^9-ny8h$O mpTrjg|D5qa[>]zLlSAٗ`Aa+0+%"ͿBlc|8y?]xXxw -`?+]nQL) DgF>Li'yj8vjM KGh^q l,aw%,4t`*2ef!¬ e99׻DqR?ƤIJV@B/.1k} -^\gF:9Cz¶=)$BOTkSr|whc{R{a -8<jU<"ѥ]אOw&/Vۓ5YNw?ƿ1T)Z-ZZj4EcĬ+Cb%!L)æF^%FBJ{'=>O?יUڎs;ݤ%i@IxzA@YaԼBY.'٪xp{֚s*IkO&dUwo Q'4ur_t2\v8nGsֺ -csm5v)H]Yp~nK6"X%BTlfCyJbTx{>K Lڪnש|<^z=Tsj` -:A *ΰZ[+N2I޽(nv勞;~ R)l-è釪S~÷; -n\G$ɏ6$<]aǽ5^~F +\]Mt!])JI|kF'-reo>yv^R*Kfƹ9;5+2l*tyYi+=oHkT2}"NGaR8;jV4i䣦z,k/gw CmT}OmN'Sit S7Y1o ENbfŨxo;3i?9Uw[]sI+fc~}!4/, `zӡ˱J}6,A7rC Ҍo̫o>nV -`ݦHxk8\0vgtNP|XC_ӯf%JԾ=~^H<ueX7qc[a( wc?NƎ۵Z酚[tfܮH$Z>~O=.2lw {CԤf]ٙ{_BNǣ)Q?_ >V39e#E=$F@~BߺoP  BOhFr>VV/46'U#kZL|3ʣ*(OeXU'  &! 8ݜy;~gVJL{9'%N&yx}/=NC0 7{D32&IrSʞ7`q_,cc ->CX qF l <^P|ढ़(LjX)o]l&#c(Ŏc z+i 2yb޽6xq[j!EwuP֫Hsl욢o˨dh;IA>͢䫕-ۋp3-*-ᾞf1!P(-6"zC\M5}-8UkTCAn?`7:XFvJR0/DJ -qaf!΀Ŭ*%AAM{IyךRӢ|P:P2MAqa麋` J9`2 A`KZJ#vZ"$!? jb^jČEǨ nt1~UNgŬvQUNb jN@X%<_<~8 -? -`&ą?uR"=Hߪf_j,g+8y@EFXFB(5/3.?191̶ b:qVZ?H[/jߙtx{R놧*#@9< LBxanv vK\;- uRFj8~Jη㲠ܼ |B$$ Ԫ+p׀ [ ` b߃ZԣZj)^.V' -ӕ[ӿ@r#"@ p>;9n!`CKpnk |u2g;7N$NA-!Dh,Q*"Dl Y,^J;VuGiՖm_;<r3g9s6KƔsKKΩjL3/%B}Z:4q@ 0* <(8xEXDݜs@%9q_O}qv>k5VuI: F!G)9,ʅb&42PCx Đ!9? n%~~*z-yH=a]$9-jq9FP,* `C?|1^pO CXzep|q==] f{Sod[S,OBD !  -x!WC} `7}e{ݺvgV:NRu5 %LbBr(2_0 dF$)>DB~0<Nj}L<ޮ6\o;kx;~S*DK٨DdFDE$"`'A* z$/t?Njbo;IVfVSׄVs٨HHi \4gUm[_2۾E}1zmcwuk,ǁr/D*x9f YI0@ij -Jb+ =ēA%]ܟ[2#tPFGQ758,c&CVlp g-F)y[+H[+!ш8T -FAa8"D zT; d'pCvk;bzkhyVR.6O~52e84ZjfBftYlH~ d:JQZW)'3" 9^ -x^{w/hAs@{'j٨e2\Uu%$ii bs N bP080Q FַÍ?_f| ہIa_=5Ku(;(LmzTR,Sd䐆pR@"ߝ J0TLg޵}]xePjY/8\ϟ`)B72__ <"Y*!+<h-r*+b՞L80|9r>WLK}b*M lҀ7aNhD+:j]s {kAֺ]wZVF Ax{!R]|Xj' IULk׫š9GdW~5u=YTm,={yȱw+N60:zGYAV* z+%8Cu5Z?m;sOW:9Ilfmn{ZaSV]M\Eجv$dEج$ć/B1 *xڼtZw4,in4ԯݖ4&+ߴh7ƌHcRc2! -N:h3y^E5^f_#Y>Z(7xd}6~X?_L8[?;-]97޿E% "QNwj+̍q#p5;}a.).u__ݛ-NCi3j rSI%Wd#g %/"XP{۩co;L;-}dz?OM<U wʖGc~Eɮ =W:k9>Y fAYN' -2QZ?xfgbqnj⠏!:q ~]E5G ّelAܖe=-n!|UF]XsퟻeS'6g!!KmG[iϦbҷ̓zY:4 G* ovezON(Crq jWK o@f+`э=x"D' 7Fi]ALJeR:`SȞW["#l% bŚZi f«2s %M؇6|oݿʺ<65հluPӭctj7z|jX ,WPl5"++ PvҋT\<0ءa}ɟ; ;5/9GŁu/iVnd[HH#n-,\f鐉´#ǏSy +d̬ |qwl# i55ALjdD9–9>JѼCDFzJ[a.Ċdxңb M*N=a[xDo -Z%<.[* -B4T<򉋃(^`cW9Ԭжl`-7.wE ?H;؄nv΄SfZ;!|ABuJMռrJ|qn{$g=.A.p4"Ʌ=_u#lziZ1J^̹ȃ{S'i -W!Xy5x]Tpy O\0c]UVxU$a.bf+dWmrwƪ!'/d,+K&p*M}Vq>."^Ԏાa.V}٢1qn&`>!.U}I%sgfB׉ߕ_,ľ l pG+rEfu''ݱu/*{#Bu)"ӧ}k^[yI]rWllbM4ted_y@pcJ벟Ƅ+Χ]GY1ҥW5*SRW2f*NRL)gn^8 C̍ipz)UHٹʏmd5)Ϳ2pϽ䱜,g?pF` ;X ɷnh#Mӷw-ޒ3&[=`+(} -m#W` }v9wY+y)C@Eb\wd;)X&;b'zDyڅE7kp>0j0}̸ؓ# 8~͘ 'k褽)CaTr97҆~b2;' /. {c|r\#ŕѪZє2M/ TE,Ys؎2q2͟cZ!uZ/A e00}|HLRMNз -W6&oԢ:^MfJjy?VF+̬vfD)$4$Rbzwl{/1%20a`cq HOͧ{u?s~f$+ޚ0F -Q@w6+Gtā]8iδ4FTiH1-aF=cwq3 NJ7[@&e|[nw61Sq883!)~/"emsO!Y/8-ykxV;u#㿈iczܳ7VrCB#f +kp*!a 6< -,KˬUL?\7 SR ~@]"l juqrv@n{DuM  -~٫xze)$d̞?ڠ>.:y ѹb$(:Ȍ,f]r(ƍ{c_:Ǿx2jaljugq }7}98z~w!nDI*Y%I/gkiubQ+ -PsR]k|mEcy7ӓzJCFilԎ^%hU -/:S̆OaG{) xWE/nϷ+MٝJZWFCi5"l_I%6}M*kczv7]Ӫck"+2Z":ZcHCE7@ˣr%I䣐sLQq^TȌk1wj:nF'TL|l:cvꉡseBo٣x&3FOʌ>e@al{|3̺&;\TTp<shF |s@>5(r I]UK[uur-d c لČO{[΄TN҆Yt1ma w~h'&q*fމ&7s?{2=mM8uqGO61c"{X0p$c u)I+pɻ=dWȃ <l޴\Hkډk8*d.r2ߙYE;XT'LbceՇ* |d^hɪ)OCs)ŴٲwiMNߡ -wg_wmO{ͻ8ʲW,}_ATij7x_{|lyIr -ueg{㟮і-x ޣ65Q|*Rs1MA6̢lKOfG/rIP}JjMZ@&/~K[FVH}m|]j2\IYf@lG":txxmʣu~5)a \x]aR&Sp4n^5ƣBW0yWn`W&8:)glT -lGH98s ~ w&D8}}4< "#ztW[}④ r -ReX9"Ww[bقv _锠_F<6<^&5]&CỈАj<:Go@N9z!ջ78e -c^MwKۤڤIMXI>FDžH Y.9.du0&x:<ԯ$T4ָeoGlɌ!}*^~-Kk,$,-sthXޛQ =abƠ -_f_j2U~s̱M<`O xtKzJxCV}U.!-ÀOR,ޛ4y~tPc&^{n@mm۽ dYx$*R~+8_}hj+VƁޜ~m_uze"{L2 $C -3TEd$<3a>쒖_FnhnڦZ3[}Z7p%)}WQ}RZs}|c瑒J>\='׹5Q/B7O}gW&&A2\}pC5 -+ /]Ň L֟| -j&ھoᄍg]-vfnnJ?gP9[z\3 7h5f➙w3bq X wR|C )WلNyׇ LT<Е2{:Zc:2>Y9x7K -(x( -Z'ɕ^9ʹF.q`ͿϽ;̗>yԔuMi8j /T#~ۀN+Yal t53DT4r}"M(%TXp6n 1 ḷ14qpP]bW-?r&7u4k&ǂ`6j,5 m|9jWۥ6M09-ti8S33-eZiYM(;. [ff""(*. -(nuuz_=BBc[ţmצ% mŜ+dHٹvd]\ 2@/? =_.ηN3 Irد?vENr?2q-(i`пHҰVTY-BxEk.kɻfh5A雡Wz\ -VŪ4PdP,ߚ G,p &o<3}uZ.0 %SRԕyMD~q__Њz%f#|qr#&gc~Bc)CE!r~<+'η"-Kc쮘k.@OZʼ:}욪NJ7e}LKP/ islebcŦra=SLb]R&n[F_T0`>4Qf\aq7~dZ}}cP^1墱צu{u\ ;I'Y>=)FE/5\Q1MK}zpR0W{~댰$G[W$\+L4<@yuէc=NɉӅsyzxy(MRxH6F8Y_촋lܯ'L0\ʲ[=wL y<7s+漹 j9D1FԬjrh}3~jĭf~sosRYF}%Y61UܝmlX1z!e>f{UhRP!!SBGnu @c 8ܛOKf3N,%aoIHiK]J·eկuTK˽#}C} SlX[]xk_]oM鹧Ɩo[to7r7&ॐqjULJ׷Z%&ƼZD?S -wPI|X\ & ,#4_ՌRCG3?C1 6l]gN_an`t qea\ -RTO.5v8]  BSy(l=/a#E/ )!`ܣt `3g -C;J=-(H*]$w}Íc# {PvT.sI}zsT bfJ8f4X:JɸG2BHz}&m|.yln4VFM^|]j=eQApHQu7Գ>>ƀ8.*7A T&&gC~<5Ll63SB:GC(X[թso)eS|2,!gn -\7\7@5U^d]Zܥ#k܆f.ĺ4bW{(uZGvOQ>6X\bVńDk6sWAڑbak|8wԤ<֖|t<x6 "^h{~~gWNZ2X?HJe!c1wvGc'⑈2@F?mJ$g=?NO`(?6kUORBSFo+x5)Jw2>Z;מu6K:16w Jb2|[^ ّNU26G0 k}[bRCJ|"&#gGJ>⺒3 (tzIB]1V^ꯈBLgߑW;qǀN,쾎M0=t@Oye~02:ٔ/?6q^= TswՌrAؼ5I\C+YhA.t#Bj+@ !g?Vۥ%њ6ԓ UP˴0:uWNkYRבL~ݝOY -k :ζhP U3`4r)!70|z&ɧI 0N[`҃zDZg"jwT41w{-1ўe#/ N_2f/.-*omv/k]Ce'bOOZ ~%*l'WlL< T4>:yA=τԬE{! :Q~دnee6aO^m.'L߆OD*Tx+k-?^iE-i:5 T3ݳ4p5HAܩ]43VycsXLu9a W`r]U{'otݽK-!S$f[S[AϐdI ʳD ,~aD'nW6F8&2B}tSbgn c%c}69{: -6ҿ"jug\t"`z*bwʢqO#x*з?s1sg<.ӽl-ЖQFm,"[CN+ IБA虥lҌ=e=3Qx4 5dS#D Nc.3bzZGE(N>Xn_Dȡʘ4[߂w̠RJ +S7'YQ|ů#Ȕh> pQJlMi<lpn%?<5N'yӨѽ(O4X@jؘ◳ўOEK¢ꫫG*䐐J<׀ˊs91^w5^ie[c=}5?=R.Npδ:>k8UwPU[ . -#*fc@jݔ ﯏]԰37a73[;̳ $cc{ߗY^ea< [_u ږ5h>_k^pL2@O Lcڙ#幥/weqTm}e<9M;/⒟lOE96SPV*:lck~ M?{jXa3#:xtTn#ꞔߙ+q?Sr.7hiZ0+ϸl2P)&l @/HYZ=wtggJ{RS]ڊg6~W${d]aSx.d(Gg윝䘓iL2M;1 QX("EH .V4 -"( EpЋ(d&|}}}~¯n/B*zUD%u9y*_CzyWlk -A1/3IkȻ Ȭ*2ۯj{h—ƍV -t&LtL Qc{Mx\c-wOC*؛#x02 BzڋjRoݞC4[B;W޹׮qR;lg>^{ggQ@C~F0ϵ5i` >7T[j9X¾kiuvplCʛ aoea=ӇO@vm~L@nA_w`K=S%`~nj\m [{zh 5f ?uEG veFy ~BxڅڬOaO_q7 _e9ԭgg0Bb&>-n(Fyj1r(vSB>NS`mČ򁘱K7mvc>=ԁD?\'UD4m>%o r}TyIXr3PdNsԢ_Q3 |n-pVGdxq03<ҹM>~jgV'[6Zs >l%\`fjOcaW;xre-c+ ~2$JQ*E\SaդIz] f-%?+:`WkΣ56*V&)tkgSUҚȺ5TFɕ2!34vıKZ}JiP:y|0;Bzz_M.t>8`d4R$[=zC9*R<~sY =n_MWeV]@m5b , -?2k9 #023/P*.^a{86fO)C_5W<+tm/ tMwNܩ]>yjgVLYH[yOɾw⃋ߵ():^s{/l쓛E[os5-2"*9O=O] 'VE5PT^YӜć vgd_2`k7rTƅYR{jaHQ7P-@#N@M.M8``yІZEh7_k!mx~A^ctAA\Ufy~c db .H)𨞃 k y!M[o E,@Gj+DAig`ܢc>lGBpY󙟣!pC& -)6P'yCe{O;;GpǰY7W: /Skxg($eU֔ -ia}Im:fEa#6qp xK(MOX &]p ="2|ߥ U߫NOA6StܜK($V=;>HO w/c&gpґ3\~5"7UgNQwBZqPor`$,\0( =ձZRg1T:'qP -1iZ45 G.Xky$9LeE Y}fE\ĩg{2p#Fݯ޵<.#kIJ}TSu~c\\f/駶.ip6J(>Тrw-ڌXqh8uݣI3?32alOE݉.y%Hb\'&@%Ҩ8 - /).4ϙݳL9iL4hƱƊQQAQ0 -" -HA+QDƂ JALvgO_.ދ{=yc4tWՔc-6I`샌tO͠K*.$teVQ - =ۇHYbCk-k<`K=:x6uOEi;K*2.;\i#E殚zń%@/:տ@.- |">}s Nz{a|WIk;n9o~J ߛC<Я::Bt_YZj̍0LPhE`]jU|%n>vUEZs/i!w[jn=rۣ$NR6>"c;g),,1H˼_ G74Y-g.OrtIeG4g5jWHńF\#0uI]ޡuUK@Qr Ԕ(#s64ZJɹdB}.ڱA=6oOQ3[x֝ԣ%jyDϨ0:ĿoYǟ*m+IƌlQjs Ƚ6C/J[*c[`)Q~xay@M,4"p"9c=ƥ%o+ suiN91: 4n_E,bMK)/nRwT3~faR,tldZP> |DL~4 -ꄢ6>5L0]_J;=Y̤L&ND$1Q *6)R4"N06DDTĆJ5LΞ?dv=@zV崸4|QJsi2lU~o}ȡi.E<$mh{E|􈁋;\ޡb -,}@/`vb~T-ҧ /ԍ4#- 16eĽhh-j%e?mp]3:fyw Vm,|#B\*9}_KJ0ql t z\ilk9XYc0vFn+p Qrb9}{C^ m~(@@i`V?owo/+IgIr¶POOĘؐ.lDm.7©E{EOeJ4yfIs(U>=.2[*Ac::RSחDw] ^P=϶2]^w020ڦJAc$h ģa&U^U/y,{#rbl B`VǏ~;am 11r$"EnKa9%RIJ;>~_ ;Fٹk߉Qƶ`,܎ \'Em Mh_HɅ,~E9o[+Sfb g,o[tHJ椸>\XU,pll;f|ZR~r(g dzzjvK#0"(?+@/$-ye4/H/Lqh!>"ֲ *%'&+Q{U%AZzPW Ø5YY, u$ᦌ`do`z΄g$L`m1V6˺G4p83 RWM]M l]^Ꜩ9!; B|c*{M6AkS,zbGV.x^Q S .եb`vA/2`z0ۋ=H͟wdl7quq,gݜf޴I,w^wn%<ޛC1o3͈@CvH,mttuHvDi1gGUW%&;d+W |Db|Apw @%O (8wr_i>m!kب`pћ^X4z@L S^\l/}%$ސ\ye}lA{m]Wfְ9DNUUMPIV$e]Uqn%N?62腮S%~JY1TOJQR@&IAj<.OҵV9'(8Zg19d;Ɍ~b:v0'#r槖iZ<z wC'Pb"4gnboymȠKGZCDPt2͢ -HY0*htwxamkyzKb8TY(m=W+}eoL')㗒x7벊d˷ӭ~=5iP'KMѐ -C=طZpg>I+SWe5=r6c~-hU !kKlU>6@)+iI"5/h-vSEUrjYTv "c\kscm58*؂?540Ofp -bm9 8[SJC!N.{*S"枊_9DNkF>y1Z-k'%=^s,*|6/.xb, )Ov @5Q&r8Zv˙S55Xbf=:Ubij A|x^b@EO>ޥݝIڙLf$8 :~6}1`礨ś:)^-a斒h/Sw&ku>" 9|08b?][r`J6r;1rKIL)`1ʜ?],wg-"TmMإnj˺ܰk=NLBWdK-n -(-66cowz:p2q٥ ڡ7P>.(SJp)`;LIS:{(p_SsM|tET֐賹:ʉGڝP*nJ~[ޢ8]d) ^j }z:Xܙ$Õq^ZaIzaRnF4 N慼m#C-y/5Jw KRӝVP[{]ҷ=+yo{ZDmg^88͙nf9s氙CanrBE"I7T*fFrRKV$"Ͱgq=|}j:_q::bw>\nY' -vyms]q4WGښ۰! Dm.}hX!'Ӷ VgAN1J"F޸Uw!gYT7d)mM#5/Zn]3ɮ[VḰqTvd!OÆG]ˌoLE-s`aKmG)7ĈWׇg̿ۥ lꅾ97X/J#|k_yUUZTxtի!V4vXFZešt( ug_I[+P3 -$4я$AEN -#aѾ0ҷYҊ$T؛@%U1HD[]$A*|!~_EpO[&WjҀS|j(&*k@ Bh$(Gvh{a#_ЀG#/= TFo v|F_ b]]IW{JBMA5O:+ -raiir,n:4EexU|l.]3[b@,ݘt`{2Ji@#5@L=ETiJI.*,ޏ@P5pb_Z?b|+c}φ'Q}h ץ0ϦO yo!^_y~Ji16Zg>>֏;v#Ʈ pn%>}s`f;Kޞ~a֞,r*h, ZzP\*varl -#&xKRgܥ !y ְnCΖZBM Z.6S2j  Lj/Lt쥅tIyal+G\{=AOf<˪#!*إ6\Jޥ0r(+WA]9HNr2̭!*TZ ~j*zwn*:S3ۻ9x/X9LJv  G/gW$4 l*& 0IkY9FJ:5q/mܫEr9EHwk#hr ,mqF:0s%V -5O>55 .V3#vbl8f@,^%Z܋e&֫e]Jڇ}`}VL 1Hw㶆Һ[r\661J1aO!QYւ~fM6o W}5 #faHv¥UytW/`;-]疎:֡m=9ÅR"Fwe5-flcQJ}7#ĿwlS5^ _T - HLSτi)ƏG|RGYW;JztR&hHDO1PcUF+>__(i 7[etQ |k|]^5#hQ w*H&~*UaDžQTrc7 GEkc, ;[fWi.֋Cjb(77P.::IFn䁪eFY瞺VΪ 2Ҵ'p@MPATIAA@@$hok=kyl9Lcڞ:}&,t#pO3asPчF|ɮy5ڐ>D{<{si exoIJMes*`Ps>`GçZz 62@\UfI^dxK5;h e+le Z䱷gMz!omh2tO)  ^ec:CމV*rK/sHO}JL殖T6p'vWؿmڝ%-s'6 -Wo  GVKЀçok巷&YU%!&U7ץaRgdEl(:  :)!ݛV9H U8jSM c}cYZE#}3moլ: _@􃌴n Ubf͈0'( w('1&CtIvTp_]2ߑ3os (?u[%]fV5¦U'var.깕f*ܣGjFOJ]-zMjoCEԤZDp=sGLi)"팢yGO]m m(L9|Iؙlzg(cC;;ՍL?!"z9oZ~hǎEWf[Q<(Erccl91ͩty~ -rJOBoV1 -f6f_Ǯ`D9W NMRR/{`q4ȓߵ¤nqUq߉E;05CtMً=U㔜sh'u !5"?R)Eo }1ťwO3- EgLi_,֯6QۉU M{FdG>qT:*٭;vwjɓ[`c;"hRUb@כYsDJh [g"u3Ԣu2&)f_κqjj6)=cc+ wb0 ܡ7tbwcn ۸JIHRDޛk 9!pr2jYr/Y=5J'82l0:>-ǧ =#" _ݜ {9Xo$ìgAo6vcnݾݚF(Rܓ8WG*_b`l ؋]W𫷵|*7Nw2LC:?ύp __{̣C]rjq^SuħDE*0:C_˫Xa VQ -ִ - [XMA=F@˪@o9)4ؒ4%<ܚ [$UTYNɽ%ISk- - whbOVZ^51uȥ 5'"20&E<!OHGwmHC |usȃ0k%WȜ:qy<jz]a21@35%&TxC 24*]O^mLJ,r~ɴ7Fx!ܒ.MSk#/ﲹk憎Mnu;WxCȸ dTg`P/:bI@Gx&cBv,5L7K%wpSI?q@fػo/wG% ~{sew+:Ɂx3窎r\]SEiM7f襀/~i&ڜ-3)ЫZa͓  ¤n*jWN :?l.85RieWH<~$xIT diMx4JƆDg]XDƭp>x{0ic9 HY'td `ndK]F.:$y+HAJ;(^%䲓Rw(ȵnQ[X Kg:&Za5Nթb[6`gmA<Ւ#5ȼ.i.Y$8sfn59E ƽ4L 2Q3Fp&bJhl,Ke fjBJ|nd% Vo_fjX94.q&d¯g=8Xc~?BHQ`9p0_WAKm/3:bg\v[ 36lNr/T-YRq*BY Ə}dzxy3CxL4B$VYO:9bzG N$kET < -v+W>-Y/K]5] ίw{}k5?@)U"r [%G`g,MF˺twI(Hnmg3nr -^%mRO# -"YoǕfXљNj42Xi/D9HX RP쯠87TbJ#۬.&[QF;/5|Z"[_z~瑅 ̍h?Yt]J׎p3;ʑA9 :|u4o*٘=9a|peNvIVTt֮BkYSUNf߁%N 2̽ * ;vȩ}xEX+{etu7n]ELZjhS} ,@] ` RfpЬ^ ;m=|.cJP~9'e{qdt_P|ჸDqdBM#bF= 3ՠ9t -i[Uաub cJg4ёٛݪTqzрA8|-ytDZ | ] ~;OHj9KMSD̋.|^VAW ޕa<P(QHh8K~Vu&*abyT~O9C -{ 7Αk5yY-W)Ea'Y0t5rP }PzH°qV@+gqay+6uĽj_2RkAs8 h+Bm#I6o>EoF -^=2KfJaM#mfuz4)Was GF/n.%5F[s\&>m$VV[UԪFjJUlm#lr@B `0m5>5_l U*mz<='6f4#o*#w<̛ؔV5[ _g_pBy aJ5sRFThDj?w=7oreJc̈xg$9ZkJY*AϧS ꥣ:#!NƕQԍ:kń.ePzưx20s -}3)’jT ȡzV7*†C2v3/ Ζ~zv/:١J]plx7R"^/cyyj;9}rI[ z.LZQ7Ȑa<l%gFNNFv \VCNv+ps+~Cp5.bp!%H~ֶcnWJˑ6*} -Ao8jI7N A,vNFȭ鴓W&)"uu$М&̳}P)*nZ䘋,Qja5οؒftlxF#B[\NyȠI*qw2VI.޳=ihK ү]Xщ@h'Z4S49'/k)ir]tdlOfγ>fހ't;1.+X%vV,꩐23QY,i㬗(N *͔?-=(p -ZҪamVeOI1 =*Hft(wQv c2>"6b*u-(XHb/F= WneGY6X3djd[Eݷgt8ȣ7J^X̡[4 -_ Ys#fObzHhՈvG==&ys$%j1z 2nvۋ1оIwƛEcAT6'?AʙlORSW%$ϳ/f4p8ôY?t<rQEbjme$]FǚHiln۞hyABLG~DԞ>],91M/Iu㔀A!!ѹRzQ P"L 0h \uYdo%`s WqA̰4xM.IXC<(7Mo̓_>=k619,nb}dLf|Sm4{Z et͸``̢].-HQt*V߱0Uoo,<]\ f\T9r~yFP.`y7` Ei{:QQbjlN4 Q -Y"S2L" k*yy~y~deszRYq{ɪ]y\ZU6[&ydjt?7]-D?tSnay)]v+f3aڙUժ]ib铤y0[I-FRCvX @G(Uf_lj?zebOWUû>g]ck$Ө| ?G5$&QhrJ9HM*up )F"hyLuuvJ'X\z TNpN~fW#;@#;c8xr)#筼]Sϻ.|7JiW: G՞NuyKli֔Nۺ²t.|e: ,J|-ؿ)^ܓjm7R7 -srm#fn5=&4UU#3QN{DZ0xZO oǎݟIh|^ z]`TY291Q7;OZ9ءyNj7="S$TUCC vӟ'=]\ޖKWƤׅlz u.nn5Ðxx#@3!9x AQY E l?f-4gL08k)cTn.I^c0kiO0U}VsCžٙ]]4UpĖ]NSݎh4vZW 6$@ZR u5 x‘иI{KځE~ OaD4cYu\H~Aq*HDRwɭ 3oؐ|FhYԤ^kٲۆ 8`$>;ƐgqmO ht>,"oR ,9 -kT -(SQw-ܗMdoR9bJhfts04^YרKA׊CqJ,ɝ p ̋VCӪzE`6 ?H8L~ <&aUӰZ& ne @ ) % CP!knӶfGQA1cT5tVQq̭%ٟ!J8tZQ䕌<7Crw^@F9RC^zv?tqǡS uxm%j{LFL&B^e@2$g@v)',RV7O&Od[ {̉[A)vn])ʖc,~H3N -sZrA -өw_%HBqSCRXJ#CۋoJfkF?VuMжPSJg!qfm\x<Mauo>kBB4y*Z| fE;./*"lŴm?}7h_^AS+L:'V@%t3, w˚.V`Ƴ״h7X*zj1Y휄rDŽ3Oܜ-ٲ ~MJ׃: ϩ ,d+^{{}֡d3%;)MF I9Ը1y?N}yxN` ZC[4 d2ӛG vI*}!G<|c 8'qJB]GG´_ڇSFfIĊ͎1N7f64L!nN]Y gW&`fs{NC{?Em)JY4{8Ѐm1TMĔ9fJX3[k\F/.ᄙ 8`sE0!>Ѿ )YC (zDdzJeQ#uh:ѹNե%Sg+;dgޠ+xU#ټ2GB%p⑎4Q1yEوk.P`0U΍iaK-Px#X^iZ2nguQ2^N^FI)~2Z{6!vX|d " -eYmC][pPzGmR-Oݧr|1cvA]A,ndMDC?*m\$+絙1naցs#`CpOؼF#6Lp.nGEf'SJRO~~ }\ ~YKj~ү,*Pg:x*j(j" W2"=Fj9|P":b2JsFkM'Jɶ!qdăv=MZ. w+DvHOާUi<#seezBƲ{aM. r^feVdګL8u}IȀ9nChiEM~¼3-=[.WM{\3۲R9'-$)[s[7-2.4!:c>=)5à0%̏YɊLtI&НnyaѺ 1Kp*ꢇ߱V7BZ,z4LfaȦlzͣK~ANJ^?Ɏ/Q|X0μ`8HF@1nA{خv.@W":ݙ ,W⋍RYkmu#T/8BcrRǚǚH|%6/G1CRvڨN 2;*k̖C+C=+L*'Sb '@I0ęN 'w)Mw ܓ3TfOiZoSܙd:LҤiLɍ1$.q#(Ȏ)IԪ,dU4i\ϙ}J>XH>C[#,ҲhShMvM {Yq=]<ϯv3qC #K^62d>?D]7ܢޛa黇 -Hs4&5Vfx{ r@O;>73^N>d.\&N䤎㵙=BWL/Z=';ڮ @ebVs8WNPBFǵt?lɚONJ؛5Q\g\HT40ϣrB˹>VRY#]RjH5Ľ ;D7r\rG@wتh֘60QuS959EtؖބWf~ߥTWnƘ8UO4ጎؙV9 kUp: -a?FٔC^&\𐼕_N#f4M9.W >N`-YrpIaELjΙ QYʨaϳZd]Dq5%Ӱ^Qe*#%Ƽ !#Y -caw -+k"k$Uc  F2&\ǙW^Dae4`#VStcFOr03-wk哭93^-&d(@FjJe I ?c 7_4䕝.&ۂ6C+}w#}w2W>:=ƴ0}e 5|Jq=E'zS\o}ֳlȚe7}"퍼LAP/f675,*si733~3RG2:&A2م~XGb Ab 0G|J^U OS>(#=ZwNK 59*B=>!UޏyoetU6k5q=3ٞ1 %YL:|/&o^l('ێu* d?e3i,k-oWJEK@}AAEMLKd+&#l悘N3x{~?W@:7HNE|$=꺜C 흥e8dgp]AЕ`aZG@Cl4kڙwg .g@}aÂ; ~wLT𐑚cu3ѣ/vSa{H];"ؙSj(jwvNR`F]Z#[e0`Mu2bTӀ7G/u쇆N̼ ̧fSɵ+qϱ2Ou !vkm5<*A<==IHV>"#1@^PKd{mIؿ(z)i)<.+It~ um,ەRmp^@j8=\9P نiU^ھ`_8&+OtlSv#j?3ۢ(TMNl_AKxЁ=545ݑr#ro曦e @y"€\S'% MGEҫ;T$=>Z깘Ruv7-^jWl|FEHPߢMmS@, SG'ͭߌb+sj`G+  .H-9:,TOpcO%@!(˙XϗFm wscl~R3LKϷҬsPWkh:"TKM(ЭaR̩F@8@zuIR~}ڇV1w阦ç:QD4DH@V'^P܅"n9z ~u=22R_1TsY7D5WL -SmTQ£TLtCɏ 1~i9.uC32Dkt)ގ "{17dza8pц7`!+?5ʞs2Zt8'}x c#eOҸs#՗oJHMvyblj1Qҋ2lK^s8[ 9[呴K!M 562|d9^0P^,|Hî%~lN"rr#K"āo/v05q#8dDb$ZAX̆(|O]W|̺#.=`нF2HwϷ2|eaLM #)BDQk]?v;;>)'^陸C-| E>ԃ&ي}bp9V2§aif`#nOqSZ%~ꋕ biazQf#髄P}{[gqZʁ6,.}ҝqk}] wlUQ"rKri -@Q7ZFsm!B=&+AVieKE~ib:t tvIn@6Km ,! jēL7Eg1neejwN=LcM}G&̙m$'ǥbx'n9/38Ԡq.bg^(wUA:C')@_8d o3¶ -cl2\`Y'\|@Tt3$gˀw"P[ڑ^m|ʿg@Q -*%'y{5C℟,յ푒8x\׽516Er9̩:H*)t"s>e·ȹ(LY!gs:RU 6_r355in6%㾔?2ΧnB@w_4Q,c9PC{Rmy!ⷕdg>V]m=9YfO߂z>ςk"R&:z?@"y]iy̖'[RkFv텚\=RK3bX"eV3mjk̂ۉ7ڐ(;@mcc |9WV|r)Kpr=p{C𝁜#-vFăQfu4KtBҝr{aa-Qnh_oH3 xV`N'2v?s;.` d~9s3ݒs oy)h7ˍm}c童{r* -`\=h] }-iS#:o*3`s|A{mӍv_Az;iMBxӚj?ƉqHF1ҏyu2NovpOu5`FaypY.t>SУN'hQz0V$HՈ~>T:`07B̙𓱋:Yp/+"hY~:८N{ֆxtܐˋ"ncuqV\ٕ1cjam"<A\i -ӁYnFVUŽ4tML -G?:I -/6.z^$gbZ}{>B es"/MtfF8>V)9:H<φQ`ո#5́0窐Vl>"0ǘ{HۓwVkjk_R^˱3̣T"mWNOLWsB2xaD[YJr𗹪LSD۟ȆsDéC%%M1{\ymy47H䅺lUğW -u`6xey{0FTPd񼈵Nߊ;j*FLGڱ>2w]CTY$2_:_pmrn͜(og<]fϋݕ%>&x ~ j7 ~paD()QCDAʑoc[3DF^bo}n¿tboaFJda32 Ʉu6=6= ^:1r8) \zAwx6B=፩3b%E@V=TiQ۽.h"Աݯ|W1Pq_Zz!##vΔGo#|ca[Jq+0Yj϶цot2v"=e7Fjc,3,o%11 -*|sD ^be(ߕj?zu ͸4߶9 ԑqxؙ9&ginicVu-2>%:np^6f4ٛj,C&#t]ׁsU qmKR7| -ZkQ2=ems݉Hr/7V;+k`(86x[ODԒ>DzoyةYv`ȯӌgA\$ͶcwZiK$6a,:Vs.ܚyG3SE;2*ꭋXi- s2*\,vd#X!,,0//=ײ%BJXnE 1ι#o-_]i>d- -asQ~ _B+>E~|{N4C7;ƎCON²GqCbnl칖Ot!vFM=;ʰ |,U=+[Bɹ%JK؛@$;E5~5!ӯi%KM}ޅXrl"z{d]|%堏d= ##|*nchgg"Wz_I]я@wQi775ǹ@z@Q.1U' -.H̆ -9rA&]ۣ`]~8ۘo&^tÒzlrC>- L j}fuMx^eǢs8 ->!# ZsU#gxTܝq\ݏI:Us'| ׿a-0\֬ևfnnni6H%r6.c̳y -wbEܒ{R.$ԅzvn >%](W?[Ne(y S p&B֌27v,TOקaa`U s  -wÒ|mf0tJ8n~(ztcU}3LLi ,%c=$}i/OBSmr{a@E -a#i!,~_C4oI^ \-4f_A_.-ԭ]oqigzzGXZieZ&)!ҋC9ycfj^X!A~C#t](:FmOί]YerB0!6K/ vclDmzoA$:B\5;dRP?IHp.R(|w.$ɓɤ8DI `E"(łhfb E(A@hL&?Ξ_ZzCW bW[dO k9Q520N ϵVM怊Y1m]͕Vs^`yM!£SQa挤MHאlf(KyV܏zxԄyI+`=twʚGؕ!s쑑ݝe!R䥕?.g>Xgy+aavg;rzn&Dm5~9wgEtl;*c$0z6&Ύ"/k|BUכ*nDR.V^ kŸUS_!>ޛŽ /$65ܐ{l&$WhꫛqSx_+&ZkN|HONBM(^ i;{6!5fp9'U. j2?Z61ȯjplK0?)98=#@{dc?y!"jqCD%F'>3hݘYL¦;9gMH?Yf"cJs1c9ci8V.a`VdP3I_8ozӡ^vRfBviDB$|LPE(@GفyV]* ;*IO}2?ՒskNzXG[?mMŖ&֏ kd"<:dw׸ܴ ڏmIo@Ϫ׿9L&"_J_^(2ߒV3 -lOu$$bbA(_B,tڰɕs)qѯh:T<=22JF^_Ez%{j(8X mgaʂWD󰎃?]RP,0Kygv'Pw| -# ΑY}*`D樱_Ӝޒs֞ sԤT0w6972 }9R}rBvHÂub SH, '@.3Tsk qeo mC"؝kWrnYmVHM,8]mpKZԯ~ 1qA_m -/y /o@.}9KV}~9\)3i Al =Jq]gu|ȷ,"z -S쥍_-qƯ:Fe7B1.lI*wew}r,n46O"2BaJˬ,P5<8KLoG>Q|DvG)k/j m7+J/u ]9<'UФd,\RPc@O>o!3ܔhd)mea)P=*?**bYWߘGZN>(6,! ͗ËJBgXbm"?XXg'_KQ* -PBa[M.{c.ܡZSkRD΁WSKX΋KEBYfKN3WJ ~uguU{duV8Zo ҄Ք*~fb> cĈӼi:~<`lN -RZƁR1 Ix`vYNZ8&߯Ɨ4$hAd69wNaovK :IHChHi*,!MYQH?6,OcA# Qu@N;?VwW68bxҖ|њsg\ b U::6>p}%%5aH>JLKJj qMGQxْu=LfX!5%\=4?M/yn,M# gf7l}G V$LZW ˻i]`y3c]s@Q| _y!氹z3*0@_$WD<(:G7В5 -be}KKdŨQ@ؘ!ʀM#+ Yk7=7ڊ;mP:Ps{ 4bC+ʸƷ!aF {<X^벛v.ݣ~&D՛#ȼΤ<4Yhh[,,J£lL^H1#:iⴁ;gTmZbEThx]9oM\}r\3ȏk&6lYRAXEDs8QXUH\aT8Z}D,>q[/%b9\@KCjX}:'FLw4j?FVW캄ix$mGz·N«tG^s(;i1L +߻?+3?̙鴝̤i$l&I]1*n; (kع"ihhP - WDјLg̛/>\t%nobXzqKτsK?W@%̺ސ2Ѡ7!WG0T[2Q^BweY6p"3՞k{VRKb ب ,&|z~%_ Y1oڃBBwև' nΊos dĖ&tr0iWlmPkvBmݛMwؼ<0`;z vu{ c%&ΨO[5[o]";6Ey4Z@GN=); jǜzb&󆐳:酰Z7k@9!&lMx2Pbgq; lJe~ n?xB=_UEjzª9ZHI5ռ*Kfy?]íwb W~5bOͭ\uaAE/N+o@'Ä4Rl&O$bRRyaeɗ%߸0?$ rnPӉݛfEP]T5晕 #qg=O%B3miG$nzU0x7Za qm**emnt2жqCI~3q#&?4ź4sY2x{79}x%dݣ 0IBxݍ(ipcě5Qc06'8l :;Q3NS1 -ʖ_*C# w&2cU$|ð\oW 2E66Î^C޽olOx{;< I}GUfUHXVN٪hL)NηfohȔ=Ȅ)ۋADGOͭy>:%G<^5^ؤC3ˀ6*&E5'cUk# 0^XC7YqrIgb၍K(Ausξ:^ /2a 6%>5a#FA,6nP,!&L\[QX׆H-p xUQSbeݲ}3ܯs |6|m"{uHWKR"A6[DED !Rx闡5Ϳy}|ignz!{%Ow|5dun!zcv@+ bw yM1B {;y:`BY[.c԰IU -wr`w(y>dg*ziG8bbV\W1ڠF\" Dps˳+_w?쇝dgvu&cƸM@**WJBIeqb:W @HBPdf~3s[wۜB-KH ?:.FC3m%-Ҟ3W)V'I?n|dݞ^ N-RJϮ˛9M%SK9Jy zvUdUYL b? k5f7^jF󂆼7眬O["Ɇ7i洄zV89!zRO[":)o ~*gWI5D2QAߞ컽?C;A|r3lJQ"g_Uv>v_5RюҸ׻(j=L4K١D~"z9H:C? iS& ;L\KeG=v pT'CAE)#!a`V;˷'`vj6HZx-=0MOJ$ܢG* +W}bJ̡VcAYx$f7C\q b hy˼Tk^՚K!H{UQbY -;No GPB8mxif4 䈞 Q=Rn3Ҁkr!!gSBc5^u~IS69|ⓩ~ہe>|zm!Gps|UXxM Oh ZG7pg*%FP1>FDY1wO9q$Qp[s 0CUCӜ3ՖYAx97rv̭OYYq= "Vh0&# d0~׺?4 M5t!eTtB\ :Q+fѣ@ۓ߆f[ keebYt~?ks _ǿUe]2Nh<=i?sy+3[ Y5#;rfڣte?c% sPC'i=ؙ|X7CsPUˆ!<$3 q5"V5/#f%`!'-dYF]<[26m)|9* -7TZ?/e工®!BOAG&oJo}9Z0[1-hv)3Z3+N5ŜbF~Q5oOw!=!n &b6Fotݷ[wT@WҌ '#^4ca6c([1+ n!د4@]wc-L^CgsͿj?GNv3|]O.+ H&*ןί`g&vˁݔEt3죌Cb>?1:""bJ&xzyU:^Wr.v#{ʏQ3I󳁸@h!ۖs;FfuȩMn9tSɦJsIIh3V5"A$cW% PTSXJQt@+"z9@ hYzwb~8{]"_8IͲ'7 1-Rb+=-.~C%q3f}\5I v p"FA3fbF=nm5z7z~c%`֛}l^$Iʮ=[[cYp/fWM8?%4ZЭs@YI'ԙu@m'[r(eWk%!dshpCE=e{mKQ]omtMʦlR!VJmXep[o#KqTrej/' ž1e;/ vwQ=Av!yCvQHH=^(9+:v)8Ek[iGs#ifBDP)zMjuxr-fP@& -In]!=(SL,!c?ά ̞J8|55}3USS:NfRY+t4K+Q8,nQf&n5@@îhTdVMd8~ϓ z"f*ߖUb`ۑeՈ݄JI^}*yxאɘGsBĽvG˫7 sk(H<0n`N^9#'朒V3]*(ĽQq`8Q)o9.i97/~vK K00qzZe -bFzޭ]6r2IZzM->QD׿L"#e} 5©Lܛ>1cxqwǬlhսaZu38{vsn?YԴS=k6!Bb. D9*Q0.p9#)q(JTYϨ"iz"Kk[8AYQ=kzq{*"Mljgv)ȺF̚oR˼Բײ?G^=JAؔ3陬o~;.s|_v! -^T=a/HF =ad5L|Ȩ >%2 ~tewI`n?8"yJUeN{'l}TwfШrX:4#dmŽ[(fޖw| M J fSNUsXYߐϦ!{嬝ޔqQQå|`WtM&zyT,=U}ᜨc5BB" RԧF -[ ^=8~6mvU7Uh|p]>|]#_> _BMԫ'AVQiDG|.ڼC:wCZTϼjFqoH.8Z2^!1oNEZv8芔XtBMܓwJ(%KSJNZ`FPgQ2R69y-r3 QIѭk\,B>vЧ9?ag7V^0qo2NV ;m(YpWLɹY-q1/D6sY~L=Gޚ2ѫgOALBObvI't긞 -/ʻ`ݸ$%IӚ Yg*A\R6b6.aM|l $L¨N52J6 5:;kKyجMHCC$whҊњ?d,IDȪYXF:. -Z!@?a&:cLiLz5.F9ә~ZD]^]'n|pфA|U圙t 2&6~o<~D?2_-ѫu"98j%IY_@ΡQu܆sLd*e$&A־Ϊ:eGi8F)=?݂v --1nP1J\wL7_|jfFTYLxek| - C5qCJy[=7z_1v5E H9d0faglJ)lVyeK&~i@&]ZnڐXhA|ҩW:r{2fFZDC< (&FvJ`U9ߘJ[Taxw-+_.'OS1)&GܭCDyePG?ۇ b%loAR ޠJ;dܐNDz7O+ šފ:.jż#ӬS8x=92KH1={e5մRw ~իB3[a;Eru%jC89իZ;g;co615eK}(vh0sr"a_) kI8#!M-LUaՎ@քtQNCG_F_BbZpU< \tb1Y}A7z`8WйQgD_ v5Ħ8؄0roI*#v9q(q+\ -F\Yu8!ǎ'yb0kDqzzO,M2ja# 3/e*6ゃfR6K-Ǯ\wv19cR.!Հ.Vs!6>2^dԺ.?@y3С'^D>6XؔEƁ׃! vOecOBr.eWăe).Ӯ&49ϩsNTdl* K듐y< :c a wA aBs̟o_~^BxˤҰQ jFMXh̒:Ng@QAG;vѮB|fQpnQT{~UլՖÄvigQ o 7ҍwW?K+|LN<>Ki[oewʔy7m`GEZe\+"_KZuL/8afTGV3[.f'/T#lwt}w{#iau\|jg&<|d<< 6 _T;ѓMhČpu언M{8?lN7d 9Z:f Ӵ[+"LMiu{2㐈seFc$-"58]5ƞ\]X7,B|Ҋ0 -뒑SN LG%hSCf;囯?k~?bP{Q}1K ìE -eL -`'.)HYqZBaƁr} -TvrnfO;ma\-k8-I қVKɱ{7mcOW1-9@YX"$lc]o~^쾖6B 6KA4HA'I+/1 B(fD5uƭON,cfHe;EV,V˙y* .3o8EmHcw$kkDŽY!{ff y?e-S>ZPk[0 qqzofC"k{rT'We|#YUoO7}<𡪰מJ<5n jvsDiOY )froU4V1=+J%ghxfylkYs/mROz?}|FJjX6A.C{O"sრS&+;IGRVD;QD$t3hbJjWA5i}EwOwLMP^ZrN#;WEYH{sɻu0nks;Yﴯ?Qt~^~.ĤY+|,YƌZ2RҫGb{V<]2d2&&da5MrOaƿէ͚͹Yy؜y.ى *"E̜j4PCK %DT0EW@\E=m׳~UFɛ$~ -]ه]}GMݞewW>weEwί)>l})[)hG(D`Q(3_,M> a3ZlKZWԦ0dzt }HGHɥlJuMVSZFF.9hc0nߙwI >8Rr&H'@yPa^! %$q4Z!lwYz̕Uy} 1p袸/ --Mir_ǧ!ѯW(NycJY53_Z6ۀ> ZծUAE܈YwvG|gP65rtU37;U':eWf69%M, {K>E}.`&l{75){OLC;;)9S?qR|k^RVm;_z۔=%[nTYFjn'*.[ -@ /e髯rrW15*<]8r-UkDu13U8%!o\$.H_ /,4/͋ ikP:Χ4|?ZhjzTZLji9f!"X]vdKѐ~dR~#C]f~ -0dO -endstream endobj 27 0 obj <>stream -HS[kL53i;j$cv$Ŏڟ!un76j_[!MvgZOf:L>ܟFқ}=~}(P4h@Ѐ"E@ (P4h@Ѐ"E@ (P4h@Ѐ"E@ (P4h@Ѐ"E@ (P4h@Ѐ"E@ (P4h@Ѐ"E@ (o[B3-L~~zV -8O8!v]h@Bh[3A@%0\={14Q+\%*ҸMK4]iҔ ;OD[+xg083 A  /*jf%W5TJ{%l2H'͉ygJT;%xÖ_ Q]JU|fWm֣*֡ې[ӚoA$qs"]`^OG&&|FZÍr+cFQƧHyM)Э뺨o3gLXv/c6S.jGIvMv)JFƸWx%11#ݰ2lϭl"0SATrCZV{̪ cRf Jddz*$o$Wkb EE$z*jcQSw1I[q)ȮADM2*bB}>+?6^=^w8qg"hQPano:&^d-$.HGYq旅t.qc9\sQ -Wud|*t\ejͪO4s noJ>J,(?#1FI-{Og69Ab^v- ×蠸 -fC=C353 ^zvzhIXE<fƯ<ED4r=M eC'4lF/oMwwO슌۩+Me;t#]#s72*v}|qrkz['|Z=khܮ%#fi3Ȱ]ǭ/7w1Xl/hrws[TmWʭm'\ڎlXߛhFA:*l'2~g{3+*&:fdb ̮K)(fPf bQ1l"\+x:=Xp~<s6pAvna"ACZ&4dIlܚHf9頺"Ueg.N ÕQ|GԊQ(~?>Yrٽke֍㍛ջSdGYͩ)s_0F$sNQiQ[CtԬ`_!Fawg"6}mvy{ 7-|xקzn?ڒ]9XWo|g_'\̪nLԊaY^,ǫV-Ye޼>0} ~φdXIw?(bd,۝eTyYfʩnKmBD !@f $Nwe4",! 7BȖn7 2yus}G -3ԜJH&6Dm+˿?ZVޒ=dM%UQK}6ѬG}pq"οMT\ܧ;(<=y0UO| !wL,,f>0^h潉{-B«. ^Yh̳y(lɯJF|Mvu>3_~egRo8K7^#.< d @Kx5nԒf#R21 ꫟96_7T YMަcm%taS#έ+1$i36IMj,hbAMb,`c&-J?ƝT¢#5q1 -xdN?augY%mΧ=Tܼzs&a~Y^`'ʻ-Oq3Q @8/i$@7Han)%.ԎQ~{VX97#i~-k:]/fKE %iKɭn2 9n!ꁼ߄dHyx=@G!]~^?RЀ5/zE-O sX껙%R~+4rp|fn0?,]T}|hi;w_3mL_lߌ?[5Tئ[ƑZ1]} _ݝaiݽsՑKvCؐ|tCˡ-qlEɈ^6ԏTB&IfY ˺*qA;j,3Bvt orGj/NSV)h-|,hc~ E^0!)e֓#9_<":~)ZH4^KIWRĘ -niG5bPԒԫ[z%GxyNU)yTФM -2b~~3VV or^31:jV#F4 -qȿRDúڎ2)EtJ9T𰬷D9w{^|uwABdjm5tj听q=Xp(lYuLL<,'D*ef2*lj̬':W(x-5^=wC쒴hYYXn*<>-Y^ܛhz ->ò5?D44 -ޢ2`CVD@yh߸H2 DrH7RI;sͼW~`Ax |˻h}ʶߣK6> -K+*q;[5篹eepMw)e` Rp..s/F-w,@(%4!6z4qyƘ=\bUϙz|8'lؙOH~~*m20K&ɹE7EYgT?ĖjB+·V\iD2f]<}CN i4mVQzS9t n.qm4-yW]n :W\X刎O#"6: JB@ޕ(fJԦ ;ID**n3Rm2iNgcp!}|PIN<-f:eP0S_ F92JO*DO \-,y$7Piryg9fRқ^TϪ6gm:A2<`S2(Dtb]sdSO;,9A4c+ Ut o֒_z +9eD~S[[>ֵ֭ūAp%!\QVW gI`rL[&LHPH?y L3~p?32d㡑g+NG -{.*~>gf Ad6_6ނޘquEνyZkYZ8njQeV/VUB+bǩx*;N;M3:o9~w}]מH:4:y{Y{gvgsg9ܜ꾯Ԣ:P^#:6fZ޴ By-W\I+2s=oT7v$r: .Tez/Wss/e'G_E>73ǵǍ9\~O;*? CV)ȁܬ -6]!W9pYy9@Fl_#"zPy`Oinpq_(|2qZVk^?K r6f),kȰZTi 9Бo8aiC'elYy쩂5*޿qzhWaޯ:)[f+]pQ|sILj!sft_sqA\%B=U,o)1 x40e!k,Fl;c+F̥N.rNWfD=b.ꀿJo ./~["6nڛ>K/YliTHg[Vs7OoaKAubdx oA.<ơ_Nw,ěNg#u7w_鮭PU&IN/$zwF'_nLu؛i9k>U* -~ 9=,;韘@WXBVx'# <~*9y:ugM(E]y聝[-Gk~"G!cK=- =LfBX^C3@LmS/wk~ygޤt87w+%0S͆]ҲE\P{~ kQ6{$ַ碈A,HNC="nݠdRw1n3 U&,r~mpfS.O)fҷUÆ)v8OPb"{kڏ K)'Ttm&rQ+ -ndcLabcfVrd7oV=QQfX1@41 RW]$ȍ;+FLE<bkWie/gWZ;Y)Hlmgg$J~FZ Q>bgrϥ5=&f!q߯O):8B.:+u6y/xe~"A6kzG_OS"/cj?<df96?clmϬSnv)ɗx#Ă~LTy.;bCNb*-w_^y7GSuuL$~.9M~GT1W܌LS_bZ.8׭̒77m -HN!UnIq-Q}15WNk8*~o &vdQgco~c3g -33/Z1A+„:hSC6%fZ?8]`c.\SY.9d^ݢ,Ƥ.8Z6^j` 2,$1#Jн>A91CJr|k= Psi!?}9ȏ}?zA!KI Jc"!Y-[;Go_p|ҬbpKd7+iܽ Bb.;OL3lbP al_~8 墣wQ -x\cb6-gv3,.t> .tǧ -VU[8 -#40j).gVV>#Y5KVR9H3ťoq}iIKCfn>8?<־nY5V:[^98 -&r'ݝN:I8uTFQ/ 9;w 7!gw; _OR=D2ye>8GG+tT'eѤ I"`=‹[n+Ls1Tu -(9?>-?oNuλlP/!k@n~6?Αʒ֡2myb|)A;y7_giڳ>, 6P/mN|oʛ\H'5nρS݂y/KsJ[@0aV,eb&۵IHٵ G{~6:t?wmUDZ[ɲ{i,_/u/>CN{\,TPK9 .:H!OM.zi#e`0Wuृe7WWHB̈́x;E8 = Czy9dĨmbmmʮau99Qo.!r 8$j4#y`0)k)Tq`,6gxYRƆ'ɔ)o|`TT;PO[աk)s&f6WJÛp֟Ve\_{4< 4)];%T5e78EZhQS+7 DZ=`ˆޥAMٗߪwz&SU SW˧!ǤʺN.B\T^3Q\ g262_.)nfƴ0s wÆ w3aH5?3z$P):?om!\ WsQ!d^ Nm;^zuuRp8+瑶]«հ>x(TV_<Ϳ>//:.V!DH9קo<&璷]1m WN\+M(&<3֒\\}q,?h-ud7McdP*Q~ugq_?<@z3S*b4<ftIO+wEوATDFal31 -0KMhlR#OAt\Ҟ&=(#,H 2S4%4")`[&`TQ^l AoYgQ+ٹ׬&puuQ+ê9]ԈiIt$ParTI+5'/*.*2;2a,&jSF -+JJ!>Q"mEP l}TM;^JeZOC?'qW1rlhR]O$t>N<[67D1%'%Vu#*^)" -餕m$`R}XOZiV_A5r%ڻh+QO}t{F~` 'ng1$.-z]~qh%jZ2x]#fs)+4]cg^ђywjREwګDI1Jlۚ<8Ӗ?^io=:`'m--ZyrdEǷOg-N\9 -0qG+xm9Oi* j" -~魣#N0Ix]:ozwD`sl ->#^5с9Uk`p`ίW3skNs(r69WRng%ia 30AK*J8J#¼[y^.X2SE珄DQIHnHYpO~އމswï4tRFp Ew1}"ʹ_'<  = ; l_lER} kZz!;S ؆ LAK!D\o -OAeti> 듵#v~26tDEίs.؛cϐvr(FHEewؤپEvCn<.4a1x-j{%؊q8.&3 _67S\*UI%ja }kk66C v [kKn;^d-x\K>SW3GM~5"ӹQ[_mU^N/2V{1{%S|k4ZUszZAx혪6"𭓅uDm2;ECzuϗP:4?]'`cPglc> >UK) C~*lVQՅ_[ZKY 4%UAH|iwcþ//U̢cfY񙶵_s' UX7ʢPy[\_lv-20p:ٸBaD'8:*m"c1p)NLL"Zʄ@4YBK_J7 =uc~0Hi|vH^Xq[b!lfISQM,s9vUeL'8s7LdSc6'vMs)6k!'o֍]o_h۫)3kEAWSV;=~ku?^g0{XXg;ٔ(:9B/.4WA7+ه\{ID~R6iu 5xL/A'rЕajl8zcks0iIKx/ȭ ?|.9Ot0G" -܉D -|fPSwo(.ԭ4tLJg3O~Uamoa^=o/7ΟO;fcy`@(]6pwѯo#7~M ,Zi{*U}dw3٥?S*>`mܪe5nWw~`ᨬ If p>Pm4jUPQ)8FΠbv<؄UG],~?21ĭ\fl5crIŇ -X)".5sE6iirw{c2j5S:-&.$qIpU8d4z*f7I3`y5%"@J>5$M#O_y+}&wd3")4fqD8"hGU{u6}{΢_rZN5CG)*;萲 ο׮|x%qS0!ei߬%T~=QZB'532(&)փj9˧T 6 ^fcjܦ^fAMº|e(oY\ŧdT;LaNS3`d%UH8TO/|Z>ފݵDř#EB߁3(7[Uz5{[+>!˳*[u1EV3եKbҲ G[nrmSt:Eo`:K##+!|>%Cp)YOrvJs|>x_R>%G702ƺRϡݥ[G`*K(d&&=[{ΒogqMP6tff9|'nz#MF]\5YnObFzM!z v8)mM+\t4wW/fxCgyeP CSlrTR]سȉB3EcJ1R,F^21'[NFR#}"P#}iOcƮ%xR$NwGD-=; 0[IwfƮ9uB?_M8?S}h #bFb̈́ l -q:NTμlsuam_tٮ5ޙ)'4'd-4cnM3J|"0)axO9[p67ݜ{tݧkLzp>Z)f|b0x=W# -.Hk/֢S̈́M 1PMq -flxŸ7<>sW%j*q¼xehVqCRlN׌燏}|3p0" H)Q*.T5O)X2.[fſ:)Jbp -gOIOT1E[F } -}~)^! ~>>onEw5SWuݭkPnֿt[W06Q1hmW -C\:z$-CZ訐 -|< =~5?_z:d}|1tʈM),eFClw9'}oPM l_Y3]o>5d[;ot~Q`]l-d}г&=9fqv7map]̙7szf83SJ!! ,mI-/Z`( -4,M/ $eZ-'c}#սysFO^ϻ,6|ц? }dd}4zTp6~٤m`sP 3բ|:l"h -]Ϲm|=$(S0Ǣdf|}#pQ lL\w/L4 @Ƹ1.E蹨Cî!?M|~A"RN.TABxG`G{4F0olp.LUR<=|k:x?_ O֒h[_R`8pB7%C!Ӫ1{P -JƦPA^o -rU~^+\bְ.wh:j8bnb\{e`x8a 4MF6?zlpIIJn>wKiD#g SC L9ࣙFJ}ggu+QppOç\%RHg܏59_OsYèHyY觻/'>زL*rjje^yIG||"'mrrknIw3-ڄ΅/X< 缺fVY5^,//w,X..hϰaE&lTs]Z璏NO?y%\nk䣶>&=W^9#$]778i9.Zb'XoexpQʲK1a:;:j7h[?]:v)Y|'}|ɬOF>tO? W{?/7vl⨚K%bND Kw9Jq[S;~kLWŌ:v>$&^` `o g"Qi*&16v >;nPaNV&ZT82SɅfe@=S -G5; :pFC׀us"+ȠDoko % LH8~_ݚI&Ltx&16&>|JFNmjs: \O˽M9!b=-p&71=R{h{IWi7bmHp[:Z[M+X]ۑ"rϧAkFIWC ]FMّ T3Qzja椕sI(?$%y\l畸U :f_fHs@˭JEAIP$-H~-SN)lS e:Q275;$r=+O! wHiI/ĩ wVɭJ,zIt1ˆ;R5E-®?y&ˢ^kI.;V3%QN -}I[!LB®=ݔ0eA[!LyJl  "+rfbuPHsngpmUAwČ2&x^!$]aBƗ'?oxudGE{s p8)$<'](πߟ.[UI)h7Σ*,fty d]op|<Yܙe}ϞDOq]<d^rꃋҠR -%zE~@'=xT o2`f|Lùs<N*;+ hdAwۿ.rqu_՜U4pkΫ=sc%ҠA5*Gj77^q:sofq~p:D+Ǘ嘉]0 13rkfEħ itEF iE1b7= /ЊʞLn~uMSx7TOSʌ9 e׵FWrrʡ`Ox7?fMaAҾ3F~s85gCߵc-TzS>JY@Ĩmʬ)u3f~{5'EXwjJS\&lIo -k S$pEEAPA Q#qEp EQDQYԲg9<{ \7ynTEip4Y-@{qk<'C*)Vī.Mhu>Kuep9[ !:E=ZE&[v;'uh.I\~idcPcBb^/sWPy&E$蕜]tP5M -27QL߇)`1{ 96LFȈ~k|U}c'rZO<2Rh5^6HߍtM1 ȸK5]C[w\om 2A'(6JĺZ`"lñ<ύ.hso,nTR:.:'>YH ~jNSie)&﫢L^0iG]eC'>5WslYR?Ziz -|̓HSQQDY⫢r{\mqA;91u̢q$!&!V 1HߏѴ;0'RJE\c˸V/0Yy8+ ;DZ0M0/m3GSLou ږlNvdO=ϕϔFlkC퀹 s)ijb['> ~VqI#eWO&I䌌 @SԘcIk􉆗&C/󍅗mMSnxrdَtմd8m p>T7Iium )Z^*tIRKsVw29vC;Vg\ʫ3--u w\n}2B6SU94N -2 )@yG <&gmOlhE‘Mfnv_(V=,~P Ԕ`w JهE2=uY/M%:vfkUך&p[PiqB/@zLj& /{7NNR?;ALY (v:RZ mh'z0畖OHwAi㮂 Jh{4Wa/gg?\nF>3SP=Tf<${ڌ\Elv廯Ui!󱂙z.gX\MD Y-^QHOiVڕȷc1)p}OlCjy`@yo+ǷqJ v8Vi&H57A߇xq9vBlӬTۗ$ %:Y HazGx|w:0\c|w8Jކ~Z1BI?LwNR0Rq+|!qwD"NdѲ4 -RsȀs -䁗>aSB d 8}:[U_Yg@mBj9ؗM; ؖ1wt8F?Jq}DI%D^wY+'(}?)@ 0Ջ hz ~ aq"WA LTg ouJ"*|`v{Py}}p@5b;OH{k!d|Xu"+edH+@ ueUL,3YjO-&E[hH_&;FA.x($Cڱu+>z⧦fz:Azs4FNJv1$ƨ~Hiab_nwz ?Y^텲cAgtmY7KIRa$TÈ[{H>}ůviӭXt?uתbFōSsȫSW8QsdņT{-/뱒 }4W$"n&>{i(ֺ39r vS-2JuT\W QAQ@ \pI%QA\@=A״zf=gΙ/2suaܕ XPI?Eז F^YonbGK)e(? pLņZ'mߏыrN0KaEPЗ* -d ncLCqz̉K3>v6|m*V˂Za^c!1ڏFd=$c$^ XFiDz0'7ny=[mph}݅YNMnga:bUAb|r"P; zefkZvdxL%/aDx߅&8/iK.2VC7$(MIh}Gzu"JXxNJ~4DNXO(jyVOv۱f))Ԭ|*tϦ(1Აa:gņWC{?9 -yeTajc?)O5;Nx,np:Eo(Ktcac -j^?SV/ <-6Jc-|C'- `җk(ʘ I.FYqx] m.&y?tTȅM\'͵9rvʑ,LA?SV&"]_*/BӉ"TY>LUfo JMIDSە#!4[z~ ) .3z u]~ï0u ≾:| 9,g}p7j6 )*4ս;cEP&t"#ԴZd07lU}x؂!N|$`öe2X}?* 7 Kx߃>h'|$cۣ}i.)}wtfkb-rB;gڊ Amg^ΰ[hЮoApc[Y-W3 k5;b -Y~8 3\ULVr腊ձЗsc91tՃ_kEe0%j?W'#гqB4)pJmZĹYj1 -%7 (#Vk75ͦdle'I+87u-~reUqD_PuYV5B9lznv܏og?t"jw>/:'ZqBł}̬O;RĦg%j#j04,Nv2ɄUDgzA#quaB*xl*k;}!FMɰ))gLd Ǣ}PZ(.WAk|[6_cej:nc'>V?Tr;('kM.ie`{{Z-n{!'ĉCVE\׻'- "YOeU1 Hセb9'^ky(4=y84c(=-1txmK|F!PL:ggYL4][IZ}FWW|9_Yl(?X>LY ba6X~4N7o^)Φ,OXp65%~.$-O$(T2%1)4473sdf)jILYc21&XcA@K(EP>@؍b M&],A* &ٳ~s޾9’’Hv!}ԋJU_*|.c?WE?+hA*39NA|ⷝ[hDz.t ԰OPƄg?[^ T_g1NgUuGRRkA,,^G[eQ}0EK:(!r8.gKgm4WL[|z -:?`֢Ñqb0>.d\R=Peg[!nծ-@LUM| V.-i\꒸PqE.tPlRJYt\DʫhEQc> s&&>~{>%%\M ve7Yb2i<+]Iyr*~Wp:Ԏےnu_Mњ݁¨%Av5gԳ6e~Qk}Րev}:V - #xl)yאr70jA]"kvq0UĮjHOKmY6w ZVh/ :9&GӔk34T=鵝*ާ!s,u0YC7U>mt͒~]'իӼ:snidk*j:ʧ0Q[F.ٿ(h8?6耡>ˣƦgw-;00oo]+#q#mK6r[K -)(5Ա.ͭ5E]±ZVzb Ws4Nq^m69-w]}253%#ȋd2M6,QR>W;I&@u伃i}Ѷ :l 8V-+3sp\X)'5'.a;)U߃\!#d16͢Irq>N5lq -"ig)Y[ -k:<܇%xE8<9׿;XV\ ՞;l_Z䇥h=?f@Ϭγ*?SKY%'v%~ji-n*%:d$ȳVБsLV:2$,4!#`F~K ;D'|ּHuV yN91LCF D豴IKՖ 惾KDiԥifkȫ /83RN EnP'yeIT5z9,ݛj0HZ2Aܚ|pi5672J\S\^_ifm1&Cb=X$1`M,XA(J"K,Q,(*cԈ3|Ź:kuo?{2x`UM^x#Q;aue܏Y g4hcjT<| 6˯GLUdyqrK -Պ\f';0ZGn96'xwe,}\\ .b,@Uh -u>+_,0m:~1;bd'-%-7eh˒ZdVTX:0uoJ.€׊\~:.(9喝`6Gq+l{ -5(\jFe?Ffe}},ܐ~JĦaj$򱦡(wy>';*5񊤃 rQ,8]r0BM;9ֱmP0*zLn!- 1=兮]B*c^ڷvByPO2,i:{7 řZ -i~sQJԠzWY]5绔 -;(;iLSej-tq{匔$ҁ r6j24f#'|ViWuWe Z2}M8lZE{'#쬳) Ӧo|NqƖISS$L1um6s#-ݵVɧf^n(m +Ɨ<^0Od*|ؖ - ) \il$km88m3V<[#{WDSQ<р䅛[eg_g_?{yy3kbх^7'elM -,\uZXz>WCϦeArNg髳QzՌp8&.iȸKvYS0..2EUb*zg%VUerD+9ֱ .sv,X'J,7:fv?%< W/'e'ʭVZA+\SG1w J$%l[t1+9HG?D9z.1 6ks3uxC~zaFZJs| Y־.V27TSs\t2nߨѶ5x}c󕎼GSUIkWFEU+.i,5Zsx,q-,?lUȧ}O:VH/A;07rbR{Hp 7.uB<@nN - vEտ6D<}9xF$OH^C|G'u -|  !JT QN=kq>2Csy{! QWqzaeu&LX/NxTWɱ26ơ8Dud@a9o ~  @B:GC 4YۙVz kusu5Q$^RK?uA '=Uŏ`QؘPgӡS!.{ օK9[E˝ieE*0($C1aIkNJ 2H+CgE-)xюU ?ʘ7p(-6솱*|K>lK5--2^k?&,Zme! JvVy;*襢VR9D+/i#R@8K?B9I`~מ x w'ƨ8Q_@f#69pCkJz5|E^{0z3HGM9bg] <*Rc%īۤN0OF\ٞ&5)oG1!~l}99b‘ׁ xvunAoQ>@>i-tz %.neX%C̠&R 6 p%DBAza@~[ <$z}2}KweW@Q5Ss%QATDAYܦIKF=FM|*n=ӝ/3=s><H(>hNw0anzfUGmOS},Scwgi&#JI$B L*}= $<:/t@:b嬓MҹkPzjSϏM5JZ}V TE) N!7 wߟ\lrTSWszv>i_<0t3VJ#7!NevpgP!$Oo z }QLBB}1iCjӺ6BG]ΰE}šhUKpEṖ)A&BP>sL=.cO~0f 럇c}_f_ٲN779|x!h4!Ix8N6܀@ м8 V=?߱}sL|}X3lnaNvTuDɍBL 1A/4?j y }@hG?N-'WK=mGGp{k3rU%1Qh y"$ HK2w1?$b\wiߛ{Ͷ1ŲUvFS6z:H@Al)4`!7:8lO,(F[ެL.:o>r*rS1JQ* C++: -9H7Ąq\[I^*cNҗtins iv(I)M]W% aPE@& ~^%BZpg|/ EJ^ݷ>$/*ֶIE浪O[u/!!g: B!Ƅ XH/&.ow]n/w޺Ԣㅦث-}p_LE:Mv$+f)1 bFeŏLȏOv+ yb;[|M9{0Ri'˿v%}E^oƝ֠ N :CR|P.Hg8!Z(p,hpˋKR宂ۿ>ߴ}w:ΞXԿC[j=|l!/S+@lb~hnD^c@Ҳ$:hnl5lšڌU%{̝w{`uYsN_ - 7^ZX5}ش|A//gɳmfgytmޘCFStth@ZJ*%2`YZ?eY燇}!殀ԝL"d$nnzaE+Ѱ<ߗejV}?M:;F9[]ӳRKO7;S(fGǃ$! CLFA7iKfcژQtz9x53u6"{]>z:nuBvoa\bPͷuy%2QH2Fޥ"Y<(K]e-hGvՎjP;O]왕˽Mf}OΨW]CqVVNJLH\a2fDxIR -F^Q5|:F;x0inX5V_- m8G-/iгZ"PސWHҽR UE2S[N;ttLqi&Bl8f3Ƙ KWEM !$\hC Z0Lw9=o@4MscZ=hƾ("~:^0i} {|_/ܩɣn_{ڛg?ko}t7u?ž~Ku@yGLl^ҡP$9ށRBdּ$@5*Աn},j1Om<| " Xpb٩&m챴G9C(ط<^V- '֠fP5W´33k 6֩xS X8 -̎2/ -C5^Y5B->ԤnJ?Tb0/2bK *9$` LW1KnPG[ydMl QQC-57EE RZ>PL_w*]f9 -|U$|lYŔ fᤉP"c$> -+Ll;Y rJk\PB3?-5U︔̦t$ 9m9y7䫕МV}eEDh)'>-RdV\_1?֊dOU\NNZKQ{Q CHZʌ}F)(A.3D%}l?!CaVIF]Z]Ŏ޸x֨1; v}`>0GXP0'[DZMПHO)7%4m̶E7-VB]AiѪ͋6תݾQNKe¬$o`Z¶ߞl|/`)@:(y/H>s&f}EjgfQuXU4Zb{a##i"kد -^5mZݔs?9Sٿh M#mRM:4r+/9䔉<Ւ?vJoT"ə=Qt"̵tԵh.bcSvr&87M 0tn1Xr#W}䑔홃1Rr+;s`.ťbf-dL23'kGքI9ANlKIlMx5곜;QuY+@zPY/+E[0ƲvIe^9'wH2v)/S6hHl>26#cC@gT#W,9]G!ڕs6lKbcf=Ey7Ar) QBJ3pV-9G 7GYK¬jؗAc @pfs.򛼗M(sΧ]H߅^e-Ci3s&A CC}"Ծ>rP?؛߬Ya'X8.kRI{8Bk魛9Yp6`y. 3&K>AP5ɡuϯjؔ '=B*u#'bLڪD^7O覼[s=~d,B0ebͭuvcO( t34r/3}/ \0h~h~zޜ"h<&?/_xKEqɯ4V! -R]yG*!ga+PM. ϓ/7F|+^Z^Z/:V -fli#-uf#v4#ww7S覼-f鎊>^vQ^#swRG. MHوRf3lC{޾ -Հl1fRG0Rwj?<@s뒛_.{䚢K9\Tg I&{U@Eb\١s/=V|Ttk~ͣj Y' [8DZu %}DmK'xʮ1cl.G^~tIB|ST2MG$GF^ۢ}*H_K{QXM:YDl).&48NXNM3:0"dP}@tC',"*B@ وIvFp=ɴW_WMƆᓿM,r;i}'{ٛ4ЛrVv_+CF..k`KgE>>+cb uo5|<+߄@o'S e_⬓ e]tL(5D[Bg#k!e珰CEW -2! *泔ٖ}!5 -t;'g!Ҳێq?Քe$v;ƒʮT5 G;cD}1 2U B'7cX !'MIH=d$=jp5uryv$܂gZ;N@HhY_㍮됅j-6ozfRfOTY+w齂xK{3PXt{.F .aot|҉wZ4 a@i bFL-ф a9id36z{Ɩo3Pr_n~+9hPe6+a"'Ev2|aBN -oއ]LL7(w9ZB1rV"Qa`J~[rvF*f:?iOc:ⓘ7QjC/-GMv&BfgU?e?졌Eޤ?*إc90br ؁(^9AHA[r~ߗ/yIrݹwDSLQAsVX`T`G6(*YeI/g2@W8ήDt9܊^6e DC騍Q!e5Vz{@w&ņyhCVF'ש]h yWSFo/#߁t@ǡљ odԾJl -{3l+9gU'H)슐]2C[Tt@<N -Nlg?/ [긞皧MdC1 ii# /iOI;\!܊װK>!$:b]ɖ,2V6X{<3ʌS>@dbڨJȞFV5-Z(#^Jf!9CC[i=?f6 g&kHO.dݳP]܄|;: *6n7'*APl`!߬*nܮuld` [tܐ啣'L|Fx ճ:2,/KpeQ-dwԙӢ7~-p ٥5%4̎Rj*rk5M"~9Y'4̠+틝bNWFx斠%J9_Z ]6sz[v)5JzehMΣx\KDצfDj RrJ%66DC l&*;]* ֜FqI=;ӠĵbJTаhbES*T^9'' G&,j4VP`nj恌5_>X"7^'}ۨԐ*F-#$෷@ -(׹62ȵo{Smk|#Ck;M*2'|;^u룳}J#>3czX62lf8$ tT-=tܳζdLyX -i+2Pgv~MMn)|]^wJ7O ꙩn|$i2SZ6"tӘ}I!)&ⵟvf{V:ssᲿi%{ zi;>qXH``>FG -jJ\2 ah5l_hEu7?޳@,3Kk+ھoDM Ú~I)@K6կ?FTqz 'T$L{WاvlrdWśr䞀zG͂7%>ioP;\Oxo5%nKXۜB["k}wk#?c>+ p{]Ok=У6ȇ֬qqj6:2?gد}W!YBG w? nx7p{JkXBږڻN)S0tXEis\~IEi0F^RjRg_y!mSV-i%#fBMLOCȭhݟL|d\yk>BHI #Zˈ (a  -넢G>;6dˉ}-%r * iX"&2˄E[݉nӰ[~r\h%ŖlpwNj\T}2{5-t5޺?+oYZ!5ӏ|(AO$kS `Ԑ2H,#eTgPwߴܴMfy[8ZWBtKwɖ;#u3¯vlz[GF5 ~9@@R6ШC|{w䒶}ĖAgt,[F,6O"o$ /W+?K<\|2Cʷ*뫮Az庂YgJx@=K;8D@*% z78Ptdڟ':`Y颟 _ -Z K20ȏ_M'[/C6%^~#dƽ5G)ʎFOUQMfa_$$1ucKC8W%5"JZML/dac3{;8ΤfK411UcWlA*Ej1ƈEDQT,Hiؒ>y߻kpEi -m/F.mKX<=eu 쾎(qَψ=`y@[j3- @X!۩@8DGclW/v{TF!.¼1|(c.-de^"y b6d:VZOxY;g鸃ŖN4)PN9=ΪPᔒ˂K&9_\,51 plp8-pL}WuEW oWవAQ. pwHeIᒛ쫳SwjrhŁ&ġM9d !zsHϚkbWk:awvLH@3kC8!+u6FLUzPI_LT wG;&_{Kn -- `sTRoQC.}[Rh_f=5z_1)в9'x8Xsza|J:ZK*wy8@}5O t^A+ pHH}ΦZR׏1*n_ھ9VahM=ol&-1~5{܃zvသw -aV .=ۘF\8TQ~U+\ncU߾倎z+BZUF.?^6.RkwW;'VQe, #- Q *zp#=ffa9zem/lNcUpA<r} 5ofYo[7{,c %%eՌ|h `}563)rϓAs@'k4.׫%y&ʡlz[[6L ,bջ&ev1}uvO/Q ljo)8Ẁ1v˪hF (2i.RNVؓU%+blbl_ĭI }$+*~WFpuvnEJf%nrGDX魼1TԽu^S/[R;cYص[b!*;}&q`>UK\rJᶐݴ|3 U~1z:ÃK$/?S@".!)ٹA5Нփ9Әq)Qc4-%@z -,ڑQF4B [A, hӕՐK}jVÞRX>"C˯щ"zy EyCL+syh$1#eYF09s-e5+}Wp9^ g}ݭQxⱱsgwU l/䙨93⻹*rjdJ}3Rih?IξE4Om%m}zh= :0aR1_V&d,fsnڞ KKi`UV+) -c6鬔@qTp=5q[LL&֏J~ֳ&O/ ;u ŀoSON%4H9cd`Ow5[a0S ב6ňgn*`~/<}d|1:ƭV{7‰Jodr;F{ !r9m[9957HuⱁG>s3LK+E~em}"~+/86{f}B&8%!W8U϶M_m F1!mcMUB5OPfFJz9NQBQc%(pM?ts  twp Dx;?d;Yv8=ޞDc3pNЫ!~)$5lf&昉LbǬGRc.xۀ7 fFĄ(xʡ܈A@KT6٭G燷dQf0@ ,‘h#ȇo&AOyCIiSkD$>}\f9[ 1,!/^l@R /XbW^w mM3fhezC@jvqYۀNցUyNT1 *6&[EnB~DZXmLL7&+ڞxgH9ګ nɣln 5o:*2`1y~zGzuAՔ -96R{vE̺Էgk˺'/uO6#C$󠨕C_"ς 9o*ޯh-G0]iE+#V=b5'ƶr <<\p2R[< )vWĢ9F[rbg&.1e *Lg*iWޝE~G1 -p%s#;N\q^v+%~-ɿF{wdA-mM6%{7<~tWM 5t`T-![Ax5{k{9ACD4 e'>٩Mw -( sKj)U@yf :>tkUTqicWV`$[G=nK*F?]-ڤVg8kyR24P;K|P|ҡ @빸:qb̵e[$ 7?Iq.=G[Gn oQO%J7f&-9_7ÀǚRM=e|ӍDKi=,1ji/ Hp3K*86 -#D:X.-`5;m5ZV}%^ȏW% _piT'q{$6k};2xO #-pJM.x$8jc3N/ xgyS}ѻ qOs4XpGi9"PrоĢEQeJԢ˾+Î>q'AC`5B<>g Idʖ-Ťʹ?N*97\Yn5TasB ˬƠݺ)C毿-OsHʮE,j)pЙSPmycg` A2~URx1j5:^'̿!28;-bSZﻨWyf"Ҷ SӜ2|APj X -YBWNQ6QCז/Z]9am5W-.a)5 -FEGlRhM(E∹{fŃgNavվU*|u;j]9=M+V*);?eFν3iLib6 41kw\X7$Q%ˢXYEpI6}=3s9s ejeZ8D*]s$'#6i!@/>HӴ<`OE_XN& HI'R6!GʀW< )h5]Zex1ԫ&y_k'_.G2*=wi)JMXMڙ#-k𜀢ց,nWY)d*ja cfJC܈;j띤}=`qvՄy\gWT-$D;xG1C"6;7N)wg&?#*JL~5X٤Sv@[]ёqgā̈s vM; ةS[b 871]s8.ޏJISԇ߄WuGQLE iH~q@I(.rjr<h=0 #%?6CB@/̣ K?n䒏l]#x|`Ej;Mxn&~|ѕmhk,VX^HNz<`B<+C:ݤ>{O=S<13QVE)t6aŁf/J!RzQ硞H(U 7o \V^L!rJf:}6#XwG kUw\Ԓ_o`]7Bjrپ:vq@C:6ҧ$&Ll[қBpk;,/#swE>4# :zOIG 踘]>㴙9 i{\ 3 -̢!>`hՠ6e-?a 5 @Ӱ -[\joԝctA'l {_yga޻ B"NO[L_n",hZfTGVH:r=v.D[BK:H#PUg6ԒߏΊGUlᶬoC{gveo)E!5$nGZBQ!'6Cqpl籃JRY&yCʎ׺܅q -ƫ=O,tT]"}X{:,-HsU_ݞm9'PY98Ɔ%!ձa .?W`VT|7ߔ}`|_}|Yk$Md: whaW9j2%КlF^鄍p?I;: yjkywk={ua簊RT EҴ5IsξFH XȤ|h%48`/;F"lbc䆰'r6a{3wm#LpP9_TJ5~9.U䞳)G(4!28,!e1IXzNRMΥT_UzF& -zS=aFHIchTl E9W‚/3퐨Y(:t'"&nG% XN.Z$U5I-8%Fk ݜc.Smz} -Գ,OEVOqy}Qx֜NL&mJ3L4DLk+ -F)tTH4^Q)"RbAę3y^|7ŻqV %틑'(IU%̩h=\΀(ա^ef{tq~X kḡXG0A@Wublg, -+ -Z^*ۖZn?ٶ XEVo/!%R(ȯiw)X5 KE<6J?\")r24-lveRC_O#YZgU\;(ԒG0}kwu|wP6gU? -2(!OrƖm蹙a w+>\<_ja >iCrHњճ3r\arHBV;ȗ|hHZ9~}y(s7n}>BwKH$JG !82y^Au/XErM9SSWOH!tmhn}jzKlo!dYt>2˯—n cr?bJnEz@Ksq溎wh񊡺yшzNJl1;ݡ¬2|N?*M41#O6U{^%`#ܚd{iwMRƥb-ӄC .~w*^g@г !SOo 6M'֣D{uC+\ڞVruFÆ84urW]vwk"%o6-jb1#f:=!4r\5 ->cuFQ'Sv(Y72F" 1n#eH4~Ew}e'9 "B".KIYtD}`}A]usx#o.)``ߟj|?.~-!#쫾uԧT qo@l†N,!6KPyNqk{K*3~^vGoY'ko{q-$TDs2UQJ*8ÔԨCI3rcJXMwO䚨?#0Ztsc:zuɺ{RB15F rm#ɿH1<|OEw7eD(-:N2 rlS7.Y[9ʏZK/~ n{L|PMit@{x_ pMoӐN,_-dxԀ~S €6Ni[mi/n#_qsw䀞YY{4XbCOjkeCگvXAu舠IiPJ?xE(}&^aֹ$䢣iODrkKLg =>Z)y):K/ZGye𴘆 -hzitaKͿ[a[?|tNw0xdN}e@KYHIo,P ,C}kkXsVcG${9$TTSm)w`I -gS?j1L~%Ynn1T:ȹH o@uB L6m7)W.)dD˾&aܳ3CZ^yQp W0LbX$<:7Y-wGIGǫGKz S-k7.`rc*SK;ꋅnbk-ٿ9܈;rv/aV@4P=%ih?oRxM&S+šlOuO+sP"™nFѪ|h˥tUk mS=pj̯tbťoTȫzf˯|nѳta˪jQ̪rZ˪rSb7Z$jAʲŮqnŮp`̶~|ƮwcŬxaư|зȪeLU)ͭtjrlڻ~ÀзwnֻulͭiNža5ŸgF]Jǥ}aƢyTȣ|\ÜrFc(sNdD e>kDʞw\pLwJƘg>ΤoIͦmGΩnMɥiIápMq@sU%ɝxsҺͷ~жs`Ըwjг~u̮uƩsm>]Ho^ӧx`\#h3c(xTUȚkArJ|_?dBάydͭtZ˩iCY*xHsF\)Ği>~]=R:& uD'ұЫ{kӯoX^0M[/ϭyej@ 2? -endstream endobj 28 0 obj <>stream -H [51Mo3Lhbjq&*8@D2DpF[8PqDh!o9 m,1Х"ۇI+Q'D`0ѸftcWHm,mCů-k#xB34X5rkn 30`&J]{c>1B#rc׾ \8^ͤB2/@ NgM#ߩ+ TEu@BLܳ2MGy"+ÉG\!$~@Gntl pu*Kِp*yyd"Yؔt}'7G/0Ȼ V'!^8}2 iƾ"B\~E:DuKO-q@3}=m[ũQOEubQKOƎ!&j4rkiB@{E kgYfkJ jA᝽)v!PI9DS(;p>+Q+*Zys٦Ze]B(湖 }$NwbRNo# 7 UF̓:1PUvN6ƸE[yQqRf xӇɰ|Z;Y啯,%֮'8,d]Xe.#, Y4xMRɑ_U -kO5bf/%}5e_.*]jFEMP IϊRCk/pS(;Et=8L]Am Q$񖩭ou@̈́m1; -[=5ʼRs^bnGDI(?[`ZF1OOi!ƶKoOJ %t}׍M2F ! 5%|GJ jX,f$\5uf1ڔWz\BS\;6us{wy1p+c.kcU0:=/6`g fc%#GU -j1,{@ɂ/6U;4 r"dKͬu2QͭDTRC=B}Ⱥ<k0'8H ʭcWyMuO4.LZ ):eeUH$x$!$ϥSIw1*t[Oߖ9=Iٟae:mg6ӭ9ƱFIރALfXD31/+=aBN 2\MځJ jN =URn3ҷ'M ]M.\F6Qr -[m#5[i|jU_l{ϭ,ҙF$C,bC{0Fʴ"vթ;剶җ%:f -!3G -|ڕiJ%I~}_#F -hI?Bz瘬};/ - -`KSsHD_Ve^332}U̢éVƚ(݅ʼYGIIR .|CQs6٥ǰy;,隓ԝitVwG)Ⱥ#N ڡ{O g^Ds)هJx] -B?aSύB bMJ=s"gNgm֪h꩹ 1@HAd -Lq@OW>VD,4A$%t|7.YjG9&hKwod$\wA& 5)42|a#-gK>3.gؐ~J~,j;&nuW[JpGP~4B厈ar]a%PcnoUΰZ. fF"䟛u=@'/~ri)O<~jAku(.>zkPв --|L8Rs*),҇f m7[\ l#U#s!@IrjS)@Zt]<|]N˙`m]Wqb﯈^.]I-ҊOJ _v[L?xϱ-G>>J7x]XuPk\;\8Ń.V˵qWGz0p -NDzȞb?:1+UUiwx1׍wz.ꅚ [k rfBkTҮhܕ>:&!TPq}(!+=Nt,wPRN!RG)=`gp)R<@]W`%|؛V6sv'^>u;w_񅒩hZdRRp皤쩥3GGajZ޸i#& ɎNXg萙6$؋MpYS# }wr*bF\|kiġAǪ7IAR_r&|[(y)aBSq>S}|A,20ȿƘ~y.jHYRdZ7䑎gm+hEp9'S'VPDm0ĭ LOEXPyQy st?|2;6Tx0ScMаgzُd969*'Z/ ޔѲۄW ݥNOZ!kpnrf]JW2v}#rE2teԸ2Xk`*vI*^&1 &A^;M% ~LQ 6FAh{J;1Z*/<ڹ#'Y&!>xr*0pCZ rjR3쁁[+oͽk"r}Լe䑥 -Gj־&X8lD8e 7Z0Ir\(PI,B5@hUkT&x*.?Iޟ"ghO ®^`dC_em )1 >CAOA~Kxg]Td4 .s"ʕk;LcG͑a樥']>SS &h;kmӳ t< }@_EMs+v]R#<(~\BWEJ/vT|)l+\6.֯u14xVRlz{ -~ZGΗxLQfb"r&b[FͶ}er!'\8)N?2%w8HMutcF%~7jBqTO:7 Z:*!^`m_2jtm6:ygߺx4@!d${F1"Xј.JOȵ>D䱞9_5:8ߪ$\ %z>NS3lj:ԩ:!6MQC5AFZ#HKPAT\(`ec52`1!*gu#WR{eyA#.}4X~WZ񓥶gkݨ(CNY cC)*:ئ (jslތtI$thyMC`")^-7="F`k5ᾖ.tz^z1ߔ0FxK$R+͞ҘK-a Сn -104T00p'J|ág~} ġۅ'>7C5e~p{Y0[c@c"Y {z3GlPqMɲvblxm#%z⽩:]foag[cTz6z 18X# ~` -kA yΚto -;YԮ(P\Z,-gzoWnM$qfKOBEpId] d/tOi~ShYocKBE"94u[S0I6lKsځ -_( [gaӂ_ {+#7Ps/%n]\^@6GO6{1GF~ujB↊=7//Jy{ʶ (d,5HL݁Eiw[;iPk;OC2T|#ޮCdܐ>>!vb ~"IJ-2jrd:BXrQo,f/ $E,xo#npsTlE6n+=QW-%R_bd*kx}{*'w~(} HN B "6eOMmGjK\~yݧCG0Zh:P|bR?&޲vjE}(>odmCmIN%&uJ}s[jfxI|hpk~ - Yv94hyb`VD3ۓ2@ ʪ:33V>'@qgM<4I%dGРIazS9r9pEW[ޮ@{'!#3rn~^GH >-hmM< s)<^c5Wa-:,-'&TGsOcX|"m*-@xՌ`?ٺ)qRYfl#Eo se1}!t$CVF%Ey=ņl6pюp>j{ N2VP/LׇfW BR(,-=6婵E3DPSb<#4*#_o̸WPR7'7B{r7ށH<֧31ۣLS@=dC3KU¾N@]h)dļXR2p HPU]%{YLW J -R8.ѡQhk H{)8$XRdeTӓn&]$cqoVxQdr'3XQ3rCX[;WcZ2G @,+촵G%{ZҏCpnYZHYFk?<qqݱ5'a[y57w(fAM%3't \yVx4KO&iUtH>yOIfMrtVOӑ[*2d (}]~KALN2lS vv0'LM>8_w]* DDPDIRH/PDN !=BĂ^ggO?ߝgi&y&1x.6L~$l)j@s]<ۚ;Wq7I)w%UI;lxD3 }:ro/ț -e@4[@7r$>1Mۙ! +-#Z} D@OCΈqV~5Ivٲ]]$,[ 1{]%"Y$Y+<ȃ?msһĮz:T,Xw䬖א~h< y5NbMۋ|û7^L]RHIyFnO_WK' -N;޶o̰am/Ӥ|gF5QJB ڱaS::}<'>Jm"W$176ĽGk"A31 >65fƖ]h#{HWOwP ։ؙMѣ3۾Y\юqpp_ =4+QBjriDo x-ԧQcW fezw1PS_kTr[NiK~YGv.^YfF_;}n7nh5q%⣙ִ*kɐ>I]aלC=tZ3 n91?n`6vuXeWuTWq̽F - Fz6ed8Q0 kB^xKFLw E> vV8(!{u6E#\Q<贌Xxk2a.9ثdBzƽ,h*V!ˆ?t203Ԃ֨hMظD"b[ͮk8 j!~!5 &Y9ewP_ֆTTX'$b*~͆}?4>Z;ǚÍW< -F)|Y{`\ >ÔHO"+d؆uc-k#{kZxG) kYl,($7C 7tUlM7r߫>Ybš% .cQQCf~켥nBfaneam2أ>n{tׄ4g_t:&n,r\Y{当 tt_>Пn:uBsهS-!5Y7y*';ZF=\ԐsO~qHQZ4JInˏZfւw#O-aBDjp^>dž`=Ȼ #_兗eEzO_M#*,S5Kl?OquaSUQUMJmLa2i )BHĄ6 ^{0f5رz -&_3wHGs>̺jg&Lx(gAd -l.XSHǿ(Tun֓VG8ϊա!﷢OB#0t앃?jm2juҺllĸ2L}bf,q oȯю<{9Syߠ[څ9Et\_;׏̾}I.yW%qꆗDԼ#tj =mKGѤNFvI{Ѣ3icf[q5\˗N*^ֽ.6P˾;\iTtlb F%:ʧ W'ɯB꾿vG%?)5z\$BҕZXTHu?QASj15n Ko}rmY#r)}RrNV0 a;&^:Gsݨx2.au"♛8SSNh_FՓ_6H]pVŹD3.ݢ&V#LLLm޸NnI>x+MTu7hcɬEmND}p6BAV^ܩNGlS81e=o9cqy5{Vew{A<2P șTN nX/nJ@V11q@ Iwj!>[}Y -ɵ h{[a \tkP!;ctJO몜C> -~κv=m+5Vqq_4 T\g[ݴBNHoZEVGE6& q(gQĤ";/޳& 5?o᎗=bJ 4k:Җ\nw3z.3q{xtuC^}~Ds޿Ĕ. -A8L(ƳD/ƻcZ8,ޙ)i*? y= f'6r9U/}]5U<j٣~SZzP#(wѿGalkԦH_CSQ=З'gUʘc%̌={|)Ԍ>:z+^(qD#T"<$x7?XS=[>slwQ1!R=?ძc;qZ'|31C-f{湛9­^%;fXcLOpG9-Mo"/ t|.^XIyq)+9T˙E/Dslf0 0@@KV3?Z^n $\E5ĆVq/Κ}vsR_֎1LI#7jJB:#堌^&dnKx8^#1%pW2n 3,BڼgK¾L;UXNp' -,PHֈ9͸Bt_srԭ]: ËLGES`11H҉HXG<{Cuo>H#Hƀ^suNcg% GQ$y5Hf$'?fF/PWΉ?O(D%tجLꉰQOStxJN Me̦_Hۄ5;2 -~4S_O,قàH9>3WYME:;Zyt!ݾLrrE{ -Z*D/Z!ET0Fx?@N[Co;( E 8A֩cMkk;N,eeIHMyGq7%qQԠ(QhR>/wr7E ~;wQB#!a5™ZD0Qtq-7?&'dxQv_=.k_YQK:ZsͩڎA E!L@P\㤘 ȟ_^yWN - -aZۋja!V[ i#Mf~W~zv1+A-T !h\<,V7+*YQ: O0|˳'9huBF|+jrzo)`ăMg4mQ=6-`5Isijk|aN:)Ӓ&E?(5 @q#f{k⅝VUTz!jk X!n -*q`/G¨'wˍ0Cĕ817FiК#fDcW4.{EXXwƝIG4DV{[r&A't7;Gnt *K2$Ij$ F1XB -^;ԈzJ Rȹ zT{LV -&)gԢu -?=y[a?h7Vۄ}>q>&n$ފh2MνMrv9g>ț]QԢ M}}U -\Ij"hu2A 8 !-$>_b\(]EҤWCj1vawb獴`{I!}'rJy}+LyH#wzSdz5 }2rorl MVAh]~vZMu EQcʋ̺I2 1B?7Y;J଴\^= --tÜ)'ޔknݗ~6!s$4rRA5^V JnTkN~FE(s[|E]R|%@74;K=0\2sϓ˟dW%# -liS79a#p?<;%WF$581T$np͸p径1Ha~0̠V@q7:SB"@"~O>:a1L$;d\U r /aʣeeQ߶%/I:]]⶙=r;NsKe wpVd6ܘ+ig*A&pAUa gm^X^>ܹk\T([.i@lOƝ.02E9^ЋIo ^3 U\dUt&PRΝ}>>뙺1~Lו X&myNlxô cpu,tϙaݝw_|vo(xӂ:͊Q 5¯GePA F_wj,-7l]e/>y0ufCfHt&W37rhr}%͂\C2.Kdx ZFӫ6cu,_/QrlT$`#mY ;ƿ(ˌ1c02e/Y -,V"QB%K} 3:\God_ ?7jp,k'2AP?2x4V5lm 簌A*OIۿ$Ii%dXf÷4b+k5-=vj=8l|PQ7S/S>,p^jXg)֤ 7_S X:SJbo6Ǎ/\u^8Ϳ[>̮mN.ݵwf^:Om2ZluT#Ej6 -2_Aӷq6hޮ1Tyunn4iG1!FI˕3wR3%-H*EBfR@U-C :ƱW_ů)NNm]ז7]h{Kſ*t;oVm>-5閾Ouv^0#v{D>h /5Ь/L9P\dRPPG݉&[ՖvbMJ&O8]vc8_Z6(Fw↹qA2H ,/|K,,s'jhjp{".Rڑ:n D+DctOqa(2(BHǬhv~=Z!mk3X*T|J_Ƿ{es@QVY)X&xzKn1Ҧ 6jv,{~m_fmѼ(_#7 @ -3 - =|d<J^Y -W%H]5) wɓݥ)D"neH'ы8R//Z @;O:- p <Wo8jKʵ \;q0#{_W^S:k/^WA4OྤQxNFG%4M~\w,RݗHa -!W:_ Ldzoq(g1׬>2=bMD:ZX[,_e|Gُ&>Q7,Mzf/ oA436bJi3$cUO Ə١y׎YVn6i:gJwEOn gcM-8 O/ -M=8`LsuZy:PpU}^+D G>ݮ8A͎mѣ[ةgvY*g-S+ߘ\?ѭ`z3a746@Ʒ(2 BdAbFymwJP% gi9JI;qS$ b潳hKYhzDK8*ݘ0>K\¤qOyr dP5ߍ3%1[B/ht9v$Uh7ٴ1@FexfoG)/%5 -$fw<8cDd^u3g<lN"` 64ZG]uXe@ -v8GK5~S^'5e'eҽxt/L@Ѐ9aO ;ddy"NeGk&=&jRl4턊+dq2YYOF49V1Bz~ܺ3rG7z>dWE=O0z^|&RODOk#w44ХYh#iODmu jgJT090X8XmPjk\:#ToMKAXvvcJٖC|Qk>-ݟ%ҽ(9.وe< 03[ƕA#]#l:JaB &~g~Ȉ 6So9L֣epI r~1wc`Y -ɇnȾЌܬb~Z"ϧ}&}(N -b <$qy5mg r5biT)%eZ6 lF9O6"εjYגj"%wWW{q _|bBn֤ٞQ+3 SR~_K!-yHw=i+ 즈a!a^:srbd}ܭ`+*̧fT &ad"酯WN*9XVxVWi --&g㢓&fa]1-b* ½nܗ' -vO3EG90g!EuĪ&&2.ʹQ%Gkʔz `n1oO#:WÉf◭Qhw}?m`B61%yP}薽|Q.NC_ҩ.ߗ.|ӽ5L{sȿq~C</$"gWd|3<ג^g^yKQ|r IڥMAW"Of[Y1, V»3 Yy4Jul&9S33l)c@kBj(x4|Sm;bJz]Rj .3_jPHkFI-b5_l0Z&X=2adv̜%) ,u=fP*F鮠/gS&XX• =/Õ nU:">NGW}X *գUlm("ʘb*:$/zj}ZG/G[WNcT6V3[8m ꡖ_rl޺:g"LP5эg:*ceóA|nkTǤqٿFG]eI3>R_M~Rf62W_rL6U)2gfB 1\DMu̦u\6ews3urm@s ҂ư7c⁔h@1-$3DOh+s =DdĶېۢ%Zh7:q~_/?VQ iTPPS4ĺX9kpΘ_<¥)  -‹}s;EK@$Cˤ쐡.R癃[Z{E_u>@)3)\8X}'ԭ >Z$SW"O:¤MD,O\|[rCi^Ʒ-QKGQq@M$spPqeS() ~п0B -b*1gqF3ZOybgc>pT[k뜦R%6GrKwPQ@DP*M3͍E@W6 TD bf:wY>pKB359+ݯK%IB} -:V'k3Ecy|Djܙ$&[ ,Es#B%]v]sLE`6b㢜Ԍ冢5-??_Wk\vj&YU !xʵLqncOܷM=ߴ] l,jN )2NegK ̩Qt -dII<᣿|@jJfԞ5Zq:E٦ -VqMGB5Ns-w5s̓} %RAλTJREBI'+g BS6D Ӟؖu!PՄVp1|Vk}*){nYx.ɾ.'0 jV0KAvQcq;Bxy_Ԋ`~z4KF\2ǭjQRtt}J3{Z0aח{J:LH{Ț3n,]~(ZŵI"j!%, )+G 籲.1T9Ve#~8,j ũYo[k ĥl~, -&a.͙D%o3}-*B3v[@Tm#L%OKmp5 ,=Y]slDw WA/VeNc1 -RSY+;9fsiUvun)3( \QR#U m0񫢕9~zep}o.[7ySIkVĭg+:nζ$T?F_h22zVSDXE6!1n5k:;xWR Uٟ&yi=tl [Eؘђhj`.S˯!a }P;\*%'{W'~_l?FV "w&G3/fhi̼ǿ\F]V f¼i!RC!"%\Q(|0K]c?Y,r^z@W5o~ۓ6dK -nz#ܪ6RS6L3@޲VETp8WC>rie.-bB6:)&2%,x`KT!ΞdTwD)msMݙ Yι͉qLCZ uUPeWp./3 |=ZyWx_ݝsbL=i9׵6{{{>&h;]L\[017U?>\=j[oxc,SѓĵDnGGx0.U \*==?U (f*X1Rw.)(MgyGa|K@ϖ -v ڑkv)^l;/)eZ=,)M} p7Lߗ/Fm1 !=J6캾#xU'׾36MWPAbfכX>[2| cW{6T(yI:~WLH^tD}j7'K%T4܈I2\6r?u̻^0٧:jk SNLqpLQ&dP@DrDEsH Pr@CDܵk'9>}_'kWLITUJq!{1+8I&{3 bCB-mޒk8諥*QB^i>Ox>FTqU r ߁q.:9EK˓,I0oG$YC0]Gc\g.Tf<r<񰣱őV- blP_I܃ zeMEo[j.X^gST 8MWc;KT_{{;"+I4Aڶp_ɔ/'9ÛˢuBhE[K͹)cY-0k~˕ev{4B'Zh~v1n=XqZ3?`GZ+- -mu~$XeeBs{ƦAiZ.kS59ۉqJ70#a1yr𑙦 HQ7cݒ'6Df2ĩ|:-:E܃yn -s*:1X׊4ta-O7_r1rڑ/*1zxbHs^nޑppCNB䃃+MyZbsF?)bR}"cRvJ#FP@VLs,ɥhw)t9Hae0~{o>?CrKLDYCq<2+_83MIұDI\cc;4 -rr9N]7C9dqĂFv>jNᵮm+:v.(g+Mru(%_ǙɧҞ~0U9P.ӢlU,nu4TSkKi3ݖ'@6yyR]4+{58\r(^lWp*QdyčE~CII]GA]- -6ԪƘ1ǀ{^@%(uWy;k0$!'|7D/gIXO8XG:L϶1 '|0=  mS?gSBۆ *HVfYjW2V[ӜN>5t(wT 谿B|Yd+$zueG7>5][f1{9 -ws*YNm+cߘ rL42Ln0A m-c%lB- -:>ˀۦ9v= ؓx P\ -#,O9 Ezkqy`Q|ܝ1{431fL[,1FcDbA#ޥ&jLQ"HQe6sOy8!9AYAa:Fg -89618G}wn~C  -zךh[])"f#76'k -΋뷺1Ouί{&ən%{ CICĺT$W/B-WnlEw!ؤ#)%?-?VQ߯wֺ*Ocԩ W3WzQ;kZpIMFf?%PފQ hFGj|y!Pyq8:յm]u1 Sܬsܰ527,NRd. p,ᵱμ: 㜡QI<ͬkK[բ[0J -Gls+m-+%2\kS4~nIl gLl?gC -`aVSY1Q.LpI Bu<-* p-H/*gq5}7?A~#> 6Wu&F_G-wGM0~G>/vtj#>b/ E=e7;+mztgTH%%CUsAޥ@&o_ 變'mI9_ɿx)3-y0YcT&=` dLbs68m4H_[FSE┄y\"+9weC\e3.[l -\S/@.`Ԙ76P[QUP>Iǂ$B=6t?Wƺ쭱ðm.e[.(PILZH;_m> cJ x{^+=?0G+^6n2mM>5=IzU@k8r2φxSꨃ D+<~ּ0! $Dé8 -l/,b70|܀薹Ijo 6t@KB|@/n.G*}U }a8rOC%=[e;i)GqW0 [=S}#Qsp ^enLHMȿZf]6ѩcr:B1OTyS/@K>7 ):6ӊ7̔ÞN-k']7>oQ qPUIDh-73#%)o $^)'f72|Rn/ubޅ3_bOd:!ǑZP7NL[E7EY< yH^m+C KhY(;ͺ2jXfŰhYӓ Tc" 8}kQ2 0Anq ,ao7~ xaL4t{7P %.|1JKt&vff.- K2\MJ2^hk?Q6XfN$ݻ"jU>K}N@O.r:@xR7zJ#=JLcdj\*ؗC&ѿ$"4“9A͞$*˝jh* -{etu3-ꖒ^YАu}ǫ_QG*Vɮ -6}} -W:e3:aro#g3KԽp)1Ikh <_'@?#bV:s_Cν0̢g@ ~6τTs(,L1Jկ.!m_Y9[Ŝx*:xOI Y8glK +ſkŧs{J\V cČarƉoh0ڕHΗ.9*>xRˬjaHSS׉ܓ" PIbSEH?ҐV 5@|ҍɱ\.lj/֨sO]k?:#dlytc# ~<:lh3 -(̧cg;dr\_ii;̞c$1mDMYM&&Ă5VS -HtL)v,X@t+HL;be_{νw|gޣ_@.K BpC(*դu"F{=C VX~tjj Py8rA>L,)pZiin|l  ,m6t6 \%&kyČCY1Ǭp/1Jr0b2.;e?2/%tdHsuDh9\^wo*5eg pY01YBCl;ur" ⺶|PƅdH#gw9 䈎mNM6)J6QcKklWvί BcF2c5D5'Ι͡sJR IAM(!PN-(p(=s#{)Ч(>vb!QIAa|3͊Da۹Vz$B# Ituv Hg>5Uscw6)ܶm;^SͿ^a2}r!pH> -lSSBJ|*#~k -SylTJ2п^xWs.׫ԩkol3F>-> FwQP~MW 舞>'Ofn[ږ5N=C>EהZ|eLOC|7P <|<֘101Rvo1VpL)q:3 N0+?YjbWrYZ${5\_+d|\`UstCƽ9F a22{{gב`1kcKgm}qkyjݩLcJ@M]}9gǶ{irwZ2T $Q@t{CKK)G -ðV%zQ@M9d|:2̧>o:a3w26{ jGq;l_V^fǑ hp]o[7'g 7_-r=Չ?R>9J\u{9r5ϲO,JOٰ[d&Ҭax[GⰦ#cT8EJPnH늮aIPIG8 -\ҡVy~fOGݷoU'tbM\m.[t((;?k*LQ]qk+? nx@$'t=TrWg?d ?\>>)fJO~_l|sB/dgedc&'n!X~3OMAC{K{ƴ5@ɦαUDvD%:f0;[{A m ҇E8PqkߐGJ[7&kH:;m;lU1tFDĦԈM٭`͞= 쩅â}yY#] XE*Q_c=_3_Km.׫V:%^^@-&׻ZrU")\WobvSG'8I$` ԀQ/m~N -P1=&fb6.cp= ?JPF_2'퍣T*z8s0S_d!\~pK&9ks85!߹ԇ3x%6xk!fiz Ϗ!Eǂ]Ocyb}VWuA>1 _xN]?酏ɇ*ef1 -J谌i $ O.lrJ:Oo{3mniڬfLmݼe)j^ -(_LQӲDETT"\$HvgC۟y>^#Vpg+g鐘^{bk$M\O{`C9|!:;ߘ,W~jk*`{rU$P>*~_z.`UeI,+/uU8:]K^W,:wo!=0 - Ůiba~uQXsλ<̳*4YF,;2 PAOum&V9'3ո")7ߜ 'P#&7*^"%њK_JVաQ_4E%DլPe6LjA awSwCƪX#&iEcJJU(L^]-Ōv91=J|_ED:&Jm -;A&?;QW+˄(eAëBv8{ WXql=udf6PSmA݋l9|PCk5ٓrXgE|Nwl<16[q!WRWQXvSjzYLM(ZZAɐ7B THjWRLNw=#-1 -2@|Oiz<0X{qz/z\^Hǀ}Qt)E=~OV$oW ѹ9샬 \ -1uOQNS6F`!h{ N3ISt 怒R1Κ`0r\g# y9>> U 陃g*I1 v㰈vp(E'/Դ򝷘 [xn`ݝVi~eku\߆ , }J|iewq'a@^p_r$yZAs켿%g=s⠺<³SF\䚇!gXmA): - kaq=&>#eG5ts)otK4 o -{+.$"T& CS4WgYFWzg1%rujNYm {^.U9rӋ>)VΘ80RaHZr~K]{A5LJkmp &p;ZrW$&4bzlj9ȄF -ԫ 7XwM7쓨ߒ&m.5af#OfJh" UjC@|cׇ9uzϡn'խ踞 rV_$dpz{rN=X%ϓJx/;OnuT~z<}ι뺯rY6Zr1a5w -{2Iw ee~@E͊3ON+SCj)&ր`y1mzA|>~sگ=z97̱"=bLDS.J0A}hTu K`׻ lEj]zzoEEX_bZrec^^|x3WB\p> Yװ,fd5fti7etn@_d,ӫa)cg,DuA-*ᵥcrtOpIJWqmo'8琉l;[1 %AKJֵ?q~^$x=ɩ?_?#\ )t>x{/x)k: K$h؛mʆN<ەPN5B"bd?6 -ғ*zbyx[ؔ j)+xtdg(^8 8qy7oNv^^~u-ecu(۞6oN[}9kwd%U"a::C)wٕ;ӂ)2$̓n+׷`KCZa,k{,"(d<"2,朣 -#|@,KQMhh,dw%rUaw&TA]/1EA%*D=uFv$oQ|+)Շ>@˂7̆|pˤa5 "i#`[#a`":aw1"O:CI@_&՘2$—%t7՜rPgB0cJMbT.Li 3sh 3'=3%>U>5n"AEN+lP$!č܎*%UfVyXARk21!YDk/]/j6iO=#R[Rh̯@3Iiu)'mlCf!hէ/6?fGqE\SnI!nXMu ?'m-Z0l.WePLiIe1-Ρx3咏VEaہ+?q ^% 0ulKvجB㧻bȿBivbSH|W`6X0Sn(!5 -3I_ttH/#%2V"^V$yRvojiY 8bd6\_`VEAÿm,2*Ģ*aj z Һ?*~u54_5k+iɭrPG6xT7yK*Kw_9sfXܟ@]EBk#Zju:tp{ qN&`['`OAE L̮Qy-1if5;G dl+Nwb['6[#?}r=ڇ0з;K̠ O~̬0G!^"Dꎜ2aG{0;^L/I+>|D5zT)uPB%};Ө/✓=dIv'n4`wtҸ\u0]UbRv1-vHaw3&^w@Zdw 7~9ސ $-ef9.;?Mg:ΤN/)NK}’ZVvK0}E+BHHBH}btFy8眙s# .P/GPweHکR<iDm,n%W=C1zv*P (yI&Ӗ7s&Zs! 9#U;o`xW<) zOc7 )d8gTۃFNh7gR 2G2ὌՔ5 [+p˞M8)[ʑLYCYR/%j]-oci%1[f?jό]LP{ͫT[bMYx&_RF\/9~YPBڈz=aZSF38Gs;+n)T[C(]$s 93- THx^1+{sF!<3x.:8m9[hWVQ -{+آk\IG#?zY%|rݕ1KiVb_ٙn@+^ܜ!])VDx y)}HFmN,}}⇈ӷ2ׄ ? :B?+)dBo-*:2QXЊO"oTy}Ya#dp?&LAL̒Mx޿6huC>^E/ג W@U#E󬦌EHy8S DEo(a4l-n95>@Гvt%+t~Rjn)\<֊_7<\D{hevBR(&$5uߋ#g.Ǿwg Vt t1)y1VSʀO9Ĕ |1p>4PrnhV& Oŵ*Љy~kZ TŽSY=p uv[9C>udte"qM¨`nHhLT##; *w0:OiOMF+)1 ӯE5քQпb{r^ZZ&v4H7-fkz9c7'u̻E2멭 WMf2ffNd]xv0ŀa ItX\7\%!zE Hrtt(^g i4G}a6c%Rb -jܤf #Y[ӁCU$]rN1"%ϱo =}ʣnS.,;F Ī:Q2fW<]"61P9/J#-{/)rs\f5w#kS.`U}M'_dyW@M -Z*"cѩKFڹ i+rw4am>8z/j蒳A5 -N.K8i:4RyD,0p833Y'NY6gZ._kOo_O 4$9 CJEl{Ot7znQx@ah8s+&| )"jfzF{-.?'o*]֡bf]μWؙjvMWxd2NݸVqhz&)]Qq`8Q>o: :rbH|:$΃,I[q6]-2w{@R&IK "s :Aq/VU whֲ 8eWsR&v{^]̬l,v$,#X7C3]7g{E=~(W'ܟ/-S#sPyW$n\BH ERŽQڞp Ae*Yb\~E8<bVQhxq9ca7K)0K;YU0}ǫ?#~MZXi+:e.wWDtmDF#zfeEe^]rY-+>)_p GQjը /_ VD7ʾp1^-a࿑g0ȬCN=yܨqmOg滫`3phG+jϒn+!kh|t 5&Wπ` au0s(KQ=;ӦKJ[Y «bʩOo1aѰaېiy]%@쀛bJj{>.i/(AstMIb{i}LH6<Ю}wZNr :FELOĪX5}b<(E?zK`6a[dATĄ:.#,Q⟮ģPKIv A{9-s5mQ)nLy=tAwl,+FޟML.?UF5}Qzgu[v7'E)eY7. -jim@ -v7O.c'-B<&7TPnm!R%8Uw+ʪ-M!0H9/ND~_qM>;f EsTڄ)p*f`]Qg~Dlŭs,IJGڡ"Sعcp'~4Q|g_ϮښnlmϕxNf 3Rua4L21wɩYϘS֕SEsI)+lv&-F6"FU՗uALd6lS\OMY:U5U=cSV`.(*[ % !B*- %! YHB ٗ{_2~?{y -nkȫ⠀D "mǿ6^M9Hأ垚9ȺL*k/H+ ,U zp)>GԏM?UMAo>1 ۔Q]<+++H+qԜ>4۝= cگ!bI|wK1(!(?lT -GЯMɦK mY2 2q96 U}K}U?-*FvEc5T̆tD -OVI0ĆR6m__Kc&~יG&CjnZ]kκgVfFG̀ܣ٭1=B9FdbC9Gg2ﰅ $j-\&]2rq X!N>iMoҞ&j$:raEowvsUw&e7;oj/mrG.K;= >.iu$ } 5ݣqk}#ʤY[t:Vmy!frA-UV;cFfCFy/'۳9L9Xjg -D# -G8ǫ` ]oÆ꬝Ҟ4,TT+h1ׯ-sNC-ɇ]Dm xE|i? -;I|)HB@d7(vקu`-PK*b_M(Sj&ZmnWmL:\çRN, yWQ+FrM)dwo3W+GzNKnAl|R'H軖Ɇ{)XP;To?8?YŔIۀzP$ao2̯!?^5IJXs3Oxpߧ&=IVMVkbD 6|'}yT LbࢣRD%ٖ p32ŕSu1Q=`E䜬}X:]"w-tM0ϲ{|6w i^͊_iu<(ƨFԪt*{Y $*o:*0ѾU*N_-$ևKjoWm[9mtij,|~b2Ho {˻^Sw#I12d`_OVxMIBJo4{ujU -d6qbzꓰZL,G}褙Kd wn_+h9V[ˢ9]qT꿪[M910r0RZwA)R]d,a@q=#LHྜMLބCpkr@-Yzv*&cgRt m )٭!>B:bJA_P>PzE'\ -i JKۆ1#3cwVLUpwjWXJW?;H-q+> 3!513DjakˣUM(%nҍ;5<'m xu|g{Ew G;":)яw]OmHr\ 0~c7->O?LNOhJsO1Χ|{GQbx[F? p0E JLlNӼV2+ȥQD绾Orqeǫ<"TrHEN)+ E|c\1ѨMD-3- -P3k0>z262vmǃJrIxPY}Q|~OЛ.^vVC!`Kw/ AO:z> zXQkV>;-M%ڋ^%S*-!vk=6%llTy}{ւyRWCoL؆Wa=G`n^5i‚P{I"&VgΥ79PPn PwRX%Nڝ-yc{皣=T{sipB%yjeʥwKf<wYRK9 È9E-PI;qk8KY%C5}W&pa=;\oW=D(JϢ+#k"Wɨ=͑P`|b`W~ El^00Qْc{xwYRu~}Q=BIOCQ}DzܬjrF?BPA*?qѬK(q+5SmW(;v JTvnO_S3 EVc&`e7T qc#dwoߝٗ\.Az^oNݑ4Aq$#֘P^OBg;՞{k?6w*Rz ႰBlWXOCrNSt_BPGN.*X]갆Z%gwg;n6irFqO>pw^ł CI Q7ڑU]9sEI]*&0fFƌALYxU5j1RRA#/j?N %G(9Y,i -cxUo׾0L9C30;e3eߤ^"x̵?/g$ܜEU5; 䂠N%̂2g]3W3VjCo.0ׂ*6E{FFzCHd,b4c b~-5mH^^{4XXA>9xsI+sUr7$>jb&1$3Y -Evb YfgO)3 -SbB[m4ԶX5)ԨP)[f>,|s>w2ccǭKc]?̴gi闙>q;Ο&cN,]όPxꍦ/SCmzϝ͵^|08=Q\=9u90xgg'u]p+#N|TG^z9\2AYnkZD 3l cص!g/,-T+*r N(ޞk\I=E*/RqZ;Zf҈Rvi6NU=HR*;\8 -N[HA5~>Z3->iWV+U{ӺV5IҪx*XJNtm[.Cl1./Y!lw>stream -HW[o}7>HJ;QRcs1"'bQ8lhR!%Ql>ly,Zw=|OOItzYҕŪ΢0z/"Kss5H~,e6g5S~\VѯEW|+kx=/U]BCb34 -a3Σ7AheHP qnUs>T4a&DWRB2&<e3ev@P`5,B(ܬW۲Y]tu\&mv3̸syWu 3`wNU *,,np=[ -Ͼ=yϙzW]^7ӓ}X^ fѻO eW\C~oeExB+~kc֧g$NA?5(mb;FE]4E7N,W8J'"p-AFSGmUDq]6V{s41g?n?u\ݪُ<>]?j^~.۶vV0Wvۮ:&vInb>с'm:8vaO&yxO?(3^>=޷f瞭UQ{+})eH׍lR^S;dp`$ȫWB y]4ǿGYOO~?=A7pǿ'4zvw 4G謁*h +1x+0yPxگӝ6gﺢ)VuݟTu82qq IhD&*щIl$I,SҔ<LUSԥqiyF2 T)T3+x82D&.O43FikMbR gVYm=s6MmfsGuq'tigu.9=q\,汈eb.Oz ELX!>i1Ox3Ë҂ !PB #p" *9J%BJFZd,?J((Uke*DS4BKF[t ӹ!f'<R b0T.p 9 35C:g2DXD6jBfJ8r!c)-Y/;:Fn#>&Շ~E4)h|b8ID,_f51G$!)lC=:NQB55%:SXzܫF_ U  e80?Cmf,x~pp\urA߁s> a= Pe0h& C/WFҀp T`EeHtA4Mq[Σa(7Fa1*@< $[0ۀV&#L8fP2@ :y6} H 0t -" @=Ǧ&Psmhڠ1-l=TT-.^; ҠÅUKrt$Z$0:ʫ@}:!XNaUȂhp .٨&xR:<RQ݉ct L+S`bBTt L-S`r:*IAϠ)A.DS&hW sczSV۠+4tn≧]hw7H B/wS=0^H~z)y]Po AR=$DvOye cڿSmD"KQu} -C>h! WE${Y3Syx_{G\KQ7fSQfd/wɑ+?iL>LR-ȒX`5Ԇƽ|Vk5 -bkYY|ĉ5o-ZM:2m]rkq̶w8GVA͉0bȟT.\'gaY*,Tz}<Ég{tBlg.xtiZ7r,<0L(&SLxDA7Dadn r9%]MSL$2o {B>eO0ɍU MF‘Ch(yN g([S;%aNf,w13`U XaZNtk[c[u5Ȭ9nڬ.JEn˫=qNyCmq7%*ܻABu>Hu[/^$_^@vU‹JڸEw7l('N.[(vZzۻk?I >c]%hq%Oyh;ю̣Xk { @`3wfP$Ϣ".H$FD 7ג!f`DčCm#$,R1"{e #tX3V9Q Iv+9rIX`(`KU [R(Ŷ#4i3fW7CfQp5ؚ[aKCjKP!*#F"54QOMfc߬ (`}Fa୚SugˆuH\_o#v`X~[?%GOHwct8{aćlCaċwݵ\K~X Z$i۶kvh ?݋ 9q bd1N؎؏8q,'Utd.qcq3MHv~qiߒ v'r+Imc6m2'4[?C=h#Eh7'5O9zUc$}XSv gq1}H";uǥPu.zi);uC{|7 {=hN=^J(S]Թ|(*c] -+m: v[UˀϼI?qKh=@_^`'d -w6UYk ?B[@۟?ׯ/_/^x____WmխZa؞=MYaC،=-Xax6 d ɰd\XrhvdKɆc"(W p@\*`f֛l]7Rv4b!dY̒Ũ.\-]g%K#Xab($ f-Y.60jqM5 -7Uer*śjLJ]VҰ#J(\NOw囪 #O*?VD -P :٣s0*AhttH p8T* KؗM@ؕC8~,>3hcp9Uqm j|U0h74 =X¼PPѱBvFuGUk|* g9Ik˙W,JA GAÉKjDaħgoDs\JL^KICvR]Bp$Òo%.o{i}wswLWer&o -Z`Ӷzg 1kAF81"\i}Fa୚SugˆuH\_囪;v} -0bxCWu8{aćlCaċwʽw?FZL5==A[CCM%! t &mLČ_2{g:,.[vs% ( cq3%mrk EQ#)}K~/^؝ȭ&$Ѻˤ.'}΍nXum۷:Pr.J>WNUy8}W;sCm.3ˆxG#^omp@KjZr!k4R7a4RṔqOMT5H`M4f`AY*ʚFLL)U֔~ph4ݳjdDא{T)פT?$2fSRor.g[di;ksmOA`sŠ^u~seVMkҴUu_c'6A4(NR^,UX17Ҵm_V)#$$]kjrM \;%sT(虄1‰o8lH 70xy;H~5PBz-pc@`Vha?7Ja[nPĈXnr@߈`( -Hc"'22CR ؈1G(|.'6c~P;ˆ:PAuQu3GH -mHZBpd<hO&m|×\4?x%8/Y?Y?]D+8n D&W%_bOHez}YD[/ H\%PLP֙_fQp *֥.]컘yq(JHP# -uTPDI+5+2u-r\qF# |]r+3ZIyWY%¾hVViEZQ/xWej fW?[w7v␝z᨞|B+U:zP}TPⵆKr%{u>q/}^(8*!*V˽Qc-sQnnQg%cj> ܿdy dɤRy#Ͱ%o<1 ͢Q:VO|wus? p,og~ ==jgCd(+#zt@V#?: 2Fanv=Xq0T~zȟg=쟧vfK8G5Ox^#0\v@DUкzxkǰT¢ џX?!!>81yCX4 #" gE[&俈"-Rdт\gĄy]ICܰ+v}7:mVuв#cm7xn#n-SRO0X/RjU4 Kv2ׂ5!%1:av}|ŻV%=E>O"&LߟU|D&gq4Dގn8VQNTәس\?,dk$jbŧR.^tM/x0K8S V2=ΰ^ o$v]`hW8 c֠-`8Rag S\ #A z^%SeX6y2]S)OyKy%ou\- D X ft-]aP7rja_=aj'F%HNݕm2PٜBYÉ 0{ 3hk@ə9ъxTZo^3x*^7}N۾`Šx+.W/w|F ԫMI_jiʪUUbQmjTaU9WoZoRՏעʪUUThQLFQM,먲*򪠪 ϯ/n.reժ۽2[t1ɭ߫ܜ|ݫcN=6_n nksܗJ#~0A8X@I͂{0ĘC&DJ\\(9&Cw _vV7A/ Q/L$TH Jbݺx }$Ai4w_FgFE{ιsgMawB?j2nPf/l:ab9>&w]OPw;4y}N{쏴qu^3Й>?Yٯ]]352;|I)QJw|g>9u%{⋪l*67Ǜ0uiOjH̼ #DLɉ)O4=V5uXVvRof!\>+'DAbP=?6Ș>hhhqz#FT=jZ i5"=",U:bhA%$Ȅ '_yC -C}.NoQ4c. 2QWaGՈ2ܬf5Jră9B2tnW`"Xax8k4qB^aKMj}+x=+!!㑸Ά>h* {(<*MVqXEjLڤJܯXiҺ+i[AisV['NLJk71v sN};SOOǶ :g)8oȸGҪܡ5H・nMnWs;R7(n6LyGnOuvUϲTGWkcK SIKIiKVhG6[ѣ+C浒ۤ͑+v]\(I|XA"YzmPbJ(Ul-`+O7R y$D!^WЫ @D`}2MC%xZx6Sy,V3(,Üσ;ChVyF8 r*Bby솮(x 7=@m{+CJQ)_HRဂΰR$u%EV>R2> -+@ 7A\]S3 r]`FxG x͑ WKFa'~Ta:L2 FQ$ R{UϹdI]kv확qlF]C LEZG^ ( MIZG R:gZoFծ(ߺ})F.֖7~nx5P2B2ᜱݍ9131Uws :43J.ph\oH{\75 E ;#K IU6 LEFxC;Ү!gJͲM7} &H'Z[+uW-1Z lqHJyZ>ޮEXdSڎ`%P voЅM3s{'wr!ZֶPv)ƚ9EAdjI:mK$い!LU¥=QxE^îFN=fpJR۶([NBpo[ϥc&ѯ$n@R2>1В-uo_8sIyg4crW .>#BU8[0Lj-K\0ڤ ;L.hS QѶ,cP[xd}{R?^b؅ .읉N[=|ɚ݀HU Bw%/bl :2ʶ8lCARN]j!y ]F 4lc9/qEHC~ѱofjw_3I:$mw~jf3|I - cɨ+L1 BF^ָݯ@, 'Otpw>f?{; 77?/>Zy<\6Go/ˆ?"xkƧx_J|:|ou:gH`ORD\Lѻ}M26_8fSHۥyE=SJu~RWP( ;uن5%ΛCRpg-zg(wHn -Mo߰\zTb6r-%&0Mu|f!X4 n,P1Ckݯ|} M(Z79-Yڇ6C@nܽ\MyO![jm2m@78ECtͼ~la.IC)kdi~{6;%diV& d̹3rRؽeHa>$BjX;.- ړvA|0r&mHYt!4}v2u+`XA{r'lpq:j~`&loy|GaóT&zQީ`7jxHt0,L=Heqpڜ5fMkT-OXJܭp]S31XNEGQToy1>|d)mfF9[9_eL1H8[ֶlK>| -slm2lQ5B4PQRP[|5s9ZfyoYka1AIK׊Zih\-`B>]Wj{ dEE3,4:sb vε.w蚹tI/JYjf"$4gLT w$\׏lzѝ%;-$}WxTx1+0}PVVH |xbyzS +3 LĔ rf\\WtW`<C*ݸRc׼=tj+TaۈKj,k6r1gkkiFg{wa -O!rlW>=6&baF<tj}zWsu??镋3;@Zc0GeC2%;g({Qf:"4*(.<,xE'ǃ;.rG40C4;oNC襞tuqVqfq"W -~hX5Qk*8CK<5sh|}nk+=ve}ҝZ=ygPJ`/wvτI>V>mO/b] `1gVumOI7*"m&R{arFUAxC,PNSJ~)JmsN"Uݯ~_]\t8'}gsn Zc['wte85["AQѹgq Sgqz|-wPW~-Ln*Ξ6O \VOzYb_G+RJ*x=AыPq:#vmڞ}UM `"B-LN}Kfv}YJY=d}n/co$nboqkd c֏썩O[=f -Z -!-,Yn -dZ˚=.N3;H;DB`uc}Sl8)+) s4A%?u  -A?H6)E|i@=26+dAz] !i촔Z o2ii s2ä*O<,|h][[ 0"@T(=Q1*}=;#3O< TpƕC׿s b - 4}P+lnĆL E:]z6? h9מLz#~D:nܛ -R;1)wĸ1Uy{;!|?$rŠ|OɡG(9AXM)㴏6tw`muKC_eh7(./@Q -#Hsx,c^'ާ-O3eVV0u^q)|K}}+NPZU'y/ghwSq(lm6 >y%f [q%v̂R#E(59ݼLG/K$:n- -&'"9V,];%r;ĈѼ6MqccE8vl?MeFSߩVmJL%.z -DJ),%6R=`Z4܅MXsEODthq-nUNHFSٜ5{l$#u5wZ(u{J6?LP=5;R {NycJch5 !.J6w;W/6 Ʀ,uwX򄳯;~Xm|4$}-4)O7@^~K1˜Wh -n'M u">f[ 3ǚ voTE!_;*Luw΃uñj+Ɠ7]KHF{K;|)xXz/Jq-HQs}n=p᣽ *w֙|1O -lzé*g>% s v~ c`Rx10BLT1'K0]ΘB}YMnAC_|eqہA'x@.kg`dt8áM>@`6CfPт:py.dJ8A+\VؔUhP0p0+Db4cq"b8q sƎRW`6[Dsa2̜3Q_])X-)օ.=[Fg|A d;{+pl4F(TU=/v+C35it]CG 8/fg )YbܘżA$RҐ{ȋeKt"SRCnCZtQN3t0+\D,0 D0N xQ%ZB'4۵'a( ,䇃H4_kr?]SC )d6.=cPlB&AumjNXLJ–Ԯ)@YtOz޳B{dBIqPHAjp5j%LW4(f:SM'I r40w!ǼqQN -s 1 liR160Y_f!|>iE!yVXΛbc"Z1jv e$ۚ#mxdxd*lozn5|J~iC^vQ"deVFJP-Q +s\0OZGW{MǵKɤH8Jkv@г֖Y9S$6F^mLFCR&jO*G.-'K -_ϕ41.PRW'6b7_[UX1 -DOyPhO2aA,\s_ېˬ}f$ _ʪJ]ZN -_3}rF7tEJCYQuؑ#4'Bl,B;:.ٳxBĠc$P" zgC"6i|KLiTPHv"^O\㺦]OvH,T*A> {a8":U *^ -r9egN~%%`dfsp;'&Ēz,Xx@5), 9v(rH̠d\Z}颵{0ח_[+'̠B4J.mN -!1 -Yy(0,^2pnUcWϐB°H`sf>_ງ"}2lm]g/0" tCm-JD0ӗNr_<#2bٻLaT Mnl?[Ї!*@( ,%3fVv)YuaZ]&(II=dEiK31" Q8b,+wcD! x 5R,4}ݳq SV]cʧ#BAm-ѧ@JF}Uxi&yݙpDxFLe*S9[zATmh/ǕP#* ̣qFСL Ķ%wM63+%xQKN,%ónCa Ƨ0]@(:KCЇOznA1E$0]k^}9'HN;; 0B -endstream endobj 30 0 obj <>stream -H]o۸*7FuNq15ФJQ_dHM[ͤjm̼dW?~ZXv&KYʼn~v\ }l2+pd!x9"?0vVjTG5'K5{2ꪇC)47s0;5ם♙%&cgʡ%pIF"(ou'Fy;zmj ܃\Lgf&Qt_s`dug]Jbf$9h @ooOU]W_pGA$iIOҒlDMة@]xXeyf5YK7R!C*AjuL9-~P FPi1c,؞GW[ͮs*yxɳXsn+!-8CKNNvʁ k;=69q yX)v%$3dhs kqr/ʉje4͞1'Zc"PL) _* -ҐEn[E_˾Uj<FC\ZVz"^oUVh/#'~VVz1/)gF4i7.Eb4>12Х)_#/D11yzD1fAHޭ\36MozXM/P;dl2+T%10~8Y t};3Ĥ@>;3c=AtQM:+D67 7JI׆6$fbq#KV]ԭ'3=4K7ine wzא'/vK Iep pIyDu#q.fIǀ{l# -70k̹̂.K鴕 _lּGm1j`?(\KW_D%=SK/6Nf_,"1zs{W#nf/-8C7R/ıɬSİˉ ʆJU:762wtljbКJ8[%Je^&'b2 Ez+tj:MjԂp.L:~Y/f1,-H 'Oj/¼)c[ Ld:]r4i9&p7d*o 7ХRbxxkn^ZIל2=B 04qg=C g1hj -34{2ٴO#=P3m[lt|U>񋏌r{>y hgF{Af%'A}H]4Ĺյ+N%[ghv$MҩE\3I*Ei_ɶX̝d6"1=Fxj8:9 LIP(q "`!Wj56яBdLpb|Az%/sOt`y&H7QgXވ銲U*3?N1\8uI-g]cW4Q \Mo%[@C[h;ZvWiL4PuidJd_ RI'b2XZ -k2a%ju>N s2Ԃc_y+@KF*9R1d/0$Syof}N(m楕t*S#ԛUw3 )hy֨ƨP8c|O#}Ѿ'.M4ҳ e(8C. f^no޷>񋏌r{>y hgF{Af%'A}H]4Ĺյ+N%[ghdi*ATF< F3hp!{¤MOmvJepҽpIyD?}{; -2ӿ7M/a!Y6}sQj2IЂ3/EB -YHb)VM|[cYW+>Kb+)+ t+(HvTQV[Q$4*G6n/hs kѻ5vl;mGNnKGp[ -5Gn`2W7?ě _̹^k^+ gfNuT*V╿KTUGdz !C\!@+(tJ"d]ː5zS›gQ&Jjz(q h -^!i56,SPRR/HPgb:n|q#N󿻝sIȘ置OfOMf$BF'(uVעU|QNTTTE3S<,WlVԺ/Xю) KS zPl IЂ3 k{Ƶ,$ >OYbR*QG:re YDz,3jc vDG jY!y]DoDo.cz,}˜;YL֛ii?/hV@{$ziErH!N;'yFEh9B'xb.h3 L`gx\VҲLE}Դr83-_e\Rךse -I0cʥ_:%Dt.eoiޔ&x.6})j'@ RH4rn/pY˴WhF!YBTb|Az%=u/Cr"cD>oON{uo{Z2mu|ښa; TvRHҧ؋բ9 c[SY$N?ʋ-|JjWKO4/lufA-UiQ>xHe9ٟ~%:ᇉ5-G-Ȱ#u2|#ԪXl`2ЁO^`uj)E8Xf"R,|]mJBbXoOw&hA7Rr8*anHz&$;DJW4ٔ0`էGMf=C#q-0 NGC'ixiصʴw,Fkr*h ݚ3)BA?*mTqؼpY(+PtP3Wpg[M)Ϋ\9l%=wt -?e^a)1@Pp`R|D k!9{T/K/AhkHTZVluj#UiacSd+()u?cfy*ZYϫ'~4 -jq_+dzԭgV!Pf+0a&Y)&-P%'Ҫ€WO" #|Fb?q-0 X)/>Qx2oӰk5iA;>jYƖ$7ΉF ح;P0,:zaOWIl4 rFl|t؁~[PbB(h{+|g\ï̷癱=SW׹sJz{=?ݛlcX -F(*;>Xo"QF6#V|Ya*83=v,m>Z -endstream endobj 31 0 obj <>stream -HWmO:ȗ.mIJKaKj-;; -C9gA(x$;[#2xuvXSL G8 vO|gK.3T7XOՓ,CH@bu^gù>;Čm3pžo9CjS&ӀD0ȜJv'( tf'Eh;ͿXP{^]>+ykW\,]Y/yA* i}0chDl%DB O[59Q51J pRqҼHYWgb*='_V!Kd>>Amʸ(a% iZ )YR^F{;&NN-+209t ) -ucVپkc}V -s W;"֡^RUϜJ˻!Г:Yا1-6mˮ4ڵlƈtA9jfS'6J kqKwgȂCⳚ'_V!Kd>>Amʸ(MGf] k߯԰bSmJZk2 H0yNPb>MOF.1Sj0cV]>+ykW\qj&^r{Tzk.b 9âfJ -7ӗ9G)I,(_b, -jN8-7a+hQh ŅG6Zzn5- )D JGrEd9P9J!e0L)K b H|,Q)D b,1qEa48V E@@Wpb\,k$vaN M $y _@LaT_6A|ݺ@f7Dt\ǭi6ԸYX8b#|057Q1S,h!͑'%@2mn&0@-;1#W,1aF, !qWYCܝ-YS!d]-z61`y ҷ)R&/cȘҜ,#sWD - E+~'b|J}cppsּ]180ϊ' F QP5al?l%@[|k"̂͞ᅒ<*~h W9R+m -CFԭ/b|xP>̷Z].Bլ5x {}35p#[ -4GgF~Z? q mtsOd1c4"Y -,ո*_ ArEDQj"1gq؁>D5ڳ@X $qļ")Yjj8I^2#d8b߷|DpE׵$NkdKɛN&X,Ic8GpDsX]V k߯WlTd|Xrΰu\ӗ98chk 6f85&㗘 " (@*D9o\!T||B\qkŅ^P6 [qLVl6I,(~^zzzcxm_qE /٣nM*a`Fe6Y-$[ l4Z91Q#hWBOn -S1BLC -c>m|cg,_E$X!ymV .oR? ==w "!fwI ,kZUe䢮YX)Mty;~N?ܚi՗`nQU<}d 4Ǖ§oax(>0x[8m;8iĵ3 -G|:WB2ādpK^IF_)#j\^mV׺Vur5Ҕb5~^үH.[n@E2 Hi!v'( ލN -g'GsF?O:cti?[sΖ9#:)sM;a0 -wRVe?.gP}s{u~놇AS^}x~Q{ -}e)V2пat rWvAw0Ϧ >v{CЋ~[WAgCv'ڑ#IvUHreO؊Xɝ?H]٦*?A!̶ik}{0S{®G>1= V[Y?z2qfe}cAގ ʱfɟs<_hܧ"l&"Qz$4v5Cr ;'n7bHpu\XO~K -:G%?Uynbݘ3bٱ 6s =02Be#Pmh8=v-cug9z9M"[r/xyO+B Nʹ2'im!rPMl:Z(3t>^ce -$/.9TׁuQ>d>(fAtpbEuoC-@.8OB \YZ`;a_'St U\QT, CA^(%yShcC(Gꍼ+vN(H1e3l] -Foyˍ@F ]\.y] $g)ͥ/$4߭Tt":`t bX24N.@NaA%5D&7"Kzn`dQ%}1  -Y(& -Fyp xcfWCkeRv ɘ+F2b=5S9J)vɪ!@, ;۹g"AU_WAw -\v>mVsjdNt\{Y9/SOB^1(x^jhF/\ciwNT !*#z hݺ5f_"1/ENV!]MZw[Ӊ=D1iU*K^fKXhr3Vk*GMLXJ-zM:w=Ց*A⳹Vۼ>o -[^w `B/ufh^ڜK9Yœ¥ Ջ Ip6?UF''E1K#+z0ާWD]@.[ HҗDG2^=^{'/;1^y1&`_ -J5+UG) T &)g6Ρ+2E9 -6 -#tqoʋXTg%q@̽9A;]/Q.FͰkN1~tSyl{_V}/֡G!?N)'N\0X4%IJ>@b}:[<\!ǪwAi:)=7Pf_;F$y38b>?NF@hè[.Dg[V|Dު_bcr\o.jLe]BQڊuJ)t z20ʺG 6Pk:ksU]#ҹ}52*<䩫U@Οz|>[9i'zKO#}n9~\rK6)_~.wz,Sg`Y\@k9Sr)RXf~E͹B# 4 sqo~ 'Hi iE^c;~T\J@(L:ϥp\5";0Κi -ğBBE9" $$egDjICByD`/wj;ه͑ ψroC٢Y IՠF+j Y6(JE,>oCߪf*d, :qVLts)PJKML2k7-.MiQ'c)h<dAe^!Y>|1=og7ؐ(] 8 %aPr2ʃQ$;[YAR(B0ݕr/Խѹ4&o$j@4Bi"GHEo6e;wh^˜V5}bp_:Iz -c [7׻Bt<Be2rYPgM 'a.(e^KD碨qnuGgc6I" nYWvEn=*tA<%MMdlܒ{XX'ÐpcCb [SDCiBƟiL 㻂⚷%O FE0ФUE&QGZ 9)\*-хG*WRcS,KMz*PC5a*F(7나hTbC\ӿ C QR) d&(o+ko99[ ԫ _'C+øIO삆.\z.~?)?HLvdi|M9UzijAr)g=$B*b32Z.]5De2dR ݨO!Hoԏ.+i]z -6, -]쉊\/UIQ7/3[2Ǎ# |]wcpI!fb5"5Op<&ȘXб0BuZؙ4@@78 5$9XRTGd("=nݼW: ZFCH9EG^q6Br ڂ$U$BqLǗm&7@B2qY^ N(ݛ'`k6'A n0bFM '6" c6'aK6ؙd\X~w}$LtWKT^o<].e5gW:zY*UADX :{ߜ sLߟ13_\fSyE]rӹJ~:ϲ -5V =W׃%P."ⷣe?9jg^}܌"n4|tCU[pMRJg*^҅| ;`MGEgAZ - 80R:{-"X`-X:Ld;)b%V@o(sRE_9ɏ95e tq$Z?+l9`$ms>stream -HWn$I(d>̅ܵX[s2w#YZ]G0YQ(5oG9<#zoSq㩞k?gp#Ѣh1]ORůG`)v<\e\m%iҽY̼0gc3U5nZ6֐Nj±֞_9J5VtFh&u;\AK)c}oE/*K-6pD@ku%몇± ² -s(HŘ60εf g(M/{Ad -#ysT+}p~O􀱏H| T|MGkJw-0fb7|GVFs3ӔؼrSLF 5U9gF|F9.1B;=lD,ɚ>;\ՅW9`Ju!w3Y|:N|G#ZzjW?ǜʌ#D;K`g2;3UFK /u/۝K*<0`Uͽ%Ĕ1c5B]N&Uֆ`o-1q \UԑdFXU]geu*y`tTJs^ @$gꋭ4 0|-zj{MbCfu1D xg!D3YJwkc>Ai1̵W^zMdvgͯTAХ&If6* wK~+ݠK lz|+2U=|{e^&@[1Ʒ}5Eæ&JS6;uTHzQfS3Gҫu;&)IRoA'%5Bq"yZrBBh8,Jl*S3N/`7N3/MEj  -tj֙&#$q~ݸp$=?5X,4f LF+Rf͑>VI:W^4Dʿziy˜_IE{Wm6}jP=<#\D9FQݙ`B񪑍l7LQC κ2 ^3!jknz'zK])" n"!Q|Gk"4Ȳ!htg&J0 +'3N3NsPozeM⩴)Di -( um -XJ9MHo)rۜVKD$^ųda:E*ʍƎ-sd<^7[Z1'#&CM'sHzԛj)&רÁ6+*ގ0LfA#Ҵl)&| K\'օٱU$w# -CI֤2dV:^ȘDnMY8H2sA{$ Hmq".8Q^h6d7)M-11d;R\9=Z4{|h+]A):L<+&\&qMbkA5 t6 Qh+ouieI{x (hpN̔6Arz2t'  ̬jmؒ<[N, Щ4#HuqK@ -quXד7=/UM7R\RYC)p, -Vl@ZkH3OKed " P4THȪ`Ȃ8U]`zt٨NR#QEFiz+~p,)|Qp4HfK=KbԔD xI6Q-\7kWErvO-Ju>Bms%haE덯>P]z$^Mf˃ž|MG-H,9{ڞ>"g2y{Qάς>'Ӫ<*e܄TPѯdoLtIW6N7jŎyDDa1{cB9=^ ߱| ?[7ѡP׳cZPc9kb2ΫPzZԎ ՓMH3qtITN!_3=?Q3 -bn-neAdʝ& `4윇f*.a\f¾ZPHü׎ ;yh&5iǰBФa7ͤah1Mt^`?|Fei£wG/pdC; Mmg+;<[唦B5MpHnU9g";νmU;~<8BºG% |$(Fo@6-$+Ѕl߳ {ڂvxğAHѮV VT'K[ ,G;81 v~orr5ZGb|P(2?iqݕ@f C1aH}-xAGmۈ#:bPo}*Vie3=Q2u% )e31Hz1t mNږ~p002:ȮmlCGry{js{R r[JnnӛXh7̆CL qqaC-k.#nk4POʉ|5PdF([FNR!Λxs>c]}bDh⅃PT˭M9͊]c*ƺmg<܂h -ۛVU,ʝJGL)aoc[͸4rԐRCJw±xxKמmh8MNc'V3燧Ϟ޽]ln?kޝڞ͕ӓfz۳09d -O|;l[} u1g$z6fQj0;62l3{ׇD??40N83v{vL=;ޭX *̕eyhU9;kboѹ e -Ff4.0w{NBIN.g*N6\LbxNn \N.fW#p0W;t/w7KJM_a3Cለ`) Ҁ:Xx2/exX2)^LۤIMpbHq<7'&o.zDO_ٟ1w ]p}'Uڙ~֮ukg>PP]UĹeǮU(AVQf˞B,3b,PR~-[iZ bW,ɣ4:kP.') P"d@Bg1ٗ'Ұ]Xp'I~~:kvlD PmZADaj"(U;F>azrA,pwn ;VށQ^_laPM^LG}r;yw7ӋOH'}ƽws$bsYJvztOs@a㟖U(V>_C&k$ {fu3OϷ7[={yٞ~Z}E[X={Qk}vwwq}7w\VNnnm]]uʔo,w?~~wse嶙}{q?/8# - ]<ئ@!P#IA;3ݥ73U:ӻ4P~Dn)ojw|uZ?# -82d,lBI}i'IY^(2xͺm%Jry:!&&d |v`D6l˚Kڮy\K7Ny]3\vaHcq-+;:Wz!jB!|fr=kN9#{n.JBՐ -g:מqU U1q.9LbH;":uh!EjʞճnSQ uf}rմ s{& ? SySϸY V*ڍ;)`ᒯ@€e5h^c8 Qk]cŝ6*IŠ~l14~g5Uemں!djk{Lc]]Ph,q/h"k+GZ1qdոڶO?؞f9X2n; v󆸪w(rBmݻ)}OL4(;6[hud "mzlC_rZa:E!}m!vha7rIlPYz(|z#{F֛iot;D,efz|}Bϯ~}-u۷tpwzOyWԍ;w{oV[]_B&sdzWϞ^]^~;ޟ}~?NN__|W~|˗]W} ٨n<) #]!0$7Zѹʴݞ`jUYmL _\sh;FۋXhebąnR%r`OO VqV#29a:)'G^)\pb3naұ2UUR7a &]Kzi\H+, -kb\7ӵUΪ"*fUƚ9G+/航])VfHuqלnTHrwEո-IR23TЕ~яvU{1hp8VX36C*:@CB HUc#!W+|V=$o;ut볋ӧ?:ѳ3zዓWG/O/[\>8zS_9<>ç~8zˣ]>e_Ǟ5D9T -@\*A//3Q Zd+`yV-ʀ|`zЋ -:@#eˠW-JgDCiefWl&AOSȏJn:P@"b9 ->+t'z -&J~I*4ţrcuRa)"*U:'N -X\N;®R5>Lfp32^< s%8W -lÏ`~-Qu",`>FJrdD ? A(=<6!F{GƧmL8e<[ b):@#.f[Κma~e8 LBOe5IbGM8zTj5 8 bv3%j\?`yrƩ)0c)$]d-yݙq)ׇ2pPQ pg@0 ;%Ռ{'`SΠ t'qcZW9ƠɁX)vl`*Y:GSL$eJ@7$a}ZJ9hMՠIg䳎eUe\4☝QԚ dM_Y OQIs"ΜJYE?z}HkRRf[APB;9Z` -bI1cӺi! -R+0&*T c̢AjjXDذsB"tKD{HKJ3qDjaar `i\2w; fd&UA[?0ܾ3RR -ohS.4p戕RNT6rptRu3)OSDfwIwlIJ s# -6R -Q"sU[?"MoCK0fU7/dn>5x0!u8 ED-Cd)/ȢaLܓQZ}ABiK[UL ϮsH#BsnWj!U4v moimЦn#Y6YUFg>cFk4p^iƢ7w&1AMa'Rۜr_Aײk` AȽ -3EϙX'ЈJףbm2U 2M{b_TrI߮#p_"Q!>iA0Q nl93{D\3gΜ .DЦtGZx;bA: _8iD%giLJge29Fh*J 33 -b{L!dD!ؠX$r;) )L]GkAFޠ? 5(]7чFGidJ6;iI%xUgrAh!S/wv }]"^ߕٔ`W ;k^qRq -H2*u%B>/cm a3xT!}^Ѷv(0 %g[qoh:xu‹Ru\+FvZGE6t)=>߅44am|, O"DQi9׃#U3r:*M8?٭q@CА8+ & 5 -Ce(Ւj,(6[$פj,fQ܄-Ȋ - MP;9 PVC5@\ - qR'C 4I9Y3!4"ެPVDeL|a450BRi#/8.&bGZqdbƀ%c%p|vAlP[`8hHrTL%?*QB0UGR*3-:CSc0ʼD|LnS! Q#%+>E1X.v,+GDiaVr?EK3M*bK#[_zaEJ*LDBD(yN"n 4-eU(`iAN2⾉ajNtoఓunWIv`F!ƙ .k RN4, T'4>/,iAцaBX WQJ3<"|Rh4.8OUZ3kD*Q0ϣy(X1Y{&eqG$VP|vic^ -dߡPիoNnᄑ<|{svi+mGA1Ŷӻ˛_'ON?\?{wƵ_dӧEo]:f} yZ>>t~AY?ȪϿҼ{קJ Փ_/oOG/,O8Wz?O.>;tvO_#:lg 6t+4Pxkߛu7<_<_yw+W!=J2}5o6X#~3Nn޽OYɸػ)kտ(v?}ʚ}'~}YM~&[~V~ޭzyUoR.|o~ՙqqXo~9];A~Fm -%2z,Q=Wck^39 ?܏nQY*ѰNӪSM MI'M%^j7QQZ`*h%c\McgέF3⬘E*s\8ة*V~ȟĔH0@i0 ㆾ:tk(CBN #=d4 S7B܊,Mf+IP8FF7*OSw1tIL6?(m D R/tRpӬ>^u-jA1m&vWHMkdbFsd b5b!MOC*4&>ʥ[HKG1w60iʦ;; XJ ki %UpJ?CR7 ˭TFL {JJyʎu?Mzԭ\gXzL|!b:XgǖI`Z-[(+ ]L_)3g]ѳodƗ2#YACq}ʂ\ +*J[5ǖXGh#a+) - mњ貚rCԍi-WMy=hGhLn&mm,9at؊Hr,4WPS[RL8LBu@ !IJ36j[n&?TVL$6k`w7^,aV6ӊPz#ik!56?a$WDZ_m)>xB蝪ƒ jtZh'}˘ k[%cշMbU2L3Bj{ .jP#<@k(F=<2zV6-yիŸdk}O!2Xu\P'6i:āxGMIolYg_4v=~3Lއ=β[cazt]v}8]弟|P9kQ -7uY(+t,[nR)%"V| `:I~ -2No3=~4NHY1jL U_qk4uꋭ>`LC쥳ygڸA6drd鄙>4)ЮLMk&^.r~I8DWeH}jo83. .[_hBDʲ~#!9>|q\s%ts|߷:>v)*0S{TQSQuLKKP*E熨 -}ءeNo~f^Ց9_A2 t<$䱊H21-hw3)li;l \ZAɧ,h+2]COZT6m$ -/yq:gXozId#XhO*fXnٕ)41wUJrd"gBz:V -[6H|#4uGm}ev Co}^ 2DbWOCbb51W*jrEf^TxPOi;h{StxކQTg>sb# ݷe4"6 -)#t(zWܤ ,jm\I=Gr|@\i^ʳN @R|kJ)S-?Мot6 \"8DKѾ|e`TTb_#mPubŖYͻGSX/EQ2ڦEsƍu0"5s诲[p|,Gz QmXT}ӑr\-_дdA3q?fMH=Bt -DtO-x0oT'HL!h:2!uyj" w#Yݻk)hIڂ*fϴZ j;D/KOlkhY~k?e#6~XtOU_`iN #yg3e]K0@T4s jNdyEQ2R -īz+:u[B3|#c^s7o9\K}SV=Nuh$+]Bg^ӄ'ON*T=ةʘNSR"[~{A6uqrճdM+B)p]c -; "۬[EkiRF8%i;'!Nejb'_wE8Ps"KcͮSL3Yp˞ c}pǣ 5>?$T|CPmF۷co_+_~_/?/ן㏿~;/~Ζ|^g,δnuF%l`.c?Lyu8A:毥Ns$wʄg:NI%|ۻI Oc#z '0gy1Wjuâ;oc@'ɗw5IQC%iՒnKg6:AF=jNǪq.nnS`#+8vFҭJ#`V١?QlL粥MLV}$7+A_t*#&Ǵ|j0\}Y\(9[<6?RcK\1Փcښ 4hq=![w=t|$S bc%s;P{cô8vA E[»ch]عjzۿ"\Yu{ʺ8Ԗ Vj>YZ:(\Q?g-{Pfi, 얼uNjoc: -Mζs&vW1퟉0ݽ5S :1v]v lǴ7) -nbsQXw51WEFψQ/eTڸԘ6b~seg:$ y.¨/ i+ ?pLmX!OͧaTMxH DW!y؆Ǟ!z{pqT -*a]声8|f;"ݸGh:chѮPs2#,ZۛB볒D ; -z+:4@>N ZԵZ}ZA^Mr^1uP0 ]s -_kc@ -b\׆By AlDw\/>COpdTɉ lUWk&o 4 1)f\[7IqŒv|ͅaPIFZ3xoR;S -<#۸{.nK]K_;zD]%<,0#Jv5[hpB9H؁T&nԹGل -~yڋ|BL4K_hX.Iʝ@Hte|M)V=^z{ẅ́~ڜ gX7scd>x&3[;%?*nwm&2,MߔW>+FFy!A1܋?EKKuBQ_8V|yr]uQhJ67~^ZDzTm[^XǠS䖫S Vyy/?|_>/Qμ?f>?][ROFo6U_RX] eaWi$3IPC{ -bS Xp(%'9|>-Y4(R\EEߺBڜfhVȻ#NDںhU-"{(5(4tMҏ \=1͵ 9DesjI8doǬEQd܎vtK&> wg ]vtM֬hyUbV %t`d Qݝa]U==ֻxWU,JDBRgEL5&! -o  Z8b\$r y^GiLbc=x26Qxtt R#} a#I+țZXvҭ{x -T6Sn<4 &Ia5"餒V2,tkAo㮑wX-ŝZUk,C^AD)]_/u%oeYV)0kV 2 t@FkD` Dxu/ЙWݹ6ϲ"| EA11v%[f{T` sվ?K^dKͿ$)4A]*`A ֡$lv0ݶhIx@Pk6ۈkPf_)N&LA.&}>A f-1!rZň -Y:LZ炖_mu7݀ahsi ֈab  >1R ˰]RikYg.r\$?l}?kaW=4*Bp+}0˟?$T7mv]]HɱZt/i1Gہ32}{C);(]KgjH<(5JMɮ@TK$%ln5@7ـBq4芆QOk"EԋĩBSJrG8HsVXP%2G4uFUgJUL. -*Q-uC3=YC,zMNlYqEWiߔB%tJ\sT0 -{[t~{=T2p1>qoz&7YW*`(:?P{RL@qJCWlLA: 9?K#o6&.lh-UF,w8%Z2B1hLДzG/jә{IKE[+IBYjV5hv`;y:УF[=FUDz :Թ G^h9vEj6'RX=}vxssY߾]i{|+Qt"'Q6]4pklUgx,`Nɓ uspܹ(弊a1x0(Pz^_6vHکL]҄t 2 O1VugW*B `@c줴eKfkL(hr\snBEWǪ/(aMoC'*6[afF M "d/QģXc;Wڴ: %c'$Vs~DHmU ԩ!V>Xc#s -TzYAkrEB۳%|`8Fte҉BKUhAEEaP"_+x5==EHz51K YvM4(mӕy5qf@v`ez@I{y=SyZ9ĞaaKkSGo -XzT);uJfoGa^Ӑ:߯de5=ȺԳw%qQ -pٚ޿(꒨|Nl-iXW/m}TVOln i]ʤIPzs~t$uĊ\A5\o&"9rĞIy)Vj-ʊ5q}ωݸԎbd?NR|ѳj"U19F&h"0y32I -=;'ٍ{X+UR?>E!w0} ~׹dZ,m\jW9d^j1t =l#y5]_[ JyOJ̦ :=b=tmq]oF&/ɛ{ytC`tMJyVaС\hQv,灕C3M:=z?Y}J__ -II~J_*@[0Y 0;uhG朻!I7JgՀݬiCۓܻ)]oܫ njk߃;ܤ^ޒ7b7A6hÁrqvv.9ۤuoʝw; -:,|J_eLd\usnNo^>|ty+zcfyrB==:\Ã3^)[!t0D -"|prtqٓӏu8>?c].?Uouwd]û^X?X^_^>>nN~9\s^e Gѽo焤 GXzOa˓hy}1T}t@֯c 5p -CGX^$}0J%\&dJYnEa".^UUY)]qxyLUc{I+霊7jc_ŏRhh\| 2nJmqXEN`=635]{ڙ8'52*g4,2QxEHCvI8X+L51j%96XTq\ GB4#2TWj8z=;y FUAؑ2 7 -ZŦSjc( -endstream endobj 33 0 obj <>stream -HdWKd wD!//$~Diַw^@eUwϪ1H$z?ǁe?g{I׳YZVKCGC -`7t_N4Uޠksn@F& 6 uVR6S|Z4 CqMRhvo7^.9|k&mL@A-l0X@D& fj=b36 0`ʁ/[%@%Y8#%PRXuh#N`taJFg#=4 D,IFӠQ'FQO\xIȁ{REq< {M 6& 64qڠ( }@6C@鰴 6nC 3fڵ[ % >,#~d|$lۢE,6}oӷ"=񊢆% )W{!^6xe/Q v\]+k¹'1*b $bb%X-M^l!mbT -&SwB`@ -8nd0W(֫aHO0]D&VGR]%&=G']T~8N(vc\H(&] VGk0j*o&T WINcU`hO>bD!?'Smb {v(8zt JW۾՛RMx9(a}NhEgL( ~|>ڢ+mdëWC]%[j#ԲYDׁ&] :;H: ޶įfNvsySqT1⯿]u= όҞ -x=a6`&mɢĽ.cQyXqA -.`)Vc8ב`<Þ9y&e69B;eEyJA N]68QעA!Z$&(\\@7af<;Ux<' Y)aMe&h" 0}Q0l,Zǘzt.VdwYwh;"' vn޸rw?O.ϢWN_Pm_:2_Jg ^v~!" -PF ],Zq1.V;N2 -$3j)W MIpIt˳gNc!m4m3~tEگ 0MEhsG|IZHL@=h"8䧓KW>Zs:zfz~WHμZC선l)Р-(( Ƃڤ.7#_z5ۡ\t L*ΈmE7$!K#4ԩڲ^$gj`'t.Zab}j;8O!PX5s ՟7`^1^*=,ꪐ AHN]6,>l|txi,R!۾mhn1&7]7={Ornٽh]lj, 7P % p+H>U- -Gv]  AOğw˯#%-,Xs -% R jΫkϮpʂgȁxuэ(9͇2!&ڙ}cXTO/b;# 'oHزoǤ&N(^GKp_T-iV+1oql;w&1l74!R=.?ѤΨG=(v4\len+| `r^d%Ņ/{# id"(^FM7,%.|y=(ޔxtL3s.wn>TP%g1oiIxUn*K]>&BETBJ9Ml}-ѐL0YolITfbxMDkn\ 8cRrIC$\  -BnX8 ďb5\@$~ICzǀ x[͓ǠdДWk}FL5HJ˃q"3GJb.Hh2~ Qމ% (.)JjیK{sz,}vhmGnhJ!%NJqޣ}RB3pANrhfr0arBLΌ;Y}[Oǎ1&A,RtI_Druպx8qL#R 2uIl `+ Mٓ#:Xoi-xyW&CQ Wb0 2]1vI$7MGeJD~k@KsҋvA3rHfCVV$`yirO,)fV$ts=;&{ -"\q`DwwXG#IW8-I^DgLϙ8u:I+x}=՝6%Ȑ.oTgHLK<"*ƅ&sR;|R:;K YdW$FLJ$}4vR( Ѻ)xiXqaI=$ZQ򢐐4jcJI( &=.&OtW#x@'KXaO2y$HGk7Lf;ո,Z<ŷdov Ʈ11@KP*E -Z )KN@45FaH Х") ]s8ja*s8.edB(6tUb8L8ZIY'S%R T&h[Z lyҤˉ 5Bf z. (D: -*C5b]xrE<>U%-P\a%z#qh^lY0&O*J TIJX̣g /zʒ2@I݅LuQ+(g bV8'# tUK)SUN訷 +WEZeR`f DDjo$Yb£7YjhMJxIrSj9 a38yc2YOb01N'dtZQU3}(ȁ&=: < -T'(t%jR~$M\+Y%Ox$DXmB9HʰhMAlχoG-~w|L>&X^]vۗ7Wr؞,_y_dsDr(@7P&*1Njyڭ?Wr޾a>ii/.6_C.~'>ٞ65>bb^ab1?w/w|iߚ{[޹jxl]Y}f~[^]K'C.^*+Nn?`;׷\K?[n~wR?+nW -{{c#?rwv %w=^Fs_q~͜3_:l]xZ;?|?Do>V~` 41$V藡 ~<}7+kp+VSӧI&4WB6C"B(nlm ^ o~nv뢨g+ߊ~ȼGbZ{GR"-_;Za, fG$ű#?`66$@KHqԩTDV  nG?uƮ~D@ET燷[Ne^rЈ4@'=Yza.JUCD{NTqS&]ЫIz($**Ǥ :AxoD>!{ G78p]AT( –LqDwNeEV&z)#}7FE7`zbOS:7J*M (xc-8Ea  < -A.TML$ȸtf d qvG-)߾ksL\*?"b%-G]9$IaUaaO`Q-ɤruNH^̴#I\1ڮONmty^t1"e9 }8L"/`j/vLN\8jn$PBVl;ƻ788YՐ;-(%2 Ia.o&"~~_|˦AW)w}]k ٿ쿃0Yp`/y)"^ ixsGڒyu#l+ -7dc=|FXHJ p4|Asg=˟; cbv¹Wc~"27xd^mꤩ^z@a7"Ծh9IBefBZjrVo+[§CMmxyN~~\m4vVqt,G׺]8kZ/bW> 6بۃܴކRߐRJIPsVڐ @w`luCz1jx}Ц%!6LC&Ck/0"UksJ\?JKfvC<R ۤ&` oIr{_|BNq3j0qG/ax#icg䍂,Zbv`zb[-*W|#,G#[T m"Uwe -l1"`!jHHN9aX9k!uD78Ja;o+7K1ө*|Gsy7dL˧ٲ^IQFHW=rZzmEa&f 7=|FfzÙwϒi*}^Zs/|1Hg;ܫZm? g#M`P;ӨW6 }ql~"~#V&϶v}]3BC8!TQX1yP-$GhAʨi% -5#9ûos5 -;ѹ.}sNF?)- SΕ\&=0j'Ŋz`PrFV 4&4(.axIyzh<27F1:JU}B/SFݽ W/qgD8D7V =DAq8 @Xa~ۛJ͇qGg-VJP'TDu m^=e+B -\?% }툅|o9],Ybmd|.^b}"^'7Y?N@  9%X,g&ŏ;enaSet<tSpCl 'aZ߻cw+5U;#zBv?n@SUr) /of ;BOww0;Ծe1$ {;}ߎFETzfxjK'^N>hA7pԽv2|di[ -\NZ; =Da8OPfnx ~~AO}[O~ݕB;\,l0#_s+c&dD޾2`'kB(c!S Ff΁bğFcv}}CDGZiswxàPeeas$!%Å!J`_}d47c3,=)Ul vp12vXnS rY "8@Qʨ|x4< +We_DRćh7ʖ$iUڪ["Izf]Ur CHКdL3E0@w)T .52 ^C}8X\"]TVML'@CBQ) -IrsX/?sSvǘA4;za !zFϙ?N/52 <5bLSn" |{y;efc~"ġLB=L5 uCɔ¾.* :*n kwղ9y0D}.i!CeϏ !]-A2ttlr)aٴd9C:²6h8E2A{_̴vg Ї6wE2;+;,XXbgm|V4{:"?8lsTĺ+!VC,)B 0gҘ4+J@G ˤYD^'wV ui%ᐩߚ]]ILVi+yHtӖ{?}˥;1xŻ^ʢ 1A k txߩ -퉞<1n8YiUd9/h)hh1~wrΫG| vеq00.[G$}J9-8<6wuèHLv&zt4P&44!2Џ]ۏJz\P Y!w0[\J!N9ia~5'c :ѯIX\I"xlTs_ZVrZU)XRo%᠃;b`G|0ؚ5Lq|3T*pR 5怦ZFUW6+]\=hHBj]θpM!M̀C*kW8;.j,dAT&UUc-1bvq2t*UEKGtC5Ql -Rؤ8j l! Hc-'Vw>=OQD`1A -i4xzNn2UHVEX㑩ҥ*)P- -#U k*o5HM dS  dt$zx4p -KƞޏXzoSsd ٵgqUt}#A[uX<;i}DH܃zs)+r凰2Xf6dQ[JT|{f77ֻ̈X\̰*62U(.MTdnk2_q -P_ 3ԲdE"Dc!P/S2&El!sD@&:1)QzC23' ;ŖH_J6~ʗ#X'R?,9 -Bʥn|,T'Tu*4h[o'.*~-L -}xX"SJZO:OdPPa`[K&JB .?~p uȬ4nUs1ث1w. H1 oֻ |2KMEMqn|!pɤ/UL5O-3&KIGo3.]iUhv&9 !iBת7*vv\#Z#x-|!RmЭn'L./\SMkL']2ԭ9葛hzm'[\v'ƛrHVߐQ%BV/q}Nڞlg$je>ׯI1+WW"fABJt-O -_4KtvPS,Cgr$@64T_gD=rWbSH~u>]/犃0lB_&-! fjJ f55^ -6Bگ}1@ϤġknxLĦB2: TZ<5~"pC\ƶn~&[}U+j #N7 j)fvɼ}%5V%>X%.S! O(4WTp4+1jN:]u6d[y7i"DR^Ĉ۵qQIβNX0vI5Ӵ9֬,ӻUس=eFK~7~rI>{`Zy7E0:\ߟcEsn2bmMcwh)tnEa87M`ʗ 7#Z &igZ-TdFٔlvI[qhdDjhjaM q_][*[[̊ JP6 -&U`n{P\k/AcVJΆGdͣ_`4rK*9{̔P"V+'ĀܭyʷPA=jYށEYWlngX:*L)nmqԥܱs`ϔ;H*3TV 'l{c$lr)hYjO9": "[i><_F!#Q6i*8ILU(#ԝY@j5Ţm |&ៅZkP`@A. b)^~\t0JnnqcwTpdf=LFvMedR=MGظgw% -MnRCnz*e!~S*T1_ *MyV T[ ^Lw Ng$?is :Mm4BƒCG p >I?لE/[mtMxqbKN`eK*H|wu.ugX2ZϸClLzz4 ҙSIxV[g.ޡ~/*l .UB:%G/|47ֶ5Bg".w t닜In5 hٺp d*)~,.>7,)Sg/!\Y# RS v:hw!BI̳z5$ȷFN\ViNA|'%/daO:,dMƀ*T>i-2mh -:(7X]_JJn k CE{̃E=ÇaK6>;d&[&J󌎴5n.cg(%?b%@F5 TW..'T*s4:9ͽq/ZJ^k`!-ޜ[ 53 1R]Eڌ򕍴a*[j6 'g@=U/-j2{WD89М?;3m`\^9eـzWvQ,3;*\X`DwT 0gb9Fw}CТ^s)m - - r-n=$@Gh[| k L Y@ w/]#k?D@*\i{|e:#{nCHM| ݮ8XJ'~Hp5[ &m_ oi0[ /p=E 5ùEHx<>·SGyP1)4`0B”+ZlX\!{ - [NyLYvAAAP!鴼(ޘnIq>stream -HYJO. - * -Ks1} -V?<1wteV>ofeKO$;+W&i9! o3Bx pI^u2GrP+9ɯm6ob 041"zc]Q\D?IC^>F :W?4:^Y2ƾ -8SU>܉Nn"B…{z !xhrtɺpU%z'+f'Pu7.\Nym̥Q67tc.(c"Ϊ,)moԥaFG^np1aȓHr!"5FɊIuܞh=ێ[|ޤV(A;T I>AovQFXTH| & }|5!|v*iGE!Y?M2(>\Tcp09qܬi;]68>=3]-5xÕՐ"C_b3FgQH6wU%H=&M]Q@.QыHPUqJ;_Ŋb9vӤb2qs1BVQ6r6Y Nȥx\هw|}۞(1wpq:E~/!xE,{WV C?)5 G:'wnO}#|#Yştߕ^tZ J57d>z@6TlTctb jW?ݠ*sU+1J$ :ɭ'q}x$z}b3 IH@0,>cɨ[W l1c,2ylȼ%.U1?0wO#J4ougeȾV.0 3{nj ^=ǎ8YJXB&i{ -B}>Ѓrjc-.ó(|ջE ~_sVKd5Fsk,F#Z LrF37o4Hcn* MP>(ޒl B0&ʸo猼\,[狜'TIOX{Z-"m&I2A*M:GX6/zڦj6"D}:}Hx04IZKmܴᬩy]+SO7F$US7WUyqTT~fQ%t76_Db.~ Ga$RIx[QP!$bԀ_Idr 9(_Yst,EG5 y/ -Gi3lm ݲ7^m$Q$? -;tp+i)[Ʀ4xD1/E-/T`L_3sJ2V!]q}Q~,d17 sHGڃ -YZ^[n%b}5<ֵXpm| ,,'C%XR/% U#[Һe9oNt EpKy. ̈́n'z*mθ(T(yO*nt%un6['5L]gԕ:'wԲ_[#'Ly'?jS;P!MnT(7`E^.>"mR}B|t MFEEP}K 6Zjɡ#-uc6h%@%ϿVVxTqQ~+L글+̦F|8[ݨhU:j!s$wj-4jcHrj7X҆ [p&!X @A*P9wKjd̛7{qeSC 'mߙٕBz|@0e ">㘨_!'#7&PQG`:*рEwz.cOJ_`1QҔpڞCԸyϳ5"i'ty6P -W5B -@cIV gv{P7dRoq#Rx5Oz5pT -QSNRp:BcżPr3IFJҎ~_*9b)Cѯp#@&yVƠmt}HZα/"SI={N_r>-e3UAY%M^~1Osr1fx8I [”$@Z|KbG1UX -pUK'I(C=߭rIlWBmL>|Vֺ1C{~wT;i>Nj AX !g4tKbc |Nf=#`*41 -TRa=/hBj,fn O6g Yp `'op7GGDGl`uuv*Xn@0`-. @.1av,T]bIjبPh 󯤮byQWävϠN$,2GF-[̣jB/thɵ>+$=XdIJ :VWDJsP4Q E`34#HCT5F<0Q}z]0f~eW/duRJ`ADIó?^OI/Y`!uX,Z,d=odXc94=rJ4Lpf}N3[ 3}=hWjyڞ#d==+nZ.iP/Y9H,fsԼ:$A;$G9Y쏪"H5a4]ɷD#܊m'`a[#p2}<p޹7%CZ#j(h8}V%~OUJT|h z\mDoS%JIf)-yKْr-Į05459VE"Rk[ImLArU51 ݭY5=Mw|{z^wL*&݅F`{xK~[E4[0X5i%-ISL>rK8׺OTE+h/.$BӺLN7=_?=ŰwCNYVo#$0t{d|s谣{/OIOg(xt0 Uugy'8 nkU<:3Itl&Be lwyH]Dwq!M͝I4b4 -Chb}-wTF1iTR6}OJmwsTA߼4Y _80B4,H_}v&t?`ae+O|_<WGE!'_^uN3]nlfW{b0 zkMɕJ8's4y#AIb:>?,`'n>:lWCqYe/7vyPHٕB[qS.=)WU Wu%+76RȭX&(]mx%"el)M4D`lxywi-ht6QҬ2ʂ御oM4;cHg"ʽi B+iD@ ؀N50xܦc1 :GjOJ͵lrЫ!%, Sr2DwAS9M@k ݿ;/9>ciX6 +ϐVYAwc7 BckʩcU55:f3P{\=L,?fTih4^ټev7vmnov*my{$$.U6n.A;pn6 8ٍZif7K@g7h`7Sh1(Aj#J.Ͷwntkeq [yc%#6&A[Itٌ L SKКq ЕvYVCf䳫{CgZ`y;0]ptgu$CH5e78\9#Uӆ#c)&w^K8 *|mC 4}5`ob G:5p0xc>G=Ǿ>mjv86Uc~cfOՙx -lPli59ڛd794yۚN&mt%)XitNHɱGջ,DwfE(9bLi0'=rJ)CoxRZy~ KɀDγ.损Znb{fDY*{Zct? -,"sY(>:Xoxk)qsR?}yx9z%tb\`l AAm\AAWFeoU%*K սnҫꥹx*JQI^ݟ$jwz ׬^֑5`7 -3h.O+Y- v2^=z_6PNƛ ԓrC:3RoX䣽T{Esx~.B+x= -oCaYNz/ِOQk.L}%jZ~-\9;pTÎ"^8%Z94Tk$ ٸ ==9?뭤׭.ji*]Xtk&_wuV.WFsKx{e"YΡ5p' -S @ip^[Β[۷-&?# f"2'|`jq6i011g3ML4glǶQ}Jܖ4Ļ vLk|D@FTwA_'*7}[C24pLT5 -cK i 'gbEў -71vLvlSj2έ9Xx+C>~2= ]9ɪ%?'BƊ~-Pn:99F7ZPHqy^pPjvJzs ~Seԓy=O**1+d~R]7v - 3U59a~2zzy ܸ}; &7xtLaɯ qégQoWJ/{md&/&Ɠi9L !aL}LaomgX3Jm+3:^ӇI`r0֖?{JWbaEc4[bʫFrR*16Z{@0Y5Qv6t#g3W_ICOÎ{:@^^6|!]+g̑Q6Ij#`tu8ghds{#=~˗Gqnld Ps>0#:c -.ZQa)Qh}@E(3iPšwv::| )  E$ -/Cp-%1R:Eh|L"!K iR ZR$vҼOږb#w_7RMְ3 '7ZؔiJ>X -g5wT[XD̖FŁp~{Gq9,\Ir -$ꇵ!.B+H(Ip\} IF1"<4fe7M(0}ɶFoh. }w<2}᱋U5Wţ˞ -I\ېYCv#mPhS-Z6kvdA#qM hHx&W3D; >CX0JKzEGd-*T!j/IoLka.{^-)6UgHh>hGu)8RHJ„e_O:, -c_Ir? -.[]xnZ -t-n&,%lN1e|{ ՖAc-- ,ufSK(tczϽ :J)\e T}w+`LXn&9[a\>NӄK"H~݌H=͂{D?e!Tc"v~Abi,Hr:TzyrfW'_z&]ӠCQbJnvOo^BG| -x> ׌v}'ѿ" 8&\W^ ސ9y5q/ -\ -fBZ-|}Žt\2/HR2+ͲS Tk'iF)O֟_&B'wg1u`$>ŰTssZD %hrA6ʅf>Y lz֡F(-Rq(2yTDc6&l.~2»s֒ (P%"hUbf­dUMhS!M"rtyg.0m}sp!tulej˫q}eKv.tqb?Uږ8D [¾:#8 AGDxԙ+lݝ|<j9UuXǩxݻS9+RZtM %D#A^n}߀f7. x 'Qkyv~AkW͎ Ǎ"~Xd n GQX yP2zxߦ~5N RC4X {9pIEZ)ڭyc|Tp' -A&HK -^ hO2&FrP!,* Kye1ݔFd'y2]l蓩ZhZINBclh".FqbTuEُJTv1z޹QMehQAr09O4K"fٝʋ.Zd4(FVEqYnV6픸 jxNb]^q"oZ@A?ʡ*gѲD!*+XyϖχC#1n!})Cȋ7]^MZךZ\Kb.ߞo)":+L0t#fck"Q oIa ST'|'yAakoHkm#덊'}9JBoNiud|ߥ.`(!h%%NECÁ-@(v41x1eըg&]{kVwSڝ8oKYS,`lJĆ/3M6QNرI( RV`q'埄uy!məZ >\5-Pq$89?ڛa.RҰ~ AYdQ ӂp =r ]6KRvE;Mݣ ԅwJƨDiK}8%50㻾Gjӱ4AyQd>xg +O\d7l#S#CyTIb Z)f_{է jŔhd643i)97<#?Zs5eihf*Vx>X: C!qV -GSl̲ mh[{]`ҵFت)E˿׳O8N ~! ܠj8Y|ǘGj!#! F,sj|IHFZd #SZomujZiVwndQ@>Ns{j>fܞ07°B3J? J4ŻEE1ZgN -1lZJǓ`οP)6cm/䘵Q`e -m[>hVlR&/[)(n뢞ͱNbf 6А]rh/i[a^NB(}J?>?ғ_'S7U=L.s4:k*- >}Ux1O"o*&w!<-N}btZXqZlW)NWuk{h|Iȋ9K-/8AL+/&:Zl8)=$9%Fն:nU>[ JBu .z1 ]ugh_kt `  <;rmwMiāꮏAo _Hj˽/ h|vߺY"soڂ^-q4:>K . 3aV: xH/ey To"7V2T1JuvJ%:ܫ =6jȔ+nYw,LF&M_Aflc`Ox_+FUjё>>L -܀MQ! IipsEiAhv/ۅL!1# J*Ntvjrw,XEL -H^ fVl Β}5r0TeSm󶜊T'"Кnjx -(_ken;v i0L=.܆>5r huH&냁fہ@{%BWʊGzZsST2@502һ;(\k VQ!61ѳ]ǬAPTйQENrrqM N p`t,$kM6= ߖɁZ2#(nȫ5%H}RJ/":(?/RTeں=}UH` `CQ]]g`lª$FrDڊ`aV;"yxƅjwՆ_wGcP1ᷧJX(BclbB0RfILhQ L!C%V8>Pu{^͋ѳyzцCT3qVpmhLdLfMS?USCH[FzA)zpѡCA~}_Trԉ9s*yuL}R:!zrxIh8jo Өֆͱ6N1m4J a+RW ~,D>$kq<КamȦ s*k!0p3c! SPp_>JIQQ\K)Q{TҨ./R)dR.M/;Bx -a6|l8W{_L,RJ5Q3oy|+-5.H8;3gLYB[1yc}ߵq:͢t$F3Gz +ĶB,(叡TR>eiN%-FKo4XhLB;uțˎ(4n;TT=Y(UG65o_6hs=}ҚCOhLd9'C4GJBM7+{o!( 'f`텞 -;V4# 1CQ/J:q?x`F^(P7>Du|A +W4ٛ9(.Dp$ /4o1lجOM#r#fZC뷬 -GX壉`ƚ $9uۄ -=cGG5͒m|:k"lUA[; t&} -Z)qof E:O43㟁AӋ }PΞXĒMb=AQuڱ0Ɠq&W)FlhTleg\CQ`,t.l E.Gii3P@9 - @ wٲӴ][Eܩrž;U6`A5e=i,;fۅEEuvʳW ׭e.+ VPҹ1FdGdDѽ<ǢJ- ft . TCq {> -rبeZu_w2׺WMk/qNCZs~2$)fZÞbQN" - *lVG2d-6*H 1!"q8,{@X*gT ALї9ɢb +/ܻQfGڲ`K -M4$hI^I[vm_vm#I?\Qu=sESVj?=f+=i&(Lz~Gx3tq>XŻ0|!8Jz{ G -*& 6`c ,IQ撸ً&U_L9 - ;"*uIw^dڽ >EL:x3O$ ^^sԼFƣj W1gb]mv^@-%Eqw:\V<xwz* 2g}+]1yֳ}R ρ8%!n\lXH|Vˁy._iG̈;0#/x.wq?c.ďAO" rdsS>L' -*qٯvzP~Vgg|1͵!t׻ZHw"</r( 9bΥ/JA7 /?^H -=̗^"'cXσc_ d՜03ۄY: [JGz w]UJ~FLͦK`^>HH4x>±QFz@U2ꎖ/Ë+DT>stream -HWv}ڄ@ 6`Ƅ!Cy~K Nw9$vvǣn^;\b)v{"L -$E7.~HXcoUG ɡ1^Sj$b]̨ :CP(dt%Gla"Tr7Fȍj:rJaZkysORD9[`Z4H}>k.rCi5_?pw\sq8x>%W#O bӪyǂ FMA|c&-C ՝`4h] /6n8|qREEHS4;GELVAvG3 Я9*E^47A|}џa,F;ƞ]-, -"t3FdA#){(jGn>@hm!8Y[ah5YٙHpsyp*D7U۝%lzTW Sߣ /[iI>EJ+hxNnA@quKԖJX ITđ< 76F ş\b%L=~qG2%9롌Æ“t c#;0L'+*iشr%5?Wi6Hn*=ځ{5--ޅ}k -Ȫ!!&:hƜ, }8o2(& -ꏷϐt&D'(R{\^i(`u;N"MdR- |pL]H> 4}TaxQf8GnɃ4y9yp2RMB\v#$sD Re‹MLŨu,!vSD\H ãS1Zvgr}ִyO e/Dw -?* c.8Dd>@g cI-Tl^aki9#i?&"Kt#C~\!'Vș$֝1%m^9e;Zy/Hv87֪!"{iDPAjv=[q%%/K:.ƹQ[9}HL6ieA6L!11PCI$T֬jV7\PqK\SFB` ޔa@CQ֕ԟ?BbPqZ~>K#ȋbs=d`sLm&> -z> -WiW> !!BDva`(yrΦ゗pSIUSK?5[++zqUC\+Dq^r5RܽUM¥[Z3fN0hgxd-ۮ$bvGLS盨\J'/Ҹ''`cgP)E_͓g*ѷ&n +n@PWlrE*@q#CVK8d^aɹ&|DgZ%/vrZHthxګ,s38/E ׯ_1X]i;:23)*諤D 8"L H,@y-H O}HV0,fKFfvAb7&ie.p-Q/6>@m}~AA&Š4xK-?U~*0H,aÈ110bq .p UX ndJNairy~4ٜ|'ޥά:9. '&:Aƶz Bݘ*kKt3v/yI&+xfkEh&@': ˆmlrĿ֣#XAgho Kb }뤯;aP0=x'PV|r )nSaA!wb f( /ffsdIvV8%sLҜE{X $էbE"ZǕwhefL -} =A\`qUBnhT4qVJ|"|\mպ=ܵ4,r#f -gM/FB[c(`eϠp*=%$ޞrY$E'kkK4cJG[B,>H+bB+|& 6|st/i_轙 $s o)"kJ \, foB}.ydPF -N[W6lAca$7g -`UL/; mOb90єѪSzWJbm4ܱW @oB‰LS@ǁ&C! -aH 'Ay6AAw,ap6*A |`UAYy& = &O^@@ڳ*03/՛fFV4`s?>1z+6ouh#2GVOm0J8t hpteW:NΡQiX:R{giܵE_*ܔT9>Ӛ3m<3gZ{q|u4bPmSyFﳃW4m6%'ȵ֝ ޭ}+}AGww'uy@.{qN\.ie;t0iסi_ %m -/M< m=ujݗQ^o+}Yzj{ƾs9_m.:HMcf!0Ï&_* _(;؉îO$nXP?K‰i-{}2> e!Tj``}Pa^KӺP֗͝N)"JȯHK,}kby©_Jo#F.ee{^F*w U5Zg d: 0l[[ScRv~>y*gl\̬ 5\R @xCO΋d^Og uؕlAo oC?`/dy_L~Mw5eto,ᐕCD`OvȬCrAHO^76}|77rWo1Ȱ -endstream endobj 36 0 obj <>stream -HOo]I#)A] 8H(  -Ğ!"1λϓ0HnuSEm9[jmIB݆b+K=Dwu+|dwOb?lg1aq2Zʹվ`=^PqehY*uy5 -ﭶ4lo]="1RZZ08rRfEmJM,GsQ{&JD"ZqT{TR(БzM,mJfuZ56YysIɹ1Q2jAqE{R23QNShY9* g)zj7YQ(R1r5f{`fXP^@ Q+<,>1J6^a\Q#e}d)Qs=lB'# ",>3K0E8bVgA2_Nc ^a1{^}e*Q}S9!(1oLO0d N_r)7Sv"SYKYۇ}rh=ZC}?*#B فt0s<ӧˏWW?-ݞo21>K쑪wJ @SY(ũo}oou<nF`66g$ -ZzShr!wDXĀRݣLeDHDi9"_I#:,2ESKeB6~ 9gI=+$ @4HRE|aI  m~(D,MI v EQir=-6I_jR:~ٿr-n R[tʨڄFQ3h"(Q؅Df>iMb -;#]XF @uN:Dl;H΋.*=.,xX8"b=GֻF([\Z; Ej+v򺀁H {N5S[LjtZDVC+{…!,1ӑ\lyr*&hOd -mKӊ,VB:U?^hxJR@7h&f\ խ.4q٭:WR~ -6*{OULY-s6ltCHI' \@>ż?#OrN"+GAt2DauX3V=E؉W|E*GmYwr*<Á$b?1!b%0;GmxZOQ>=1nw߁ivh|~'(W+1-/on>'_^vk+akb=rE:n͵&k濪O*ZH SVB ASL -g)K{SUߤ ݪ]\3;Yڡ6}>|}oOj64FxǭgHԚfёE״N) -EE*q(P/1(Se6?B ݂gM_J)c(` -v9)"ZCFM6}(GyieoEU]bH9\4QE E 28bYJGW!J5.t*)Îj+WOn(Cs=2\3U1SUP%Xn- \V*ZO"tm(IVm5 Ԫ+ ciDdR:>žTP P}8JQL최hLP(Y1iKY$# SDBcrWu020#Fpңҕ׫f`%mƄ>#*{6R Y; -7׃hs(w]t)"kPlGƟ -+<,mE2˕9Vmɜ  ^ω$Yj ja bDF^opfԺ8[7^MEgKNw_2԰K9F.a .6-fSE ldNI@ZB =j|u%XkgqL@gi ܺ' HcK3C*H_*b)'=qtlquBoluo{!,V V" -ȕ_:U+CVxLo`70 5eUV;$~BClV;~-q7GJpvQ)P)X:RpD\0op݁aJâRs#\atʝDxk.D10U Y~8(ހ/#9h<-Mհ|1/OZD `f K媑=-^x\C=X,\A`Һo#N"C吷Tehl:ob_2GxP(B `GW 4ఞMB* p=`B0dv2XiEН :7jђUaޒ].8`VNxlqQ4Dx'mRFjt* 9|Fugj0قk@2[VAcFE'4Mr*-{i`Evx># 9B ->׽х|9^+6Φ՟@g[ P@* -PHў>2V\*҃1.#/r' &E9Gv@ACpU}&07&k&$|UTAwVaxD[@k{鴘tLTFs[eRjq9=.+ڞn 7#^?)q?ݿ}4.Md! C}P0mgp!4M/I@`( (Q} ye ÿ!ւRe#LeCY [`=uŷ -ܒ G_`\`d\LEpRnϬld\}j^B$q9@qu_st1b)_JvN0'ZL%+},E"ࢦEL}_tb(Mx39`SeY{x:#FF+DZD0NE@4+4}4|GdŀED-0[pWg7^HqW -Tg]s,#)j=T_қu3IY#E yQlq/b(~^S#R .kPU^&=+k<ֵ=5s( נ~,]o\<XV@7!=Š $/}f -E1%܁X*X[| -G EbGDIg*擧POsNYY4bn1yEc4қ`;fE!Շ%-h\bϑ殧GMUSJ;uKp֯1B` - f2ߑZ`ڜBMEbψV(ƃ$znNBUt.`o>>Vzq^}J(Jg̮%WmlfIQH+9c j64^ HS9_졯A{ 8Z{ |t/m -b;F\TW̖BG\!464P pN> (HC -&"F*!FgL49-~S) B(t*SnߠL1Bjќ&8LjYB3]S-~eœ11v$lyIf–?fV}&#s+qf)bMH.,I><]=#`H^gxጺL'P3ٲ#/rKrǡV"K=rNZʈ.[A>o*2LW068 &&CxN?,'TV<=VP sV֚m!R\9 ( Uڂp ΁ڋBF{E I2é [gš1;Qÿ#wjf?CP$:d )u }Ǐ&"1W5B4YpY,}MJoӖ`{Besbxཤk'ͅ1RqZa* YF nv 'Q`,vnTuG\gi[mVDc >aT ri7SluFae(L%CC R;0YX<.~cgm rY4FMSG55!|:5H\ld!x]ԓ41j^=8 1}~X -j0),Udrn=00 2,SZ qP,jH'&FOrCf+̢{!j, yj@t'{2@>$.w-RDRaS>u:m:Y]ErU[Cw :/hG VRJ]fZo8"!]bΓIT:Fٵԍ|$Z9.\ *Y`,Rt"O=塥X\LګD&q>t&ÐXBA>htQ4.ajp{:a\kUqX-ŕJ jƎ6.LM=}gƙ@w=vkƕ#V][kNʍ?/& 1\l`FY_`)`g>o??O9Іd|Hִ1YB?b:u tKS,s(HRa3}Ri 걅M"Z3+/hwwVm3r |Sm>Gqq.AlyI%!i%eYJE>eER -SqiD;o-4&oiƂݺ 9+!9<bnSh!{":p@/ 4njx/r{F3jUʥ6q ,vVC2I48b*[I| .][m 7~Havnc@Q6wUVi>؂C:nݏ[⦰J%@A'/}@* aF)&%[Վ1MXTC&c/swyg[ܒ\9sxW_y8?dC 9c>?ta.)Qw8X=TL'ߟs{ -b"=ehđAjʣЎ_~cNcs_åԵwХn 5P-r?^uR`aN`A+ ]Po}A JV`bB8 gɈvbBfZL"ѧ50pe뽬UݱPr]f9. /({PJY=n.s6wڀ]xY -xT䆺I GDXXZ9ƶ-zlm#^w$_Ё<[J*a&]3T<@)cs@BaȵmQN+]9D8pk7B𐺂Df$-tk#Pʋ%~)SsH!isuy]ZAjԫr^"~8R~:_ T%NRN*v,t#Җ݊Ue##|PVtH5g@RDy㍤xTqJ>˄̓,1IݺLIU|!/ -"342oA,fMO`U{Wl9=KXf5vkdbRVېr!)FY76(غHYP9 `ZY\U.L@(Qһrwxُ -a HїK'fvYSmjcOH0Y,SyQ&"dcy:(bbWY91"QRn4HDŊ1XuoՁl*"F~6UMr/G. ѥz,ʹR^E:/>CrZEݚ- fp -n䐊܍7- a_l;}We,[0҄3^8 M6s#;$ު mWwd&&d?W!ۍJsV -5rtlhYH4g*8tmo !dubuޘ?/RԎ3~6ThSt6$  =%y)Eݳ괦IJ)>n0K:VqNeI" DQMWe -e|^hv>]L5'nIq^7 -9E:tMm,#"8(nipXRdgkJӂ$+(~7$:!7Nnٵ\P{Kּp؎Wtն#زkq&{;殮S uNtpVSM;ooOǗ!7b/ю.\E×0*8?ΣPĠml`Ͱ( Io(wJCoOw/oP?{p{ o_?G4%woa.?\+7O7enQ-sBpfН̪񻔘U)VJ ⋆aTӏ"B?&BmٓB1^vK #@{A4~ )1ʥV.RiJQɌ* Gl?[^EB&7` ,^".d@m*i>)$ԟyԄ]R:YC(շȮE:~gwOOw}y9ʚ0\$}t$yvtc*wKTDY-C2p.2"+$%Tֱ Oz;AQHxݤT~۬́XP28 @.ƀZ Xgx6Lpn\qi(s Dn$yu `5L/+J{3UD@ :bޙTpQ} ! -Y=(+6aeS:9,"$)d%ĕF(qt\R$ǹяz#8kvOqMEA+PIJ?)f*j,WRHLIbE!S%%]k[2F<2CIkP-fe-PuQ aoc-ϤI_E8H=V -*Lj -2 LLuFU_G)%]x3\B1M"iCAq[+&( >鏌N&̬9 RfT& -Aq7"%cQSC *><䏅p\!>ѤW߈ף'Kdbp[P=pr-^H"gPgԍt~Bbk_!M gn[;ɢg%1 XfG"n@u)#T?r ,tK#S,V{!*M?<q\+nm _G֓ő<9|uHZ܏?i}[!;d*)!n0h[o"S>c'd,zϟ$tLe#F7= -j8K?)VޱKnheB -K&>AnTBv49F8K 9WZ%f̺0v80Q)v? 1w„4״![[Y|'K\Alv˳OC ]#S3MVKg̿ϼ;dM{# =*_ia.rXQ;52><}Ȣ}Sj -q3 '5s5]69BK]xmI.9lqAg +[GH4ilv߯460WOx/-^!,\}B')}M_;tJTojdzg] c]Źa[<'}W7&7lYuxz_bݿ^tfU6 \5*lnl*Fs=a5=)^Ӂ ᐹI&@mgB<~[rQThl@?63cH"⩃= -vWyE\팼F>Yܒ\tCG {y|O}>j3I6xORR͢A'q0&'N7̗Z-T g>؇"ԺlL%/ -u'WLKO;"Q MtG.-}K89ɟzy3u刭Z˜bȘί^lCϚ?b@8AoMOG\N|22+膾tDcvwtUߣˢ蝜ٕC-Bwn^Uލ˝).?-b&t^sx)>nZ ?ti|A.gfw1,OP;MYodAO 능&lnN~wdڻ7;3>|ImmD|cdhC7vig -rnWD;R;Y c/3[7 -|uzB1'ynjV޽?UԓY솺#ᦣtFzo_^jRnzFi={wGz>V%=i7}ԛΟXbI:w/_n/|-3i -列#<Da9C2 GQo9s.ĕWve۰li.A /HBd nE+a)={;)1 Ekk˾Le4zg3CGNn- # rWhc ʲHe<58&/\s{67IWɇR9W*'<]e7a==s/(v$ -xU#M-8*x1t'M>;\ElD5muqAOS -δ(Me6 k?T$W:]ҤL`tm# -N2Ǒh#LnTnI7(͡msh=<}Pb@V:y@5:5wjP_e?H~ sXH9w")M 5S*.ϱԝ3O9 q -Hr/pϪ0 > {~կl>rV]|LsΧ^F,/:..w3ݒyjo #J9`< -1llzIw͒ݳg}]D=ڗxF!+gH:bxEL{);s1HP"S*?訑KS^ {A%\ ,yx5%Fבb(S%0%i $ ->Z4I5ŚEGMyV+ơ.N'ы2bE_mbV!T.Ѩ @LF#l*YؗHp@(<ѷto$:_/_NQ,jlUb:ڗ9pw@g -貅Y|i d gaCXeW@<E>U1lg-1˦C 21]/@ZੁyhȡK4@ ]a,E -'ĭ"PM[<5)&>T~qVx:{߹;w[z{ Xf*wqM̪Tee,K2&BJ05#B"V8L)ԑB ?Jӈ41bmbsEOIf]t2ٮnL"-CBGa8L la@a[tAeөRIr -őA9*.×|e9$Fm>0Q8kp]&,/J -N#Ơ&(xuBK%?Q_XeP u1Holfj9( J2̤2bl !ݨ:gd*>_aނ'XR šI#"& -9(P4**pĊ{H۹T a[t8[< Oiӑ@DW="/TK{[ -kV3 8Iq?w<-ruSeqtyy=_jUc@i귐 b@ʚEn}( Tێ+tI 7{<ćuxn~h3XV 2C(sUQc8Vs[ǰY%?^ H;4<7=^'EY_/u.;ÙiuHx-{~kut,hv/~s@]`0ޗLtNʴx8N6oBG( jAT$Fq3pzҲn3<tK~#lDe (bx&jtVRQu:ꎚ#P$S`SlJv:˞cgP ϩ VViL*9LMk>BWXBxڕLx.:ݙvQ57-بA[|NC|[AñS @/.J%u egF|+ȞK4 -aTibmyOmN $r[ǎՆp8;$<`(.:jO6aYd'(ӼD6zQh&1ʱ´88.@hE2/MCFB:q|x4 ܇~L&n^d^+hvG7TO>dP#Kqi2fb v-xZ`6t̚^$,z]kv-{K!OjƕSqe-%݈/=?/i,C*ލvrq{- LFG+r+_9z?`wK0hhF;q7X<=ߪ:שH$[]kGs:-h-`r#9|;,z`5i͜I/iSsj ! -W;O -`wEzNRNQx@ Ppj&օXG ݁M=.2 ֧A>OFQU֗votur@/;ɼᓣl% v"MpnHKP`0t0mÉfu?ZueQ$V dw2ܡN}ܓI{o@0v0SO4;WoT;܀e :>z@4&Ĝ ."F}@Nx*N:/!4ja[ JS]`:<,Y5I|j,Mϋ ޚ >[_h|$8¨)# , S`J^+Ee-G'^p 5wJ!gG#tԂnAid-zAAH0;fsܝ0(Nrׇ=S Xj6lTAV\#xN P/Q;Tr- >M"(kGSĽrۼ~T( -B`7؁;oZhQDOW~L}EPÚ@R,}. C QY9vVLje&M~y>[B ig[O_ GwneEtPS(RJ=1aqJ.YŦb9|ra^zpAU!S^28 aH KxiLRФFqn&8DbD\||16Wo e=6 'jڣot ل@Tt1Zvzb?%@Z'%t ݥmjQK`e zIOCcNLr&SdHpaaB!1 @2yG\!Wzxy/hZ.XYkgE=r -"ֱ-SzRSIAK'*SSS`OYcB3qm7 -J!cS"`q9\5O[)z iDQe䷒"fXqw2D+om89oBUwaAŧ8T "#ޭ9ZV4S#z@gaSP@b%}RD -J*a+qg?k ce/J}^bs -W8nƯ /a1-T|! FAY<ΐhBkiAV&0Fit3cVt@A#҇u[`$b5JT])* +=Qx9cE(.%Φ $K #:1tK)9_F֥_k8.3֢D|kOexeޒQٳjf4$EK8)#*%&=S8qN胺mt vĿOlUd_FDmu_qjQCc6C;l87}EU҈ړoCRFi֐T -B!R< ,WSAk2XT@K3OH]W<@V64p .Qn$'*ܗ`,9@[sѓÇ70Wa (R[qoFM1p-*2n1'\@p -1bŸɷXYz}4[I -V̱rIJEFƗ"gUQ Q.ux E= nTPQCCj "kiR/c :/RT*F4gdTَ-^k=ٱɋ0<zYŀjD]xSB[0)S5pzW2A%-'N/y a̠f/$ѷB!k0 q: +93k[*5x#NEQŴlS]1^D RY K޵ یm ѩ!t#侀R&VLmF{`țr/eW⊢C<$'^C3 K*@CU&K -ʂj/#h}LYNsF -fD╫D7 HOmW>Vs A?ʨvhoz#74u?]b?FN-.DɾE ˗b~oo|/?|[-~?Y_<9A>+M jhSHU; -MbZpg#L hJ 2'^N LV{| -Չ18ɸ ZsDG[kzzil|46P>6]Yٸ|rٴ2aڙ{1="trt6"ArFȸ:8+r$ꉳAksg]y9\i݄#yLH1029o=vc,~ PV;+)r zY"4CxťQD4!j8Tz 40OT{ )aĴi9 I'6낳5pA4v!hZu4 Wԝ3xh^dY;|rt["{\OzR[5-; 2`!(EU;]X .8!M|@LڪemcT+8.AH+trQ,iݔڎ4EFє*_ "eqޅr]X2lPH'/g֨SNWjn7C_zTVܑa;9-1)M1eM˂F!Arh8b[ʨn]41:09%' Yf0u&+^ i> CÕ^CG>}a >M?@W9ry FIo{L:!|^'R,TMפ Hx:=EeE՟}U*? II:/%849X!ȧȡO[T z)ABFORU('OP-'Ih1$B0i&|f :(&UB a`` g -Yx))D'LMmؖhrĠKؙ *}&)`\CQT2NdM: ͦT7@Fd1Y*)Pv d9Z?j?klwB3,) -`x|8HfYs|w_;p65#k;r}&ؑ`Qys4OhWލD`$<bE:Eە͋FsA8R,gQ찖 |:& -SࠬdB}*rfHP9 -Wv(,Ԏ)+y@tzm\%0+ԃ[*SO5ƜOxaG-X6asjWf(aŹi*lvxc[d5TdќB1GTJ)N)xH4v#W_*DYjFc -vJ?{qGZi⋝8F'_8zv%{@vٿ. ֡zܒPZdH/pl -t`@k=YR߂VD# gr[W\8pw-w@XCt=/Fc"nh />a=}D]Ylp{;Xb(J#\7 -1k{Gx6 -^IxF @Tp-Z 'ȇ|&I"\1h.Ñ_w!툏Q4챿\8Dޜ\;3vl& TZ;9!>e[=sV`,}솃$sJ];SUIqOwgC%F0o|/t|w=8K.96ڐŃzgh9~{).9)_|cwk'u/j$ dxRӕ܃b8tƘYA{=s895k澥@r}"ԑe{PE<4~?rK=C9*}5S*6>stream -HMVz "ϥ+Y1XHv0F(D}}bR po8۬4?lVylo^9VǜQ,a3,iEwRh1s+gE;Zg_9z;rˬYGE؉e~>sKI\1Qyae>>'yciz<,2ɰs6SA%2kхVG8:6{+VgN?-=nަw߾xGhOU>FJ9͜׬/JEivDT~meތӕMw9GcrUI|kn[ꃂK}y8-hrM/?|vh$襦+*i*Ձo癙ڜد -οHo=ZʂgqbNY~AzңοOz:j<ǘg~߯C~oٟ>}}[`ņɳo^wɋ_ߟta{7}zn^=j"o~CeaYeQ|FFPg-ҙ8 {|j9 -](a"C8e*/`^h1ÛfARc-s:wx=r#RCL f7kWUҤ"6RR3fTЮ-kYƈ0UKKѨ vP&` -(~!\zY=ۄ^rxo&SK7e<]+J~b9°DRTJHqh7Aژz ֶE1vL<~ldˣe 7XfXlSÖÖS= +O!{&{ehNw]nZg=3Y1u署tr,Ρ8{B7(cTdo @j(5 cJrX6YI`aU:Dlf۹#P:wjN~(^[( Br9w~a秓[ 4CFR,!B$ۑ"2/[ ԇuEЈ-kul+Ys6lCe䳸H/ɕ\*8quy&M?j°IcyT3`gRkEGA#IdXZ'qMQ V2j0o_ $JJrȒ+iq:]R0jH]l=g`JeݱA\iDSƤ)9_f`q"Y!_keĦ`pY -˅(CCڨkb AC. QBiB7U?!L1lna't²y$2гF+br'mV\eP<߾c @uɊqi}Y 31c1b̒+F(=DB;7y4 -|MXѯ=7'y,P+S7.N ꅔLR1X䆩A -^MD@&KCv,!thHH+6!'UqpT"hH"aOXH+Bi_'4)%}ej_GƼy46D6Qx#6jQ -ō@cÅj.&n3w> +#%~AwF3!ng^!Ig{R}l)V'lnd)P,Oӑ") Q+t%*+>mԝ19&R{Q|g^2OޮV;s]<&=MDRfᰶ㑚b陗D*Mu$, RxH.e\uAf(ອ$\́0/fвGin8 - X4ݸ-ZV1_݇[/&ʲjs#1"DAS`z+ MOx~Gn[vGCBA_g>rUJbN)p玩9cgó0~|̯e ` F Iӡ2 *A> %ƾbJYlu'<wΌE^b|Ť}pGL:4DT/N{'r -$#{\ǝO"{@!4> -v:81 2 --(Y*[eB& #lU@I%6Byi:SliAJËp@X7U {8s=-u#6 +ֿM1 l #25jDAR«CDW1ES1)xt%jYcL0>w^NaEj7?~؃(xii^ s;n Q^:OclJo\dsO=Utg6N+``4*J g9*R^jWQXhD|jT%E!t`XԗWŬI5L-N8rr3pjJ`=M^CZxSCQPi??A_'ps<$@@}αbfPa }>Q!> Etg[\O΂ F(Nm̘al A_!`@!`(Q} FX:@Ajlɧ`LN\f&mLZtsT똜Nmuڇ]fD֍N:,\9#`Agq{߮uB'  -"ytePePś(a3Pn֣n ݐhA+y] Olr12,#~e,T*EaPJ1zj|ME $:T&EWp}?+c}mBHOZ owA'OR?ي"G -bܘ7y1s >7Ϗf҂)ۡsXi9"H8"Hᝉ3cPbb8; fTU緪0˃( s/QhS#*,^f4E<<wO-Fxd؈4,Om޽v=`B?uXDυfjGstFf ҕvwVځM82ECq`PhOGQkS$ȘcboCLˉ>c'pU;_X+Y}B;h|v-,byg3݃1|FtvA]J FErh0 dB$q% N{k Pv [ "?ŐIXT"xa<LW}s9`^A1Ҟ/9/GFhF8W֙5%Y48!Sk@qV (a-&=V >%I14?kFgA*]IO#x6L2ݪ'ٮ8o, 'v@ 5SyR}ѐKs6u8Rj2o.bnV)x b צT1ϼ-aG?eMye@AB>]F -Y -y=!9R^hpy^' L=ehS`4L~[l.H8B"G y\Bnʱ̗GWJ - vE5gfw#!FR_&yc@_=UjG %BCb 5xhG!FD1C|?t E-'C/ecIqWKD*a[+7{ѽ@̬' -*2Po2' fL 3Q!KKEδCENTKbqnFe@"vTD(ӄyPr=ΦcA,zl;{(cN."mizK)r)n}$m˯3AaJjԥޒݫO5!MΖg(f`)ufw4]B0Zҫ%K^*춉n{.TQ/$`N|uHP–xt(y$F_N~ .=JjglB.R -JD$k6ɆօJ.3{0rhk2 .v׶ٔ"H~ (TUzj9h9../hh*4E{,e339*,>,MEr)kuD$U7̥5jJ_<\ 2i0zכNsBXOĔVfaa:5üN5=q<W լ{ &IR<PXR}ĕь)wL -k)}^yMB뉫WAqdÃ?ʹ.f c'DAb(Qg7EHy͎:A=@,W?f`j%܃Ǹ)J5 |=HNAW\'|`%K+|- |qM 4P~VLq0NsCf$-4@2S/yXҢ̙"Qciδ\Ͳ> |J[PY+]`U\B.6XƵXd~Dsز  MqϖݼXC(DDc8wEj ku*1|ڇB(?{{AP,ZP~KYI&BB3nu"<c$=pO$Y&!mfXU3031,?9Y2_ ]e!h 1Luy*'aȼA)"t~jEȹ*<<%\yglMd0wHyʍ\9Jkd,GnBAC~,N1sbmYS3H{)~헯_~o|LݙbZG{-Y( -Ux3qE8MыzOrP *&xo_soos_/=UJ-mNgDfFfj$,ˬ,ceFDVu{=5  cWGFǹo曯~_ۏ8}o?{7_KGr?'}7G/_|so?qٯN^} ߽!˱>gϣȗG>KiXPOv=V.$7+ڵѬǧr\[;-ng5TTbŦ\T2@& X#N0 L9&o {ger1Uai(f$^_m ^Lh%ܖ^YEx`Dž>>Akˋmuh -abYin^ -^&GttWB.D>kxb -%m+PYm{C3CY-*ܰ>6`N^Wشvᣗ[ٽ$#5p[K?zA?ZpHpYf, t}M;_)S j5̧׍ytD&}Gtm~@7ru)'@} dḨv8%)uP#EXx4;תKM[Vj:P-aizO,scf@wF;x{g&Xq ŒS%x_i Jp*HG*K089:[VX/˾uy90DE¤İM{M 3f*kܠ{/Qm# YT/imoR,!y@#zX,GR{yܮ3@sdIQ%l}Ҕ{jGTs&Mqs씙<]%@`#YB"Cas2nUC=C8tjLnȉYwV^0~dqnK%N سr.MRSv81:\+]pRAjl5*FMN4ad!,\놴ǩw]FHJX{5=ڕ3#89:sێNu y ۳1cϱ?8a PĈE)k+8eJ -,OU2f˅caVat\v䔗U$F [.2O*>n(F)|hEʩ*qЩ^?",_uo ęβDHT"k|;w4_>x[.sZ>>ݍHaf LKc*bOn>jb$`8Pau'".f I7c`\Re/p,Pdys΂\C2!Ef89KSxC Zދa>}F`YnkP4;NJ 51 X #L*:+'vKQ_ka^Zל#t8(!.5w)nDPRV0Oe\i'i'zD2H,OWj`rxa{X-5rƶP]˘-vh%nbۛAܨDnCwCqkn -F5(%S2?SXYPURLl;VڵgŸjЬt5Ic=niro%_.cyC`eg1 ݅0n8"^ (COP $C0=;jп?<)BРR'|;LkQ `8N4fOoYjn9暣ljۋY?Xz\LƥatuxysNdѯ84,FxbK.Xދ-H6,dMlCzn2X.Okй85O'8c[@_z*i3|hRu5k:D"Y {I=Ո*<`?s6і(ȌvSSdw=ɟrbڭY0a`۔&LUU$!'fjbbcxzRs"Z 0@u4MYqiO QATqD3q1߿$Zxhm./-LsUT6@jI.;ޛǨP+׃?s*MTHPWQnaqݱYD&3[/E:[SǩoS{ZʳNͿ2 hwh÷cae1+qYF{.ы UZmCjOGn/p}'9+M|`07Hre[tDRZ!$1eluvF:F%S$F}qf YE_d0`\Ty-4`-ZN,8-wl ߺnvyj{c]p8Ώd^0 }?&{DTx\ZjKd\C@†R:D, {`Ά#U繯Y'',ԙp _iGtyrq;7񻾫qe>@`VBd!ٶ顄ηFoMg{Yf9{'be0% bCW3r!݃Xt\:#7Un*珌׻%qY-XJ\gaBuk)1D\Qu9sQy -~`T)HeYuYeX/dǒ>{Ă%*^x/")A-UWez`v环3:`/CzepaK%.s慹rK/4zȕ'_yUWtKI~U맢ĠQ8|~J hB2q& sWse؀9X"*0O J&myiLGlT18:XqdZ  JI{ ORiHVHIG[TZ0?V1@zOEnr.|K|Kۡ -tI5؇j%JAȸh&3 )BvXAdo[IX"ٹJI;6T;m( В櫑ۖbQ>GE8$u1٨?9 *}s󘪑JV(P`kG2m*0>xZNgABn"I(}YK!LƐ2*h!H9$COx>e>7 -F [^;1PTlT}s\C.nz~/_G[#$vm' a(eE::f> ׂR-j|@K BD࿜;h@{".,蘁R`;jStDp - d(lp\zpKB, T ZRn:"w[2bbH25B9,$OA>uzc(񧼔< CsB/:y2xrPCj 꿪LY7vEW|Q3m!f &Pw5:1EFq]1lҠ&MNJ=by9tm "bT -TyVcP8vhxtZ]4f,DTaLP+\|%BL7Q{)j;nS.i&(G3;RkteTzfq,UXz^p?t%M#v -/̣|9`w֦>+5lycU5k"UJ-馸oL{ zFTRe#1) [L~6-<_@;@|(֝UCɦpx7*( ?Ȕ$dLOL.],pzR,+H-~ģv&# :?&΄2D 1Pe~Xk8/L rYCPCnYB02&ݭ#=5^oB#%rq cQ=D <{,`||zc}:Qpz)Ƃ%dkoR )>`0k ±}d,;/y%b2b'Qfv$Z(>yx$Y;ݙ:XK4bbX\(ޮj爣*Tl }BVaPX4L.wO|Qr؀j=鵠xC+*^ NȦ" t~r:C=Ā6UH" Bx@=+N*]-\ ݽcE]!PFdK4j}Ut&0QC!EW2-Vl='ohhG*fjU7cyHa/B(ȑYҌ'!CؐIҨ5F}l`}T~2uOiUԤY`a:9:034J spJkA+?LR9qV:r\IY~.X\^3[ FЎCnd$8 -8v3_P{v avB71Petyw_ہb~Ph; JFh9e٥<ڲ= G$'zfia/Cyw:qAztхK=*jw0kx !%+-N.וPA `}@G+g5 <8+_̀/ r>#|1RDBUŸz!/IcH;#ՑUYu1`2~Wrg0ڽܭ?3zu㯴y}fnR{gA_|!>lTHnxK erI=ǎX&b<4=INWӭƯ٧v82D&3@4w -k}){%SSt]r.+e%yc"z04 dgF: nkз$'>~Ut -rAz:+ݖ2:u#a$q'W(8蛍*P -z$ طeF.LZGQ5MN I|J:c c:܈E>|lU@7=) ]Ε*(-ҽ)sյāXa<3[oaҒ^`6aσ`29ɡ*!A˹0{ Ydͥ:Á$1T9.){mV>8W IZ¤xm#rq鬭bi1 un|%гZJ{ֹNkZ ^[U|?YN+eb*Y28"*Pm3bxnP w u FdC쏭E98ZF -c?%YB';T1 -/RD&PY#p@WBgbSh+vVmFKpܼj+ֲQg{f_]#i`ؔV3xJģhQh .A߸iz%`X;PS4{cTeF{9Ԩ 8 Z_]°uL? u܉q> С_nKhk_ G.8 ^9lQN&=XtY|6/&S0%bgQ+pu|u45gg Kcuny_PC+6}o˒(b|'`u; -9vVnE )j @{DkpV52}ͯğ`Gh6뤱G9S^(: !q݃m&z| -~Ou oC]|PiOڒZfcQ%"9!sI.4lem'}B0?Z@IL%e 0U~+TZ"`XTJ'&%@%XS-o`q;II8Bx81ѹ\cw?sFs$fpO-{v잫y"ąu[-\0WuInXh3^ <dO^J50 F^0F2Oz9l6dPOtJ¬]{A9unq]{iF -S{jd1"4-YY&!B7S&Ɓ`nȀl,²/m8ɵG}PIFt~;2gCFeZ$,Av^7Q`+\9w\2~ w^㼾uMC>Mur7ҷ'{yϿÇ\fe==Aʮ= ~ʆٽוO~7x2([7& .R=H- Ax+ m.'dvf#/ӀӦ TDR.K}ciђCMF 'oshNX~,=M0^szYaG wElK g5v7,YkFTf*b˃:D'"Ob?Cq׭[N ~{4ѩ į!n @1-.]0ES9;B 0&w1p$v85 /~k&8*W%WC@[RjIss<4.VU~sQ?RPF4n%nHCu:M x`'w 9 'XYSv𒻙ɳ;xXpT'AmZVFTqM'lU/*>`9lxe[eeܿydƨZ9/0bDyJ}N8e# RϷ=vk\8fCfڛ_Zvux1vV*,!ݾ~=n@QG[:ze=lqi+b6ցv t)?|H`zA1jrcҘQ!#%Zdo-h/Y_8xp qVc8~Xտ%m#=( $6YĵXI]xԶRNbW㮍 ` oY-cK*^Ä}y}+a8z JxID0@PJ˃@xom9iz|KO؃Rw]hfvYΥ m'm,'}{|Zpum;jB9 c mwybeTy783*@57T -}jnYHiO1VB%bV;,W0cp3ɪd>{:>r|h:J#/'L1&QM%H.{Xx("tt:)z@e=t3*LSi,&"Iw ,cu7x*"pT,RPl FpbU5ӆ42dPZ -{ʕV@N.SBM A s!dS.U%FlbTt1R0.mIY)3NP3~8V(2gGtMW;b%B -Xj {(=BA;|_&дV-U%/8#9 -cu"AlĠfX$"8X_V`YCRvQ]e7d;q2&#Rb%`AзU耢֐Ucͻlu@G0B} -dZ2x('s+<6SdiG4ca~Ke; 8'w#Qߞ @Aئʲ( A͑GH! -XzZRd&hӤɂ6O^QCl\F[%;*<Zh%bz}E d`&D<.aC ]$bj aJ!r'1b֊PNl_SVdWI2.uAϸjy:Bb]+ gQP&yqm!3)]k<~wƉWnņX.skE$Pc 櫇jR]]B׃UB?q.6H!b|,6 wU<R%uim_);PS"|mW\@`09F XVtz ׿nwahmCWpLI{b5limH!K3ļy1S@.gTTA%F82/APG*3D9| ;C-) :-20ri^ʚtMfC sF݌g|bM'[21r.c=C &6[!`EaE^! ϙ -)T`&&v. AC7B!2򄀷j3HE)g;ҙ)'F}]soȲ@m.+%9cl`bsb)6*&We9DAAEc9%3@%y]cY(>&S`_;Ȧ+>h0.4 Cf*vllQHZ27yǽxTůx`8M!#JL1>񌍈?I$H֔3cf!䜖*Ojŵ֣&sqƧˆw E"'D1, O^ "DZB6K+(\+\cnbgiYuעA'Ù[拓 -h51;Q>_kI+F?Khrq3i}}A{bxMo9وJ^8.TcR}U'p11&U`:)ߛdԙ#[|6Ul.+c_^n]^ˇwLJ<.-^א}%) , 3.7)I}VE۽=6¼5YP\~~yݧO_><>{o???p᷿~x|_^^{_$oWoo{`owlcn/=o?GGd[w -`0)W% 06w2Pf/A+UA-(J_Z:3KBXh+@ݓy6ڈ1珫bv? geVm3u?dHL 2 =ϤbPÀ`즃Re2ˠ*ӟ@B[!DȬ׍(͏ػ=3YteG+)cC,LTBxS2(Irdߺ2WH^JM) A=*wI Y8с-fTP1fW5EUI Vz`݀9n]o90GˈEI#E'z/ίy5psWpVoC`h`up@iR\r1vp-(9-h-Oӝp Oz -lNPp v^amD҅H3aO9dFUnEAXsWn@Tҥn^ZZ"L"M`b9:݀w{I?IH,6Dew,Qs"=X:0sb9ْBoPVVH*m# -;5ژ<5"<7a@xFJDBu$ -9XiĂP,-cS<^A:nTtx8JL=^֐&gZb G6`bd [ -(/{v,4{ЛnbD -gIsaF2I% b+]-PXL"P^NHD(Jn> 3/#`̪fEPʔ,7?LE$,;eVڅwu=gp[Lu9půZPȶåL -ɥ W&yC.r$90jw,|uS%T(֕)t0WDjZ*ߊ_@^ i(ѓV巠6!9 OC8BS?1lH1[R]׎<̑03 B=b)\sv(~9e=IdGRxDP$D!?Ťdf~%5[|Q -]cej< c:*Gq\2Jvik8ӰVqqHjGH~Pm]~~hhCOӺV!*Y"IAFU2ǨP ߪ X kC |AF 3NBUWXu< ֭BUIaXizUk?,GnC/v?ޜl/7>nn7s!o;6{k|}sfwysb{y8;&a$];^^_o/rƯyW6/vWۏ?lU_~?3I q׋Agnv:=|ˣw?˖NHPdlP^6ejv -لfPRQ -7C$q| 'NP&SbMk -B3geVE\~}G;( X\rx IEb5]'/q$wčC GkxY?VCeU4-\)RDR0%)SGAA@f3}d!dJBĜx.f>jԲqn߁mgD -fFs|ܪpaGcnAE/L=5 f F7Gaj eXP@ @,fq7$eaR4ԋoEBW1ri -ra&{jb"NsDQp59p!iP.L -kfM'Moӂ`$ћTVgO_+:V%ƀ[B4,|"RAE0C!еA͗G# h;'Ծ[?Z-6nC"F<=+X`@j- !fn7SZ*Vr2sYˌFgPd<,DK2D8kȀ [E$eY#6Tֆ%,jeC.rEF9h;" '%uh3#&)uepܙ aA1Ku"A1Ɨ:"4V.p[DW؊>@ Q3XD ͠9RVxXHrBu$r7DP?AfHӐ<jV0UxzžC<\Z S@"lvb5! ӲVQ$CT, b .iau}J Ea.׬N!w&$6K7W/iK¢(4A'awކ98)}KUOiJ񑮽Zm f "O Įj@+6̴~lJ{tq@SuKT{C -eF@ -1~Us<3Wmu4ێ {)hdw|")WqJ =-mԚ6b?#+xT `ccD$5_#pgU3P,j_kU3Q :J*SwH׆ rb`+^\+V0sB$Z1m.Yp^f͆E$c$Z"\Xte0Dy =/ -Ҝ|J9 f@cbw5 ۀq:.DU g;փXH)-<-$mfmӔ6n)oރiCayT~аF_m -g,-7`@8=Rh}`?g ]*"]YV`xwO{yV앏v >? &6!q --뇥BJX$T_Ȑ56~z$DM K'>0džO蓽iνQA=g0 D*@4tTxBβҚy\ks Dϯ7Č+u17UR)P#AXr'?(5C2aePjPZ)NXS0}*2QN5ʰB &@We}e -zqOl.`W^`gTC2bM=\ĵOFKtq7sCBt_]9v=\`[Tx:P?wS fYW\D(,>W} ldIR2,R>hdWfȲ .Pmm z]i O .-h2w)V>G>stream -Hl;$ D}Ehȭ Hj2~oLt! $95mk]nu[랫7~,5#Gυ#"sfev5Ö2,k[ߣkі"k!n;#1cXq +_?bt \#LY{ntA0e7($lXeG^2[ a~efx1ǸHF<:˔S r܁jWWb1 BFw6}a]! ZZ͌ajkz8 ;1NU_ލH^~rMrQ$N|&aKۯ5 - N#ͤ9=6cZ<ӫ7aPB>lWu[%2h3[:ғ -Y -賁8+qDιi=U ͺFVTSӊC6T*شBHwQfCX.P,Oz) -2B)d}_0A ᓞ§ZLsXg9COݞY_Zbq8" -߳ gtQcXě0v79 B 37wmM t]񡪵^ L,e|@,\J!*w撓%Bo?GH5AA v*/ -JZYgY-U|]K[|>D"i5vj14nyr߱n:JQ`/jb,Ng5Lo}QmAIcn$c4zBk:'>wjs;˛PMeCoē (4w{#eUA' 3D{s\.? bj.i:3 3oNt>UĔi@hOAS>9(T m͵hQ`jhi^{Pk"Fhoɭ?}w̢e4+sAH& e2V,uHo#A.d9q F -eYʎH=%ې)Wt3ӨDZU(^۠Y~=΁`]D&d(-L;8Elv4KtyKi'بy.f:0`\> #Gk Tr 1ŃBJ06)}ꌵip`siBAzt5Vg+JFOBH+KLin4*WgIΙ;c+n̡ԡ Qdv`<ԭS:%C[DjR* 7IO/y~qDO[(DN%E*uRB鰰KӢMrx,zMb!.Qs"\6~ىgh#=Nλ%]tGC]VA?%e>a&.R\G§T&JSr׏n4aJ<7yXndEVD"( -RA5fY8Y| 4zF<w@WGHkD ^D܂6O) 4$(@c@q,T)Oؿ_BKZ#[ŦjH ʒHh M.ހbo1!+Vq@];0>D޻ĈSF_cS%[)<3U€<sd?21TsK蒉.& E5cF"%=A#a`m*e&5;8ϒl"ԉT>ƄEYz.y\<;rٹ8! KDHYIAL$>W#$qvWwUkoʜ`Ѭ} W3Y!"0 ?4k(Z't${NG#ևڲ_PH*#h59)P]i1g]N刍8Wsx\΀ hu2"fJ|p#(0^dB!B~'7WFpis͡)a"fJrAv*5K!Po -@Rc"5LʎHXvk_s$gD E˂5Ryrx:1-7MJ5әrn^vj[jYStB[ .z縘@>:]@I@ -_VS/5XYχQ5{hht!vر -fXP -z:_؀WfPC<)Q1EFw٨ `Èe$'M\3Xze]ivHN潩X3pqֶZD4|e2<)+^͘$%*oǎֲO V<4虛 o53UkR4 -z1=I@X$m">tHf" -"bd:qiaʗYR6HcQ{Ջ߼wO{߾㛿|>7_>d𗏯 ?|{vHBy`4 GI#(}9~0">^\HW oIivO:F"j|K,čG c5,Wzp5`3R|F0GL" 27]Ix7R%6\AA -֊LS[soQ"ApSPwZ'.oH~b8vO/Sr,-2m;s@,0]N@Úq+d.vh;8.0BlaCygǽ*Q*CW|}#Zѵ)ӝ -?صwXK]=|4ÖG4".I4WFJF@[0 +i2FRar 0ljnֈ?zeTCb Nw ߼TrK%lm^-ISAvga{j -bP[],QNP :5/h׶'u&WbcLG 2oeUhq†\N`kMj(dqe<6G3vQEDbG*QaM#jSTavZDFF}?NuB{Pc+jUxju/@Rf?x({j|AZ怙*`酂jb_K'(=EiT*+AK?ʖ08@&np!^=glD6W_"YDNd35UBNWhڦB*TˆeLsZ@KrDf?An#j~ W6d -A \M-&'-Dx~F۬wY{ a,*}W!tbuS}"D~VtTp5MV̶p`-nڕ5nƓx3.%岾ٱXfD@J|Z!ɑ_A;EqгU@M:G-i1OyY|W1pBTYAo< ) P֪nD&3#"AVתBʣGt%ծd>S>BHtAt&k45 ]@BڰWqt&Gum 졤Z<^Ws6fvNVK?0T(8Liq [rDnp{!Y3U+j3 !SJ0T Wmb )"jk|ކ G&@.\i6CSnjXeXvlI9 '(h־S#Tn "SEmQSP|a@7^9]tƖHm)({ '.:@,.vg|j?ιd2 &|DT,IUʓ>)mq)8j ) ,@(H[g`s6v u#&"qa#tg+$l` )17 -F`nK=fAYo0˂[Ҩ5,qW*6)-?4jUF+X~Gޤ/nH]rH ~R2֛Ifi *aVI#iEz;)ACOyl6>J{aH"@2K_W)䤋rȾRW%M\TgkAɳ jAv@Ty١BGHi 6١Lw%8ݨw!Q@(Xo²@zbJL>Qh4`›q.\ 43LҌn*d N`w߱1QD.^Զdo9֏ڎQھHtlR,مzcr 8ȇ> 2er'T852{@xN|+)>T 3X_N?ݱSzE/*]wUH"5=^q +ն@10D*i9;&v\S ª,~6B=''ЏZKA > l -"$ -zi"b\~/Gj SQϋ)nMLQ΄N(VN hh PL4DgFI web@p+bҽ *E]cj(IHJ<2 E[4=lYH&*EW htVF9[ai$|D]B!U`%;JE/@T M8v~*I*T)Xj1VH)q5U[V(g!$\ -}T\Wq@ƒ0* ^eaN*͠=?V~8 M`M@&pH72bk' |ͲHi٪f,;02{dTΌl"Im~ujS*JSAPed4zddhtG=7 -Lslg\0(%A Р؈0)9B- ӮR^ah4"m:McwS"37Q5fO6-Zhu#-FjĄ= @n&3!CrgɿSUs̡JYZ%\A#HW9ެ=h)A]d:PkD2HҌX4]'ی{1m%m)9W߁-0K`+P ߓG -'_;!Y}>^AZ,"2Y]g'oE/;2tRQ>LBT(;?PV{yExj Wd #sRUp{7Pw)w{;# -, ;ތ _GJ/3BJ6QLƍyD5RĶ0)gIܟ&I? #! -e0D2bH$Jg Nțw2#]qcZ:D@ڑn󴏤g(0]v my7@3K)G -YF#L_ؖTJodl’"o -o ]l ͂ض`LlX^`E҆|*FUة;B8\UO%T{Êy#82r$t(= ($)$5lY~!b.DIgB>S2CN(7шNDU. JI/mG5M?#]v~Q,^ŇὒˏL 6l? m2ٽG41ÃFF{3 dh&bb)8I~44su.[{K -3,ubN$! {5CfHyWtTm;*3)m[yx%uPŲHIhuBqm#g YmXMF̓E]rNU -A0•`, ݄OOz,PD _ڴAl(}e7D͗"˄R?S)"Na66ϐxE+4CmٗkBDz1+ &$mâlih<Q⨁hM95@@4<7IF˞]Cw+|f  7NF.O{PfpGp,HScDXu>6}|0!J:^˯3~[)d0dmppGSP@ -GʀBl ,($aЗ^n]E+1 ڙ>ụOQi$ 4!1juv:G B^U^]:_lvz1JNEVYZĿCʹhId -e-}& -Ǐ0ᐬDu:O ݎ3 -3ᴕqTyFLwg7B<}h4ZEPA>Z;s`rEuq|^@~}8sh;AAܓ,/FVfCE4dqȔ,ɠE5ty*wzNU5[]L  s69 u ҀR OUE >)r?yY 5qD@@Q3Xj -HFų"K躤p@VJXzڥry=i[_Y)!7$4h'U]U@]GxT`G3ۥA:I}BC7ZJ@,BQ}Q 9m6ܒm -mN ލHpsU]D3Y> - laj;=YJ ̫&*<>oNFQ`̡ nzi%BHLDTLAǙHf4amGb` WՄzz$7A*8-ah"3m&+<ܛ0¶700q#XE+UD,7%z#NP'nCc"C #֮/D7hME1=#c}̑~ ʬu"m2ŏoѳ֪!qCJ@8ΗF"b}L}Ӧi4IKRC=bTh#4[Q&g' o،7AҐVN뷊((P ]l~ 'p}M"'"'hi4W|?J|BCa()FDwxm a>lJ<=qf%O5ub,H((K1]&C2I52.@3l'Cꍚ -&S@ š$ -2 QĜt'xi1!.E}X4TT!:\9BҊV/N0&`rB>xԩ~VļF:@d-'j'LR;/AlE6N.f\`1 zMT|O*JOtkDqADvЗ +ρQgV b`SwBgN%e(޿ǐ: x -}Pq\IX0 4#I%)2״3yFl7ńQ%=7Pp\٪Og<:-j))Scrj\ -ɬ -㤦v'=:e6nQz378'PH܊=^7B9P/5aOMDtLh' }:iLM'([nJT 2, -X hB,ڛ~V$ _ yBI.cB}V!$R` }O+0Ǽ3f*"M|4M$ʎQ))1j <07(7QKEz QJ؉b?B8%f`0MEsAR?2 V"A1Oa)(SM0ӓB,Yj0/⡎CZmT)&&D3.%"|H8CT}<<CM;!Gndw"!włm姽El|8UhfAFxL%<+YX23#7 0v474Fbd2-T%y쟈;3 dp bFaKMATjث;)n}Eӓl(7䍴qC;nX2"G+M"Wr:WFNO&F@st|BZ]k5 -&dnкC|ɅM͇Aڌ ob/ FB;p0}E袪_`.d w3@,9q[aByU|AJ堣lF0:HvFIXiN1s\Ud]`aXm1 ] ݒ b>GDLէjb٢"J*6&!er/Pʮ2 h w P@L\m*ם _WJlI[tr}¹![E70\-Ky((L SͭP ij~A@/UQr(@F7T^[KD;^Ị0\d (''-U `"}0sXcK0x %a˞ TJ&=u(N{EX老LUk" ~b/@q1W%m$jx L)!}Ah PTW0D|ifȊ)(z*M 8=rqBˎ[vb"7}" !Cy O>p* F赮P޶tMFR|Qܯ_R5=).<:(Oꆔh7(Q<^wLĦI@kBx>z4Yށ)BXEV]GU/3L9Z>IA!2`*E-^iOlm =Zh"0z׺*rHUpdsiz̸(Q"@{1uPۉG)#[n% #  sbWwK R"P"US'sKWHZ)g$@LmL'MtB6DC`¿FL]sۼ1bhP3t!_dԏ8Y<ۨA4WHdWs#_& 75qkA9X霮4~s zQ7-<* b@EmW 줎%ӂdQhJ3pmtrs6%z-/n[Y6RT #2x•&qb # -&6Ԩ@'.&訋"oo#hΥV@<BK֫L#6nLss^h /-4pяdƃsYaUMb eut9TjhJ:sT!z]մn`5cU B~GvnȻ6^zg CͺچՐ&);I @ "(lT _v2"6?mj'kT)5?DU57$}OGEš>&]Y yk@}1+D&; -gق/=.|FzQrP% F",Ho]XMUu ֡A1bjU}U{ah6ŭ8R۱] 9et/ڛ<&zv,;|1^;2JRSd@@ cX\PoL MWtenP)X/Ohy.C0j-͑"γ贆J< !HbȠ#'ln owm<ԄchӁcn+ -ܭ pYev2%)&vovIag Dӵ&e~0PNYȣj_Mܡ&ʭI><=d\EK -Ʌ㾌If}r@<2kuΊ`Ut\3lJr"'Fnt .a§!Dӎ-;4Z`0ܸN)=uTۜV ݍh9 F,l#Y -5EcnR3^M:jI'/vSJSsܓ[(lB$2M9yf@߽{_/!;7˳??||o˓/~^ ]s˕ɍ𿗟O7k[|<4-^cy?zxx퇷޾cq|wo߼{Oo9/./|{nqPP1gϧUQŪ4I;Aa_(ܑD .h@BC3hnj+7"(eU1EBIhZ/(} -ƍP{GBӃ=*3yp7@l ISt}zHx.]}^=MX9<ӎ@'QMP7 %Nt{q 'ݽGCv2EL !z Q:MQ}\DY0P -la$ T=ؼeju?Ҵ:nOm^kWf4(ƇŸ ˑUQS/H9| -[nh޺S}.AQN!nB%n%Ӎ2s=6B遂Hz;nxܟ F-Y -`NXS$<*wJyaHq_>Ƕp#z^XAԝt8.ʘR0ëI>0z؇ F:z %B4 <ӭaiBͲ1YwwFW Pf| ,[&})tCs}1[9# -aԙ(G*dR'tHWj$PQ^ I!T#ں%^A%[ Ev8PCR -OΩ+#Wto߈3җ@ZX:WKz8ڜށ"% 9 &P/"-;ʒk 7:'h*}Ҽ PRp(k:ԁP֔b Ǘ㤍 $7P5InD4h=m"?ٷ o=&&TD6_(#*ed T N S$0EuԨ|bUBh`۩*r Bo$ҭ 1EOUܓrt(;4L@#p}vu@Y;'4ز-uSe.K׃m \cԃ0gZnZJp8lF<@0'Qk'U0b؝&lԀ&"T{ .Mn ͳ+@2ĎLE A3*)4Uޜiʞ>AbgEXۼ2 ƅp脌-@1 BVQM[n#k%%HH@qbCz7Jѭh߳YS-[p' GfV;Y,F[[+e L,M3: QT Wi<] - pZOV\*8?4WӂҺM6+-C1VK!bijB80z{f_L[JT)>ʔK BQtFEL|<4tw1c\݌ hR'dnR1 ^..Nh ~$)G3*smҧ;+gpDj?{,uzQpISG2OPi۷W4509tMWV͟ %s]) O<+\Mޫ@D2wXRD.%Er2 4ԫ. LI+h;1%(7-$ˠRQ7~P+< ~Es0خz+'J.oьV)3^s+~p70S9JGQ!;b:}Fʦewr/K A'YAmuy|n>S]v;"p̟l̾r8S8 4}#4BD^4U)P3tC$`n'JU.^-{\(ɘ54r,q8ھQ&Pyq -}N -p<F/QeKpn3JD7I NC1 -lz ϬIP $Kc\7F=),ʏNϛxyú@ -ڑCQ6.{$B3,% -S_q04tȵ88aa~]sPү .f,m|{9½a/SVLj@=!y( =e6(< 1Hou19>S#f=GBن .!_yAn(Or'̫+)ZQ9D~{ߙFZ|rgw} 5-@SϱtK_ za~_ tNIfћZJΩ2:IqpҀ$je߮O*6Nj^9 hd"Ǯ|igs_r6oFqCci⻢˿ǰ -߶ogٸSiX?נ:=6D4Ə$+ s}$@Jk^:JD`M&ɍ]xJsވPpt`7s{OQc*=>VO50[>Sb]mゆ&n6}S~ x>՘)hib^1,9%H'jttpA e ( d1nح_D¼f1:|[eCġmߊmV^,܎ _ }AX]=n5V5?5fS?"G>C^^YuԔa5~l"`jh\U`TWы) gxrsEIXbE#ߡ]1hq4ur;"L3{}I찊ܡ}H͊✋js:i"8b :fC0!1-Q>Zs,@ ,9Zc;xkrg[r+x2o\L3YrfW,(Vq1vpn ~j8rj"Ja}ˆ愘&Ia  -*sBf,'Oq9R7x,A&?'`C!+Ɲ -s OES}xP86D06@L4\֞{ݕ4LI !eK}trJ"yWSYCߌe9jP䚏]3%B>HuzۺpOBów5!gdĪvo0m vDYjtz7(GɧW‹O$LOXij-YŦot>(!_(u,1SB<~{P$q8H/0nhctQ:;ж6&zMدWlܦu HZskz'*^ `?M4'A;egă3Πʛ8ԥ,e;1RL$ ->7^׍ 甩6oȍ)&9Jߥ…P8.o;K-fGp-c6w3ܰ0>=^a -ʬ9\CRIE@ -$Ɔ!Z$oxPai˙zBU\,M MӼ+{Fzpjˬ`GϬNp7<3sf:IѨb N)~e|FAI,HXteڀ CɀkΊy'G3l4_ V"IM&% 62@D,_iS?]kbgwQk<^* -kiKQ[) K@J⩖KHw -`8sdMfeҥL] -D=9XO:q>+ DTjz߮?M95JBMkP Aܗ"#uyM]w:(u\^E`5YhˏG}Tv3b0oAԤVbi}er&s,_=쇍tg kG*q)8qDU7(?M;D'Ⱥ㤾. d&1+h'-nUL}(I9uE񌸇0 PB#wg"r}eAt- |y]kT|/+BnW6z#Efto~E_3%te9ؖ-[CJMə e1]o\"/R6w{ܾW#~#oW(+DOM -N~P(O$j1OzoKZqiO*[z1'} dd/]!ǰ=V)<ߣ>1yaX_pݮأ(E6mƊV7BQc×-{+^4Zm9#pl G P]Dfv!R']_Zmi:Νʘ}تlW7Ȍסb -[DVxo@#H 'W#$ 64ncdkbLGL!%NesڹТ떋 Cd0neDE܅\]4ќ"&`!xÂ]Fɖ:sDH貖cRFhmxԁH -F@1 ÓX)J}ϸEC16Exp҇@Z4J^op0PVv5}>h2th6QWw{bD4`$Y kɳ[]ʧs)FaU`rjQ8kˡi[<GnP0[V#F*R69OZ).9-—ϲY|| +x;h$ 5aN C6ϚUV;'3z&BIZ b82)$։Rɾ`˝@t׳s@$h aaH:M𸖫RV+*8Dž@ڔ:|#["ðwaʫ~ "3࠻"EWg[f'm*ҌpvZx/Gd KB^Ui4&YI/_˧_~o?W+a}/?W}Ǐ_ 2ɑHUxf/ZtC*?[D31ňr\Lŏ@/Ϡ!=|@b]_UGQ-:}cwNjc;E=L.\0$:g=jfsF*a$foiK:HD_2C#ZXGpN,UO |8P4ܪ|/+cpG-z<^ ]cҷKE`T3@8`JzcF6i8]ՐefK`n /S`jSy%ݫE%[] YT/כe8Mi0pFBUAƅP+NU.&D4̏k{0MMd(kEKFQk7z]݊Á-,NE2ai g2;DrBk(;iHx^B\_!  1L!+b"-f"-gv51s82B0L3x+^3H3vٓ*e"7nMZ l^Y.MM3 ްLl~jjE1a]BIȫ>[ PwtS"7V,+"]l+qJ-h(~#NZ "l 1k -᎗j2j9i00k;Uǻ6؂`1 Y?+bCpNs*z)\Z& =IC9TRPPt[Ng0xԱQOXsy2 |/nW)kKYmůk{MNyk -lwܵ>AW^}K+5OK~9Җr<#-)sns@N_cAEZ@E-dK8ң^RsgR~hu+wv w8.8W5>S-|Vf|MJbN%H1Gt^XokhCM-qT͑]Q]>(J5oYgh&8ug:TY@9nADNj -E~s먔{DR :@Zz%~g$ipj:g(UխkiAپ끛u4WlTUG eZl~2!9RǪ G "lT_^Yc#*M;sMz:mVFߣxu"*;Ȫ# :<7\M~̷FԐZ -YW\[=!#nhzǁMP̑򙩁vG#-P A'F,AS7GqO $йE#\ays n Hc`.ce9@p]nC|48v RF@eK7HyE_>UYV+f@4$op -4*ji Y.,6rw20tP, -+W^gb#%}}߱r1KyU4AQ25?RҫlG{5}gX q -s&ft{2)\&wm?Zb}qӁn!Dm٧GS_GJQuө_ OǯTU;\!ij2Ya4N!)} չP+4"7 AD-zڣ`-u;*h񝭲odM`󣖤VNB;yĜeFc'pўJgDv~;b3'Q}lj Lw}*9 ;?R4g.3^&t-+xi; -m|(+fc1bѹaVj(p (b4r< JAS)P٘|֋{xύhGU˳^) TO.@n6hN@zU(-Ϛ - ύP+O@anDׯ"YO 0G I -ЯMz͑@9yb`Bj n5-kf $ÅHZ >ﴘ )OtKZRijqP&LOXCƴ> *Ȃ,2kf|ݘ("kiAv~ -:v4N瀹Nff ^x*AW*3TױS _t䯈Ujy l%\ς1 -<-썹E8Ma+,xQ䱏ϔ+d L-]`E `?Px֏BRHW~ ,AIކ@R,-Ê?@DSPƣ.ak;J}?:IoBUOFɠOeFN}LsLVVq$P$$Tnd ݩK%K!6y -w -Z([@IEI.j]Z"V\TJ!lvB -Fn4V HUZB'M'[uB3>RLp>M;vb*aKX1OEl ޶XSFX{ԅhM)ǂ( ,ޓE($Ω!*7)`$ oꀅ_ /0YLC|?|_+2g? -"(먂askI4/F BqѬe@QXiYT)%B- l].Ǝ%5CHaE B3E1?aBT**pE`mm"YiK[ T^>jtJ 9AH. H"g` A hT׀(*)MyDXSh wbYU2I)+DE0Zq+U!!G`R9ʊB:4bb0 O(PYkF> %Uл@DBW?HT=ŪJRdI;R\QL]U[`NGDFp5U=*uT7*uF+w%uc'x.ѭQ H ))Z.\L>>ȚKspiن <2ƗpSoK # -b:e-|`pIVÁڶ&}acPa}}m(u% ;!.hΡ7lT9`f~ ]ˡh6:ãrj`AdAT 9\>+`#S -vV8cp'" --nJD["°넵-D69(U(p6 +y=v#H^@?&W@Q~SY?sci4,zBluCQ0P76Hxڵ+< ->NIo$VC-e4p -KK 1sB%b7aXm^a?B;Q <$i3ױ*_+ԪZ~lR$Ui٤*Q`.0"m\͹4n]]4Ob +WaF]-H Wj$8D[o?|㧏_>OH |}}/?~o?/?C|s~?l)88>\\??a wy-\ȎjN܆][L&A,Tm<$ &FQSRjǯ0`` - -endstream endobj 39 0 obj <>stream -Hˮ^GHÞ %tץ/0 Pd A)2H=ߪ1FL">ǻVZؗ==kGcXsG[2w5ReqoY2]bF'ޘdhbO7b> -dsۮi̽.nsk{ۜ׷i{8r>qnǍ/o`Zݛ]M'=ld^t>X9fYWt6foRvE#EuA>Lvٶ1Vu1RsJ}q ːmr!Q|ϙc{6gʭ_Q&d|{_i:ݺRfxѓZo}1Z;O"m"qNm̶=ߟql&t?l|_1ĩ҄_ټbr!e2jF(,"e$C f1njtXͻȉ.kpge:E2d2pvfeE;nwZ*1:@;*1(Di+.E] -( EUou3cP2uc|2CS XI&`@k3bO=[ js!d@!ϛ~BaaE2V <*7.2AVU Evx -W8ªx œ%3ȰնGLPدrF"^&i 5`H8qY򠍸-U*23| -&O;V5ƩˍgFޤQ㠍.!!{E)k`C<9& _r0sҏ֪l  =H3|솚\&Ou)+D6y(mC/QxT]>CCCrhg4rݥSso - Clm\`*WzBCKHy!)TK-=LT@>vTTb'1X]0+s@] U@LĒ^M -sӻ %/=s;F;,jÇDxW"-T  zHd]/SU֪B(x0Jck#6@ʣyv`.K aeTS+zrs0$媜 Urbk~ ?~1RXԊ ڇQgAEV`]#rxnpV)c$@( -8dSÅ^cؼwŷ!;P =wHksIGYd3ݔ~rJ7p+P1ȗS7Ksngs$!{JR\EbjYd2hP~GkE|gRЊq Ǥ7/ qGX~(ft -\ MA!/s 0b$-AFQڼÉtG< tDYcX['" 7JrDXHw gXU x'G_8BPQ‡r wH&r8ҙbq]nT)X44'pmCTmR.ԟa1vGDR;س'B%jQR)BQ 2!}ĎV#} -p -a@B@7ҩGĝ>40h폅& -?*LX8Nv%h-\Rh֭'FXk߮0'bxe"xv"b*X_ w0ac -}ջћ&\DH=F4i4ͱ;(5ª]K4NL.F*ǯ6m].뎐`G4Xy%@J"0avǂh*b_DPy}v)9An%}ڭQ~lxZSQGY,4~3aw 37儵5R -ՀQ --ڎr(^DX#D4[o7#Q㳕ZV0L9a9xd"Z|'B sX}Q5h!=1}İcoe/ݑ{ID  ѥ?;̣) zvM9 )ѺiOn}.:Úg豄5YN3 BTdX՞1F r/ P߅z"b3UhgCѣ!i}`1z6hd3]uH%PCQ-QE_= -Ey]칌zH=3MM'4Np} Cn - B -~ 0ވ@ZRy 벞¯nȠ^T@c~Tn @8T75LEkpG38 -M>]jLC-$th`HR1AWr/XiV Ip3KLGFSE -Ãrs yqzvTr3,T~;]PRn: >x x`$d0"($u8t|!675-'22#mz$%4;@\DY&,,!C<1T -!y(WĞ"G+u-A&987Q$~ӊMHPrsn8ğ|@%&>Q %b|s$I; rPrS#x56πဂߣP[5归ȥ,`PxWU[cӛULwTD̡dWDw_8A>=0nQ}=QC{g؋*nh @"c 6>zrl n@/q7]6Ga\M^ۭ8"G: % L⣊ծQ(WJJ p,8`Ί?Ր;&`w@ -!co8ˤ$!K1 K/i&5 (.ض9~ ⠆BGZkx~!qGdXqSNNĆB,*4r?{gߣpwH}j$:-1gH4]% -zg#c:FS昍o @.fgG[)1Z]Ṫe^)V7S^f?$(gukIͽV?'FiueУ!y}e6xq';g~H&!CQ}W -V,i-T>b]H([>F_ 1ۚdZiz5g̢. -@&|` !,\P $gTL*g!@9̳h -?Cf?Df z ЊRQx`c X}@:|n~^Hx5skkFɵC\k-LN^_giP|>BP8u\B>ʌnvѰjf;4&d<[lcA P~Y-o3^kzr*Do@^/\HY0H0Ֆ$b[`I]))LBz?(U}Hraimő#]P29\첹L$6/,){O3%pV]XLQ-Ht>&cFeVhh=|C2l.qCE'Q"> kqeN6s?z>b⍶CM yYwk3f&i?9ޖ1bto$vk/A$R$^rKn3zjFP͊Gd$0 bCjJu܃{Y ~8(pu.s6*;6Be#rԬF1MT$f'=<^}{4^@^ʁJ2y0.NEuIZ -77R)F}!ʌr;D/^_j_9!fU&aZTH|}وj@>3kݵ{%6gP?e,C23uMӧG -z9ƳanB$ʝtܜV  -q}27b=!>QAA ׎N!S Őn(z Lz*A"gB9bu/mwе>n9@\vl`nLQh/Yr^_^&)Z$7Lˎ)~SHtɗh3jI EL. ۋ>AX9r>hwnӯk:~Qن;wQUW`Ud8%Rs/!096rIU *2V.9$7h} ;9u4wl@zowt n/ȹ>ϺkjpD.|흃$ƛT27& h^gM})08uvZYd3M|^ٴ_2i&TIlI.6PtS\(P 1cMusH"B2Y٣9R1^DQ˜FU [f|H 7`•gAs#Е[Gs--F#TVU{QgL{ -fSYgɠky?(ҊLA}GYaFA/ΗR=0$d(OK3J6 -\F BF -u 0940g`DH^jn$$;(\u,}gmzmB|kCmIB\Uh\mA -YJ{,[0^N -AmL\ -Od6hULR<#Ha -( E+?)h rM~׻!{7v΢Ə tf4Y Gm |I/7`hD5}+nSC}#WYi2rơh*%br^Pc")x˱dsm!vh]2ːOӣx-G:+]zoJ$p/if0e>-O+/bNѧJjPUs "[e'?  7AZ#0م)m4KH$,g)2T&~ ƱهjԄVZo(nԩ5~|_K"Ih.MM`g>ˁ q9*]`#Pه. /Z,vN8Y㳀G:3όy?.M6?Qk˕_GV#RzQm r"l -9dqq}Txņ-0siM4|C;U219+얽УfeK|fnal4J.ߟOk(*lE;$0*qn\_-Tew5fM v>\h C"RII0ǠЅ`Q@m<ڱ{=8×E]ma Dy! ->־o +jV_8lg&`EKO7ZQTxE>'ܭ3 -,ǚ,8jA\륋! ..#^UDl֯9RpIb0qܔPl:U8o6B!d7YK FV_l"#y&({;"p/:δBMYz>m25>aRl+E|9 ބ܋4nd년[n 4e 1?@!D!U @fEOʙs@>g__&yc(X G'l'nADY^xVKUDb.הHmkΩ# eFьNhI1ଷdR&/!@eK ̇܂06uY.^Ϙ׎ w~%% e%۫7ouR8|HeJlfX=XtĦWi\wXqH"Q4W|:6i{o8{jt/ T)~#7B ց%oHq)JtPBT8Pwk/ :_ -&hCYhCPHE,qj=҇ eOz+w:DTFe.ha:\-JJgw*A;0IdDk%Z?kbY u6qW`D61<;Af g+tH,@9WUt$&b+#tw^a:Ҡ%qeaTWڥXŽy wBJBoAaBjXÌGdP@{񑙱ɞ6ளlrVM[C)jV&c);ɺE2l8v@rW֞jRUon#)sdR;@6ZD$Qb" SBYWUY9VAM׀e9n~/"2# ]Mч(uCÑp3*؍z33"q{!.)0q+HǦ|ڿ4`) rd`SmYꛝm*Z~]*~rJTiS6B*d-D Wf1Ƶ"!2*ؑ0aL[XfWIʱ9Yg )SBesWo%mQXE9| SX*gK;p߯8 5f*.8ۮw"|=H`4TL -R. - qj f?ցԯ\02[ - -9H0DMw3.٦dڲ.LB?[ tb,xq*i \ xRuTԀTp=tݨ cSfڜ̓Xԑ*x4(w ucF9ɨ]=^hү &qH. K -!VmrAn!O -{eb ))͍)8bnDWLGЗ%};W=^ߔSt;F)PL[>08&Jek @]H{a{h68EwkG̦ 8`F@9 =+)tAROg7arӺ5143;a$;N>[K"myyh*'ɩyM*93xt4K^;;auUzO'fF*C1L -3XCPxXW* ^C4-*R)Dx"OchGptbyAco oh[[tdH]\N*H`P'M_i2d{78"?zc,~+p\XP3ڇ -f_mS; -\ -mx=Qľf41Fx_aV#Hi Aȳ $ ,4ZN //r7j,J(\|mS!@ шZNd`^\ Cnj W?;!<2y(A(,8-DDZ)열l@Gf+GJeSr >HI/&w*A.lOB̍?J/oGnV{0?gm2F@?!c>nLr* - ㏌ob9[=Ҙcԫgב2-"Bm^#, QmH)R]T[-4dFƂ^.;E}Zyj< o9U#[feM{2*/p!ܫ|z'2sNL/)r$% D)MLҙ^NAxЌ?D+9߈}z!pI5rnB7ӑBRB[6 S"GCl)DHb["#?0,qQWP8ZmiHxC]oXE᳭#R -f]n;%bes;]O?qDt- -NI!Fr,K^=QܡJyZDQ٨U*Sim *;|VQ[81czVYXRV r3ʲEJ95o9%)Rm3+ -', bV-0TEnl ġn#F80V #M00ŰDP݈dV>ρKT[Xد(I6QX -#um%ڼMa9p 7oՀZ^Hg ;d:"U[oZK8:m?.dv0< ڠYLv'jwa=XcpRjgMEPPǞ!ڧjALrz-O/KQpͮy0ZP=GEpm|br 1%WV*g`G^'"O33Q[/wtIDq;G3^aEic$[h=cG(aGug{6πҖpt ݫLdK-rIEJ k` K H)9Z?c<>2I!7uphrwHA$R; JixMa?+dظĺ44vQ"ZP]wFYJdw}1Jve4Z5BxVX)Wm9S FQ0,3fgkK#]Dj6ַ)4،d@ɸJ&RZApAUq -ZuMNv`H1C=p4Z$Lo -C4(cob K2 Xq8J,JXj,ŝRIijӰJjql=\8_oeg˝0m`Eo˛T@uIv1+ZB4K]2#a89Hf}t[y9 (*''e@)IVKOD;)0}v։RaB6Nۻ3ϰ 1NpM?DF@jg=2Ҽz:5Y{k=: -5S61; amn:U(M{xTCCQPZ>C(I{Q?Mq>2ŝ|@5j8Xh%Qpf&y+#(4<{}_÷?|2,뫿~?_~_^7O:#ZNʐ-i9UDpikL1G-iz˺<cA >yPHKA#œu@S8Q v9 -7 *NQ4qZ.nE_u rS޶=.jE`4(z+f"#4m4G( ^(SUv`F]NWt߃۬:k@ص9ڣW{:[-)/2w28c )u3(mկڟF]Ix=A?}w|Q_Qy38H96P1`PWGG0vP^Ց֔BP\m@F$|>M&RCV*t d.pbJ@$f_h7{NRݪ4v)UR"٠Ih?$;8#-qzA -lf6W ˑT79k6ՃAb@gq[,u^;ivi=!t$y]ګ@/e fPkjR?=G61=e1{B-qmt5G-Y^4eǨsSrf 2X\1P~%76⥞$`Q7_PӷpL9䯪Q6C[_^̶ۋor$*Z uX bi9sg%kY9w5ly_R弼gFQI``Je݂ vqrNcW伯s^Cgu5sաν[]q%߶b -5 l<͜A!`rL$g#VΣSAJXP,6νʔ$؋.! MKT& ]$ؙsR۬LbK(=lWQ236I~t%V[|y-q(UuSWFSAnǢ֝*=ۢ5j}啃XG((/nFVT|d\庳΍)H Ff  -_ m2}Ifz\   [xw1ߏo6&S-<ߔ7Y@@]'&mKM3ӈuQq$m5& -&)uo WcG YLV# ߭Z)w'G3m t?1>J)L%H|t97o77iќ5%;SQǶ^8H؇n3@r/1#MфN>$2 $#MZ*j`1 # 8A u7]ћ +2g&xFQ3NarPġ]ghI@X@K>z=a=."HDI1p D84|05SqX9}ymMۜ3,eWX{s=Y:ؼ b[|k_$ c(WةOXyC >_ڛxM?aIڀ4l:Q׳SbɎ^R.'XyFmuT$X\jjOفvDe:Gq)r3U Ż/(?qwFց@װ|`>-;SqB~:aZR ;Ncm|E( mƱ$:|U "ތ1Xx -as'%KK,}.-hLSLrR@ֳB2<O)>B_5|a 1ꈈr&­PFSnZRv1cuÔ=լ3V 2"bD%XOW2< -& w$dQ)Taj- WOK1L=P+IDqߪn)`ts `ьj|AEyUovq8c?#=γ5]{&+ -; -y=m=\j!41̝xd%\Gv .K=JwFˁ4kEP(.1mWD0kShE+Dz]IG3܄=ztUwSԹBn5[ ^ְzBUs =6pJ˃ȄU^%T#P9*w%P Ҟ b:Dў0t v΁Z.KLfɤTV0F=6v:uTx]^+0awQԅ0L_BAwBVRC5F,*/-X;Po)"FX IXfe@~ku&=A3bl± J]`4gHƨV<}bL"i.sw9%aĆ ՖupҲש7^yjU+i1ehΘzX"ZaL5=0q9 1Gܕ =,XOKfYFq!*&S{{="o49?4iesWgRCA(,[PBFf}^lAkh B8p^F`[q<ǵ%?F4 T>¢:A?#j8v$IT7U(&{`u {bD5v#rˆb̈8<{KDT b/mR^df Ts5{d)S] rQd]#xL sjlKuh[<-Uc=." Fuµp9-iB G aKvdh6³ɔ7^YL9z_ [l(a\o6s{|iPzt#pwJHi {Ur=8Ȟ$ĕDqF&JkA a[&LQ0-ANN4Bߝ'iFĬ2h"2[ (pFV5*FW0; (;ھ:T`gB o(nhbDD`@026z; m SAETRTa ji:#4tmmj(TQ9h83vBT*=.m1fhC*h)..& ɭsv^"_6$!:;fE(0F b& $JX p;3zCت.N[mM>D 4<^C$0iLj^%ri8_mݓ|YfHF8A }z;(1a,雪W1c|&0< %@fsYC˞u:m9<0JWM|%>D\ndZ׾bN  3PB**BVea,+6x:Hr" ԔE1E$rQ}١,iT~sNA`+|TY"MF{l#7 |ɐ)uciϸ]P|% XMH- R޵ I]d51 fq s -V xȄ;%)k<K`*Ѡ*?-ڍc >UQ#lh~03`kt\ N'~AX[? _Hڟ,glU u!!"FoqlՊځt +F]`4q vAu1Lhe'm.4@/@p?rUCIq;7W?|w?_?#*Q>?ˇ~_?ߙ;S_?l{/_?ol6B,Qe@^ÝC\^(#+2G ]v-ACĊ=-s7QlP־1B[1'ctn2DljKlr=M:NQ@GO̲u 7.x5f$bV B0'7GMc(y#rپ9fںA[uX(Nit(}l v@,2hpg(⎖=h_Vɶ:y[_>B11]ތcRKZgxr(*XB4"7 JK4,5CHG`OB.3BXгK2$<}gySEW S P*Y!*_NxqcA9gH uw&F78lT4H =Jj$-! ~@]ϒ)\}@`1PTBV<]-$_"g#(KP~z6BIt:U0R>0e'BsuD`ۃy^Z7G^~]/0q1Dz^GQDL"uGb?4]bV`xl;x{ (`Hjt3a2MEN%߫TESG%N #4M1j!"p\6i$rR[u}z: 8u{UIӡ7O -ajZZnIN!®Ѩ_Xj㦱! ɂ д$YEAQcDH;Q!NQ(k=Lwkt|MΕkSu¡ Ǖ'8Zq!9vjXm -tMY$yƩ]EIٽOU '&Uʄtig^LpLL=KoEYKfVmg3"-?-5<8]lD64iqlrf쌾 ^jx/dвN5>-u5^H-JV3[ihO^u,Nb|).x9Az, ).i|Z&_j؜;fԧs>3Yt]0m -{>w>P|֋F< -jѽ̸-珤YcٓPF+W!BjASDA^8n&iG fT-8 yyxv k !ur@4; 3QLeƞ(Lg׊!{-gP1Z j^?Vg<y+` T5)#L`\p@GjHAq -z//s!#̶}7s%n9Zvo7T\ -ְTU`QPEJ,o@{UpoH NULZ2㗎ʦEÙƸW @ .aFE0v3&bAfB ͻބ F Ћ$-DKxwqJ#0t* \+0:ylK -heF.M1WeDM -k:=v/6 =<-̡Qd C{KOoAxJ4x7k -;S>RJ2c0 vZ'J\Q(e{|!cF3k}Oӌgi ]fɍH -O þ\l? OG(XV;zNb8_,bd۰eOň~E@^lSt -$B^`r=qoƾՙC1Y,@4C}mZ޵~жTJYP>h zgSq.o U ALw:}^*gE [`2tdx빸RNsy.o.DFtf >!ż)Ze -b#dI '<5.B`Fl=)yoZA,*]a kFXHx~mݒ&{8C)7M1 \.!\;~J|۲1(@sewIPo๾9/;]f69si/ %HRYO6\ҴTx+DcV ,ӦϗjWBs"(:=!:@pTb[-=;2dA),R5>7 - mbS5׸qi=5Xm>Ѻb,G`>G -_CDoِ\p7w%BP3 +hj[.=?p-uQMsAd"xȿ9kJt=h'q"hLPD cBLz+-1/1DC}$=J;@r 38Y4|l4͹)%^k*p9K|.ѝ&KsxD-Rlm!l[z -5M̑>L2b\ q/fU]>E#XÜ kn}&QrD蟯%0@vL,zχ'@=: *C OŒW/똳Kq \~W6su>ܮnt -{t҃Ҩ2#w,:PljG6KT7.h,,5/|k~̣(+(b 4yOt`ulzܻ9r>"ԫ`jzZ>@ԁotvy׽Â.+S7~a^}ubZhUJIK3+YM@ -:YFU f|гPfʊ{C{>iW: `ֲ;p1?q~ )c֙Y µnK]R"fF|_ˉzn $3=d%js ^$>/AURϊN=u@OFqIi3 VE<"YIG }ﰔΟk7)5ܿӊMjyc^Z:qLX} %\BNqNFxx@{ =g gZXYA=Kq)9NNz}9pAS'.V瘖}/*#Wcp؃C&rPO5=[>o;ɞO!ߙmI>< ڊy)ܚP 5Fœ:*32ނF --kx 7p9jv"%+w>|Jsmaf)sC^ϳ[SX@acdprf]ͳƒ^$UíG8%Ȓ -J$0c9-=,uOHqAFp.'rbsRoT8v:bk_oͷJg*SzOBy>o/+̬-lo -6&)LSt_Y:3 -PSߚ.Bs8vc,?{4\D( b@`4`oxApey+՘Sjئt 䝆(͏߿_|g~<)(Bt֫!h0Dl`1e V o"70u8J+8J{J.]- -T'=D/ GJ{m 4=WǠp"0R)_>kM9V!\mN zYx)GLdXI6a[CeaXUz΋.;GWYRD0lsX 2xmtlN:Xz n] Ԃ=9m ߐxŀUL0k4gQ583z8{c܃rP}[`6 Rd#ftL"̙bp9:9C+ځraUncx6;RBq, kN -n^Q)&x -a|$[aWpo߾Oo|X -˗Oro/>7_ h?ʟ_?LT/6"_t>ھGO߿~}ûW_՗o~xͷxI5abzIgl2}2#0"L\x@s~.0­"c &ʄ8A@PxB+~KϵQ::Vh3iC0MW${1s1uXkYE|gMڙ<ܘGF8!#p{k[T~dn -KQ$ /s8"SxQ[rMQiP~nv% pD-ϼƇG{!ӤrI:D - &=IVF]fvcl y.#%&?K%b=}`ł0IXԗuGr+L17`a ׬dh8AF-l[u",8ih4bL}4Â.$:)l A>B ؖ8d&*X.od<FC;Hۄ?1>dl -V',B1ԕ3RT.)y]FLX _]쇫ⶋ;C,gѕx;6 }^`jl 4P@aٍЀ&˼bp" -LPlfyDYZu@A&u,bWvaPQAY]SJ {L$ -O+4%KwE/uԫ}6(Y0mҊe^2ZNF- 1UzjӭkE|I:)jeKh|L|F¡ F4i]pwPZu5:!g#jN?V1L h!bSJ*"*Vb &XMzFEC)4 -*B"bjz)缎QRG2uo() UGDQ$"5MD%W.dɰGڜasJmEZ1AsY m Jln9"t:fHwz4%̻Լ2txixniZ)$0a `iP3Vik-vmz5AD8r ?ez!h˭CwB4wBqDp7>Rm:5zhm7&=ilx?aPv -5&TLM9 WX̍ݟj+> /`7C &08j[#rX'DpU~ύH&n%9r@u|#dE3Dc<ҝ喅!3"l&z-:%)èMsnq?2'|M]gxq%imZ' `qr0]; NjFD1/%}N\N%x I66_$Yƙr!HWOLn{A2p@Wœb,rk6=tёo4Tg4B3 -b}"ί^oN D -8+]œRJMJ"]>ƊQUN7 u:$Nk_ )* >b'XkZ-VRrԻ:)ڧr #F{S!5Hϱ@;T7epe'ͼ2% -5UZWySo F2!Cw S80pl.d!t)b1Q,8Fe#&pW)*"et 6_' &:t SSdDjqT|b>N -zޑ]q,AQX(\v8+ Ғ ByT%|tݝ77W B9L>[T+2aZ-s%Z`lO<~36 -? #7  97";>eЍŏ؈rh'LFWdɯJ2p(|"[K]pmRZPhѺ>8o}Mk XNK=5SDpATyj=UNj!a0ׄ&M8ل?lο9|+@An-=<˫pkC֤nvf^ ,d=CfSZdeDH[qaPtJ]&w_ڱofYӢXCȊ^)k]ѬiV(xZTAvP_6wxSMh&4NN|ZG鳸5+~Oek4|7^C)^2T0?I{hi<WuVstx*wء -x} Xvtp|Ac^&S/ BQZ`Wd%]mAr_7:wUU\6Fʞ=bW lD=/2P ȋQv;NmkWZals vetzdRl.0z  ]6<(RjqZP`oБ/yOP'P\8.alX%.=1x͚9KqT`āx^F_<]m oqKFjj3gV0xP,c89cxZ"ڂ1H0l-xHL_0Ot=02Xa? n#WAW=R189x:qt{anAA+oDNnχ4nx4?q7ú /b11*ΠDr93*hnjΌ=ȕy"?; -endstream endobj 40 0 obj <>stream -HleM s$a'$KݗAH/ x{r}̬f朮\._cG_W3s|gF1~?66-{+WlmWv\uYآgխ݇-sqY5sRžW1hnͮ4/)D{DpTkۜE -s6|cu" s{!"- +ع@>̙ -od-ڶj!"vj6xV4w7mܮNvLuj{n=A, 3f$`BZsG[VW!rPBaB8wtB9AJp($sҴB觻Ah-aVN=<[oGmtndaaz!_ WwKjȣuQޖ̘m4slk5ߺrfQ'R(Dܙsl|H P%r,8+8d4~O\!#EtcH(QH,!rRjа2<,I|xqqh*Zw&frT摬u뵾t1iC 5ی]rF(M"fx!mv_Ӆ f&ϨWl@Am䳉b+uyp+kPbUU.Ԛ7(}U(-ot^6UDopŗ> AEAm-2YiJ%7l6M \Tj7;$a"j6{6F(C}퉠-w`~FOWA@_h J7=Ci -']_3uGԷbX8^Y ojabu!L͑;%mK: ##!k#W!!قAMz]ڵ-t -x\wUqbNʝ~6.f֩!{4G=`a}Jݹ'Ewl |"N"@vS}5>yhύD?l2 =x"Z诰%+ Ip -=+, :/cH(f<2F^~i6%>؉AewpSR(') -5Yd,|HH!*uRn"5Z!,#X jKՁDpseɎW!mEͻh \w<'E%x*,KmxǠ67!\O\p8*_ae&R_?CTxQcDĈF. d=7Ԇwav1gK~mxK>oM %'XS 'GMU'w(\2m~ipj,/If+v\yg[/R݋ R+ y \TOKM@%*BNGa)R͟Q*L)}sT&utBӦA(fw e>2Փ'Єz}ۢͺW8!9tXWbk,@!YV*F.YS>i7Y7%c6 V͗ (!bpғtFb6ga9`(R0y?^6X9K :x窨ƪ˵YhcQu0$+7؍PDct .,L3"fT2pq$aϔYSNnFP} -%k} z*bC9uq>W)k6H5-,Y'+$I0te^0čK{Q}AHΠv4&5Lkʭ"i#\zE7UWDy\Dګo+ IӉr+Ɛ;DNB&="DA=P Qp#NO/m.U0.!tަŕS/'܁:Xº=oẻ<ɼb,:rݮdMȉb܀ډ֑4 GmQ5\(߱%6mB#1ۏ81)L~6I3:I 5+>|HflŔٟ*Q \qYaL xC' tv6"L\$oRb B0r!cF/P$_emcoǿӿծj{srӶ\"o%Ä?ob{0L=vW uH&. -1RCy#p!]fy_*2  H^B4ЩHDUӴE)Brf ]1fb ajBєMD]6Qobr-`}ä@Bj'tf-W- r1x f'`XRT$0K\7?L&+%IkvZ=;jj3 -vs^(h@4Z͈t}cPV"BҒ=>H _􏿏i8J8džB_N/`iK ǫź;2Z@( F.BwEoMz'k8 -V N/ SvoF*Qkj1=1e\~o `hQGM\(bc=Jbӛ`|L[,>@VSSU*Ȑ[,RZ{O2 4{.ڒ!*+dB1xqN&CC(|0f:7+IY]0',DЀiQw\  DQˎd+). ݚ 2BC=}̋Bya0 1"|sVFЛ6,?'8'\[c;rhuEztrLk oظ1B2iX4a40_1}f[6I2hDs h2E:XjuR!z>2uu~|󧯟/?o1J?~>g>"o˟^f'ǂUДh(5LNnt }ņSUhUf Hx |]:dvNzLt465:XCDdVG%yJKL[!\CICʞaK,az0f*eLG:w 8 -Ȼ@%o__O#ٶκ2nW{-n;&*.G.h9q~ 28JaRU`^\AQ*D`Dl -D[x9WFpZ _h P8\7"n3J98̶Ԗ%oz8[$itOc?}B͘/ꦫq$ei[c[~y~Aq8 DkJ9 fD{ LD0(/Xv!`BoMaдc0C D;K:g+~)˩M34rhf049Ŷcm %O`orX-YaɱD:Cƶ")k4.n:6kw:yT0|}^zu;kA@KJ?Fj bv@"γq5Z=VY%`,Ր'bRh ɳqNRbspAXζ>[,!5^9^Ulp$&JҢ:Y:4E4}!qKp<^Co&I}|5גr4Y6NJD8{(?L+ Ȓ E@Ɂ"gCioqN{5 W7@_ YͶ÷#,C*53!'Jt;\k]RۑTxQN*1 Op#4NW\xslxY+!١2|>B<5Mybvڰee<:[nvweZ՚Jx ȶ$pCdO14uI7`qղ{mpd P"8>"k1 $;3&<ǥ>( ΄O#Tq,Q>(z/v >[ӓp-ܑ-vu£<.O;ڑ j2>]uen -F\_„$4 *fr<+É71f]nu918:*RM zHߞ>T2F\>hVvmAt+ Q|{OY)Jw 0ǣI].d1dJnq]B.if6{EH(sv&ARLW_1|P - kw8.Kׇ.¶ zZ+b=b:D Jf*}s* ʙ*#DBM4wa1}rc9kiNT{21Zݧu}{5yL -')cM]Xm*칭sqz+Cu#rوd&Ne=d l7B̧fg;gH1.[dq̷0,msit.Y l|"`1]x(NtS 0]1F-.QgZoSx] 9Lt/A9D F6 3 ^V -`x0掭 p Lo9BF/gB6}gXȵc3rk]fmtf' dR" -EyprmR aLUJ0ZF]$M#0d̻ cNTra =L=4ͨȸ bAZ3f=  ڧ;|ƀo7^;PbCNrhsFmWGx.ɺ`.l;lx-F 9*<]'U -L 3s msBemAT b@o׉uNNO8 zabOG-!Rj½:,>ilyOA~ZO -AԞ,Dtf/ WxsbKa㸫ó.kӆ$NwP@G,4RK{+ XTh0\L8ƻ*HvD`6.9` CL0"ESYTA7Òcxw ",c`97l4Qzp˃9(m~|I ,^#áBZq xiƧ>jXj,ءwX)S(u>f]ͣDcJ։ur S msεP::CG J"ǶOSѭ[#J̝3\ܾ=ewХ1(<@gJ77º톖UnneW@eh,'Zlq`ݯ해_MsPlۓx-yLQϑ&怃=Y 0)"?qҟ&W`K(6j4*"eRQsZEӨZ"lC|RrrSB|LZ7WKwI,%*46V^>Nma0rDh p&1l87'zuGSQ( K!3cAaғQ93x̼'σw}ӂ6˰pM}kFZq J1VKnw@E;VyҞ04 [3.\jr`ujU먐~5 )V|# - -.O曜Y{$Xa+6/O1v-B8q PxN? 7G'1"Ǣ;IT >qt|mX-UJ'jH]crU)QINց-y5`I;)A3ƥL+F^cGG}l+ hr"2gdn6B'4PuZȑb[jx[1:¢>DV*kOv@`HCF54&"z=b.Y.CD8.L_Ud~X[O:T{j$xI. -uW'tvޗ}2փ`SUff,0쨚Z?z 3tP+Td; 60&؊seTIc f4=tuU_zEz\G4{L$ v 0dn etazdG4n<2J V.4$i*SetrX֌ߺ4<%#fDTsla8ZR;Y۝;>.ظ Rgz꿬Kk&ʷM"׭^fHfHl0yNuK& cvb7=cKGƤAKm+ppfrC(CK8t֧ 0[/&Y;㝲Q`U#Ӵ/Te.O>C!g8#q<61O0τ$< l]A7Oi* M S"bڐ> HQ?G(7*YxVڞS3{NҰ 8]pY`3W;, ӅQ -%kB}7Dž` /KգCq5}F>=hlRܩ]\ܶ@y;ŒO>5;tVT|'pxyIA8ںLyjP-M!z1[_y֭̽D' mLX'%WvmvXQ5ȑ2ǂл nW3Z3 & \iS[S瑱Rᖍس> p(l*Frq ;5rjĈf(i&N44 -lb^=%1> WqOLɏ%U0t!CD,թ/@aë]&قhm"^mIleo -K:8X Qd1Iv \&uB0D.\UE91ϰ3:< E-Z6s|E8"?bzăWDMu*#޹:OI[Si -yG5|bx5ɚlQYFZz$xw -"X50`7vXn5TE#uAd!*7fG̀/}_|ͻ?}^ W-g+wϿ֏߿/o,ī5{(tb5M n nQƊ#M1̺J8<&Jxq>u34^rYH*5I}ES `Bfu, K}V`d)>¨=!P f9k(^)hNX g"6s)h fB&Θ+BKczx)lk ,^Nbw1Q5PDFzgW}sM=`Ls\ o9Pե b&]p0n9;v(k)B∥H8T< rcU*_ݯR*(4?(;ӆj"qc7/i327 1x]r) ,3W8Zayk.ۂ`  -ҭj7ǑCH7rt^C:ZTq :ٗaI&i_!R(_sP:E^Q3/WMeJHk}^PZf͛6)(=c]ه%?}#MhufS6׈܎7wN+.d9=,e\럐sTRz`G|'Z&Bc<ҲXYz[y9ŔB"pESt WO< qD`1cQ+s?C^5v+[DE< dk٬lAZ3a"`hH B?=YKGLXK"gR+ V>M'9Dps!-Y˕=μVzr -E©Ǎ{ïDTI@z?u} a.MȕX>!rNuֶEyOƐwQߚWmz~ c Ҡשq/ιjNSDJEQe/6DL -=3ֻ:Z`ԂTqc{6f!.۵KnSw׎.r.GwQmqb;y6Ϸy"ROVq٣s*mhV*J7-yD *,/DT3،DA@\ Xo 2ITDob*ISVY\Vةl -#vȶ:cp%2IXSw,Nc+t 7oa&'2S79QEyl1z'\*WLI}6g/B'޲&M"YhzfEl3Qe㵂'&ߴE_/K2ɭɡV#:E3=R r9IhmP-\g;h8gu"MrK ?*j"Y`㴐hT1S5CC9YTI5L=wҘS=~,(/8l:ʫ"]Wu1s7$]$[2ؓuyVlwkQ1,cް^ۈ@`a)<4V̀EHy7 - Fw/4qŧ_ZiXyp񁆌<rpn7 Vht=_Y~g NZ*Y3{y՜=5m~e(M#i t`DX5"$r@de^D ];Jf/3j6{%l{+WD-򪞒Ye@bOw;?ݶŎ( EK)( /;Hrֿ`? A=:]^0b -i]<(|X:ڐ -oæqmF,}y8;^p8?C\86AEްHiP 2'I[싻u%Q -˙L -,q~)`v?~)XW]>`)PNG|QzRaa1 =B{ya?C!m 6Pe>q`"uS,c-԰q)-C)-F*L#|W|_Cz 3,+g EL(UW B`<w>U. [ Ff|ν͒Sk55BU[,OS08NJ3uδeHXqAB -{#Uu2g'ҙ,D.ѣw߶L -:14 }+wcv%"HNĠn"j5..BaZ߁wP p#v<1zMTQKS^l3 kB+.cc8"? ǀ٠C@ Hw¨fK3.""W!RhfåA`P;:0;aR ?5@ ~p ܈pS dd: \ۊl'Ƅь;o(Syw<< ~2=QӿEJ>X000mSY糁u8o[ht -"ڈs?' 4M5Hyw8̋&&T^D)HHq28A/t"ϳ1,̊FOբt9tAT҈{#xd^0蓆 -[919[_y#f"C(+wj[}%^`im|SNHPQncF/iڰzpANك5_ /PbO22s0Na v!s'ƦmZuP&@/vwQّat{1f`t,$Q&(ČgE0tJ٢P/gy:AR. -CCU\'F`]o J3}7|ӟ1FcUTd&B̯rFmȦ&%oVa8J{  Ԉ]#g%P0:WN'z7~lj.D0,zx閤ul/@ 4'?i?'nވiZ2>x{,WC3׮M+zZ)(x6/gPNИfi؞ᓪD,ɗQr&r!{# ~jЍF]?T9:v>,KNWx|(Ôg"ZWZUrG9~tU|mBm *-?4ȾNktDElȼ0^$SB \Ј+ԤȴG5@OU9{plI\̥5fk knnޥ"F/oDcqM<1)c.Fq:aegalW.Eoː.1x%_O<<[ o$@0U<Dl4?x5c;cfޯUDLg^|~}Gqkf?8/SDF@ @xR |!.\Ul?]?5zB!="iר=7VsY19[ qiz=ˁ"a@ݽ!qpRE)h(]Qp#<^I-Br -!d?9GWL4y8"q;\]>z-P̻!(*:vpu<`0}Ec!Ȥ &aC2a&Ĥ]1+d/t`{:V`.eD0'ىbჶք ai)഍]8#GRѿRK{파Wl@A#̪ed-^s΍jSUbŌvg~{%+0ʦG#$P I a@x UtZ/e98,g_´Lj|:ށ("c|7Ɲ=. -r[p$ v&j:۳@9U~UIvm"7 &Ӆ+z 1"Ds n%ϥy͂l/S˜h&\f})E,E-tJۡ'dGYB.SwXl*rؗ 6A)X^u:زwCPb'>"3xnq_w^[]v`n Z1gϱFF0O@TڄzIG+lOU ?&6_g#sEYo|o=t.KY7/ I]J|%ggĹa XU&e]:LWQxsa]sJwUgyX6kWZzlf&60'z|F?+\E^L։Qrz#"NO aaI_FrBiD ';ijGc <DE,}.|p&[P3".$ -S]H)M\ϬEWDQq (+ǵ>caEnA#=Wj c.ian*au%M%:rl^̦=XrfPADZAf7ʺ"lal<Z"ׯb E%C FI2M0,Qr3l] N@/|ޥw!X7r6!sv~y-jgM Dk,O @Vc j~N}eIˀG5zG/ަ)svKщI'8v> hGV:g`p0|ojPC/WaQzx3\V\ڢSaѵ(8`؇TEKd}qUD< B:@H!#kά5S(-~nO;f@9xc]Io9pO/"!vs-n"Щ4 Rj(E*ƁW_3DQL8R {aQ1ӶlkL]qA QYTs|d|a!؎ LF .OQDȖgXq0ʡEMcW=VPߡ@ 0VNN'p@# "MEvҦJgȧ U*4 kk#,Uao]ۅwLUK"nZ5ICqܪ)NGօ2I D+""3y1 R!!U *Pk BKkHfa7ʚ. Z-%TC>u -w4 qnX>=i/ZbA9b ܈.-(* 2b8PC=,Ze]iY0_ xO5IJS:Ϭ G2@ o;I_زak눂8h@y+0:dԿ9fb/.z׈}u @xZ 2M6 z -BSuj("'q-$,0cmcE?H&'ٮJ UA:VM.=_!; -s S;2lS%^=dӖȔ/R5Cxm͌m޹S -wh)b9, ٺ¸š#J-1$ yћַbREy8ٌW]8ohzi=>5Ax[n5*k ʕN'$$JxSFT‘}uqxIP*u,zƌ| <Ԉvs E@l`Up&F縲HN C|[AVȥx v -ݤ_t;zo8[)\O9*!\X<5BSk pt38-VQ*MXfIԲ$\DQG8M%L8ѹ?}篧ٯp`O/z/Fӯ'r9!i_^_ÿN^7??w|Oy7>>?|t߾9(\676nN<&bDa捠sOuF[(_lHkbnA?@ 2@D}M] ~$*A6_{];/rA4t}eD&S e*QF V# .DP1u3c XF¯F[DҭJL&¡H062I`5 3od 9~a D} O[bo nZnx&Ҽ208m@ n0I'p[ɁяcDbN=kRUZ(+*jl*Qc(k](BEdUan> CX+@xAG/jo6HDo0iQ`ftc򪙡I' FՋ9\٘LLb&jׄLV4Ʒ N!Z&L[(̾g6X*h?Yr8E+pp&]{9dWY/"?Һ܁w^-썁$Ezg+fd[60ZyR1N.N&I&QlgzLItxH~(hF3RL~0Ѓ LJ"-"[:J -Q6zbRífr= ok MȮIRԓ,^|B| w#wsh.Oy2Yk[݄{:C~H+wd " -fY Pu!3aペ?HT;a%bC3|={u !e"'zq@P)b\*!ϳUSmY#z^|4Q}!Rg|̧Dv.ґKؘ6+|bJ8!dxQmC䃰CFԇBݛxOF)C lfm ;=nJL4 M&mIs!P 1Tq(Rǹ`mzsSSAM#$*hy{(9]E SX`O#G*@@=,cU~& dz•#J3]]CմHV@jZnNt݁ vLmyyO~$N  ᆙxg7S$ Ew+א1T3"BoICV[>r ̕)G@m5Ez" -PA\y)}6j8^B8vEpi?JjM._a0yLgb5Ț: "ֱYC S0ii˯χe{~1E=qǖ*0;d̂wIC 5PZnJ/VcEǞ N9^rZ3F[zpH؅ ^ʩm2Ղu0N( P+=<|Qh6ur,Evh!"Xht/PPJ ܁@س'pgv' -gix~Nw*ϕդva(!*7l]Jw) -tՖ:R/ q3o^[ -DVs` nu;aZ:Q-]:5Y;nF@5l卛_qD;Cwc*pLCiMµWf( - #5uJoƻlM=bjÞ:"cFx1ȍơ u*.wrU!R>4os7nBPE(ar QoCeԪRu#jG϶tUAT`+vtբQhB[R2eB'פ w_M_X3`8^otB7'-;T 8 ˥JFIP妣y/C*ۘZcmqEc0}pAz3uv}q˧-b"/\f_eDpBeVy@h!E8i9 9 -Me-0)r]h&&"h]0Q@@[ -vgۙI-ɶDp([Y5Ri8 NيAjsRHr/A݉@%r P(V=n --w&ovּئW7Fѐh^Lea)E$t( {n~eTJU<ߋ3pLuϳ?!pRwѢo \Zkye8ZVxJp͍i%t#gFaTLlclBb#z{qb,idb2op7,5q` ވjtSf>,spm6m69KP}*&Tů> 7=VNy?CDs@5+BN5@Sv]IPЕ&}X{|X7kz7I#3T oXsƣ깃6HXt/Nu0 y= ^ʠ;{k`#ꅸ-H {||S:Ӕ6ƅq`{OrSUۓTLm2D|M&ڱp-A>[Y}  F_9az`pKDcA]3h8n t҉p fQYxˁpbOF ڀ }c#𚬠M>2 -+Vs+ Kh0Tg"qD`6{d -0e<*$Z&,3("yadE[BӵtM[oyiȽ&&MĪ: `^c&lWe_x/~@ϡ@c,?VӢϑ0=hO[֗u%79.[D<#0чjtl(Y>"p]񈲣??xѯI1!oSoӨ+XWƻ_\Òϳnx AHkYR#`p3a c3-dlB;Ĥ7ۍ8=L ]9fF$NU0cX$M*mZ~b5o]ͅ&za%LoxƼ :6k F)ALOËpO|` -x,D7"CwS, i:}Tth>topCQOބxͭEXK {sj.r'eEfED"h ;F E Tx뉠>T6hTI>0CDB^ux[|%G~jP nVU_ tn強{V )YT!pܴ$ ݔi/s!7>D>'sUUH?'] SyT"SO] Y`IO -Q9I# IA4¥/E TzLߘ cqx:"TqboLL mƢG%<ɧ00_/TO1VuT߱H2:_"]z#q29 s -:i8((dOo%ODN,: V\GB Tyj;vKj*38}n;pĶ($?LbSZiބP|4 1Ma@&Pˎ?TI2cI?`Lhrj y® Ȋ~}>Nef-@DGtJ_`(jpG"@}bk=K?2#X{E؊aJ \Y|0úuө0,#Oڜԋ'^ɭڃBKW>&毦$~h@yDޅO?rP{NT^,΁ohx G:f`/ry6C׼j3/˰s|najE -å1C؊'bhQ@ѣ=*! ͂(<?Є5" ;3ex^;wk Vt{,=GzHt:f>{߶wy?}+px:e`OT1raR.˷wB8O7]`yB/a?wYI6e#Xv3eNAFqKؤ\;e:^SQcFі1 _Pemh]05ena?)*~Eq?FI"a0+S_Aɘ 'dئZVy_5RR@ƠWї7 C{AByr#}'K.Op.b1/F<թsヷf?V7]Oȴ"0%aKSvLWT@'=`:f`4LM s2EwuQZ#m^}l&\JnQbhqj-0 DǛ祇.7VCcVrDɅ<߈K߈|/1o]r(#nHqblpVx4!)~&ظg^ sndJl*Ax>Mpy"ܟo;P'OaHkӷ91326| ꏘ!U7W"0n:OOrCM5UXe# P3ʮ YlE< TbHPWT5=ٲ^MFBdB@ 6p1+:TB&I_Fqs - Y"hFue,&I56إGQOa:BS1>95ьy/__L|w#l9 )RUG0; RDF"vT~TAɮ}AYC쑻h/ ]8!\̨&gǒ wZ e6t)Xa@Fx)~I3,nNFD_7@)`mDMCCK#B3h:E-o UEXk -x 9#J: {* 2U H`xF^p:DDVфEKvFl hx&$|Ba!:2cRq١޳vFEG|NZu:npy3[%=Z|葠7h"yAs1e,ATQY,gfe \x.P X_KMX҅Ƥj\2J=W/^Ք'43]Ey4xb"Y.wGm=R;X5'}V'%qFCF(n]o~_Y?UKn*1kqu 5ڦ?%bkaX4Sh^X>*̩UaBf U:nj;)ٻQ -Hd73Uy\܇RkiX|ѱ6u,]kv9A;Sh\ VT92-BE#L]gX_ ,t9O:XelA^o#i0EzH9h4OF/GAC0Lrb-JvY\R+ZܰD &b`vj{!wM2?0 &G[A2GIH! )3ZOA@on ---*aRL̓X3#x1._¯}O>߾+\~?/_wqD[ɳ (3˘[9l^UӋ-6m?fҦc97%x|Zg<_J|#=ezFDԬ'0wDkyU;5k~&\WH!4}1@Ȝ;#?L !^PW )*+uQikfU)Umx pIZ)H.KkE^KROX -F'LF6camэJ#1$O!pOs  ;( GҫFL#ptk"rZ]M}-ACtY6=xO(܊yξQdzɠ泛$5sm ʃJg/A#P="R1b)`!¤ahrl% S;-Ra&8QYxкWN2cSTt] $CfQ >GVةq굵u4{YDIo*C'rݴ]|vtT-~ήgY!IkY`4cwPx##$o21"4!-IgTASp&!G% ;>>G&k7bE"9~n`Ѱ5ULZsW"1*WOV٨e|e;XȮz鸑r+;p+k XS{(63fTx\P} %

FQ8H)#q`mtJ>>;wO$n0(V*K>BtESY`FlsYxdc硊@xeٳ^dC],sT5Ռ1YF-yØ:,d 1DԛdBȮ}TFNN -; T)ύcNoTNh]P ~K)ĄfW{PۃJ$ҞT3|((zRe]|wn]r㌁W3ޢ'Uԍ vV7y>TEZH)d顲RPq0C`l@q8h&U#yVcҶ9u0i&#~f߂İOMml%'PDuAAlyND-F0V5y -a4x.q ^'0H{߈,ua@wHU);b3&{nd魧B[V>*'b8|.LWl;t) 38X -6nY-;s$|x.u!+ʋһwN3s{>Cn4cT]9 漢E(h -HVxgV\YHc-s-{Jު WfÂ_*[h;tP}v3°\\ˢ;cnͅcؤ1 Dj2VF1D0U Pȍ+)cE0qF9̃IN=ZA|e8V~J#R1$69^}g .;<&Y˷mx`:x9'Bߠm@ږ`Չ\[2]UTarM/'0a-thpkNk#jI΀LMf .j -VXd΃Ã|AtVB7=\jk??F?lRSm·Ғ.l'/1w{b4̖rVԨxf/]ȆV uX&U.9UOUy$'rs, \y><_lD6aٮ,? qw&51c -3\_ ˾ xEE``uJuv}A:P9/Ҕ6{w Pۮ98TQ5zcv6︼٤ӱ+*@>%|S2ȮH@QTB(}Jw~e}NN3}r̽ΑۜjVsBuXݜKR.,IOln -^~*}!}O&m8G;d1eN^7YJ©5̀fxC"yd繢nz|j g3KʵspɆH|T$ cj @:Tpz9r7>(2w1qzm-ʶYe;4 hIń#XT(1\H.V<1w=ueyEţ3r|!Vj[xhUGzAh(Mh434+ÂOX\CN )h_^L,f%^߄.y/BYԥKfV8.T*E/V#@cD=h ϗy}q\03 4oZ믞.S~Tc M/=NJ y)1)B+Xŏ9eקBd%4?b i4,Y) `ba^Nzå\(W +O~hntxs&1dl" -Hmtġ|0ř[`+ĥH]qA?A gھ]v3Ot!XU+ɵ&{^ ȴlgd yA# &.8]ڲWHj'iD$xy{ŐnDƩb"K!CRGI^!>ڌ:#y{ŷg9%9 kd'08+ KWaxէxՔnЂ1+Mt:ֱId +6mJ ⳱sZ^XnҎi's/C _<XTo.W}sW:XL+b*ؾݙq3|+TRBCJP1 ȓ 4R^f#}WŪj?+-]v8fփ13N~C4 Ma))q& -G)jf)c7J<1oihgoPuj|2ǔ2lYiMN+T<"_#T_|o}|ώd *V"baz/G& z*BSTD2R*RKiqeL6wp) ۭq;4M:BHxOS$" -endstream endobj 41 0 obj <>stream -Hl;&A}$;MU売L&@/]v4ӝ]<S϶c3cc=g`w~n[cyֲ̌Ͽt-㏪3rZO9>8Ya"yiY`Nceuq?sb+6g*? b8sۉ>LJci|?`*G{a)یHMf' |sr$]^o768CwU/{zvigί؊, +U"Q -\T<}U?1 *T&4]E u,T0[6l:}Т&am[YtjX2c -5A3+o(aj/i ϲ෶i0c*nlu4|8q}ѩfP4A9LL˱9w{j9@way<ЄJtb䖧F~k>F襋GfYe*u -Jm ṫ:ǡ1~TD5%xR6knNơ-*<"T׆:6izZԲX|1{Glu%\s z2 -* `~Y6ZNLiq\[dc.V%4byD)$! (| ~3mm/[qlU[M̐zTi_G*f95kXed]>'0E-z(!zkO?f9oMџ"t}v/Ŵ ד=5==OM= /LD=(zyh:1,=Mc6zq0qNKf͞naL:;يP#N"i:ܸk#KpRi4^gE(G[F^,iAL*|[D]Kyҹ{MpEQȫǒƸv,( 4lF5PiOUb׀$sy5i؟-/;O!ߋcԏa.ET^3">A yHm /wU9j`MCq>=+1Fi OIDRtlhƱA=?D^S"G)U5ݿgS*egbI-2AE<=W0@:^oW6J #Z išp&XU+L$@^03R5Ț֌nfNP 7+a5 HUX$+N*'I˒XhC _%V0{fĦI#k{21z05j.{Lqf m -o(,ױZ- >(]KL:,B( ݖy g@pDp-FqUuFY-hJ-[+V&x7Q;7C;bj.;G& Lrfo3v\)) #JI0%aBt8 -!X{ၕrJ \ Bʃ2?$}J|f {HcAgE{mDkUX0CВYxH't5CIPO.J-Vqۘ:G<$_7; v43u'; ;iDYj]w-ClWYMӅG(%3;fM_J7XĻntGL -jiKC}V[-;X0%om~-% - 2Uq`4TC.E]zǏrٺ^A-?02R -6NOR!1[W)QI..p %n̼72݃]!*."x̀尳tA\#0蓱Edt":-`,; mF)e6m]3Zfj? թ~7Fi%ҽ_9LElLth1IrI:,s _},] [{|R6VJ/%%DFdf $]JƋ&)`+̾EqQ_daf+؁\.q;P45[anh]rT{WT%,IUfCoН}*eIS J|`)J5Վ{z|R>Kw "Қ!tnJ"XD+ˁ;@3vW有`]t!]R#=TN~[P'~hβA2L8k-%? r:! .lTN9D4H-repS;iR]n `Z3P;ji@44CUhxjA̠c_cLLu`Q#8""bG1zFYi,ywɯ$J`P6X=(F`jrߡ -,@e8]WMɻ08dQR ƋcBB UUj/Qp 6_s/i|ۈhlj94fs+0A.$@+H; D5X(.-fDO v8bsdx{G;|wkxZ΄{_4xxcaRL{vT*B9QK8OT@Pѡ)/DӞ_N ptu %.%8|v>{|F?"*BR4%%rqDY^ŷw+8iðL -W;G>}`HE' `<*X*A؞A6^}pR5^mx[w\Lb5 X"X!q@#cAePq'(AЌxrSfh1Ҷ2g*U}J9+YcDR),)6#j)(H9R5P*i`Qn?l=v0fj1٥BxXSޔ)p5--ZB̀at+$(;G,ȳr"٬sakEsI#vv_J&%:6 *03#S'=iZ :U!)s) SQS 6V3(u; @>5dJes&BwD,HZՠH]o4S4Bd*o ֶYMH4hil{4拒b -K}ʚQbShY,'`H71VGh] ĖG"!bΕ+bw)lĂ+ ru=wڮyg3h#V9`h}OWռ!y^1=@L lo?{YJJ-!I|ի,wf5PZgQd >@o V%ܽ|a:r* `1Ng dwLN -Bᑃ8] em2/3mlk8r w½OT7F {0jR,8v5//}4sØ 1VSCF$;(4<@A^6p 91EO:T񰀩¢/Ddkj87 &AAܹ\L}D4UX1s]y7n6HEJMW'q]s:.۟Gs<֌CcҴePĤGNU{=lu(|M19#@{A)~MiD9=מx,<2ηApX2_/1^>id6scwOnz= g\3@ W=gl)1btv,E)1NSiDd/y"|0U3:a 嚊+";knIW_Ogwű&g -sAgX-9=/*珢PBMw!N^®PyA:MaVɠ^ -W=cѺi> zw֮ᠸ_ Lz sطkg؁bhp g1:&cEG<퉉qjZPPe]NoOW—q"v0S+|$nm (G5vȸqOeAN{%)4|(39x _!IhގZټڥ=4]4DKC(t6< 6  @޺Փu8爏6>š^dC%Ig#W&[En1|yaO̕9m1LhW;E_a3>>2 -]>-Ë=\s];1<`=5eD~:|*EOm좃{ z.Fۗ_@`% XEr* S9MQ[{SƕS b5b F;XEbo4:ԁhai ^i4ce.~F:CgV\5C5qy& -'A.]*F1GG4RTQzjچz0*DUT]+J;G!ۦ?yeM-c|~@OY79rI  H=N!s63SW] 4%>owA젨/M#VWtO<(T~*YFn$@ E籱9q8WGilS1&M1i3sl1BcQG{*aڍKRdd85 %"v'`Ǭ:kޞީ[1?#'C17\AH*,qnf/J{3"t`RK -cА $<,q10t?5\+8. !hp8a ?4!hiQH(8L R41gr`,ϓ=طτsdjĪkPe&a@jADlDii~[{(層|yrC,ry -u8o݁N֟ua@׭AG=U3\jϩ@e^Dr{z&x5*<Rdǰ,\SqvjģDAYo ᳇q"ZFh'P' [bS̀%e)- -lEMrf{#~FDyhcM{I75L( -"c1+FKׁ2TgGРCGҖW -qgNF@ A;bB[N*NFm W׌r%#"ZEM]v؁q_\^A,hP_Im0=osB-@ZX|6u;qw 5zOU!e;G= JK!:KzlaR~f_ Ek_*``yѐE+C% i2sf2/XSIx5,F9W2CDs1 ݠ+V7a~X+\yDଗnh"0gwi49FıkED<>L.B V$b&=zI ->*:(թnE5e7RMf?1簪_:X8,C.-nI~N)"3i ϙS/B}qM=\™3dg -aβ<댸_[s6|,a )Tl~Yg#iGO"Jow)0x¦ԮυnzW (LS};gWq㜔M(-?p!aD] T̓z."#[.am>;w6("10ݼ8A#*(!#qZdZ WX%KԅvoEXI@2.b^D1/<M^5Ķg+dPr`rEp!-N*b] -g 6IM[<3G-jhjj{ 8 dgrcSNTWJq(n 蠅A!FI kabqGъ -XAba i&X(<>Dh_[T~eEJ,܋Dd: 4`(_aiV4BI iveʘƃӇ+<G:*h gp2*B(eM$n=xb'`p!yeQ!t [ 4ȆEEO`oԖz8R qfȳ㈘!8U"PvCbZḬP)܊1UǬ[q80 RW|! V x*auD-A9}PpjJ:s@p0A J c_W&qgWvXy9(MQֱ̼{/2{K 4^z €g$#WBik x؉Tɜ"4,-)8Nd :[͑:j;[!QW9Ed0tF?[) 煠Q߹I=&'BĘT.F@ΙfZ*%ݶ26?dji- azoF^k6~һese,D4G]aG[@0h=U#0+ :U~̹Gql.@ɹ]?Qw0މr)?p+a޿/Ν^gr~rNM cڟ>aHdꨓ*ToG|>Hמ0'RG$zRl.:!62&Ow9QnXU4LgVÎW~!J%0é)wbt@ ]B6թʝY/N nD(9㟀!˒0T+ -^`::Yǹ 'SSD?||>R +<[K"ډX^b~"ko BTʚjNpLMNe89,'"LC_9 ;ч2*N~UW/`"o%8v^++Ɵ -旅9(nF rayF8զG aVƬ 9[2шX'Mɘjk!]3őa .Ux(8+ou4?D`z> y((!;YX$aӗeL|AHE9LVk %<}w+軣?;&xpJ ^ S[6~T0َއ͙:-fKSԄh^v=vT+2 mJ PQEE4S R=ϳ3eZ۟ki8`4۸8+tr5_!?8NSi 5fJTr8%Vȴ= FA i/DJ*G(}|j容Y;CML1LG9.Ke\ qIGb {4-[Gg7}e<,dVM>{b"CvD/jOL^nP7S`EBp0+It0֑&җ#5 -.NZ՘6q>ɧ1#!˃SKvܖY7E3jbi5KK:`^@F< -3 FTPjDAA+")*Ψq noh't]qY\)pa}=_T;4/w獵 yb"PDR\.2Lqh6Z P8ZΪgLC^iHw!ri#5ra\ַ3GQG f髋eTiz4Ht*HԘ$:N YUzBV;bS*R6{D,Ķng=mȱ@]q=S>NԁÆ*PD |RYy.ߔ%е&EB´baϮܪW†YEf -H!U;`fRc(>n i\KҩJv*,(<0︬iO-4Ƈ\ -6մphLA@!n( > (c::{Q+ie4Ү9=/>^4kKT>x]j48Zr9F@yz - |ҤVȩ&CwHnܘK{ȫC4%K] J?z7>mr/UyllO޽/_}oߘ S~?N6Oۋ֯?/*=k[@W(o|~0j?k $FԿAv4Ę1Ol :SU7?rҮIbW' n\[I DS'Dꇖwm|%#eer@v!khU Amyz#Y1HoKAǴӣ`oC))ɦܑRJS5t9cx<*Rƫ/VUХ45 YXQx((>4Z3w#@KuV1Wm7v珵N-k{a\Ȯ -(.[nJVEyU p+<>VeZQb -/U<[k8 U´YkW'Ew;j)rTDDJލZ-TZ -c\ǧ#G;ԏ)QR .SQ[ؗtroKgy'_m3S5hd#A!i797㫡M0͛M?S~=o(gPZ>(YV:c 2 ?#b9@ AGS?"V 9lrł#l>{W1":~̅Ν|@4)] P- -{}^O ߽qʷ1]S']U?dL),ޞLxEh(*mtR1b*e=#S89.2;aEMi'Kܝ4.r Y04E-Geu2 - -1CQ ,bgJ/9s-t2 hDdq3t%qyP `٧ ӆm:ӥ6f-2lve&ͨ^+Ir'.^ m`56x,%(Id=̧9(2/8:u"D3 -.F͂SB4&!>เTax𶏣(٬ҫzU{+G*ozDb7+vxp4o,uζMGYѝ 5ʕY(&(5y9)^(w77 `N@Wr8Pã˥Ի^(u$)[=(߃D -=T7׋F BtVuzjc7u6iP Y~ tTĎT-b@-K$ZQ3p5nnI8!` -7.8xa"w'Q2[vsK}%miz]g -4ǫو-5faQ\Gٻt4s cL~F$/-OU.-XQi|EA+1O),Х›פ"BhT`0k.ӂDHFT۱c rA h"(}E2o89pƾRUAkb4k\0>HGUIgv%PTN@7w[6NNB#8&=&wo ~Bstc{L<4DiB*> :uݲLy_hN( -|&`/wF"}(旯 GE#>`n+$+oK!2W]C$~iz"k*-,߉(l#, -s;y;55%#enB.W KOTIߩg0]kr4S DrpItKlqN{IA̩g.tL1#w -(IKb5ty,pʩA;ΨbtrDť;p{ͅ-Yo;cR;(BÈB#_DLp=ݯzax/c*yO3kAH^ø;~oѭ۵J.(I]*+:# jp,c -f-c{{KbtՖaN'bO^PG `D麂HҒzv`3}A5«:^1rU#bVƴqgmy#o_ޣ3 ghmER Ga|AQG6 -}hŕF -/|V,.Y{nxD$m amHω:X;&Wzpk|C|USLJ!E{I9Ud=pu4%b0 jepWzXUw|67/RBMaXTyXcc͎ٱ3}:?Rj5>^jԏAȰF 9~7~E F]X+"@Hr$$2+\ -%擻R2`ŀ_4^(x 0lc·HtM?gdPI1@oQC*݌f6Ʌxa?+_ޮK#K"-U׼]VhYn6ކ8qW<}ŨDQk г53S>~t3(Qu%F:k|q =3iq rfRs Ȋ?ղ%J ŝsرR}? ȻCom;٩e* NWo>(葮>PT *ܵK䁭|Ҥ43 t !]Zp28f |Xc4aUF/aTAmz=4dR%Y -uw]T=9$U`;J6eaDh[$l%<  eD7=V_7ֽx\ -' -ӱ0#R G!ݢE7q3DWE,e+W+A3wl?r)x>j=rm }GQwj%  wG'D3ȚzBcщU2)ieYz:D״ -Hc"DIO(47[)߄w}}F\UCh;T㖈 hUȕ'~`HJ}me(N5&w)'`w5e VIa,ȇ>ą8^8sC+nziB;-`MJ"&(e'bҕa|{!VygdOHųtH-v 1e}^R$؊RŴʙ&A+ sE"[ZHeEDUٲ^|Kq_/"Ҫ4Q_9IaŜ@D),0#%?~s[޹ˀt AsGxuUaUj pNȑ@H0,@$,)oZԭ/P"'giv5A 0 p7:~nR>CeJ`uE$GF9 XTw@IA@˙G 8"nAs#@6'-`[ pAzHU_wR@HБ qYj/ܽ S~l"fRV'9L˝ Gw&ϣX -c288JJD򝈵c4e}5ETm[-'!lUBl\oDIOy<% sD_} - ܥdL4`8u~YxAN_pCY!Gj*>Y4 UjC`,;5:NA/O ՠ믻Z - -#- H.:8듿vGޮ홹# -Y"솩bT*RF #FP<|,AQ^+_V2sR"y5Y|9[L-OF<'%!};VF$ӊ٘ -$h:,K\m!!0x.t8ׅW0uhw`YQ r (A!je|/@$lStX,"kP3fH ,h>:[v`&-X$>5 ]K߇- "HU| E蘢9[1bAwD1Ə(k2Ƴ ]P]#f (YT_klb.̝Nz-gXX:yHxD?g9ha?K<>ZG>J%Zϱtuu6[6. uiyYntm}]0`n$l - -v@cD] \!wx*_[n*'wcURv yx<O| :بqO䂱::65^`ϢZxp EyD9 7I M;JfL[(j_bȕhp+͓ ^QR o@m&MŇ  -v|.fX.4FˑX.ˢ"xEG怜cY16&,5Բż LB9p%SFD"84!&bc[aZ9,|bx40Up`韷oǏ`8åY:=,9A -1CBݠ>k bHxL+*Cg3K!0cU?K˟;6b'"BfoODF%bsQ2(x?A¤_u6c<J<5}<rKBūHRy$ 蒀=,|]'[,ODž! $h6OcVFG77a" 5%TiBs[B׼/4@ɯ>+Ƽ(8ب61[Ac2[A-^]>+t5}2g#VnGaU&h 6N'p|H:z)J{KU -ڔ Fy7ec C JՀ흁) תּ8cyѶĊ3X2П79jD&(!V*@ @c:c -bjrfCn/1mz0*G^ UFC1J Z`9dAZħ# IB䘭8 n^qd TQj7A~?AH!.Sftde"NfwQ#8-CB&uZ4rE2]Q;\4fUx5K6Wծt JJ (^Vy4q{) -ҖKgr۬B1ta_ /\,`5P{koCJXK G|)ǔLW5:;tJkY=}e!;P/V0)lGA4H~Kn^D_f~z45eЮ4 @% -5,%Tjt`p܇2WE]("Q 7IO#I *]uif!@+I8(^R<ʡ\0WW8BC3M63gOP{& PQ~,w5x'0\^N'@y6=HdBzHx̡DOOge%y --BwZ JP0$Y?sAW֯1:M{VU[MBR$0UT5G]qU&Eֻ\:XI>j=%r)jnltoj]1` PM@6Ey5>=8x /hx[XaLnhBYoA1*j@-:atC& *K: v!Cw:qǷ1MD?P~縁Sf/Jgȇqh4P x$r{ -/ -#i?Ӳ$K2*)dx(\Ck("]%n\uj -:=s'9p*"0Q.i3 vNy-A8p}V& -JܮbtS<͞RP-6,El%R}U0 @} ~o- -u (k -GH*WH2dPё \py(U)9A$^2jׅ0x,L'+Z!Ď@-CBdP_' 7 ,P.SɕA&K*^Jo'%oNm0T J]p˲;#xo;PTŨpTQ:`"Q9#P 4~wSΈq)4vae f 2掏lcr܀@#ѮA -9[ʪUqkXes0x[Gq3"Qy|Zj(V!T6mUnZ:AZ|p)iCy i6w1j뤰E@gn^JG1,|0mX-6ZEpioPVDnb 3t `p,@? G]W5>H_nAc<u#S{ -csɇ[C R -'EO$Ö0C h߬K$GFJ/m ,X d!ے5^sNDս#%M_uUtfdd0 PhebF97S @@|8]-Eѓ@`.3hr(t-&k!P]/Js{r@+hn3& 1K+"(HfʜzԲ9Z#1FE`*ia_#*\JlxnLEWG.wm5:CLn?#CC$گ[[9ꁃORUjK.Ǽ$ȋ]r}]{cYmCB^PAP]Fp^s9<=V8i2 z,`+|n J%<[fD" -F\1Jȟ!𣴁8V"("~X; c#h:ˈkʺ9jfHA黰ER[l9Zn:6 KoʅkK< @{J궑 "a A9Ԩſ8g ar.(p2Q'>xvuji{]:TdX|KXRZle !%_ȸ()U=0qPt.3;d;J/[| -aŭwB':}BxJ)"E^P?dri8N gX]kN !9'ƸtGt q=VQ^f~jd"wuba -A<-$NA#nXQ)!jå 54PaY'[y*i]D"hxm 80aΙi57&U'FiţeНW91(cH Ԉ@-#9=Ť`âQE;1]XJϰQ#6r'Nl,3c048;Bo#բHN_u^(Ca yn6"\U=(n ǿĠ"(A[ U!DP~TY#lY7 P1}/kK*];3RHDV r9yϩTl&BU<Fd͔ 恥a -D[0c" -adt܂2;Yc}o_XÈ3#9B/oGkkSDNEм`~188[P%ɯ1bN9/E=_B"JL.k(EQIll:c #RtUN{$>3;y9@’M Z:D E -2Qv R]7&: G%U҃yj=v䆗^~ݷ>j>p}/>믾wj?_}KVV z!]<s]Iؙ_EDd E/i 9>!t {h 1UgoSI:8 Գgg3ԇ]  SVU1JDT"UNs]cPD  x߉ +&2@`J Y .a`amFv2@@o$m2-hNrĹJ'N{DGDTzjpmhz"P+ܟoH*N6f:In(.ȁ4; h,L#4O;G! y}Dផ/v{&!y Sz#!Bӫ"Ǝlc҃d.Icqrnۆo|dWvxMY8$f9Cb^vͯ#rAUosFcF/th(#k}07}c`[9^&B {|qRV.}Ήx~bCn*eaXBѺtjR![Yc9E4ߵ^C@:D04PFD^ݷ*{ FdU5 f# Ո5DLh{NÙ&uk'X(͚#chbwϞZ3򈠃^QU/P-A W?l{[0/G;Fy -RoFaƾ \Э_wW:mT*BHWO5AarG:sJCOA)<xCޕ<У`gf HN`gB w!m;3#撶M*V3H2+2@Նɢ{t" -q@d@*5;NHG49 1ԃO)0`RҚut|DWdg'` <(~[HCX)+` -}cHV\NCRǔi}+R it5)A12x]M[쿐hE< Tr KŕYT(8dlw@d8hDa)}lr -z@M3o?/>U[Wí Zߢ>)`^Y„UѳdH0cqv@nۧE;G-ckìOy_4mh;1wQ6, 48)ȎÅYC$wX͊XCG(_ ưSb&/udn*LL[$#aH6g vlDF `WZB08 Y@H82eQʨ/^MGfuC[>k)A)6~`[P}RH\#JhQks>Ml/rFwQ,[9n֑H8wxRTQyu`מ Ye5e @m!钺ԟCi4_25\nOQ}33`tBXSDxwu` ]G '!J7z\DAf^Y?etw`ߙg5EJ&ЏPTH3xxX^en|FDi2yJC]5Ze:CVqo:;eCep2`K`b& ]ZĆ6/?-BU,)w,>w *}nJMY6"}"0>7UU.:g05EQxqAe6OE8V[H3~IֿdZxk ,A$ U-xxt]H?4v 1IlA]1ٟמ%!Zǃq(d# Aϣas P^ DzRD_x(#TaVCz?筑y@͉u ඗#v!.j  -m -^D9ϲЉi<./5wIϐf -!mwS -raAVl#D-A IϰweQ@/gN<`3 L⡹yKKʃy'$Y늊<8RK\$F)-g2NK-E措c?2E>QdsnAy.Vih|Xl2M~α$5N,o\q'uC Hr@U&z}GaJP;,}'yhp=Q$_EQ+ITc-jjgl2ۈ(lgk35ZE`Xhz(]&Ɏ0OZ~_:WĘ*v`ƕ驫M)]|UErU"((`zr#w7|m lTs%t$;' [# oHB{!&&88ڇ?֮V x 1f6@:L PU$АH |#&! /;Q^k}ld#_TM,C5x::VgEI=}b٘4s]3~"3<̄! "0[(zuoD\o?KFc`ju(ەT'סE:&]lm{{)iAFx𔨜jom@FA6*"qlXOCA0+q)78pf>yj&*=Cb}QHɗ0%R)y -oТM٪+QzS*򦆞2\+V0Q36b8b#ip+t! FI B`G6: -J=k>/w#_Bl -wAM(TVXvkjoN=?Mn&_ G!Akwio5t|&Jc#kՠr^~,+í9{>a%zZUW]gL\k8dzۨ7a4đ]2z{uȕyd:*7)9%~ *g#߉ $7I ߪ< Wc$alw|U˭# l]9L%Ne\"lI'4,>jX[{6>-^Jdjg#Ew2lZZ!` Cq߈hqeJ>hm9 Zpz]!I2@*kyͤY9و7J hDTjċ> S&%Ἂ4O 0 -endstream endobj 42 0 obj <>stream -H]Gs WUw   -W8Ȃؑ ywwnH]{LWuשSgj9l5٢:Z/h`Fu8Zi*Xӧ_xE+c>OmEi1m t_J+`/V. &촾яͫ7޾|={~2$8G{ߏg|իK?:~%oC~~~ܲ0Ŏk͏g_'O޽~yw{/x -4/߼}~x߿D~gןpΟZCmVT"pY xһ``e`B*D0-%@F"^@Ɔ^JX堵Ha:}p|0؉yd"tԬ ΂ 5t&fe$N9 lQxE[8mҰ \hei\ы}RCҺx!ކ@ڠdagJsHq,GS͡*M>vI/sz_# -4<"V_(xTKyCX࢙K#˕e0f uԞ1S*lM6Rn64hEO0 $]$6t۸#0LZ 6B&H/SaRy=` XYPF [35LQn4Ԑb!42&}+z*QqA|Fh[^ꏽ`b*^"{[ّ;4˨^U %'O#'M5~(}ӡ5C>%ËOnDss샅88V`1,D]t1 =G.98A'[S'ͱaO!XaO}l%sAJ!{"uZPb;إ3nV@PuP"bFGxT5 6xyH6r'DB=M푩n DnX:P! cdtCYécYԎK^䈺J=Q$q 0'I|[Dx)RLv -Y.U S\}*k)oǩ1mV-WQOLę zo2Bt6Bݠh"B1:d$L-#8r5ils Q&=ߑJ0cy r*K`LHFdH䴺 8[h99CZz3W|z[8MLJVU^TѺ`sWjHF2 m$e& Ud .+ɑ# -ԋ}9(ԁȜ.saW~?PA+r]_m~ lahp+ߑP]V3[BwD'p0cᥦMMݞ8;d:ӟ{Ls!%8Z&`:QFDJZ' u@h[yl0- E\'BQK2Տe:!?_bD!w#>7ۀk(lPyN.{qjckX4״xt=HIK) yPt6akyR*Y5L+pX KVr Dp}d - kwxsL҄1oN#aϣj'&a色 xbP8jO2[::,ԪF,QowC;LD/NSeGT#Ʋ9c:!ftj4c,C TPac!9{_@Յ f͗v"nM m`ǽS\c@#-pJ] smar\e [BpR92nwePk#`1$I>TJe't)r W!HUÅ+ s%0KH+H`0Fh7_hzwr'HAWKC9aAB @ "v`5`ުl&EEǙčmL#2t#3ZAl$t(=pj4'`hzK8NmQA5*"'N|nCӞH~AMF7ʘ?)"Eo.v²u9x -uFEE;%ܭ Xu3EkLQzo5nU[iq6З9?'5W В!>ZM%cB}sN=QBdxDQEŌx˺ r@:JAn9Lyڎ"3~*seY5&Fh}21Qb XGu~D@LoBA픎<_+|3p~ -cP}}k1 5@<-iD1Hn Pڏ.&[G21m5ebk}[ Dr0mN -J2G -Ѹ!p=n1b:BL5$FPb"] R 0;.ړs>Rf1hPW´LÖ^^\DT'yb?;6Jka9 ^K.\F. KMLUyC_!Zf!@S)vz-=ٷ\.`K "-!]&Hz-yЖCB 猢X#&b@N@+1|'BRKD #P: ȍfb"zm8̬ EC[߭u^jNUAz])΂~;1G!.yHA:H͘C I:u}X_k+hշm4-[3_& Q'#f,$sR{/1(+#nK"8Y8[Ӛm7w7>vOi;Gn+4 6vJ~~0+f8"Б ]GԙEf^(m>aQd]x 4\(xs$0?>`|A*ьmՊ{ԏ<` dXv7*)`.e!Xf˭˭YOߐ<ͭ -LiS}=5 -n].qHw'?ɼyхE`f S笱as mY~&CRf/cF߇!daQ0 Rr~#.[P\ׅ5Yl=Pv!c3gdxA,ڭVǡ6oXW>Tfer#vE(q+Lս71Iw?j{ "t D&t{룉Q7.|Ԃ϶!7ӭwvDԞ2p0Q0 ׷K̜f%H>D^PC/^a'Kl*y.a!ðΠT;g4^_oȫnSb7`C;!d 8LJK|(nHh1tD~AJFaB8?LFD'kD -01UtB5ͤ㴿F0Rsv6%,PyŦBt5֙Xx,5g81b1w4 3䆍LB;gyW p`I#8Ԓ,)(]GxUjB7V*iR㯪j|0-{Ntc^v!ƍh;E0#8QUf~oGEJU -APAZ="'t MA\{"YrB4ֽEEQOgw|egcMJ0`nRZz|-Z]X @=-|-=}X [Pxk% - *羁Q@0I2w]0l5S5 숳UgbY oJF -](?\G㈢2A)@&]MPL{Y@rnefEFenTxF7Lՠ4?E!G_ԽK扅;Ujް:X dXQǂ+sAkwj>@Y+7}HA||ѰP0AEI cxL$R9[ hdYx`bjf#X`7UW6mi BȊBJ> AU벘!4G-_'~d兡~jM%,0NL'ם*7W{W\ԎW2@^z5yX>Jԛ7$S S#+\Ok}-恠gA9+ ,ՏruPm;[!A4'GDtb!-\%~I%{]QmD\EAqO y;wǐ -:m'"" AA F0Xh￰uEb BIs44&21>TDX[K1o":\41k׃8cgco:GUSi>Da u%hBX`*T̾NIKɉ(I?~)/Qǀǖ4n]>C:p Q(Oz+n@D'm0dYRż~E#ӄ$$RKs/4c!%/±pg7H3Flؤ6|G0]N(Xz9Յa-(E4ԅ  듎h?8I<,Ū»0`NLX̀CѳAļ:AD\/[[=$[[,"p^#tpdÄrX!{t ,T+phTDm\85-; KVj{};~v<P+I9,tЧj@q %8B$Z ?((jA -HwB<8`z7U1MGS.L@1k =?yG,@7IYC吞.Sv*Γ{++t2+^fr[t -ch$=Zߝ^ (/;wE>;g!G㉍5tĴs_ۏz F8Gjʚ:r=w` 9jLy:5LѾbcW3%n쐎 b=W7LsoPm,YۍAHB8RʹK'|#<#2JNs.Kh+U熞j`B㙧' NHp;g±/1͗`y!ҫۍmI3')%lC`uƭe }rɴ|AScC,4S6~B5mT|U۴cz ӡͺB+ϟm#8vADx' _uED ~Q54QGĊ bx.;WG#gg 'r b`RMmy" -fJjReROLȬOƃEָj87 D\jCya!&i^(+'$A/ҕ~HasZ6p]fu (1Paa‚ru|b9"/m;lDhްw+E+b=`hl |اbJ'ew|o{I)cZ.(!vF00F}Z+,!2wC/;{?뱏MHt!ā7J2`~]\h=7TﴑplT;OBǽ 슱l/' J_0L4Ys ,7 `enl(xO8%,/h.fnӻL_3+ΈLrPFv{g?7,+ji>DSxL35A6t ~!Җ|StU-0`0jn2L84]++I`$Gؑǡ[Z0<65g"[ӕAtXmHC:[)胦~mD򗝪v r{$9 acg[y[\ F3%0թ;_u~"`<s3Է t)Z}/Č\ c/Mz'lV^Fa^;bfi.ss溘so^Rn` -ֈ tCm$9m\2BJlљ8{I5Aѿ_EʬH-9 -R%*?dJIWU-j*D*q'._Qo -`ɑ -\w2v -8s>]B} ]@  -g\72^X 1w5KJXO1}Ku9Xq(#oVzχ1H9:3j}ZqPٳS^neCdv-=cEm>!|Ji&L^7vXnLt_E=AxY>숳4:R2.2vVEF5GRӧP)VA3+'$RWlUF)K wC(u E&x#aX]!W) 1hy6+.TKWz NpsoPBѠ]'qN|YjZE/*TLQ -%}a?^߲;W\R&'=9nEѸTV5{Y -{O?}/?{?LZ\}_|o>'K;+fJ<Ƅ)oOOo$= >L t"d0KhB>uE/o/9 -fa MAY. -T;N[%yg#y3?,kxI.|=Զ7/ a&t%I1d4)[N8",EM;| bUlMN}G<K5s`bԮ~yh,Q#}\_/ fPQ}]`SS?S}m -iW,VQ@ =Cp2[o^{^piYہ#F!,U{wB& -A4 -}8TZv(AK=1LYdϺH + ]: zxƣ@UMq)JYRG {s,B*VD,5g9V? B.yV7ܟGE> YY1e[Md{FH.G!`x"Dg%\@ӵ3h/[<<}u3Z'!z{a%'6h 0\w0BZZ:ǀ-49͓8 -^Gazҫcs Д4Ea³ ڔϻ,\GYRz -ڳl 񼯦Gj߳o3m|,!a6qL^.G/U^{|@D+I䏭㈈'y> S>&rQ\:94b-ZWn1\>,_^ P 9P8D`C qt֐V-+`gͯ)Q\N#d8p1p*:XYGL$Bz [{o HFFl`q6zhZ|2 -˲lEi޸h2h1GXvD -bxVv -+253jf}X˸߿ f̐Ϲϕ[%~"Xyw2|Ց[**ϩS/Z h[Vek镊(LVW4\ -<ðI_NGus'܉Ӈ]}=[Ł&É.]< }G*|ƾg?\5}c(3QYZIjya|(>` 9V)5wY߭0ǵ,tI/N9]kˠvW@ϩ<[I I7ev ,B7F/}+`<Pw -08E)_@us(s40Ǹd,8pN?0jrUBIKmL؋Xx nR9);Pq -Q} lRNL8VqHc>o礼#Ltl8l8VtPyЋu u<=BQbLI< *M"/Q>dnX}b)dg|%'j.!-{b@8ƕ<{ j!/5MKv -Ì:߇^R,H~_A%U)XhkfAQ4?Ϭ1UmgYSMj^>d)A[Deu0W -\YωvCMgɞ\l@UNf]=D`?3oڐj)E22p'UsH0.=a=k׸1L>:'7%ܻވ ܆MeO|vX`cMɍȦ[Ntn7=H&40Q@|W8?`H5\ 5f~ry :ow&xCG0ٟʃgt%.BRKP?c:?5+LFiUh$*دScdrEZZ]^H3por%h82~hVbk85T skb.%ΐ#nz+I!]Qw?5I9A۟3 -,.N9'؄>γ "ODm;If\5S]#ԡpF3vb4)rl'm奙p:RD؀zɃ`3PĽ`,Ûh4j ?]"fuRqWfk@c Z%=do^(XK 4a*#%7Fp#)ddDZz?wc ^ d?thaR2iT RNdi]a4 1D3LB4 G4oD^nIf{a[uADe/؏e6B=Z>vmoAh|-bcJOk 'QΑЇV$b |]5j"Fx[;KE ܫ $nU\hn9bO3aK4VrڸWݶHp -b>[Kar߯[s7">Q:{[?G@Yxta7jOŠ :_hRy&}De.R$0U2!1ŶA+8E^2*-a'l -HR,qۉ(ՀGJehGwj2c@RaEd,ynW ME>\kz9*EZn$*=P?K7ƂK3Oo㜯-ݧ=Y*]D,pc ϞG]wˍ+$l 9RV2bG& !IW&-s]VN=޳U|YaWv-=Mڧ\d3"j6nkrPc%x^ 8Mj8r=Hq]+4L$5 @_?[=qWaR'Ç%m^ @ -EV),C[|odN\(>#}K*ė\Wg --* G{] f#rf&0)ʓop -UEW/0xEvJ]`mNa uO)Thrbϟ&*ڋyqS e$KR%{ LDW -lzǍWMDži{h}Y๤z胧*`SCVŦ $5`OmPV Cg 8aJ|-:Z  `x#|Dže yJCK'J p죕þBZ@},iؼ6* 4?p7gf]! =#YL26JA;Odcވp6I:p58VD :OH -Ws8?PaLPNLE|i| z^)#QЊҞ:^WT SZM 0ÿhK:4u@̯*k\tc{ro#.pV?Lv,"T$'aĊnӣTwVG9y<>Ym<*bxFu 6U~,eBї _cBc]#gc4M-N6I$)Z0U_xݗԂtO7q^rVSAG(%B/!^`(NЫd2 Cׄ&t0Bt6+99!WQ#7@pP*xk6˿[T&JtÁ䋀2kGFC0x_RE]OL ϣ&vZZKp_wtQ0#sJb8N^sU*DGtXa%WK1;V-dوNTǿ΅T9SM#phc2Lxh t麮&ҡBcG,D8nE:R"vZU@:qXBPxf8˭U -l|ЫJ m!B3Z${CJHӽq`X6T+Y=gK,2!vWH9,bl{)Kѷ°&ˉZ9~YIGrx~ڀ.&I_901QN!k v!")?aؚz`->=VKogf>o/!."K61;OHn6-WA,ds٤^jRlg-IypjֵbOZXH\ԁԭbc@3ɤƦ8b#NQ!$]8A;xwbk,`Ёe5xjZK4SXΏv.d= v"Wre%|a6E1? -w/_?7߼}?~rxC~/8Eš~Q^Ʈ+V6vxXY`NvT}h nvɢ(H` ~"њ,x vo3{s3FԀ`QS[Dֽo p̑H;"# 0 5rLDGG=̥qN5P!AhEOL,8Hm874n!*>,T6t ͟afzپq'oϻ-ԬʮEm C$R{П;J5V:.S,EBJHqJ%HJfZ7 -˝8Y+xjك ペ8s-Ilp R -_"`;&TYa a{A .:s8+0-7z&r-bH 6 t8h%z^!#XĕPAQ0|RC -r!:,F+\fW1*K 9 ~mfkZ¾W6$ C}[ZRU{B 1略Q)5Fe} K@Fܰ<ID1pvkf |{B6F9 -1nY^& -6ڙZ - \g"\$;@SW9j`,1;TfO"edq;6ZK/q`Zg#ۋ>Z= VD{4pmi5[#&4ڣm_"R.J^;Beqo03ZmsUa`fS2i7mT!P$>Y16#i:=~ըQ?e>䖠zP$@#2?^Dd:O2 JZѴ}=~qBvOB) fp7tuzD`mEMX@$/?+3R< -R>X^a^A9̻ @fw$Kw8.1-\RJ|0z\n&g"Φ N'A4rsaP&r*j~ȧ4i\((L$.O *l?s&N(իƵۣe=!0GYv#XRO4@l:tk lf+ >Bdܖe-DA`dQP x?V"Jh10h1xl=Z|wmVb5dX%EWyf 8>ʉ@^A3j|"" zhKD;aY)YDP6@d)$m']VTIe3n3Mb /Y%cy*N1HO+qQuqKTMM$[,HTJggxE -Z4/gaP4}YP@ˊ%S%}fJʮ`AYm$V_ڙyŸaa|62O+PAb*<>&Tf~VuQ . 5kN n^){/L/PGR'e<^e2:F"൉~x|63{ gtp$Li^8ə>obs,Qe%|BKHɤ鹿Ui -}blu53D@7"4spM"P6!ٚϥVxl *,:'<\9qWuп/O7^a]!ץ\jvD#`-8 ޽s_$G]Ǣ[y+o=5sḢpjRQ]I"yajz i*)!q)"}Jx Z~N6¤pC] w|yAuuDy|D{٫r` NcL^v/SFP뀑Ô5S_`_A7=+7WuDY?KW=d;ڶ%ݒ u*FTސrWNhjBcxm)`MɃX<%چ4/`QЛXs4:n"o}Uh֙ˬ}8xoQf݅.DwD >Td!y+`|A=!78# #D ~#n)65SB}"?LHaST"ìbDhSOΏ#qp״Aضd9>/vzYf!<0%`Djkݺn_5a -ӊ-׀P/i"Yf":TS;[83e̅[L{qzMTA!bkH{Gxn066tCQ~n39 v t2W_K3GGLOC.)˅80Tyz~R}5`:#]})0@"賝}xv,5=X86SB9SgPo?'4 -dl[4:VKʦI8pMg|ZhK CjMíZ9UN8Qp*xl߈<@_7&^oX #+۵0qqӽ8ADlfIOq=u?q"z=5 @(pm$UTyP*j|Rj/FU@}AА!)ojWHo9=oqTmӤ>cpΗeꁔ(*Ѱ#zf8IvR/mVJ LxYX\7yԌR4"`\&C\竽hE( pm`x  Ya*͸OU -EdSُyb30O 8=[G#(1 ^c._^@x -C!YʇH<2Fgǜ/#\Bs'?C0h_YtcFI;+Vgm=XKGw*rxmkp 0^f"c aSʅz?ώbG`G 2PZ)вUDg|rPl"w(4juqzj]_AtKz--D981"AnfOEQ;L -,l[U~( (w-젧(->"i> Kb ,˫""u) ?}MVT@efb*WkOUZV&bڥ/sW' JQ8s ch:]{'T'RP ;ٹkP^d -%R]߿ma,Й@!*YKĜm(:y 9r}'(6[^j(ro&R~ODa[&۱>ZN85.u02S]-rP[F5Jtڢ!Μܸ03І5ʻZOG t>Lbp!QAMӪ^ -Gi0~]} -L^3¢XV^)SʊFM -1/T+.k'eDH6A㿟>I`3c"u#G I|ޡI/}f v=_X&~ -39/D,j t"SVn v@_dD5ՎMEߥ,-WX7&,P=o9!ƒ@j <w!Zs{RX፜L7Ag1t п:® - -}{1 Sq!`[ K¡ѯNE,s!͎ Ҿfᑷ > #uLnS3X4b~R Cl+4NE0_oh]6Vn2kSrv}\IV*T8IU`ֶ_%V8Qe+B\n:.Xr 4NDQOy -[F`ԅ3m8w؁QL"&CL~2/[L90˅zN6EW' -H[=@D$`a|`3`*QpIJ |˵A f;;72rîL(.Fo~Q4]E ]'Ic?a"--ӀPaPU -Q;iƅ  m -t8[mz' -"> 3= A}16Zu Ҿ zMٮiq a$fL/:?kYdb^fu O9<`l*}Bsp̫`JmrB26 q/' LX3,:mؔ~*0][ごXebm^W>P`YO˚ҹS:<찈:CpHP| ;2QT/=T]-]$DBR/0m-`}.J Usmsƚk0RKK0b"i# X $}5CJIAAg\&iU H{F$Z' ي۶3Ӷ- -Lq32L*ck8I1P ÏG.g: r&{)Wzp;u_ׇ?uQP rFƋmlC?xFs .B.B#r="x#ԹbJC8o̶ K_gȨXo3MfxM'10aXd -~ , + Qp^* -w[Kir -][Lği< l/h5ˡ'8^z5+urؼM֝(fIZR`DyiUA~8!0Aqn@=@ 8;U - p!P{5r`H -ٮ,tc( f#&1GQz8{t$%Fj:nTlj2Mls $vɻjj[9߻q\Ɗh.ToGkA\x9G\%fS)w7sAB{CB̄.n`3eצ6c+K8]q9ۜIP]ﭯ-+B5FCw+J[[]㾥FINBF +&3{1Fb f1ʣyجŶֱ(x[g.Sx\Yl0p -ǚEًNݸ+_8*n摁Y*E60TC;Nf]2@]-$,R0z~?^W4{nWa1̪׵~)V͵g@AJ3 1H6YGKIrkW]ݬ>K0M9-;M =QU61ײCWt!US»F? )Ucj:I. j"׼z&DL]],jLsH -\"d%{H) -Ӆ BYIEsFO =*P z}&!2nbok <2;9wP/;[(Q\Zl8ܪI!%T=5 -ُɋ߼7s.E'\^N/^y}{ӇW>{}+8|?/n~~oǯW9._=~ns&\66nN,&Zi#G5as/W&"hG3%m1h͚ܞlp# 0}Ɂ/ Vd}e]"u#@t+9ѥt<@P"3f l-cJ(:h{<:MI3"z?md8>fh(͎ -  "H hFCK~?A)- Ʃ8JoAvhFx k[_7 6aQOsv:h䴤Aspݎ$ >nf1cX&_3ޒ>>W})Kj.1W_ -n!fg*4~~f%gs?78T`Ў̖Uo3B\ȔR¦̓͜Vu((xh40RC OhgڸJQBQITA8K|\ ,!H{]kQc:零kLAC?-v%eeehO + Swr=נ/!҄NdRlgSEvEagն3bME,@AJ ڸS8@-|FKҰ-@R}D8;q%e;.YNI\Tn/LK-ۿy &"Fm[0z'`ߗdeU$f!į SLS9יMhr4rtľMtt qs (50 T6ĵntO>&*&7WD^&IH?AarO$֪]0(" Bn_>tEWT#:Uȿmق8cw%Gq/BjZ$Qw>6O?7ڄ6鹾U,>.v2}&Nf[ &qbl/Aݜd(>Gm06puxԆڻGM JTzB1Afk"]Fe\?iA2uT]J Xc);U,!!+ MnNG(D8Oȸz@$eX`,OquRZO,y)aX~>M0sq(`5$丵7,F=(=[ >WA2tכf"HqB!XbkQja2xF'@d^U8ӊ| gYWXK0XX(,>V?|1(څЃIW~ 0[J}>(A,͛6@ C v75W>P`Kj dCI0WSNqM yT(!!ul <ed#?Ed:2Մ" hV>G#<6h`Ӓ! *ؖH N?ZcDB ^/>?&jH0TrgLv^`%]!ϩ:4L,]IB iRF>/pX޸JS5!T|Oru _CS EL̳h۩wU0 H -8asp_4Z҄mkuew"j1/Bj9ۿ/iBdb -ڙn`~/K1X |UrԨVk@Q]H&M u:SbX{ p9XU+06*Y,a@`D y[ *!т= -(t+ exBK(F@fnaH#>`4d-(LI ]6TTz tR#.s!'5eLdqu.CfU*A`՚3V}49Vy&ht:jmD#t\hL\HiDv̱1[ .ߡ;UmEqoՂF;֛ p<q|.{Mx0Y~yө , یaEɆxD;[9Ʒ6= m3̍ΌRGQ5e7"Ԫ%mIoZH$ioG#VʓaԉSd3CKFl3.".9;Cݿ/ݒl @0;E \[,<Ɖ2["ʧ6ګ6OBFӅ L`E'@jdW ;U,|iF&{/ v֕QPAf8."I*#᧤O+pEu -Um -D\K˹ ub 9ܱQT>GGv:,B #W@S"0R<7Մ@0K}WV~B @ԲQ\}z$FDa6e3nǂ0=U]h ⼊Gxx_6") KI?f Qtİٿ@u!af҇ѩ4p]ep]H>rJ#Q!EEe#UjCQoJ,]s; Ä7Ol!2a%r[JUnaqZuWVr n9^PGLv}G>OIO{Ed\At"ER֪c>"v7P5NH -@,i3+ =[,u`0%F9)Ԫ57E]ItR haRx;;r; ==c$T:$w۪FO.3F>jhtY~O%GLd h7#Sj$x-.fAqO ͪ#! ?7q,-EΝ[4uh-Qo0\@F<ǝwD9l>x5k𰔘 1=0Pxs"h ٿ?z*bay}_)fNrO>7jԤDH '_ɶ kC7Jn&6yU<խfؾ>{uo@@԰SLl1ѩ<W=b#Q >a(u{lCK ^!CM1i*1 -'3 hot]Bf;~`&qI6+$V6B#Qk O sЭ4%CHAjǷ|`;Ac:#,:/x Da萟UJɣ('u JnCَl > Qq ?3jq?TSn:"bLW:hp.6Sn>=[t#!Owh8tSc0&Br|Ѻ0ch~ؠEiPiV3܅v6əljU2%U&.xx9x;n/rs`7wfʭLDaDX Ɲfh8V[:dnoqh*A"%*aߋޱmB|@'Q.7"gV"o..0ӌ-lh˖ɍq*g]4dV{+2m46x749gFΜf伊:X[h\pQzQx =1VNqelki]>3ݎb;srӽ:*p{|(Ė56!NV#fz?Wc}lAT}!'? V!Tg*$7AnJG>NSA\[! -ߑj,N=(-+ByL[j3qmوߞۚ* Gߏ=$jǾCUly #3P6VYqU&"O^ -;W!wFXd87*L͢ ,D>gFqi(`GQuz!te ZuRZhO :SS䜳,qD.ỳ݄fy3i 04qLB`YdHsC.F)l-Fp;S%j\50<KNsmjQ-yc9//jXC-[y鳜‘,.%?5;*ɒ-6ܟ~eN`G5r hϬtچh|b̽]A13{~q?WH2\>k1_>ؾƊ -81yl4=ZgSymˊ~/@`ӌ6zZun}O8|/Q 'd/daBqSw\ .*Cz_G]{m{!XZ0zfZx%cO͟V$]Ո;ɴ^ LQGS{6-WGm??+݈h_n=o(gZ$VRbd uI)OWT>?Ѻ,bHjw#i}(uC/UPoP`NVcFDlڃ_};T]h{9'=-?WBLE0-1knڭPP1 oM]늈@dpy4^'buZU -k<aƘ0%GP.=b뤭#uVZv.Md;,-)ר'rHw#Aos#dD4 ꋄT%G%oLw^L8V)WLx}"V=W2ò:Ɲ2I0&iOEU oE'mxKkϏé8C[c}vY_Ӑw?#| 2#/Y5p>F =Sh'&_L3` qy^ Qrq[GAl^:t@8Lc,r+F#o\/ę1BRIFRFQʣrWq[GoyĝZYܶQ|L0>ch*e:Aq|E?<=oesC*='5 bn=x[D9N5j;|4MB^|xGEB,m@41q;cG8, +}EʓZ2|k|q1ĈM -?ĨQwkBZF!09XY0w_Q\FS [ Dq+y"k+)/\G'y|x4,CdcK.7 ubwԌjI.|>f.?z UeL ? e> endobj xref -0 44 -0000000000 65535 f -0000000016 00000 n -0000000144 00000 n -0000041305 00000 n -0000000000 00000 f -0000042728 00000 n -0000785366 00000 n -0000041356 00000 n -0000041670 00000 n -0000043027 00000 n -0000042914 00000 n -0000042007 00000 n -0000042166 00000 n -0000042214 00000 n -0000042798 00000 n -0000042829 00000 n -0000043100 00000 n -0000043850 00000 n -0000044979 00000 n -0000047256 00000 n -0000076652 00000 n -0000107961 00000 n -0000171552 00000 n -0000213382 00000 n -0000259039 00000 n -0000318851 00000 n -0000360528 00000 n -0000423383 00000 n -0000466067 00000 n -0000506443 00000 n -0000525584 00000 n -0000529321 00000 n -0000535961 00000 n -0000555763 00000 n -0000571456 00000 n -0000584869 00000 n -0000590453 00000 n -0000615200 00000 n -0000640887 00000 n -0000670215 00000 n -0000698999 00000 n -0000727959 00000 n -0000756786 00000 n -0000785389 00000 n -trailer -<<477B29092DE61B4394E02E09C2040298>]>> -startxref -785584 -%%EOF diff --git a/Nynja/Resources/Constants.swift b/Nynja/Resources/Constants.swift index fff625bf78022e15c7c8f67132eeb985b5b56f8f..9ff01b5171c8b61cb4693af9e713634552cd708d 100644 --- a/Nynja/Resources/Constants.swift +++ b/Nynja/Resources/Constants.swift @@ -101,6 +101,18 @@ struct Constants { static let separator = Color(hex: "#2C2E33") } + + struct marketplaceMunu { + static let activeIcon = Color(hex: "#d80027") + static let inactiveIcon = Color(hex: "#505255") + static let activeTitle = colors.white + static let inActiveTitle = colors.darkGray + + static let separator = colors.red + + static let gradient = Color(hex: "#C90010") + } + } enum tableView { diff --git a/Nynja/Resources/Info.plist b/Nynja/Resources/Info.plist index 8ee31ca437b3408e3f11100c5c9b8dc52d4f9b4d..21192854c099df0143c82e8ff82b2eb12edbfffb 100644 --- a/Nynja/Resources/Info.plist +++ b/Nynja/Resources/Info.plist @@ -21,7 +21,7 @@ CFBundleShortVersionString 1.0 CFBundleVersion - 0.2.135 + 0.2.137 ConfServerAddress $(ConfServerAddress) ConfServerPort diff --git a/Nynja/Resources/en.lproj/Localizable.strings b/Nynja/Resources/en.lproj/Localizable.strings index 4ffac8afe5bf88bca763cc20ed87f5dd07c8c814..bdc66b985307e284a4ee4760a8e4856d83846be4 100644 --- a/Nynja/Resources/en.lproj/Localizable.strings +++ b/Nynja/Resources/en.lproj/Localizable.strings @@ -829,3 +829,51 @@ "wallet_created" = "Wallet has been created"; "wallet_add_title" = "Add\nWallet"; "wallet_transfer_deletion_info" = "The message with Transfer Information will be deleted only for you in chat history, but this message will be still available in Transfer History"; + +//MARK: Marketplace + +//**Submenu titles < CircleMenuItemType case naming are constrained to this values > +"marketplace" = "Marketplace"; +"freelance" = "Freelance"; +"accessMarketplace" = "Access marketplace"; +"virtualGoods" = "Virtual goods"; +"stickers" = "Stickers"; +"mediaContent" = "Media Content"; +"groupsAndChannels" = "Groups and Channels"; +"bots" = "Bots"; +"apps" = "Apps"; +"interpretation" = "Interpretation"; +"nynjaSupport" = "NYNJA Support"; +"design" = "Design"; +//Submenu titles** + +"interpretation" = "Interpretation"; + +"interpretation_type_general" = "General"; +"interpretation_type_technology" = "Technology"; +"interpretation_type_legal" = "Legal"; +"interpretation_type_medical" = "Medical"; + +"interpretation_type_description_general" = "NYNJA users who speak multiple languages will help you right now to interprate your call in real time. Use this for general language that most people would know."; +"interpretation_type_description_technology" = "Choose this for interpretation that require technology specific terminology and we will find someone with this capability."; +"interpretation_type_description_legal" = "Perfect for legal topics and other interpretation work typically done by a lawyer or paralegal."; +"interpretation_type_description_medical" = "Specialists with a background in medical interpretation will only be assigned these interpretations."; + +"interpretation_time" = "Approximate interpretation time"; +"lang_for_interpretation" = "Language for interpretation"; +"from" = "From:"; +"to" = "To:"; +"interpretation_type" = "Interpretation type"; +"total_price" = "Total price"; +"search_interpreter" = "Search Interpreter"; +"price_per_minute" = "Price per minute"; + +"asigning_interpreter_description" = "NYNJA is finding the perfect interpreter for you. Go ahead and continue using the app and we will let you know when they’re ready to start the call."; +"coming_soon" = "Coming soon"; +"assigning_interpreter" = "Assigning Interpreter"; + +"enter_interpretation_time" = "Enter interpretation time (1-300)"; +"interpretation_time_out_of_range" = "Time is ranged from 1 to 300"; + +"interpretation_language_not_selected" = "Please, choose the language you want to interpret from."; +"interpretation_languages_are_equal" = "Please, choose different languages."; diff --git a/Nynja/Resources/ru.lproj/InfoPlist.strings b/Nynja/Resources/ru.lproj/InfoPlist.strings deleted file mode 100644 index 0a2c93b6597889ea10006610ac03661bf8536eff..0000000000000000000000000000000000000000 --- a/Nynja/Resources/ru.lproj/InfoPlist.strings +++ /dev/null @@ -1,12 +0,0 @@ -/* InfoPlist.strings - Nynja - - Created by Michael Katkov on 3/9/18. - Copyright © 2018 TecSynt Solutions. All rights reserved. */ -"NSCameraUsageDescription"="NYNJA нужно это для того чтобы позволить вам делать фото и видео."; -"NSContactsUsageDescription"="NYNJA нужно это для того чтобы позволить вам добавлять новые контакты."; -"NSLocationAlwaysUsageDescription"="NYNJA нужно это для того чтобы знать вашу локацию чтоб вы могли поделиться ей."; -"NSLocationWhenInUseUsageDescription"="NYNJA нужно это для того чтобы знать вашу локацию чтоб вы могли поделиться ей."; -"NSMicrophoneUsageDescription"="NYNJA нужно это чтоб отсылать голосовые сообщения."; -"NSPhotoLibraryUsageDescription"="NYNJA нужно это чтобы вы могли пользоваться локальными картинками."; -"NSPhotoLibraryAddUsageDescription"="NYNJA нужно это для того чтобы позволить вам сохранять фото и видео на устрйостве."; diff --git a/Nynja/Resources/ru.lproj/Localizable.strings b/Nynja/Resources/ru.lproj/Localizable.strings index 5babdbb26ebbc766dece34f8bb1e4ecbc5fe894e..cc25b1f2164a4d8a00e20607c4136166f9efa116 100644 --- a/Nynja/Resources/ru.lproj/Localizable.strings +++ b/Nynja/Resources/ru.lproj/Localizable.strings @@ -724,6 +724,7 @@ "wallet_header" = "Кошелек"; "wallet_created" = "Кошелек успешно создан"; "wallet_add_title" = "Создать\nКошелек"; +"wallet_transfer_deletion_info" = "Сообщение с информацей о трансфере будет удалено только для вас в истории чата, но это сообщение будет по-прежнему доступно в истории передачи"; // MARK: Data and Storage "data_and_storage" = "Хранилище"; @@ -746,4 +747,51 @@ "files" = "Files"; "music" = "Music"; "gifs" = "GIFs"; -"wallet_transfer_deletion_info" = "Сообщение с информацей о трансфере будет удалено только для вас в истории чата, но это сообщение будет по-прежнему доступно в истории передачи"; + +//MARK: Marketplace + +//**Submenu titles < CircleMenuItemType case naming are constrained to this values > +"marketplace" = "Marketplace"; +"freelance" = "Freelance"; +"accessMarketplace" = "Access marketplace"; +"virtualGoods" = "Virtual goods"; +"stickers" = "Stickers"; +"mediaContent" = "Media Content"; +"groupsAndChannels" = "Groups and Channels"; +"bots" = "Bots"; +"apps" = "Apps"; +"interpretation" = "Interpretation"; +"nynjaSupport" = "NYNJA Support"; +"design" = "Design"; +//Submenu titles** + +"interpretation" = "Interpretation"; + +"interpretation_type_general" = "General"; +"interpretation_type_technology" = "Technology"; +"interpretation_type_legal" = "Legal"; +"interpretation_type_medical" = "Medical"; + +"interpretation_type_description_general" = "NYNJA users who speak multiple languages will help you right now to interprate your call in real time. Use this for general language that most people would know."; +"interpretation_type_description_technology" = "Choose this for interpretation that require technology specific terminology and we will find someone with this capability."; +"interpretation_type_description_legal" = "Perfect for legal topics and other interpretation work typically done by a lawyer or paralegal."; +"interpretation_type_description_medical" = "Specialists with a background in medical interpretation will only be assigned these interpretations."; + +"interpretation_time" = "Approximate interpretation time"; +"lang_for_interpretation" = "Language for interpretation"; +"from" = "From:"; +"to" = "To:"; +"interpretation_type" = "Interpretation type"; +"total_price" = "Total price"; +"search_interpreter" = "Search Interpreter"; +"price_per_minute" = "Price per minute"; + +"asigning_interpreter_description" = "NYNJA is finding the perfect interpreter for you. Go ahead and continue using the app and we will let you know when they’re ready to start the call."; +"coming_soon" = "Coming soon"; +"assigning_interpreter" = "Assigning Interpreter"; + +"enter_interpretation_time" = "Enter interpretation time (1-300)"; +"interpretation_time_out_of_range" = "Time is ranged from 1 to 300"; + +"interpretation_language_not_selected" = "Please, choose the language you want to interpret from."; +"interpretation_languages_are_equal" = "Please, choose different languages."; diff --git a/Nynja/Resources/ru.lproj/StatusCodes.strings b/Nynja/Resources/ru.lproj/StatusCodes.strings deleted file mode 100644 index 269e78deb067081f06281601c63f98ea17a4b8df..0000000000000000000000000000000000000000 --- a/Nynja/Resources/ru.lproj/StatusCodes.strings +++ /dev/null @@ -1,18 +0,0 @@ -/* - StatusCodes.strings - Nynja - - Created by Volodymyr Hryhoriev on 7/6/18. - Copyright © 2018 TecSynt Solutions. All rights reserved. -*/ - - -// MARK: - Room - -"RCHIE1" = "Канал уже существует"; - - -// MARK: - Link - -"LNKIE1" = "Недоступные символы"; -"LNKIE2" = "Эта ссылка занята"; diff --git a/Nynja/Services/HandleServices/HistoryHandler.swift b/Nynja/Services/HandleServices/HistoryHandler.swift index 98abbd10a9480b35ec8b274daea67dc0377857b0..6c1a6106d86f0236cbc9cb902cfbd90966c92d41 100644 --- a/Nynja/Services/HandleServices/HistoryHandler.swift +++ b/Nynja/Services/HandleServices/HistoryHandler.swift @@ -56,40 +56,20 @@ final class HistoryHandler: BaseHandler { } private static func updateMessageHistory(_ messages: [Message]) { - func shouldRemoveCall(_ message: Message) -> Bool { - guard let desc = message.files?.first, - desc.mime == SendMessageType.audioCall.rawValue, - desc.data?.first?.key == FeatureKeys.File.Call.users.rawValue, - let ids = desc.data?.first?.value?.splitByComma() else { - return false - } - - let phoneId = storageService.phoneId - return !ids.contains { $0 == phoneId } - } - - func shouldRemove(_ message: Message) -> Bool { - let status = StringAtom.string(message.status) - return status == "delete" || status == "edit" || shouldRemoveCall(message) - } - - var stackForRemove = [Message]() var stackForSave = [Message]() + var stackForDelete = [Message]() var systemClearMessage: Message? var isHistoryCleared = false for message in messages { - if shouldRemove(message) { - if !isHistoryCleared { - stackForRemove.append(message) - } + if message.statusString == "delete", !isHistoryCleared { + stackForDelete.append(message) } else if case .override = messageEditService.getMergeActionForMessage(message) { if message.isSystem, message.isStatusClear { systemClearMessage = message isHistoryCleared = true stackForSave.append(message) - } else if !isHistoryCleared { stackForSave.append(message) } @@ -100,8 +80,7 @@ final class HistoryHandler: BaseHandler { ChatService.clearMessages(before: systemClearMessage) } try? MessageDAO.saveMessages(stackForSave) - - stackForRemove.forEach { MessageDAO.removeMessage(message: $0) } + ChatService.removeMessages(stackForDelete) } private static func updateJobsHistory(_ jobs: [Job]) { diff --git a/Nynja/Services/HandleServices/MessageHandler.swift b/Nynja/Services/HandleServices/MessageHandler.swift index 3bcdb44c5f574cc0a3f4f29a1ed355e8b7ce7436..2b6b0be231fb8c57f99b5345645ca5eae48dccd8 100644 --- a/Nynja/Services/HandleServices/MessageHandler.swift +++ b/Nynja/Services/HandleServices/MessageHandler.swift @@ -86,29 +86,25 @@ final class MessageHandler: BaseHandler { } private static func deleteMessage(_ message: Message) { - MessageDAO.removeMessage(message: message, notify: { (msg) in - if let id = msg.id, let action = MessageActionDAO.fetchMessageAction(by: id) { - try? StorageService.sharedInstance.perform(action: .delete, with: action) - } - }) + try? saveIntoDatabase(message: message) + ChatService.removeMessage(message) } private static func editMessage(_ message: Message) { - guard let link: Int64 = message.link, let newText: String = message.mainFile?.payload else { + try? saveIntoDatabase(message: message) + + guard let link: Int64 = message.link else { return } if let oldMessage = MessageDAO.fetchMessage(serverId: link) { -//TODO: need to discuss -// let mimes: [DescMime] = [.base(mime: .text, payload: newText)] -// oldMessage.files = mimes.map { Desc(mime: $0) } oldMessage.files = message.files oldMessage.markAsEdited() oldMessage.status = nil do { - try StorageService.sharedInstance.perform(action: .save, with: oldMessage) + try saveIntoDatabase(message: oldMessage) ChatService.updateLastMessage(oldMessage) { $0 == link } } catch { LogService.log(topic: .db, text: "EditMessage update error: \(error.localizedDescription)") @@ -126,7 +122,7 @@ final class MessageHandler: BaseHandler { private static func updateMessage(_ message: Message) { do { - try StorageService.sharedInstance.perform(action: .save, with: message) + try saveIntoDatabase(message: message) ChatService.updateLastMessage(message) { $0 == message.id } } catch { LogService.log(topic: .db, text: "EditMessage update error: \(error.localizedDescription)") @@ -137,10 +133,10 @@ final class MessageHandler: BaseHandler { if let desc = message.files?.first, desc.mime == SendMessageType.audioCall.rawValue, desc.data?.first?.key == FeatureKeys.File.Call.users.rawValue, - let ids = desc.data?.first?.value?.splitByComma() { - if ids.first(where: {$0 == StorageService.sharedInstance.phoneId }) == nil { - return - } + let ids = desc.data?.first?.value?.splitByComma(), + !ids.contains { $0 == StorageService.sharedInstance.phoneId } { + + return } for subscriber in subscribers { subscriber.subscriber?.willSave(message) @@ -160,7 +156,7 @@ final class MessageHandler: BaseHandler { if message.isInOwnChat, !message.isCursor { ChatService.updateReader(from: message) } - try? StorageService.sharedInstance.perform(action: .save, with: message) + try? saveIntoDatabase(message: message) if message.isForward { JobDAO.deleteJobs(for: message) @@ -180,6 +176,10 @@ final class MessageHandler: BaseHandler { } } + private static func saveIntoDatabase(message: Message) throws { + try? StorageService.sharedInstance.perform(action: .save, with: message) + } + /// Play sound for incoming messages if chat isn't muted. /// Play outcoming message only if chat screen is open. private static func playSoundIfNeeded(for message: Message) { diff --git a/Nynja/Services/HandleServices/ProfileHandler.swift b/Nynja/Services/HandleServices/ProfileHandler.swift index b3f1707984dba22c7f5872dc7ab973f91de90483..4cb64801bb26bff2f7b387e3d5b77b67a408a72a 100644 --- a/Nynja/Services/HandleServices/ProfileHandler.swift +++ b/Nynja/Services/HandleServices/ProfileHandler.swift @@ -24,10 +24,6 @@ class ProfileHandler: BaseHandler { return MessageBackgroundTaskHandler() } - static var voxService: CallService { - return VoxService.sharedInstance - } - static var alertManager: AlertManager { return AlertManager.sharedInstance } @@ -94,8 +90,6 @@ class ProfileHandler: BaseHandler { }), let login = voxService.login, let password = voxService.password else { return } - - self.voxService.login(userName: login, password: password) } private static func requestJobs(with phoneId: String) { diff --git a/Nynja/Services/MQTT/MQTTService.swift b/Nynja/Services/MQTT/MQTTService.swift index 78ac98ea2fa74638effce2de6e11ff472b297ae5..251618b11daa33aeeff486345625a478c528fc25 100644 --- a/Nynja/Services/MQTT/MQTTService.swift +++ b/Nynja/Services/MQTT/MQTTService.swift @@ -256,7 +256,8 @@ final class MQTTService: NSObject, CocoaMQTTDelegate, ReachabilityServiceObserve if let error = err as NSError?, error.code == 7 { LogService.log(topic: .MQTT, text: "Something went wrong") - } + } + isConnectedSuccess = false } func mqtt(_ mqtt: CocoaMQTT, didSubscribeTopic topic: String) {} diff --git a/Nynja/Services/MessageProcessing/Operations/DownloadOperation.swift b/Nynja/Services/MessageProcessing/Operations/DownloadOperation.swift index 23b35d03b1d1e9ce8c5a2c2491de542982e2cda9..aa9faaea09fc169b5122e5c9d47455fb768361a8 100644 --- a/Nynja/Services/MessageProcessing/Operations/DownloadOperation.swift +++ b/Nynja/Services/MessageProcessing/Operations/DownloadOperation.swift @@ -13,7 +13,7 @@ class DownloadOperation: AsyncOperation { private var message: Message! private weak var delegate: MessageProcessingDelegate? private weak var listener: TransferProgressListener? - private(set) var url: URL! + private(set) var url: URL init(msg: Message, url: URL, delegate: MessageProcessingDelegate?, listener: TransferProgressListener?) { self.message = msg @@ -23,42 +23,10 @@ class DownloadOperation: AsyncOperation { } override func main() { - let group = DispatchGroup() - - if let thumbUrl = message.thumbUrl { - group.enter() - - TransferManager.shared.download(url: thumbUrl, listener: nil) { [weak self] result in - self?.thumbnailCompletion(thumbUrl: thumbUrl) - - group.leave() - } - } - - group.enter() - - TransferManager.shared.download(url: url, listener: self.listener) { result in - group.leave() - } - - group.notify(queue: DispatchQueue.main) { [weak self] in - self?.operationCompletion() + TransferManager.shared.download(url: url, listener: listener) { [weak self] result in + guard let `self` = self else { return } + self.delegate?.updateProgress(TransferManager.shared.progressForUrl(self.url)) + self.state = .finished } } } - -// MARK: - Private - -private extension DownloadOperation { - func operationCompletion() { - delegate?.updateProgress(TransferManager.shared.progressForUrl(url)) - state = .finished - } - - func thumbnailCompletion(thumbUrl: URL) { - let transferInfo = TransferInfo(progress: 0, speed: 0, fileSize: 0) - let progress = ProgressModel(url: thumbUrl, status: .done, transferInfo: transferInfo) - - delegate?.updateProgress(progress) - } -} diff --git a/Nynja/Services/Models/HistoryRequestModel.swift b/Nynja/Services/Models/HistoryRequestModel.swift index cf54557a6b9c6d9250e74aa03a780183ffdad1d9..5373aa86652174aa57fef54a5d89fdab5d5c9c02 100644 --- a/Nynja/Services/Models/HistoryRequestModel.swift +++ b/Nynja/Services/Models/HistoryRequestModel.swift @@ -23,7 +23,6 @@ final class HistoryRequestModel: BaseMQTTModel { } struct RequestInput { - let rosterId: String let historyType: HistoryType let actionType: ActionType @@ -37,7 +36,8 @@ final class HistoryRequestModel: BaseMQTTModel { enum ActionType { case getAll - case get(lastMessageId: Int64, pageSize: Int64) + case getPage(from: MessageServerId, pageSize: MessageServerId) + case getBetween(from: MessageServerId, to: MessageServerId) case delete case update(messageId: Int64) case defaultStickerPack @@ -62,7 +62,6 @@ final class HistoryRequestModel: BaseMQTTModel { } private var bert: BertTuple { - let topic = BertAtom(fromString: Keys.topic) let rosterId = Bert.getBin(requestInput.rosterId) let action = BertAtom(fromString: actionName) @@ -75,7 +74,6 @@ final class HistoryRequestModel: BaseMQTTModel { let feedIdBertBinaries = feedIdComponents.map { Bert.getBin($0) } components.append(contentsOf: feedIdBertBinaries) - return BertTuple(fromElements: components) } @@ -95,8 +93,21 @@ final class HistoryRequestModel: BaseMQTTModel { return BertNumber(fromInt64: messageId) } + var data: BertObject { + guard !self.data.isEmpty else { + return BertNil() + } + + guard let messages = self.data as? [Message?] else { + return BertNil() + } + + let list = messages.compactMap { $0?.getBert() } + + return BertList(fromElements: list) + } - return BertTuple(fromElements: [topic, rosterId, feedId, size, messageId, BertNil(), action]) + return BertTuple(fromElements: [topic, rosterId, feedId, size, messageId, data, action]) } } @@ -132,8 +143,10 @@ extension HistoryRequestModel { switch requestInput.actionType { case .getAll, .defaultStickerPack: return 0 - case .get(let lastMessageId, _): - return lastMessageId + case .getPage(let from, _): + return from + case .getBetween(let from, _): + return from case .delete: return nil case .update(let messageId): @@ -144,19 +157,29 @@ extension HistoryRequestModel { var pageSize: Int64? { switch requestInput.actionType { case .getAll, + .getBetween, .delete, .update: return nil - case .get(_, let pageSize): + case .getPage(_, let pageSize): return pageSize case .defaultStickerPack: return 1 } } + + var data: [AnyObject?] { + switch requestInput.actionType { + case .getBetween(_, to: let to): + return [makeBetweenRequestMessage(with: to)] + default: + return [] + } + } var actionName: String { switch requestInput.actionType { - case .get, .getAll, .defaultStickerPack: + case .getAll, .getPage, .getBetween, .defaultStickerPack: return Keys.get case .delete: return Keys.delete @@ -164,4 +187,23 @@ extension HistoryRequestModel { return Keys.update } } + + private func makeBetweenRequestMessage(with id: MessageServerId) -> Message? { + var feedId: AnyObject + switch requestInput.historyType { + case .p2p(let opponentId): + feedId = p2p(firstId: requestInput.rosterId, secondId: opponentId) + case .muc(let id): + feedId = muc(name: id) + default: + assertionFailure("History type should equal p2p or muc.") + return nil + } + + let message = Message() + message.id = id + message.container = StringAtom(string: "chain") + message.feed_id = feedId + return message + } } diff --git a/Nynja/Services/ServiceFactory/ServiceFactory.swift b/Nynja/Services/ServiceFactory/ServiceFactory.swift index 0bbbfb72313f457d627db2a1984dc50e5d095fdc..243c17e63bcf49a834185b3724026075c86b1133 100644 --- a/Nynja/Services/ServiceFactory/ServiceFactory.swift +++ b/Nynja/Services/ServiceFactory/ServiceFactory.swift @@ -43,6 +43,7 @@ protocol ServiceFactoryProtocol { func makeStatusCodeManager() -> StatusCodeManager func makeChatScreenAlertFactory() -> ChatScreenAlertFactoryProtocol + func makeUseCaseValidationServise() -> UseCaseValidationServiceProtocol } final class ServiceFactory: ServiceFactoryProtocol { @@ -155,4 +156,7 @@ final class ServiceFactory: ServiceFactoryProtocol { return ChatScreenAlertFactory() } + func makeUseCaseValidationServise() -> UseCaseValidationServiceProtocol { + return UseCaseValidationService() + } } diff --git a/Nynja/Services/VoxService.swift b/Nynja/Services/VoxService.swift deleted file mode 100644 index cd4e16fa45bbb4865500b3c3c23f4791c1c63eec..0000000000000000000000000000000000000000 --- a/Nynja/Services/VoxService.swift +++ /dev/null @@ -1,378 +0,0 @@ -// -// VoxService.swift -// Nynja -// -// Created by Anton Makarov on 23.05.17. -// Copyright © 2017 TecSynt Solutions. All rights reserved. -// - -import Foundation -import VoxImplant - -protocol VoxServiceDelegate: class { - func logined(userName: String) - func incomingCall(call: VICall, isVideo: Bool) - func ringing(call: VICall) - func callClosed(call: VICall, isError: Bool) - func callConnected(call: VICall, withVideo: Bool) - func log(text: String) - func remoteVideoStreamDeleted() - func startRinging() -} - -extension VoxServiceDelegate { - func logined(userName: String){} - func incomingCall(call: VICall, isVideo: Bool) {} - func ringing(call: VICall) {} - func callClosed(call: VICall, isError: Bool) {} - func callConnected(call: VICall, withVideo: Bool) {} - func log(text: String) {} - func remoteVideoStreamDeleted() {} - func startRinging() {} -} - -protocol CallService { - var remoteView : UIView? { get set } - var myView: UIView? { get set } - - func login(userName: String, password: String) - func answer(call: VICall, withVideo: Bool) - func cancelCall(call: VICall) - func call(username: String,withVideo: Bool) - func switchCamera() - func switchSpeaker() - func switchMicrophone(call: VICall) - func disableVideo(call: VICall) -} - -class VoxService: NSObject, CallService, VIClientSessionDelegate, VIClientCallManagerDelegate, VICallDelegate, VIEndpointDelegate { - - let voxClient:VIClient! - let domain = "@videoconf.nynja.voximplant.com" - - weak var delegate : VoxServiceDelegate? - - var remoteView : UIView? { - didSet { - let remoteRender = VIVideoRendererView(containerView: self.remoteView) - remoteRender?.resizeMode = .fill - if self.remoteVideoStream?.renderers?.count ?? 0 > 0 { - self.remoteVideoStream?.removeAllRenderers() - } - self.remoteVideoStream?.addRenderer(remoteRender) - } - } - - var myView: UIView? { - didSet { - let myRender = VIVideoRendererView(containerView: self.myView) - myRender?.resizeMode = .fill - if self.myVideoStream?.renderers?.count ?? 0 > 0 { - self.myVideoStream?.removeAllRenderers() - } - self.myVideoStream?.addRenderer(myRender) - } - } - - var log = "" - var username = "" - var password = "" - - var withVideo = false - weak var call: VICall? - - var isCallInProgress = false - var myVideoStream:VIVideoStream? - var remoteVideoStream:VIVideoStream? - var isRemoveVideoStream = false - - var isConnected = false - var timer: Timer? - - static let sharedInstance: VoxService = VoxService() - - override init() { - VIClient.setLogLevel(.disabled) - self.voxClient = VIClient(delegateQueue: DispatchQueue.main) - super.init() - self.voxClient.sessionDelegate = self - self.voxClient.callManagerDelegate = self - self.timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(runTimedCode), userInfo: nil, repeats: true) - } - - @objc func runTimedCode() { - if !self.isConnected && self.username != "" { - self.voxClient.connect(withConnectivityCheck: false, gateways: nil) - } - } - - func login(userName: String, password: String) { - self.username = userName - self.password = password - self.voxClient.connect(withConnectivityCheck: false, gateways: nil) - } - - func answer(call: VICall, withVideo: Bool) { - isCallInProgress = true - SoundService.sharedInstance.stopIncomingCallPlayer() - self.withVideo = withVideo - call.answer(withSendVideo: withVideo, receiveVideo: withVideo, customData: "", headers: nil) - if let endpoints = call.endpoints { - if endpoints.count > 0 { - endpoints[0].delegate = self - } - } - } - - func cancelCall(call: VICall) { - call.changeCallStatus(to: .unknown) - SoundService.sharedInstance.stopIncomingCallPlayer() - call.stop(withHeaders: nil) - } - - func call(username: String,withVideo: Bool = false) { - if isCallInProgress { - AlertManager.sharedInstance.showAlertOk(message: "You_can't_call_when_you_calling".localized) - return - } - if let myVox = StorageService.sharedInstance.voxId { - if myVox == username { - AlertManager.sharedInstance.showAlertOk(message: "You_can't_call_to_yourself".localized) - return - } - } - - isCallInProgress = true - - if !withVideo { - SoundService.sharedInstance.stopIncomingCallPlayer() - } - let call = voxClient.call(toUser: username, withSendVideo: withVideo, receiveVideo: withVideo, customData: "") - call?.add(self) - self.withVideo = withVideo - if withVideo { - if let endpoints = call?.endpoints { - if endpoints.count > 0 { - endpoints[0].delegate = self - } - } - } - call?.start(withHeaders: nil) - - call?.changeCallStatus(to: .connecting) - if let call = call { - self.delegate?.ringing(call: call) - } - } - - func switchCamera() { - VICameraManager.shared().useBackCamera = !VICameraManager.shared().useBackCamera - } - - func switchSpeaker() { - VIAudioManager.shared().useLoudSpeaker = !VIAudioManager.shared().useLoudSpeaker - } - - func switchMicrophone(call: VICall) { - call.sendAudio = !call.sendAudio - } - - func disableVideo(call: VICall) { - call.setSendVideo(false,completion: nil) - self.delegate?.callConnected(call: call, withVideo: false) - } - - // MARK: - VIClientSessionDelegate - public func clientSessionDidConnect(_ client: VIClient!) { - self.isConnected = true - self.voxClient.login(withUser: self.username + self.domain, password: self.password, success: { (string, dict) in - self.delegate?.logined(userName: self.username) - self.log += "Login Successful \(self.username) \n" - self.delegate?.log(text: self.log) - }) { (err) in - self.delegate?.log(text: err?.localizedDescription ?? "login failed") - } - } - - public func clientSessionDidDisconnect(_ client: VIClient!) { - self.isConnected = false - self.log += "Vox connection closed \n" - self.delegate?.log(text: self.log) - } - - public func client(_ client: VIClient!, sessionDidFailConnectWithError error: Error!) { - self.isConnected = false - self.log += "Vox connection failed \n" - self.delegate?.log(text: self.log) - } - - - // MARK: - VIClientCallManagerDelegate - public func client(_ client: VIClient!, didReceiveIncomingCall call: VICall!, withIncomingVideo video: Bool, headers: [AnyHashable : Any]!) { - if self.call != nil { - call.stop(withHeaders: nil) - return - } - guard MQTTService.sharedInstance.isAuthenticated else { - return - } - SoundService.sharedInstance.playCallSound() - - call.add(self) - self.withVideo = video - self.delegate?.incomingCall(call: call,isVideo: self.withVideo) - log += "Incoming Call \(call.callId!) \n" - self.delegate?.log(text: log) - - } - - // MARK: - VICallDelegate - public func call(_ call: VICall!, didReceiveInfo body: String!, type: String!, headers: [AnyHashable : Any]!) { - log += "Call Recieve Info \(call.callId!) : \(body) \n" - self.delegate?.log(text: log) - } - - public func call(_ call: VICall!, didReceiveMessage message: String!, headers: [AnyHashable : Any]!) { - log += "Call Recieve Message \(call.callId!) : \(message) \n" - self.delegate?.log(text: log) - } - - public func call(_ call: VICall!, didFailWithError error: Error!, headers: [AnyHashable : Any]!) { - self.delegate?.callClosed(call: call, isError: true) - isCallInProgress = false - if let reason = (error as NSError).userInfo["reason"] as? String { - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - AlertManager.sharedInstance.showAlertOk(message: "Call Failed: \(reason)", completion: nil) - } - } - log += "Call Failed \(call.callId!) : \(error.localizedDescription) \n" - self.delegate?.log(text: log) - } - - public func call(_ call: VICall!, startRingingWithHeaders headers: [AnyHashable : Any]!) { - call.changeCallStatus(to: .ringing) - log += "Call Ringing \(call.callId!) \n" - if !withVideo { - self.delegate?.startRinging() - } - self.delegate?.log(text: log) - } - - public func call(_ call: VICall!, didRemoveLocalVideoStream videoStream: VIVideoStream!) { - log += "Call Remove Local Video Stream\(call.callId!)\n" - self.delegate?.log(text: log) - self.removeVideoViews() - } - - public func call(_ call: VICall!, didConnectWithHeaders headers: [AnyHashable : Any]!) { - - let device = UIDevice.current - device.isProximityMonitoringEnabled = true - - isCallInProgress = true - log += "Call Connected \(call.callId) \n" - self.delegate?.log(text: log) - if let endpoinst = call.endpoints { - if endpoinst.count > 0 { - self.delegate?.callConnected(call: call, withVideo: self.withVideo) - if self.withVideo { - VIAudioManager.shared().useLoudSpeaker = !VIAudioManager.shared().useLoudSpeaker - } - if self.withVideo && endpoinst[0].remoteVideoStreams.count <= 0 { - isRemoveVideoStream = true - } else { - isRemoveVideoStream = false - } - } - } - self.call = call - } - - func removeVideoViews() { - if remoteVideoStream?.renderers.count ?? 0 > 0 { - self.remoteVideoStream?.removeAllRenderers() - } - if myVideoStream?.renderers.count ?? 0 > 0 { - self.myVideoStream?.removeAllRenderers() - } - VIAudioManager.shared().useLoudSpeaker = false - } - - public func call(_ call: VICall!, didAddLocalVideoStream videoStream: VIVideoStream!) { - log += "Call Add Local Video Stream\(call.callId!)\n" - self.delegate?.log(text: log) - self.myVideoStream = videoStream - } - - public func callDidStartAudio(_ call: VICall!) { - log += "Call Audio Started \(call.callId!) \n" - call.changeCallStatus(to: .ongoing) - - self.delegate?.log(text: log) - } - - public func call(_ call: VICall!, didReceiveStatistics stat: VICallStat!) { - - } - - public func call(_ call: VICall!, didDisconnectWithHeaders headers: [AnyHashable : Any]!, answeredElsewhere: NSNumber!) { - let device = UIDevice.current - device.isProximityMonitoringEnabled = false - - // Force to use front camera for next calls - VICameraManager.shared().useBackCamera = false - - self.call = nil - isCallInProgress = false - SoundService.sharedInstance.stopIncomingCallPlayer() - self.delegate?.callClosed(call: call, isError: false) - log += "Call Disconnected \(call.callId) \n" - self.delegate?.log(text: log) - } - - // MARK: - VIEndPointDelegate - func endpoint(_ endpoint: VIEndpoint!, didAddRemoteVideoStream videoStream: VIVideoStream!) { - self.remoteVideoStream = videoStream - isRemoveVideoStream = false - } - - func endpoint(_ endpoint: VIEndpoint!, didRemoveRemoteVideoStream videoStream: VIVideoStream!) { - isRemoveVideoStream = false - if !self.withVideo {return} - self.delegate?.remoteVideoStreamDeleted() - self.removeVideoViews() - - } -} - -// MARK: Call Status extension -extension VICall { - - enum CallStatus: String { - case ringing = "Ringing..." - case connecting = "Connecting..." - case ongoing - case unknown - } - - private struct CallStatusStruct { - static var status: CallStatus = .unknown - } - - var callStatus: CallStatus? { - get { - return objc_getAssociatedObject(self, &CallStatusStruct.status) as? CallStatus - } - set { - if let unwrappedValue = newValue { - objc_setAssociatedObject(self, &CallStatusStruct.status, unwrappedValue as CallStatus, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) - } - } - } - - func changeCallStatus(to status: CallStatus) { - self.callStatus = status - } - -} - diff --git a/Nynja/SyncFileManager/SyncFileManager.swift b/Nynja/SyncFileManager/SyncFileManager.swift index 1a9a4f2ad0d77e0ddfc94b9be1d69e3b350aa414..e445f52c67782d2f1e73fb58cbcdf5f2fb6b6ad9 100644 --- a/Nynja/SyncFileManager/SyncFileManager.swift +++ b/Nynja/SyncFileManager/SyncFileManager.swift @@ -65,7 +65,7 @@ class SyncFileManager { } } - func getFileLink(url: String, from destination: RemoteStorageDestination = .default, completion: ((_ localURL:URL?,_ transferInfo: TransferInfo?, _ request: AWSRequest?)->())?) { + func getFileLink(url: String, from destination: RemoteStorageDestination = .default, completion: ((_ localURL:URL?,_ transferInfo: TransferInfo?, _ request: AWSRequest?) -> ())?) { if url.first == "/" { if url.split(separator: "/").count > 2 { @@ -91,36 +91,29 @@ class SyncFileManager { if isAvaliable { completion?(URL(fileURLWithPath: fullPath), TransferInfo.finished, nil) } else { - let request = self.downloader.download(url: inputURL, from: destination) { res, transferInfo in - if res != nil { - self.updateDB(url: inputURL, localUrl: res!) { - if let fullPath = FileManagerService.sharedInstance.getFullPathWith(string: res!) { - completion?(URL(fileURLWithPath: fullPath), TransferInfo.finished, nil) - } - } - } else if transferInfo != nil { - completion?(nil, transferInfo, nil) - } else if res == nil && transferInfo == nil { - completion?(nil, nil, nil) - } - } + let request = self.makeDownloadRequest(url: inputURL, from: destination, completion: completion) completion?(nil, nil, request) } } else { - let request = self.downloader.download(url: url, from: destination) { res, progress in - if res != nil { - self.updateDB(url: url, localUrl: res!) { - if let fullPath = FileManagerService.sharedInstance.getFullPathWith(string: res!) { - completion?(URL(fileURLWithPath: fullPath), TransferInfo.finished, nil) - } - } - } else if progress != nil { - completion?(nil, progress, nil) - } else if res == nil && progress == nil { - completion?(nil, nil, nil) + let request = self.makeDownloadRequest(url: url, from: destination, completion: completion) + completion?(nil, nil, request) + } + } + } + + func makeDownloadRequest(url: String, from destination: RemoteStorageDestination, completion: ((URL?, TransferInfo?, AWSRequest?) -> ())?) -> AWSRequest? { + return self.downloader.download(url: url, from: destination) { res, progress in + if let localUrl = res { + self.updateDB(url: url, localUrl: localUrl) { + guard let fullPath = FileManagerService.sharedInstance.getFullPathWith(string: localUrl) else { + return } + completion?(URL(fileURLWithPath: fullPath), TransferInfo.finished, nil) } - completion?(nil, nil, request) + } else if let progress = progress { + completion?(nil, progress, nil) + } else { + completion?(nil, nil, nil) } } } diff --git a/Nynja/TimeZoneLocal.swift b/Nynja/TimeZoneLocal.swift index 37f8b4a109c0763c85ccfec845680e4d5dde2890..4a455861eec6e414c945a14fea3da6ee52f668cb 100644 --- a/Nynja/TimeZoneLocal.swift +++ b/Nynja/TimeZoneLocal.swift @@ -17,6 +17,10 @@ struct TimeZoneLocal : Codable { var text: String var utc: [String] + var name: String { + return utc.first!.replacingOccurrences(of: "_", with: " ") + } + var timeZone: TimeZone? { if utc.count > 1 { return TimeZone(identifier: utc[1]) diff --git a/Nynja/TransferManager.swift b/Nynja/TransferManager.swift index a1ab38e0d93645ac801700978afb8d5946367907..5b229b6b5243924c7db145388bde1fc7a4d94a56 100644 --- a/Nynja/TransferManager.swift +++ b/Nynja/TransferManager.swift @@ -15,7 +15,7 @@ protocol TransferProgressListener : class { private class ProcessingDescription { var listener: TransferProgressListener? = nil - var transferInfo: TransferInfo = TransferInfo(progress: 0, speed: 0, fileSize: 0) + var transferInfo: TransferInfo? = TransferInfo(progress: 0, speed: 0, fileSize: 0) var request: AWSRequest? var url: URL? } @@ -33,7 +33,7 @@ class TransferManager { func isURLProcessing(_ url: URL) -> Bool { if let processing = getProcessing(for: url) { - return processing.transferInfo.progress != 1 + return processing.transferInfo?.progress != 1 } return false } @@ -41,14 +41,17 @@ class TransferManager { func progressForUrl(_ url: URL) -> ProgressModel { if let description = getProcessing(for: url) { + guard let transferInfo = description.transferInfo else { + return ProgressModel(url: url, status: .notStarted) + } if description.request != nil { - if description.transferInfo.progress == 1.0 { - return ProgressModel(url: url, status: .done, transferInfo: description.transferInfo) + if transferInfo.progress == 1.0 { + return ProgressModel(url: url, status: .done, transferInfo: transferInfo) } else { - return ProgressModel(url: url, status: .atProgress, transferInfo: description.transferInfo) + return ProgressModel(url: url, status: .atProgress, transferInfo: transferInfo) } } else { - return ProgressModel(url: url, status: .notStarted, transferInfo: description.transferInfo) + return ProgressModel(url: url, status: .notStarted, transferInfo: transferInfo) } } @@ -147,12 +150,15 @@ class TransferManager { } func cancelledDownloading(url: URL) { - let transferInfo = TransferInfo(progress: 0, speed: 0, fileSize: 0) - let progress = ProgressModel(url: url, status: .notStarted, transferInfo: transferInfo) + guard let processing = getProcessing(for: url) else { + return + } + processing.transferInfo = nil + let progress = ProgressModel(url: url, status: .notStarted, transferInfo: nil) notifyAboutProgress(progress: progress) } - func updateProgress(url: URL, transferInfo: TransferInfo) { + func updateProgress(url: URL, transferInfo: TransferInfo?) { guard let processing = getProcessing(for: url) else { return } diff --git a/Nynja/Utils/Money/CryptoMoney.swift b/Nynja/Utils/Money/CryptoMoney.swift index 69db605c5aa534f40d3f311926936a47bdd1b85b..87f837a6fa7dd580a74ee6aa9e2a3f6337741005 100644 --- a/Nynja/Utils/Money/CryptoMoney.swift +++ b/Nynja/Utils/Money/CryptoMoney.swift @@ -32,6 +32,7 @@ extension Money where Currency: CryptoCurrency { return formatter.string(for: amount) } + } fileprivate class CryptoMoneyFormatter { @@ -67,4 +68,5 @@ fileprivate class CryptoMoneyFormatter { formatter.negativeFormat = "#,##0.###" return formatter }() + } diff --git a/Nynja/TextInputValidationService.swift b/Nynja/Validation/TextInputValidationService.swift similarity index 68% rename from Nynja/TextInputValidationService.swift rename to Nynja/Validation/TextInputValidationService.swift index de8c9ff13d7fa4259b561c4651da641659725c69..c9f090947b32d24670717b6bf7433b4d92002163 100644 --- a/Nynja/TextInputValidationService.swift +++ b/Nynja/Validation/TextInputValidationService.swift @@ -11,6 +11,8 @@ import Foundation protocol TextInputValidationServiceProtocol { func validateFirstName(_ name: String?) throws -> String func validateLastNameNew(_ name: String?) throws -> String + func validateGroupName(_ name: String?) throws -> String + func validateInterpretationTime(_ number: Int) throws -> Int } final class TextInputValidationService: TextInputValidationServiceProtocol { @@ -38,6 +40,20 @@ final class TextInputValidationService: TextInputValidationServiceProtocol { } return text } + + func validateGroupName(_ name: String?) throws -> String { + guard let text = name, text.count >= 1 else { + throw TextInputValidationError.groupNameIsEmpty + } + return text + } + + func validateInterpretationTime(_ number: Int) throws -> Int { + if number < 1 || number > 300 { + throw TextInputValidationError.interpretationTimeIsOutOfRange + } + return number + } } enum TextInputValidationError: LocalizedError { @@ -46,6 +62,8 @@ enum TextInputValidationError: LocalizedError { case firstNameHasNotEnoughSymbols case firstNameHasTooMuchSymbols case lastNameHasTooMuchSymbols + case groupNameIsEmpty + case interpretationTimeIsOutOfRange var errorDescription: String? { switch self { @@ -57,6 +75,10 @@ enum TextInputValidationError: LocalizedError { return "First_Name_must_be_at_more".localized case .lastNameHasTooMuchSymbols: return "Last_Name_must_be_at_more".localized + case .groupNameIsEmpty: + return "Group_Name_Empty_Message".localized + case .interpretationTimeIsOutOfRange: + return "interpretation_time_out_of_range".localized } } } diff --git a/Nynja/Validation/UseCaseValidationService.swift b/Nynja/Validation/UseCaseValidationService.swift new file mode 100644 index 0000000000000000000000000000000000000000..f7d868b8672a83b213a7e7389949276b4b62cf2d --- /dev/null +++ b/Nynja/Validation/UseCaseValidationService.swift @@ -0,0 +1,40 @@ +// +// UseCaseValidationService.swift +// Nynja +// +// Created by Roman Chopovenko on 8/7/18. +// Copyright © 2018 TecSynt Solutions. All rights reserved. +// + +import Foundation + +protocol UseCaseValidationServiceProtocol { + func validateInterpretationLanguages(_ fromLanguage: Language, toLanguage: Language) throws +} + +final class UseCaseValidationService: UseCaseValidationServiceProtocol { + func validateInterpretationLanguages(_ fromLanguage: Language, toLanguage: Language) throws { + if fromLanguage == .empty_default { + throw UseCaseValidationError.interpretationLanguageNotSelected + } + + if fromLanguage == toLanguage { + throw UseCaseValidationError.interpretationLanguagesAreEqual + } + } +} + +enum UseCaseValidationError: LocalizedError { + + case interpretationLanguageNotSelected + case interpretationLanguagesAreEqual + + var errorDescription: String? { + switch self { + case .interpretationLanguageNotSelected: + return "interpretation_language_not_selected".localized + case .interpretationLanguagesAreEqual: + return "interpretation_languages_are_equal".localized + } + } +} diff --git a/Podfile b/Podfile index b198b57c0eb47026a361257e2a1cb7b182297d55..fffbdb1311c36379ca91a804c37d2ac563a564c4 100644 --- a/Podfile +++ b/Podfile @@ -22,7 +22,6 @@ def commonPodsForNynja pod 'Fabric', '~> 1.6.13' pod 'Crashlytics', '~> 3.8.6' pod 'CocoaMQTT' - pod 'VoxImplantSDK', '~> 2.12.0' pod 'libPhoneNumber-iOS', '~> 0.8' pod 'QRCode' pod 'CocoaLumberjack', :git => 'https://github.com/CocoaLumberjack/CocoaLumberjack', :commit => '12948ff' @@ -40,7 +39,7 @@ def commonPodsForNynja pod 'MaterialComponents/FlexibleHeader' pod 'JTAppleCalendar', '~> 7.0' - pod 'NynjaSDK', '~> 1.4.0' + pod 'NynjaSDK', '~> 1.4.1' pod 'CryptoSwift' end @@ -50,7 +49,6 @@ def commonPodsForNynjaTests pod 'Fabric', '~> 1.6.13' pod 'Crashlytics', '~> 3.8.6' pod 'CocoaMQTT' - pod 'VoxImplantSDK', '~> 2.12.0' pod 'libPhoneNumber-iOS', '~> 0.8' pod 'QRCode' pod 'CocoaLumberjack', :git => 'https://github.com/CocoaLumberjack/CocoaLumberjack', :commit => '12948ff' diff --git a/Shared/Library/Extensions/Models/Message/MessageExtension.swift b/Shared/Library/Extensions/Models/Message/MessageExtension.swift index 65a2e134b33a075c55513ee912bf0efa5f072645..ddc0e02fdb6af1a0c65d0c2c04764629ce599617 100644 --- a/Shared/Library/Extensions/Models/Message/MessageExtension.swift +++ b/Shared/Library/Extensions/Models/Message/MessageExtension.swift @@ -8,6 +8,8 @@ import UIKit +typealias MessageServerId = Int64 + extension Message: Hashable { var hashValue: Int { diff --git a/externals/mobile-sdk-s4 b/externals/mobile-sdk-s4 deleted file mode 160000 index 5bd083a74dcf787903cee307fbf9ab945b326708..0000000000000000000000000000000000000000 --- a/externals/mobile-sdk-s4 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5bd083a74dcf787903cee307fbf9ab945b326708