diff --git a/.github/workflows/XCTestBuild.yml b/.github/workflows/XCTestBuild.yml index fd8244b9c..04e098248 100644 --- a/.github/workflows/XCTestBuild.yml +++ b/.github/workflows/XCTestBuild.yml @@ -9,7 +9,7 @@ on: branches: [ develop, release, hotfix, feature, main ] jobs: build: - runs-on: self-hosted + runs-on: macos-latest steps: - uses: actions/checkout@v2 diff --git a/Manito/Manito.xcodeproj/project.pbxproj b/Manito/Manito.xcodeproj/project.pbxproj index 75a21934e..f90823f65 100644 --- a/Manito/Manito.xcodeproj/project.pbxproj +++ b/Manito/Manito.xcodeproj/project.pbxproj @@ -8,30 +8,38 @@ /* Begin PBXBuildFile section */ 333BF66628571CF00039F77F /* FriendCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 333BF66528571CF00039F77F /* FriendCollectionViewCell.swift */; }; - 333BF66A285864CE0039F77F /* MemoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 333BF669285864CE0039F77F /* MemoryViewController.swift */; }; - 33BDF5622856E03800564211 /* FriendListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33BDF5612856E03800564211 /* FriendListViewController.swift */; }; 39018F4228C4708A00C78DBA /* UIButton+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39018F4128C4708A00C78DBA /* UIButton+Extension.swift */; }; 3904F4FA2AA36F4F00B6264F /* UserInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3904F4F92AA36F4F00B6264F /* UserInfo.swift */; }; 3904F4FC2AA3729300B6264F /* InvitationCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3904F4FB2AA3729300B6264F /* InvitationCode.swift */; }; 3904F4FE2AA3743C00B6264F /* IndividualMission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3904F4FD2AA3743C00B6264F /* IndividualMission.swift */; }; 3904F5002AA3754400B6264F /* MessageCountInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3904F4FF2AA3754400B6264F /* MessageCountInfo.swift */; }; - 3904F5062AA37A3C00B6264F /* MockDetailWaitService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3904F5052AA37A3C00B6264F /* MockDetailWaitService.swift */; }; + 390E5CAD2AB89BCE00EBE52E /* RoomListItemTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390E5CAC2AB89BCE00EBE52E /* RoomListItemTest.swift */; }; + 390E5CB22AB8A70600EBE52E /* Date+ExtensionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 390E5CB12AB8A70600EBE52E /* Date+ExtensionTest.swift */; }; 3915D54B2A7516A2002D6C25 /* Key.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3915D54A2A7516A2002D6C25 /* Key.plist */; }; 3915D54D2A7516E9002D6C25 /* Key+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3915D54C2A7516E9002D6C25 /* Key+Extension.swift */; }; 391612D829E9231B004AE982 /* DetailWaitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391612D729E9231B004AE982 /* DetailWaitView.swift */; }; 391B3003286198C200421F1D /* SizeLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 391B3002286198C200421F1D /* SizeLiteral.swift */; }; 392EC77E2855C388006918A9 /* SettingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 392EC77D2855C388006918A9 /* SettingButton.swift */; }; 392EC7812855D17D006918A9 /* DetailWaitTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 392EC7802855D17D006918A9 /* DetailWaitTitleView.swift */; }; + 395402D22AAF0B4F003F3012 /* Navigationable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 395402D12AAF0B4F003F3012 /* Navigationable.swift */; }; + 395402D42AAF0BC5003F3012 /* UIViewController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 395402D32AAF0BC5003F3012 /* UIViewController+Extension.swift */; }; 395B5BC22A25E20000CE1420 /* DetailWaitViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 395B5BC12A25E20000CE1420 /* DetailWaitViewModel.swift */; }; 395B5BCC2A2F6A9700CE1420 /* DetailWaitViewModelTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 395B5BCB2A2F6A9700CE1420 /* DetailWaitViewModelTest.swift */; }; + 39632A282AD81F2E00A6E61D /* DetailWaitUsecaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39632A272AD81F2E00A6E61D /* DetailWaitUsecaseTest.swift */; }; + 39632A2B2AD842A800A6E61D /* DetailWaitUsecaseError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39632A2A2AD842A800A6E61D /* DetailWaitUsecaseError.swift */; }; + 39632A732AF0DC2E00A6E61D /* DetailEditViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39632A722AF0DC2E00A6E61D /* DetailEditViewModel.swift */; }; + 39632A762AF0EE6600A6E61D /* DetailEditUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39632A752AF0EE6600A6E61D /* DetailEditUsecase.swift */; }; + 39632A842AFB24FD00A6E61D /* DetailEditUsecaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39632A832AFB24FD00A6E61D /* DetailEditUsecaseTest.swift */; }; + 39632AC52B0B477700A6E61D /* DetailEditUsecaseError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39632AC42B0B477700A6E61D /* DetailEditUsecaseError.swift */; }; 397A241028BA494100454E4F /* APIEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39C957F0287D984900A04A2B /* APIEnvironment.swift */; }; + 39833CFE2AB09A6C009D173A /* Keyboardable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39833CFD2AB09A6C009D173A /* Keyboardable.swift */; }; 398B1C9B29F10B0300DEFCEC /* DetailEditView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398B1C9A29F10B0300DEFCEC /* DetailEditView.swift */; }; 398B1CCA2A13597600DEFCEC /* FSCalendar in Frameworks */ = {isa = PBXBuildFile; productRef = 398B1CC92A13597600DEFCEC /* FSCalendar */; }; 398B1CCC2A13599D00DEFCEC /* FirebaseAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 398B1CCB2A13599D00DEFCEC /* FirebaseAuth */; }; + 398CED082AB9E48F00F50CBD /* RoomInfoTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 398CED072AB9E48F00F50CBD /* RoomInfoTest.swift */; }; 399D17AB2856C83B00F50D9D /* DetailEditViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 399D17AA2856C83B00F50D9D /* DetailEditViewController.swift */; }; - 39BDDCC42A52BB4A005E0A71 /* DetailWaitService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39BDDCC32A52BB4A005E0A71 /* DetailWaitService.swift */; }; 39C957CE2876E2ED00A04A2B /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39C957CD2876E2ED00A04A2B /* LoginViewController.swift */; }; - 39C957D02879521400A04A2B /* String+Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39C957CF2879521400A04A2B /* String+Date.swift */; }; + 39C957D02879521400A04A2B /* String+DateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39C957CF2879521400A04A2B /* String+DateFormatter.swift */; }; 39C957D22879523200A04A2B /* Date+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39C957D12879523200A04A2B /* Date+Extension.swift */; }; 39C957ED287AE4D100A04A2B /* MainEndPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39C957EC287AE4D100A04A2B /* MainEndPoint.swift */; }; 39C95802287DACC300A04A2B /* RoomParticipationEndPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39C95801287DACC300A04A2B /* RoomParticipationEndPoint.swift */; }; @@ -41,12 +49,12 @@ 39EE956D2A401ED400AF6857 /* DetailingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39EE956C2A401ED400AF6857 /* DetailingView.swift */; }; 39EE956F2A404A3800AF6857 /* MissionEditViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39EE956E2A404A3800AF6857 /* MissionEditViewController.swift */; }; 39EEF65E28CB3CFE00437654 /* LoginEndPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39EEF65D28CB3CFE00437654 /* LoginEndPoint.swift */; }; + 39EF30F92AD3DE6700466A90 /* DetailWaitUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39EF30F82AD3DE6700466A90 /* DetailWaitUseCase.swift */; }; 39F1C12E28D756E600585B83 /* LetterImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39F1C12D28D756E600585B83 /* LetterImageViewController.swift */; }; 39FFE32828EEFFFE008442EE /* MoreButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 39FFE32728EEFFFE008442EE /* MoreButton.swift */; }; 39FFE33428F457AF008442EE /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 39FFE33328F457AF008442EE /* FirebaseMessaging */; }; 435B17682913E71400212663 /* DetailingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 435B17672913E71400212663 /* DetailingViewController.swift */; }; 7E0C5C362855B22700F698D1 /* CreateNicknameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E0C5C352855B22700F698D1 /* CreateNicknameViewController.swift */; }; - 7E0C5C382855B73100F698D1 /* Splash.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7E0C5C372855B73100F698D1 /* Splash.storyboard */; }; 7E15F67E28B35B3D00441305 /* TextLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E15F67D28B35B3D00441305 /* TextLiteral.swift */; }; 7E3058C62854B47F00489E6A /* InputTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E3058C52854B47F00489E6A /* InputTitleView.swift */; }; 7E3058C82854B64D00489E6A /* InputCapacityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E3058C72854B64D00489E6A /* InputCapacityView.swift */; }; @@ -54,15 +62,13 @@ 7E7542B32923744300D725CB /* ToastPopupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E7542B22923744300D725CB /* ToastPopupView.swift */; }; 7EA25C2728C4FEA800746AEA /* ChangeNicknameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EA25C2628C4FEA800746AEA /* ChangeNicknameViewController.swift */; }; 7ED845BA2876BE530075AC61 /* SettingViewTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ED845B92876BE530075AC61 /* SettingViewTableCell.swift */; }; - B50B1AF92856B2C60080992C /* CreateLetterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50B1AF82856B2C60080992C /* CreateLetterViewController.swift */; }; B50B1AFB2856B5180080992C /* IndividualMissionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50B1AFA2856B5180080992C /* IndividualMissionView.swift */; }; - B50B1AFD2856C3500080992C /* CreateLetterTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50B1AFC2856C3500080992C /* CreateLetterTextView.swift */; }; - B50B1AFF2856D3820080992C /* CreateLetterPhotoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50B1AFE2856D3820080992C /* CreateLetterPhotoView.swift */; }; + B50B1AFD2856C3500080992C /* SendLetterTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50B1AFC2856C3500080992C /* SendLetterTextView.swift */; }; + B50B1AFF2856D3820080992C /* SendLetterPhotoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50B1AFE2856D3820080992C /* SendLetterPhotoView.swift */; }; B50B1B0428596C900080992C /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = B50B1B0328596C900080992C /* SnapKit */; }; B50B1B0728596CB10080992C /* FSCalendar in Frameworks */ = {isa = PBXBuildFile; productRef = B50B1B0628596CB10080992C /* FSCalendar */; }; B50B1B2A285AB7650080992C /* SplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50B1B29285AB7650080992C /* SplashViewController.swift */; }; B50B1B33285AC0970080992C /* Gifu in Frameworks */ = {isa = PBXBuildFile; productRef = B50B1B32285AC0970080992C /* Gifu */; }; - B50B1B3C285AD2B20080992C /* logo.gif in Resources */ = {isa = PBXBuildFile; fileRef = B50B1B3B285AD2B20080992C /* logo.gif */; }; B50B1B41285B048A0080992C /* OpenManittoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50B1B40285B048A0080992C /* OpenManittoViewController.swift */; }; B50B1B44285B146A0080992C /* OpenManittoCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50B1B43285B146A0080992C /* OpenManittoCollectionViewCell.swift */; }; B50CEE912A445EB700CF1C76 /* UIScrollView+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50CEE902A445EB700CF1C76 /* UIScrollView+Combine.swift */; }; @@ -70,26 +76,46 @@ B517C04A28D1F7EC0008BED0 /* TokenEndPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = B517C04928D1F7EC0008BED0 /* TokenEndPoint.swift */; }; B53A35AD2A962E9C00B720BC /* MTNetwork in Frameworks */ = {isa = PBXBuildFile; productRef = B53A35AC2A962E9C00B720BC /* MTNetwork */; }; B53A35B72A963F2100B720BC /* DetailRoomEndPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A35B62A963F2100B720BC /* DetailRoomEndPoint.swift */; }; - B53A35C02A96F21A00B720BC /* MainRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A35BF2A96F21A00B720BC /* MainRepository.swift */; }; - B53A35C42A96F50100B720BC /* DetailRoomRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A35C32A96F50100B720BC /* DetailRoomRepository.swift */; }; - B53A35C62A96F6DB00B720BC /* RoomParticipationRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A35C52A96F6DB00B720BC /* RoomParticipationRepository.swift */; }; - B53A35C82A96F84700B720BC /* LetterRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A35C72A96F84700B720BC /* LetterRepository.swift */; }; - B53A35CC2A96F99D00B720BC /* SettingRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A35CB2A96F99D00B720BC /* SettingRepository.swift */; }; - B53A35CE2A96FA7A00B720BC /* LoginRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A35CD2A96FA7A00B720BC /* LoginRepository.swift */; }; - B53A35D02A97194D00B720BC /* TokenRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A35CF2A97194D00B720BC /* TokenRepository.swift */; }; + B53A35C02A96F21A00B720BC /* MainRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A35BF2A96F21A00B720BC /* MainRepositoryImpl.swift */; }; + B53A35C42A96F50100B720BC /* DetailRoomRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A35C32A96F50100B720BC /* DetailRoomRepositoryImpl.swift */; }; + B53A35C62A96F6DB00B720BC /* RoomParticipationRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A35C52A96F6DB00B720BC /* RoomParticipationRepositoryImpl.swift */; }; + B53A35C82A96F84700B720BC /* LetterRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A35C72A96F84700B720BC /* LetterRepositoryImpl.swift */; }; + B53A35CC2A96F99D00B720BC /* SettingRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A35CB2A96F99D00B720BC /* SettingRepositoryImpl.swift */; }; + B53A35CE2A96FA7A00B720BC /* LoginRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A35CD2A96FA7A00B720BC /* LoginRepositoryImpl.swift */; }; + B53A35D02A97194D00B720BC /* TokenRepositoryImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A35CF2A97194D00B720BC /* TokenRepositoryImpl.swift */; }; B53A35D42A97266D00B720BC /* CreatedRoomRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53A35D32A97266D00B720BC /* CreatedRoomRequestDTO.swift */; }; + B53CD0382ADF941300BE7353 /* OpenManittoUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53CD0372ADF941200BE7353 /* OpenManittoUsecase.swift */; }; + B53CD03A2ADF9B6100BE7353 /* OpenManittoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53CD0392ADF9B6100BE7353 /* OpenManittoViewModel.swift */; }; + B53CD0402ADFC25C00BE7353 /* SelectManitteeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53CD03F2ADFC25C00BE7353 /* SelectManitteeViewModel.swift */; }; + B5422CA22ADD113C0051A966 /* SplashUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5422CA12ADD113C0051A966 /* SplashUsecase.swift */; }; + B5422CA52ADD16C40051A966 /* SplashViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5422CA42ADD16C40051A966 /* SplashViewModel.swift */; }; + B5422CA72ADD191A0051A966 /* EntryType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5422CA62ADD191A0051A966 /* EntryType.swift */; }; + B5422CB22ADD2AE60051A966 /* MemoryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5422CB12ADD2AE60051A966 /* MemoryViewController.swift */; }; + B5422CB42ADD2B040051A966 /* MemoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5422CB32ADD2B040051A966 /* MemoryView.swift */; }; + B5422CB62ADD2B190051A966 /* MemoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5422CB52ADD2B190051A966 /* MemoryViewModel.swift */; }; + B5422CCA2ADD3D1C0051A966 /* SendLetterUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5422CC92ADD3D1C0051A966 /* SendLetterUsecase.swift */; }; + B5422CCF2ADD3DEC0051A966 /* MemoryUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5422CCE2ADD3DEC0051A966 /* MemoryUsecase.swift */; }; + B5422CD12ADD3E4B0051A966 /* Memory.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5422CD02ADD3E4B0051A966 /* Memory.swift */; }; + B5422CD32ADD418C0051A966 /* FriendList.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5422CD22ADD418C0051A966 /* FriendList.swift */; }; + B5422CD62ADD42CD0051A966 /* DetailUsecaseError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5422CD52ADD42CD0051A966 /* DetailUsecaseError.swift */; }; B54741E029A3A4DB00B75BA3 /* LetterImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54741DF29A3A4DB00B75BA3 /* LetterImageView.swift */; }; - B54741EA29A494E200B75BA3 /* LetterImageError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54741E929A494E200B75BA3 /* LetterImageError.swift */; }; - B55469B32AA96F2C004D9FE6 /* BaseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55469B12AA96F2C004D9FE6 /* BaseViewController.swift */; }; B55469B42AA96F2C004D9FE6 /* BaseViewModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55469B22AA96F2C004D9FE6 /* BaseViewModelType.swift */; }; - B55469B62AA96F54004D9FE6 /* Character.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55469B52AA96F54004D9FE6 /* Character.swift */; }; + B55469B62AA96F54004D9FE6 /* DefaultCharacterType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55469B52AA96F54004D9FE6 /* DefaultCharacterType.swift */; }; + B55469EB2AAC3B5F004D9FE6 /* UIView+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55469EA2AAC3B5F004D9FE6 /* UIView+Animation.swift */; }; + B5546A0B2AADCB13004D9FE6 /* LetterUsecaseError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5546A0A2AADCB13004D9FE6 /* LetterUsecaseError.swift */; }; + B5546A4B2AADFE5C004D9FE6 /* MTResource.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5546A2A2AADD638004D9FE6 /* MTResource.framework */; }; + B5546A4C2AADFE5C004D9FE6 /* MTResource.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5546A2A2AADD638004D9FE6 /* MTResource.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B55BCEB428D8449E00AF7E45 /* MemoryCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55BCEB328D8449E00AF7E45 /* MemoryCollectionViewCell.swift */; }; B55BCEB628D99F8600AF7E45 /* Settings.bundle in Resources */ = {isa = PBXBuildFile; fileRef = B55BCEB528D99F8600AF7E45 /* Settings.bundle */; }; - B55BCEC628F5AFB200AF7E45 /* capsule.gif in Resources */ = {isa = PBXBuildFile; fileRef = B55BCEC528F5AFB200AF7E45 /* capsule.gif */; }; - B55BCEC828F5AFD500AF7E45 /* joystick.gif in Resources */ = {isa = PBXBuildFile; fileRef = B55BCEC728F5AFD500AF7E45 /* joystick.gif */; }; - B5706BE129ADC20A0093D198 /* CreateLetterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5706BE029ADC20A0093D198 /* CreateLetterView.swift */; }; B5706BF429B710FC0093D198 /* LetterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5706BF329B710FC0093D198 /* LetterView.swift */; }; B5706BF629B9D4650093D198 /* GuideView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5706BF529B9D4650093D198 /* GuideView.swift */; }; + B577E4F42ABE0B8A003CC102 /* SendLetterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B577E4F32ABE0B8A003CC102 /* SendLetterViewController.swift */; }; + B577E4F62ABE0C95003CC102 /* SendLetterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B577E4F52ABE0C95003CC102 /* SendLetterViewModel.swift */; }; + B577E4FD2ABFDE6F003CC102 /* SendLetterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B577E4FC2ABFDE6F003CC102 /* SendLetterView.swift */; }; + B577E5002AC373CA003CC102 /* PhotoPickerManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B577E4FF2AC373CA003CC102 /* PhotoPickerManager.swift */; }; + B577E5022AC37998003CC102 /* PHPickerError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B577E5012AC37998003CC102 /* PHPickerError.swift */; }; + B57D574E2AD9248500626BCD /* MailComposeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57D574D2AD9248500626BCD /* MailComposeManager.swift */; }; + B57D57692AD97DAE00626BCD /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57D57682AD97DAE00626BCD /* SplashView.swift */; }; B5A085DF2A972D2A00C8A98D /* LetterRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A085DE2A972D2A00C8A98D /* LetterRequestDTO.swift */; }; B5A085E12A972D7900C8A98D /* LoginRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A085E02A972D7900C8A98D /* LoginRequestDTO.swift */; }; B5A085E32A972DDC00C8A98D /* EditedMissionRequestDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A085E22A972DDC00C8A98D /* EditedMissionRequestDTO.swift */; }; @@ -116,31 +142,37 @@ B5B3C15F2A41D6F400AABD6F /* LetterUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B3C15E2A41D6F400AABD6F /* LetterUsecase.swift */; }; B5B3C1652A427B5800AABD6F /* UIControl+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B3C1642A427B5800AABD6F /* UIControl+Combine.swift */; }; B5B3C16F2A42B37000AABD6F /* UIViewController+Combine.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5B3C16E2A42B37000AABD6F /* UIViewController+Combine.swift */; }; + B5C0BF992AAEE6B000E9017C /* capsule.gif in Resources */ = {isa = PBXBuildFile; fileRef = B5C0BF932AAEE6B000E9017C /* capsule.gif */; }; + B5C0BF9A2AAEE6B000E9017C /* gifMa.gif in Resources */ = {isa = PBXBuildFile; fileRef = B5C0BF942AAEE6B000E9017C /* gifMa.gif */; }; + B5C0BF9B2AAEE6B000E9017C /* joystick.gif in Resources */ = {isa = PBXBuildFile; fileRef = B5C0BF952AAEE6B000E9017C /* joystick.gif */; }; + B5C0BF9C2AAEE6B000E9017C /* gifNi.gif in Resources */ = {isa = PBXBuildFile; fileRef = B5C0BF962AAEE6B000E9017C /* gifNi.gif */; }; + B5C0BF9D2AAEE6B000E9017C /* logo.gif in Resources */ = {isa = PBXBuildFile; fileRef = B5C0BF972AAEE6B000E9017C /* logo.gif */; }; + B5C0BF9E2AAEE6B000E9017C /* gifTto.gif in Resources */ = {isa = PBXBuildFile; fileRef = B5C0BF982AAEE6B000E9017C /* gifTto.gif */; }; B5C7FBED2AA7607300862021 /* BaseViewType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7FBEC2AA7607300862021 /* BaseViewType.swift */; }; B5C7FBF52AA859E300862021 /* BaseViewControllerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7FBF42AA859E300862021 /* BaseViewControllerType.swift */; }; B5C7FC0C2AA88AF500862021 /* UIViewController+Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7FC0B2AA88AF500862021 /* UIViewController+Alert.swift */; }; B5C7FC0E2AA88B3500862021 /* UIViewController+Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C7FC0D2AA88B3500862021 /* UIViewController+Keyboard.swift */; }; + B5D3D2932AE0D0500016F1D5 /* FriendListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D3D2922AE0D0500016F1D5 /* FriendListViewController.swift */; }; + B5D3D2952AE0D0B60016F1D5 /* FriendListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D3D2942AE0D0B60016F1D5 /* FriendListView.swift */; }; + B5D3D2972AE0D0CE0016F1D5 /* FriendListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D3D2962AE0D0CE0016F1D5 /* FriendListViewModel.swift */; }; + B5D3D2992AE0D1270016F1D5 /* FriendListUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D3D2982AE0D1270016F1D5 /* FriendListUsecase.swift */; }; B5E1F2AE285B2917006D880B /* Int+RandomNumber.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E1F2AD285B2917006D880B /* Int+RandomNumber.swift */; }; B5E1F2B2285ECBDF006D880B /* SelectManitteeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E1F2B1285ECBDF006D880B /* SelectManitteeViewController.swift */; }; + B5E850302AAEE4CF00652AC3 /* GIFSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E8502F2AAEE4CF00652AC3 /* GIFSet.swift */; }; + B5EDF4A32AB056B50086230C /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B5EDF4A52AB056B50086230C /* Localizable.strings */; }; + B5EDF4AE2AB078180086230C /* LocalizationScript.py in Resources */ = {isa = PBXBuildFile; fileRef = B5EDF4AD2AB078180086230C /* LocalizationScript.py */; }; B5F31BB028BE1CA700F61D0F /* UserDefaultStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F31BAF28BE1CA700F61D0F /* UserDefaultStorage.swift */; }; B5F31BB228BE1CD700F61D0F /* UserDefaultHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F31BB128BE1CD700F61D0F /* UserDefaultHandler.swift */; }; - B5F31C5028BF922E00F61D0F /* LetterViewController+MailCompose.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F31C4F28BF922E00F61D0F /* LetterViewController+MailCompose.swift */; }; B5F3F1062AA3516200FA1A32 /* RoomStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F3F1052AA3516200FA1A32 /* RoomStatus.swift */; }; B5F524CF28519AA000614FF7 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F524CE28519AA000614FF7 /* AppDelegate.swift */; }; B5F524D128519AA000614FF7 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F524D028519AA000614FF7 /* SceneDelegate.swift */; }; - B5F524D628519AA000614FF7 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B5F524D428519AA000614FF7 /* Main.storyboard */; }; B5F524D828519AA100614FF7 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5F524D728519AA100614FF7 /* Assets.xcassets */; }; B5F524DB28519AA100614FF7 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B5F524D928519AA100614FF7 /* LaunchScreen.storyboard */; }; B5F524FB28519C2A00614FF7 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F524FA28519C2A00614FF7 /* MainViewController.swift */; }; - B5F524FD28519EEC00614FF7 /* UIColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F524FC28519EEC00614FF7 /* UIColor+Extension.swift */; }; - B5F524FF28519EF300614FF7 /* UIFont+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F524FE28519EF300614FF7 /* UIFont+Extension.swift */; }; - B5F5250128519EFB00614FF7 /* ImageLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F5250028519EFB00614FF7 /* ImageLiteral.swift */; }; - B5F5250428519F7A00614FF7 /* DungGeunMo.otf in Resources */ = {isa = PBXBuildFile; fileRef = B5F5250328519F7A00614FF7 /* DungGeunMo.otf */; }; B5F525062851A05500614FF7 /* CreateRoomViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F525052851A05500614FF7 /* CreateRoomViewController.swift */; }; B5F525082851A05E00614FF7 /* InvitedCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F525072851A05E00614FF7 /* InvitedCodeViewController.swift */; }; B5F5250A2851A06700614FF7 /* DetailWaitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F525092851A06700614FF7 /* DetailWaitViewController.swift */; }; B5F5250E2851A07700614FF7 /* LetterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F5250D2851A07700614FF7 /* LetterViewController.swift */; }; - B5F525162851A0F600614FF7 /* DetailIng.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B5F525152851A0F600614FF7 /* DetailIng.storyboard */; }; B5F525242851A25400614FF7 /* UICollectionView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F525232851A25400614FF7 /* UICollectionView+Extension.swift */; }; B5F525262851A26D00614FF7 /* NSObject+ClassName.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F525252851A26D00614FF7 /* NSObject+ClassName.swift */; }; B5F525292851A2A400614FF7 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F525282851A2A400614FF7 /* Logger.swift */; }; @@ -149,21 +181,21 @@ B5F52537285481C800614FF7 /* LetterCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F52536285481C800614FF7 /* LetterCollectionViewCell.swift */; }; B5F5253B2854ABF200614FF7 /* MainButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F5253A2854ABF200614FF7 /* MainButton.swift */; }; B5F5253D2854B8CB00614FF7 /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F5253C2854B8CB00614FF7 /* UIView+Extension.swift */; }; - B5F525422855C97900614FF7 /* UILabel+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F525412855C97900614FF7 /* UILabel+Extension.swift */; }; + B5F525422855C97900614FF7 /* UILabel+Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F525412855C97900614FF7 /* UILabel+Config.swift */; }; B5FEE9DB28C8498A00DEA07E /* ImageCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5FEE9DA28C8498A00DEA07E /* ImageCacheManager.swift */; }; B5FEE9DD28C849B400DEA07E /* UIImageView+Cache.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5FEE9DC28C849B400DEA07E /* UIImageView+Cache.swift */; }; CB21C33928C4C10200128D25 /* CharacterCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB21C33828C4C10200128D25 /* CharacterCollectionViewCell.swift */; }; - CB2F0FCB28A5468C005F04C8 /* gifMa.gif in Resources */ = {isa = PBXBuildFile; fileRef = CB2F0FC828A5468C005F04C8 /* gifMa.gif */; }; - CB2F0FCC28A5468C005F04C8 /* gifTto.gif in Resources */ = {isa = PBXBuildFile; fileRef = CB2F0FC928A5468C005F04C8 /* gifTto.gif */; }; - CB2F0FCD28A5468C005F04C8 /* gifNi.gif in Resources */ = {isa = PBXBuildFile; fileRef = CB2F0FCA28A5468C005F04C8 /* gifNi.gif */; }; CB358C0228A564080084D001 /* SettingDeveloperInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB358C0128A564080084D001 /* SettingDeveloperInfoViewController.swift */; }; - CB5AE3E4285AAF7400382EA3 /* RoomInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB5AE3E3285AAF7400382EA3 /* RoomInfoView.swift */; }; - CB5AE3E6285AB76800382EA3 /* PeopleInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB5AE3E5285AB76800382EA3 /* PeopleInfoView.swift */; }; CB637A3B28595C1200FF1240 /* ParticipateRoomViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB637A3A28595C1200FF1240 /* ParticipateRoomViewController.swift */; }; CB6637E82959CBC60050BD04 /* SkeletonView in Frameworks */ = {isa = PBXBuildFile; productRef = CB6637E72959CBC60050BD04 /* SkeletonView */; }; + CB66FD6C2AE7FCDE00358CA2 /* LoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB66FD6B2AE7FCDE00358CA2 /* LoginView.swift */; }; + CB66FD6E2AE7FCE900358CA2 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB66FD6D2AE7FCE900358CA2 /* LoginViewModel.swift */; }; CB674C1C285966020063A6B7 /* InputInvitedCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB674C1B285966020063A6B7 /* InputInvitedCodeView.swift */; }; - CB674C2128596A310063A6B7 /* CheckRoomViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB674C2028596A310063A6B7 /* CheckRoomViewController.swift */; }; + CB674C2128596A310063A6B7 /* ParticipationRoomDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB674C2028596A310063A6B7 /* ParticipationRoomDetailsViewController.swift */; }; CB85DE1F28A76F3400399109 /* SettingDeveloperInfoHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB85DE1E28A76F3400399109 /* SettingDeveloperInfoHeaderView.swift */; }; + CB9332562AFCAE3100B7F87A /* LoginUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB9332552AFCAE3100B7F87A /* LoginUsecase.swift */; }; + CB93325A2AFCAF3F00B7F87A /* LoginInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB9332592AFCAF3F00B7F87A /* LoginInfo.swift */; }; + CB93325F2AFCBD4D00B7F87A /* LoginUsecaseError.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB93325E2AFCBD4D00B7F87A /* LoginUsecaseError.swift */; }; CB9592B22855C09A00847751 /* ManitoRoomCollectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB9592B12855C09A00847751 /* ManitoRoomCollectionCell.swift */; }; CB9592B52855D52700847751 /* CommonMissionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB9592B42855D52700847751 /* CommonMissionView.swift */; }; CBA15C94285CE1A80051EDE2 /* ChooseCharacterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBA15C93285CE1A80051EDE2 /* ChooseCharacterViewController.swift */; }; @@ -171,25 +203,45 @@ CBA4D7A62856D48C0018BDC2 /* RoomStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBA4D7A52856D48C0018BDC2 /* RoomStateView.swift */; }; CBBFF77C287AE491006A5964 /* DeveloperInfoViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBBFF77B287AE491006A5964 /* DeveloperInfoViewCell.swift */; }; D70220232A7A22EC0024BACD /* CreateRoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70220222A7A22EC0024BACD /* CreateRoomView.swift */; }; - D71939532AA1C84900A73D6C /* SettingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71939522AA1C84900A73D6C /* SettingService.swift */; }; + D71939532AA1C84900A73D6C /* SettingUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71939522AA1C84900A73D6C /* SettingUsecase.swift */; }; D71939552AA1C85300A73D6C /* SettingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71939542AA1C85300A73D6C /* SettingViewModel.swift */; }; D71939592AA2343E00A73D6C /* NicknameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71939582AA2343E00A73D6C /* NicknameView.swift */; }; D719395F2AA23F2300A73D6C /* NicknameViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D719395E2AA23F2300A73D6C /* NicknameViewModel.swift */; }; - D71939612AA23F3B00A73D6C /* NicknameService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71939602AA23F3B00A73D6C /* NicknameService.swift */; }; + D71939612AA23F3B00A73D6C /* NicknameUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71939602AA23F3B00A73D6C /* NicknameUsecase.swift */; }; D724AF5D287088310003F280 /* SettingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D724AF5C287088310003F280 /* SettingViewController.swift */; }; D724AF5F28708D830003F280 /* TopCharacterImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D724AF5E28708D830003F280 /* TopCharacterImageView.swift */; }; D739C36728C7B82500161117 /* NicknameDTO.swift in Sources */ = {isa = PBXBuildFile; fileRef = D739C36628C7B82500161117 /* NicknameDTO.swift */; }; + D7537F612AA70C40005AC78B /* ParticipatedRoomInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7537F602AA70C40005AC78B /* ParticipatedRoomInfo.swift */; }; + D7537F672AA75AFB005AC78B /* ParticipationRoomDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7537F662AA75AFB005AC78B /* ParticipationRoomDetailsView.swift */; }; + D7537F692AA76DD0005AC78B /* ParticipationRoomDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7537F682AA76DD0005AC78B /* ParticipationRoomDetailsViewModel.swift */; }; + D75DEEC52AA612CE00BD3AC7 /* ParticipateRoomUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D75DEEC42AA612CE00BD3AC7 /* ParticipateRoomUsecase.swift */; }; + D75DEEC72AA612DB00BD3AC7 /* ParticipateRoomViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D75DEEC62AA612DB00BD3AC7 /* ParticipateRoomViewModel.swift */; }; D75E8C7F28D76FFB004A6C41 /* LetterCountBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D75E8C7E28D76FFB004A6C41 /* LetterCountBadgeView.swift */; }; - D77224DF285DDAB7008B760B /* Notification+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77224DE285DDAB7008B760B /* Notification+Extension.swift */; }; + D76E024F2AF5DD9800F079FB /* InvitedCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76E024E2AF5DD9800F079FB /* InvitedCodeView.swift */; }; + D76E02512AF5DE5500F079FB /* InvitedCodeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76E02502AF5DE5500F079FB /* InvitedCodeViewModel.swift */; }; + D76E025F2AF7D12600F079FB /* ParticipateRoomError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76E025E2AF7D12600F079FB /* ParticipateRoomError.swift */; }; D777D2C928C7780E008655BD /* URLLiteral.swift in Sources */ = {isa = PBXBuildFile; fileRef = D777D2C828C7780E008655BD /* URLLiteral.swift */; }; + D78B97E02AEFD20A009D9522 /* CreateRoomError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78B97DF2AEFD20A009D9522 /* CreateRoomError.swift */; }; + D78B97E52AF11DC1009D9522 /* MainRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78B97E42AF11DC1009D9522 /* MainRepository.swift */; }; + D78B97E72AF11DCD009D9522 /* DetailRoomRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78B97E62AF11DCD009D9522 /* DetailRoomRepository.swift */; }; + D78B97E92AF11DD7009D9522 /* RoomParticipationRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78B97E82AF11DD7009D9522 /* RoomParticipationRepository.swift */; }; + D78B97EB2AF11DE2009D9522 /* LetterRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78B97EA2AF11DE2009D9522 /* LetterRepository.swift */; }; + D78B97ED2AF11DE8009D9522 /* SettingRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78B97EC2AF11DE8009D9522 /* SettingRepository.swift */; }; + D78B97EF2AF11DED009D9522 /* LoginRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78B97EE2AF11DED009D9522 /* LoginRepository.swift */; }; + D78B97F12AF11DF0009D9522 /* TokenRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78B97F02AF11DF0009D9522 /* TokenRepository.swift */; }; + D78B97F52AF23CD0009D9522 /* CreateRoomInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = D78B97F42AF23CD0009D9522 /* CreateRoomInfo.swift */; }; + D79938882ABEC80300711D5B /* ChooseCharacterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D79938872ABEC80300711D5B /* ChooseCharacterViewModel.swift */; }; D79E9D8A2A826E0200C031F0 /* CreateRoomViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D79E9D892A826E0200C031F0 /* CreateRoomViewModel.swift */; }; + D7ADDA3C2AFBBDA600755835 /* SettingError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ADDA3B2AFBBDA600755835 /* SettingError.swift */; }; + D7AE5ECB2AC1C32F00A20D0A /* ChooseCharacterError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7AE5ECA2AC1C32F00A20D0A /* ChooseCharacterError.swift */; }; D7B6C97C2858B2D40024F326 /* CheckRoomInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7B6C97B2858B2D40024F326 /* CheckRoomInfoView.swift */; }; - D7B83FD62A95A48700BFD8FF /* CreateRoomService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7B83FD52A95A48700BFD8FF /* CreateRoomService.swift */; }; + D7B83FD62A95A48700BFD8FF /* CreateRoomUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7B83FD52A95A48700BFD8FF /* CreateRoomUsecase.swift */; }; + D7BA95B52AF8AB9400455107 /* NicknameError.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7BA95B42AF8AB9400455107 /* NicknameError.swift */; }; + D7C0D9332AF4B67D00B5B9B1 /* TextFieldUsecase.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C0D9322AF4B67D00B5B9B1 /* TextFieldUsecase.swift */; }; D7C4A1A72A0B2EB000C3AE4C /* CharacterCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C4A1A62A0B2EB000C3AE4C /* CharacterCollectionView.swift */; }; D7C4A1A92A0B895300C3AE4C /* ChooseCharacterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C4A1A82A0B895300C3AE4C /* ChooseCharacterView.swift */; }; D7C4A1AB2A0D1AA400C3AE4C /* ParticipateRoomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C4A1AA2A0D1AA400C3AE4C /* ParticipateRoomView.swift */; }; D7C4A1AD2A0DD8FA00C3AE4C /* SettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C4A1AC2A0DD8FA00C3AE4C /* SettingView.swift */; }; - D7C4A1B02A0E522800C3AE4C /* SettingViewController+MailComposeViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C4A1AF2A0E522800C3AE4C /* SettingViewController+MailComposeViewControllerDelegate.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -200,6 +252,13 @@ remoteGlobalIDString = B5F524CA28519AA000614FF7; remoteInfo = Manito; }; + B5546A292AADD638004D9FE6 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = B5546A252AADD638004D9FE6 /* MTResource.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B5546A1B2AADD618004D9FE6; + remoteInfo = MTResource; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -209,6 +268,7 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + B5546A4C2AADFE5C004D9FE6 /* MTResource.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -217,28 +277,36 @@ /* Begin PBXFileReference section */ 333BF66528571CF00039F77F /* FriendCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendCollectionViewCell.swift; sourceTree = ""; }; - 333BF669285864CE0039F77F /* MemoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryViewController.swift; sourceTree = ""; }; - 33BDF5612856E03800564211 /* FriendListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendListViewController.swift; sourceTree = ""; }; 39018F4128C4708A00C78DBA /* UIButton+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIButton+Extension.swift"; sourceTree = ""; }; 3904F4F92AA36F4F00B6264F /* UserInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfo.swift; sourceTree = ""; }; 3904F4FB2AA3729300B6264F /* InvitationCode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitationCode.swift; sourceTree = ""; }; 3904F4FD2AA3743C00B6264F /* IndividualMission.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndividualMission.swift; sourceTree = ""; }; 3904F4FF2AA3754400B6264F /* MessageCountInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCountInfo.swift; sourceTree = ""; }; - 3904F5052AA37A3C00B6264F /* MockDetailWaitService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDetailWaitService.swift; sourceTree = ""; }; + 390E5CAC2AB89BCE00EBE52E /* RoomListItemTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomListItemTest.swift; sourceTree = ""; }; + 390E5CB12AB8A70600EBE52E /* Date+ExtensionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+ExtensionTest.swift"; sourceTree = ""; }; 3915D54A2A7516A2002D6C25 /* Key.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Key.plist; sourceTree = ""; }; 3915D54C2A7516E9002D6C25 /* Key+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Key+Extension.swift"; sourceTree = ""; }; 391612D729E9231B004AE982 /* DetailWaitView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWaitView.swift; sourceTree = ""; }; 391B3002286198C200421F1D /* SizeLiteral.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SizeLiteral.swift; sourceTree = ""; }; 392EC77D2855C388006918A9 /* SettingButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingButton.swift; sourceTree = ""; }; 392EC7802855D17D006918A9 /* DetailWaitTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWaitTitleView.swift; sourceTree = ""; }; + 395402D12AAF0B4F003F3012 /* Navigationable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Navigationable.swift; sourceTree = ""; }; + 395402D32AAF0BC5003F3012 /* UIViewController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extension.swift"; sourceTree = ""; }; 395B5BC12A25E20000CE1420 /* DetailWaitViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWaitViewModel.swift; sourceTree = ""; }; 395B5BCB2A2F6A9700CE1420 /* DetailWaitViewModelTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWaitViewModelTest.swift; sourceTree = ""; }; + 39632A272AD81F2E00A6E61D /* DetailWaitUsecaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWaitUsecaseTest.swift; sourceTree = ""; }; + 39632A2A2AD842A800A6E61D /* DetailWaitUsecaseError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWaitUsecaseError.swift; sourceTree = ""; }; + 39632A722AF0DC2E00A6E61D /* DetailEditViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailEditViewModel.swift; sourceTree = ""; }; + 39632A752AF0EE6600A6E61D /* DetailEditUsecase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailEditUsecase.swift; sourceTree = ""; }; + 39632A832AFB24FD00A6E61D /* DetailEditUsecaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailEditUsecaseTest.swift; sourceTree = ""; }; + 39632AC42B0B477700A6E61D /* DetailEditUsecaseError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailEditUsecaseError.swift; sourceTree = ""; }; + 39833CFD2AB09A6C009D173A /* Keyboardable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keyboardable.swift; sourceTree = ""; }; 398B1C9A29F10B0300DEFCEC /* DetailEditView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailEditView.swift; sourceTree = ""; }; 398B1CB92A12415B00DEFCEC /* ManitoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ManitoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 398CED072AB9E48F00F50CBD /* RoomInfoTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomInfoTest.swift; sourceTree = ""; }; 399D17AA2856C83B00F50D9D /* DetailEditViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailEditViewController.swift; sourceTree = ""; }; - 39BDDCC32A52BB4A005E0A71 /* DetailWaitService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWaitService.swift; sourceTree = ""; }; 39C957CD2876E2ED00A04A2B /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; - 39C957CF2879521400A04A2B /* String+Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Date.swift"; sourceTree = ""; }; + 39C957CF2879521400A04A2B /* String+DateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+DateFormatter.swift"; sourceTree = ""; }; 39C957D12879523200A04A2B /* Date+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extension.swift"; sourceTree = ""; }; 39C957EC287AE4D100A04A2B /* MainEndPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainEndPoint.swift; sourceTree = ""; }; 39C957F0287D984900A04A2B /* APIEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APIEnvironment.swift; sourceTree = ""; }; @@ -250,12 +318,12 @@ 39EE956C2A401ED400AF6857 /* DetailingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailingView.swift; sourceTree = ""; }; 39EE956E2A404A3800AF6857 /* MissionEditViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MissionEditViewController.swift; sourceTree = ""; }; 39EEF65D28CB3CFE00437654 /* LoginEndPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginEndPoint.swift; sourceTree = ""; }; + 39EF30F82AD3DE6700466A90 /* DetailWaitUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWaitUseCase.swift; sourceTree = ""; }; 39F1C12D28D756E600585B83 /* LetterImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterImageViewController.swift; sourceTree = ""; }; 39FFE32728EEFFFE008442EE /* MoreButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoreButton.swift; sourceTree = ""; }; 39FFE33528F45E44008442EE /* GoogleService-Info-Prod.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info-Prod.plist"; sourceTree = ""; }; 435B17672913E71400212663 /* DetailingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailingViewController.swift; sourceTree = ""; }; 7E0C5C352855B22700F698D1 /* CreateNicknameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateNicknameViewController.swift; sourceTree = ""; }; - 7E0C5C372855B73100F698D1 /* Splash.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Splash.storyboard; sourceTree = ""; }; 7E15F67D28B35B3D00441305 /* TextLiteral.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextLiteral.swift; sourceTree = ""; }; 7E3058C52854B47F00489E6A /* InputTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputTitleView.swift; sourceTree = ""; }; 7E3058C72854B64D00489E6A /* InputCapacityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputCapacityView.swift; sourceTree = ""; }; @@ -263,42 +331,59 @@ 7E7542B22923744300D725CB /* ToastPopupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastPopupView.swift; sourceTree = ""; }; 7EA25C2628C4FEA800746AEA /* ChangeNicknameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeNicknameViewController.swift; sourceTree = ""; }; 7ED845B92876BE530075AC61 /* SettingViewTableCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingViewTableCell.swift; sourceTree = ""; }; - B50B1AF82856B2C60080992C /* CreateLetterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateLetterViewController.swift; sourceTree = ""; }; B50B1AFA2856B5180080992C /* IndividualMissionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndividualMissionView.swift; sourceTree = ""; }; - B50B1AFC2856C3500080992C /* CreateLetterTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateLetterTextView.swift; sourceTree = ""; }; - B50B1AFE2856D3820080992C /* CreateLetterPhotoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateLetterPhotoView.swift; sourceTree = ""; }; + B50B1AFC2856C3500080992C /* SendLetterTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendLetterTextView.swift; sourceTree = ""; }; + B50B1AFE2856D3820080992C /* SendLetterPhotoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendLetterPhotoView.swift; sourceTree = ""; }; B50B1B29285AB7650080992C /* SplashViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashViewController.swift; sourceTree = ""; }; - B50B1B3B285AD2B20080992C /* logo.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = logo.gif; sourceTree = ""; }; B50B1B40285B048A0080992C /* OpenManittoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenManittoViewController.swift; sourceTree = ""; }; B50B1B43285B146A0080992C /* OpenManittoCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenManittoCollectionViewCell.swift; sourceTree = ""; }; B50CEE902A445EB700CF1C76 /* UIScrollView+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIScrollView+Combine.swift"; sourceTree = ""; }; B50CEEBF2A6FF82600CF1C76 /* Publisher+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+Async.swift"; sourceTree = ""; }; B517C04928D1F7EC0008BED0 /* TokenEndPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenEndPoint.swift; sourceTree = ""; }; B53A35B62A963F2100B720BC /* DetailRoomEndPoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailRoomEndPoint.swift; sourceTree = ""; }; - B53A35BF2A96F21A00B720BC /* MainRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainRepository.swift; sourceTree = ""; }; - B53A35C32A96F50100B720BC /* DetailRoomRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailRoomRepository.swift; sourceTree = ""; }; - B53A35C52A96F6DB00B720BC /* RoomParticipationRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomParticipationRepository.swift; sourceTree = ""; }; - B53A35C72A96F84700B720BC /* LetterRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterRepository.swift; sourceTree = ""; }; - B53A35CB2A96F99D00B720BC /* SettingRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingRepository.swift; sourceTree = ""; }; - B53A35CD2A96FA7A00B720BC /* LoginRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRepository.swift; sourceTree = ""; }; - B53A35CF2A97194D00B720BC /* TokenRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenRepository.swift; sourceTree = ""; }; + B53A35BF2A96F21A00B720BC /* MainRepositoryImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainRepositoryImpl.swift; sourceTree = ""; }; + B53A35C32A96F50100B720BC /* DetailRoomRepositoryImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailRoomRepositoryImpl.swift; sourceTree = ""; }; + B53A35C52A96F6DB00B720BC /* RoomParticipationRepositoryImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomParticipationRepositoryImpl.swift; sourceTree = ""; }; + B53A35C72A96F84700B720BC /* LetterRepositoryImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterRepositoryImpl.swift; sourceTree = ""; }; + B53A35CB2A96F99D00B720BC /* SettingRepositoryImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingRepositoryImpl.swift; sourceTree = ""; }; + B53A35CD2A96FA7A00B720BC /* LoginRepositoryImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRepositoryImpl.swift; sourceTree = ""; }; + B53A35CF2A97194D00B720BC /* TokenRepositoryImpl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenRepositoryImpl.swift; sourceTree = ""; }; B53A35D32A97266D00B720BC /* CreatedRoomRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreatedRoomRequestDTO.swift; sourceTree = ""; }; B53AD4122A8BC6B300B83B33 /* Dev.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Dev.xcconfig; sourceTree = ""; }; B53AD4132A8BC6BE00B83B33 /* Prod.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Prod.xcconfig; sourceTree = ""; }; B53AD41D2A8C88E100B83B33 /* GoogleService-Info-Dev.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GoogleService-Info-Dev.plist"; sourceTree = ""; }; + B53CD0372ADF941200BE7353 /* OpenManittoUsecase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenManittoUsecase.swift; sourceTree = ""; }; + B53CD0392ADF9B6100BE7353 /* OpenManittoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenManittoViewModel.swift; sourceTree = ""; }; + B53CD03F2ADFC25C00BE7353 /* SelectManitteeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectManitteeViewModel.swift; sourceTree = ""; }; + B5422CA12ADD113C0051A966 /* SplashUsecase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashUsecase.swift; sourceTree = ""; }; + B5422CA42ADD16C40051A966 /* SplashViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashViewModel.swift; sourceTree = ""; }; + B5422CA62ADD191A0051A966 /* EntryType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryType.swift; sourceTree = ""; }; + B5422CB12ADD2AE60051A966 /* MemoryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryViewController.swift; sourceTree = ""; }; + B5422CB32ADD2B040051A966 /* MemoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryView.swift; sourceTree = ""; }; + B5422CB52ADD2B190051A966 /* MemoryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryViewModel.swift; sourceTree = ""; }; + B5422CC92ADD3D1C0051A966 /* SendLetterUsecase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendLetterUsecase.swift; sourceTree = ""; }; + B5422CCE2ADD3DEC0051A966 /* MemoryUsecase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryUsecase.swift; sourceTree = ""; }; + B5422CD02ADD3E4B0051A966 /* Memory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Memory.swift; sourceTree = ""; }; + B5422CD22ADD418C0051A966 /* FriendList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendList.swift; sourceTree = ""; }; + B5422CD52ADD42CD0051A966 /* DetailUsecaseError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailUsecaseError.swift; sourceTree = ""; }; B54741DF29A3A4DB00B75BA3 /* LetterImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterImageView.swift; sourceTree = ""; }; - B54741E929A494E200B75BA3 /* LetterImageError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterImageError.swift; sourceTree = ""; }; - B55469B12AA96F2C004D9FE6 /* BaseViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseViewController.swift; sourceTree = ""; }; B55469B22AA96F2C004D9FE6 /* BaseViewModelType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseViewModelType.swift; sourceTree = ""; }; - B55469B52AA96F54004D9FE6 /* Character.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Character.swift; sourceTree = ""; }; + B55469B52AA96F54004D9FE6 /* DefaultCharacterType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultCharacterType.swift; sourceTree = ""; }; + B55469EA2AAC3B5F004D9FE6 /* UIView+Animation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Animation.swift"; sourceTree = ""; }; + B5546A0A2AADCB13004D9FE6 /* LetterUsecaseError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterUsecaseError.swift; sourceTree = ""; }; + B5546A252AADD638004D9FE6 /* MTResource.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = MTResource.xcodeproj; path = MTResource/MTResource.xcodeproj; sourceTree = ""; }; B55BCEB328D8449E00AF7E45 /* MemoryCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryCollectionViewCell.swift; sourceTree = ""; }; B55BCEB528D99F8600AF7E45 /* Settings.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Settings.bundle; sourceTree = ""; }; - B55BCEC528F5AFB200AF7E45 /* capsule.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = capsule.gif; sourceTree = ""; }; - B55BCEC728F5AFD500AF7E45 /* joystick.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = joystick.gif; sourceTree = ""; }; - B5706BE029ADC20A0093D198 /* CreateLetterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateLetterView.swift; sourceTree = ""; }; B5706BF329B710FC0093D198 /* LetterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterView.swift; sourceTree = ""; }; B5706BF529B9D4650093D198 /* GuideView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuideView.swift; sourceTree = ""; }; + B577E4F32ABE0B8A003CC102 /* SendLetterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendLetterViewController.swift; sourceTree = ""; }; + B577E4F52ABE0C95003CC102 /* SendLetterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendLetterViewModel.swift; sourceTree = ""; }; + B577E4FC2ABFDE6F003CC102 /* SendLetterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendLetterView.swift; sourceTree = ""; }; + B577E4FF2AC373CA003CC102 /* PhotoPickerManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPickerManager.swift; sourceTree = ""; }; + B577E5012AC37998003CC102 /* PHPickerError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PHPickerError.swift; sourceTree = ""; }; B57CB3032A763C1600474042 /* MTNetwork */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = MTNetwork; sourceTree = ""; }; + B57D574D2AD9248500626BCD /* MailComposeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailComposeManager.swift; sourceTree = ""; }; + B57D57682AD97DAE00626BCD /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = ""; }; B5A085DE2A972D2A00C8A98D /* LetterRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterRequestDTO.swift; sourceTree = ""; }; B5A085E02A972D7900C8A98D /* LoginRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRequestDTO.swift; sourceTree = ""; }; B5A085E22A972DDC00C8A98D /* EditedMissionRequestDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditedMissionRequestDTO.swift; sourceTree = ""; }; @@ -325,33 +410,39 @@ B5B3C15E2A41D6F400AABD6F /* LetterUsecase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterUsecase.swift; sourceTree = ""; }; B5B3C1642A427B5800AABD6F /* UIControl+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIControl+Combine.swift"; sourceTree = ""; }; B5B3C16E2A42B37000AABD6F /* UIViewController+Combine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Combine.swift"; sourceTree = ""; }; + B5C0BF932AAEE6B000E9017C /* capsule.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = capsule.gif; sourceTree = ""; }; + B5C0BF942AAEE6B000E9017C /* gifMa.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = gifMa.gif; sourceTree = ""; }; + B5C0BF952AAEE6B000E9017C /* joystick.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = joystick.gif; sourceTree = ""; }; + B5C0BF962AAEE6B000E9017C /* gifNi.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = gifNi.gif; sourceTree = ""; }; + B5C0BF972AAEE6B000E9017C /* logo.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = logo.gif; sourceTree = ""; }; + B5C0BF982AAEE6B000E9017C /* gifTto.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = gifTto.gif; sourceTree = ""; }; B5C7FBEC2AA7607300862021 /* BaseViewType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewType.swift; sourceTree = ""; }; B5C7FBF42AA859E300862021 /* BaseViewControllerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseViewControllerType.swift; sourceTree = ""; }; B5C7FC0B2AA88AF500862021 /* UIViewController+Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Alert.swift"; sourceTree = ""; }; B5C7FC0D2AA88B3500862021 /* UIViewController+Keyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Keyboard.swift"; sourceTree = ""; }; + B5D3D2922AE0D0500016F1D5 /* FriendListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendListViewController.swift; sourceTree = ""; }; + B5D3D2942AE0D0B60016F1D5 /* FriendListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendListView.swift; sourceTree = ""; }; + B5D3D2962AE0D0CE0016F1D5 /* FriendListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendListViewModel.swift; sourceTree = ""; }; + B5D3D2982AE0D1270016F1D5 /* FriendListUsecase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendListUsecase.swift; sourceTree = ""; }; B5E1F2AD285B2917006D880B /* Int+RandomNumber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int+RandomNumber.swift"; sourceTree = ""; }; B5E1F2B1285ECBDF006D880B /* SelectManitteeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectManitteeViewController.swift; sourceTree = ""; }; + B5E8502F2AAEE4CF00652AC3 /* GIFSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GIFSet.swift; sourceTree = ""; }; + B5EDF4A42AB056B50086230C /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + B5EDF4AD2AB078180086230C /* LocalizationScript.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; path = LocalizationScript.py; sourceTree = ""; }; B5F31BAF28BE1CA700F61D0F /* UserDefaultStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultStorage.swift; sourceTree = ""; }; B5F31BB128BE1CD700F61D0F /* UserDefaultHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultHandler.swift; sourceTree = ""; }; - B5F31C4F28BF922E00F61D0F /* LetterViewController+MailCompose.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LetterViewController+MailCompose.swift"; sourceTree = ""; }; B5F3F1052AA3516200FA1A32 /* RoomStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStatus.swift; sourceTree = ""; }; B5F524CB28519AA000614FF7 /* Manito.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Manito.app; sourceTree = BUILT_PRODUCTS_DIR; }; B5F524CE28519AA000614FF7 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; B5F524D028519AA000614FF7 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; - B5F524D528519AA000614FF7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; B5F524D728519AA100614FF7 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; B5F524DA28519AA100614FF7 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; B5F524DC28519AA100614FF7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; B5F524FA28519C2A00614FF7 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; - B5F524FC28519EEC00614FF7 /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; - B5F524FE28519EF300614FF7 /* UIFont+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Extension.swift"; sourceTree = ""; }; - B5F5250028519EFB00614FF7 /* ImageLiteral.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageLiteral.swift; sourceTree = ""; }; - B5F5250328519F7A00614FF7 /* DungGeunMo.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = DungGeunMo.otf; sourceTree = ""; }; B5F525052851A05500614FF7 /* CreateRoomViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomViewController.swift; sourceTree = ""; }; B5F525072851A05E00614FF7 /* InvitedCodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitedCodeViewController.swift; sourceTree = ""; }; B5F525092851A06700614FF7 /* DetailWaitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailWaitViewController.swift; sourceTree = ""; }; B5F5250D2851A07700614FF7 /* LetterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterViewController.swift; sourceTree = ""; }; - B5F525152851A0F600614FF7 /* DetailIng.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = DetailIng.storyboard; sourceTree = ""; }; B5F525232851A25400614FF7 /* UICollectionView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Extension.swift"; sourceTree = ""; }; B5F525252851A26D00614FF7 /* NSObject+ClassName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSObject+ClassName.swift"; sourceTree = ""; }; B5F525282851A2A400614FF7 /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; @@ -360,20 +451,20 @@ B5F52536285481C800614FF7 /* LetterCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterCollectionViewCell.swift; sourceTree = ""; }; B5F5253A2854ABF200614FF7 /* MainButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainButton.swift; sourceTree = ""; }; B5F5253C2854B8CB00614FF7 /* UIView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extension.swift"; sourceTree = ""; }; - B5F525412855C97900614FF7 /* UILabel+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Extension.swift"; sourceTree = ""; }; + B5F525412855C97900614FF7 /* UILabel+Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UILabel+Config.swift"; sourceTree = ""; }; B5FEE9DA28C8498A00DEA07E /* ImageCacheManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCacheManager.swift; sourceTree = ""; }; B5FEE9DC28C849B400DEA07E /* UIImageView+Cache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImageView+Cache.swift"; sourceTree = ""; }; CB21C33828C4C10200128D25 /* CharacterCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterCollectionViewCell.swift; sourceTree = ""; }; - CB2F0FC828A5468C005F04C8 /* gifMa.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = gifMa.gif; sourceTree = ""; }; - CB2F0FC928A5468C005F04C8 /* gifTto.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = gifTto.gif; sourceTree = ""; }; - CB2F0FCA28A5468C005F04C8 /* gifNi.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = gifNi.gif; sourceTree = ""; }; CB358C0128A564080084D001 /* SettingDeveloperInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingDeveloperInfoViewController.swift; sourceTree = ""; }; - CB5AE3E3285AAF7400382EA3 /* RoomInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomInfoView.swift; sourceTree = ""; }; - CB5AE3E5285AB76800382EA3 /* PeopleInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeopleInfoView.swift; sourceTree = ""; }; CB637A3A28595C1200FF1240 /* ParticipateRoomViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipateRoomViewController.swift; sourceTree = ""; }; + CB66FD6B2AE7FCDE00358CA2 /* LoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginView.swift; sourceTree = ""; }; + CB66FD6D2AE7FCE900358CA2 /* LoginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewModel.swift; sourceTree = ""; }; CB674C1B285966020063A6B7 /* InputInvitedCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputInvitedCodeView.swift; sourceTree = ""; }; - CB674C2028596A310063A6B7 /* CheckRoomViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckRoomViewController.swift; sourceTree = ""; }; + CB674C2028596A310063A6B7 /* ParticipationRoomDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipationRoomDetailsViewController.swift; sourceTree = ""; }; CB85DE1E28A76F3400399109 /* SettingDeveloperInfoHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingDeveloperInfoHeaderView.swift; sourceTree = ""; }; + CB9332552AFCAE3100B7F87A /* LoginUsecase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginUsecase.swift; sourceTree = ""; }; + CB9332592AFCAF3F00B7F87A /* LoginInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginInfo.swift; sourceTree = ""; }; + CB93325E2AFCBD4D00B7F87A /* LoginUsecaseError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginUsecaseError.swift; sourceTree = ""; }; CB9592B12855C09A00847751 /* ManitoRoomCollectionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManitoRoomCollectionCell.swift; sourceTree = ""; }; CB9592B42855D52700847751 /* CommonMissionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonMissionView.swift; sourceTree = ""; }; CBA15C93285CE1A80051EDE2 /* ChooseCharacterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChooseCharacterViewController.swift; sourceTree = ""; }; @@ -381,25 +472,45 @@ CBA4D7A52856D48C0018BDC2 /* RoomStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomStateView.swift; sourceTree = ""; }; CBBFF77B287AE491006A5964 /* DeveloperInfoViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperInfoViewCell.swift; sourceTree = ""; }; D70220222A7A22EC0024BACD /* CreateRoomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomView.swift; sourceTree = ""; }; - D71939522AA1C84900A73D6C /* SettingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingService.swift; sourceTree = ""; }; + D71939522AA1C84900A73D6C /* SettingUsecase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingUsecase.swift; sourceTree = ""; }; D71939542AA1C85300A73D6C /* SettingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingViewModel.swift; sourceTree = ""; }; D71939582AA2343E00A73D6C /* NicknameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameView.swift; sourceTree = ""; }; D719395E2AA23F2300A73D6C /* NicknameViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameViewModel.swift; sourceTree = ""; }; - D71939602AA23F3B00A73D6C /* NicknameService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameService.swift; sourceTree = ""; }; + D71939602AA23F3B00A73D6C /* NicknameUsecase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameUsecase.swift; sourceTree = ""; }; D724AF5C287088310003F280 /* SettingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingViewController.swift; sourceTree = ""; }; D724AF5E28708D830003F280 /* TopCharacterImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TopCharacterImageView.swift; sourceTree = ""; }; D739C36628C7B82500161117 /* NicknameDTO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameDTO.swift; sourceTree = ""; }; + D7537F602AA70C40005AC78B /* ParticipatedRoomInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipatedRoomInfo.swift; sourceTree = ""; }; + D7537F662AA75AFB005AC78B /* ParticipationRoomDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipationRoomDetailsView.swift; sourceTree = ""; }; + D7537F682AA76DD0005AC78B /* ParticipationRoomDetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipationRoomDetailsViewModel.swift; sourceTree = ""; }; + D75DEEC42AA612CE00BD3AC7 /* ParticipateRoomUsecase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipateRoomUsecase.swift; sourceTree = ""; }; + D75DEEC62AA612DB00BD3AC7 /* ParticipateRoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipateRoomViewModel.swift; sourceTree = ""; }; D75E8C7E28D76FFB004A6C41 /* LetterCountBadgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterCountBadgeView.swift; sourceTree = ""; }; - D77224DE285DDAB7008B760B /* Notification+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Notification+Extension.swift"; sourceTree = ""; }; + D76E024E2AF5DD9800F079FB /* InvitedCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitedCodeView.swift; sourceTree = ""; }; + D76E02502AF5DE5500F079FB /* InvitedCodeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvitedCodeViewModel.swift; sourceTree = ""; }; + D76E025E2AF7D12600F079FB /* ParticipateRoomError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipateRoomError.swift; sourceTree = ""; }; D777D2C828C7780E008655BD /* URLLiteral.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLLiteral.swift; sourceTree = ""; }; + D78B97DF2AEFD20A009D9522 /* CreateRoomError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomError.swift; sourceTree = ""; }; + D78B97E42AF11DC1009D9522 /* MainRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainRepository.swift; sourceTree = ""; }; + D78B97E62AF11DCD009D9522 /* DetailRoomRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailRoomRepository.swift; sourceTree = ""; }; + D78B97E82AF11DD7009D9522 /* RoomParticipationRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomParticipationRepository.swift; sourceTree = ""; }; + D78B97EA2AF11DE2009D9522 /* LetterRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterRepository.swift; sourceTree = ""; }; + D78B97EC2AF11DE8009D9522 /* SettingRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingRepository.swift; sourceTree = ""; }; + D78B97EE2AF11DED009D9522 /* LoginRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginRepository.swift; sourceTree = ""; }; + D78B97F02AF11DF0009D9522 /* TokenRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenRepository.swift; sourceTree = ""; }; + D78B97F42AF23CD0009D9522 /* CreateRoomInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomInfo.swift; sourceTree = ""; }; + D79938872ABEC80300711D5B /* ChooseCharacterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChooseCharacterViewModel.swift; sourceTree = ""; }; D79E9D892A826E0200C031F0 /* CreateRoomViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomViewModel.swift; sourceTree = ""; }; + D7ADDA3B2AFBBDA600755835 /* SettingError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingError.swift; sourceTree = ""; }; + D7AE5ECA2AC1C32F00A20D0A /* ChooseCharacterError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChooseCharacterError.swift; sourceTree = ""; }; D7B6C97B2858B2D40024F326 /* CheckRoomInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckRoomInfoView.swift; sourceTree = ""; }; - D7B83FD52A95A48700BFD8FF /* CreateRoomService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomService.swift; sourceTree = ""; }; + D7B83FD52A95A48700BFD8FF /* CreateRoomUsecase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateRoomUsecase.swift; sourceTree = ""; }; + D7BA95B42AF8AB9400455107 /* NicknameError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NicknameError.swift; sourceTree = ""; }; + D7C0D9322AF4B67D00B5B9B1 /* TextFieldUsecase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldUsecase.swift; sourceTree = ""; }; D7C4A1A62A0B2EB000C3AE4C /* CharacterCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CharacterCollectionView.swift; sourceTree = ""; }; D7C4A1A82A0B895300C3AE4C /* ChooseCharacterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChooseCharacterView.swift; sourceTree = ""; }; D7C4A1AA2A0D1AA400C3AE4C /* ParticipateRoomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipateRoomView.swift; sourceTree = ""; }; D7C4A1AC2A0DD8FA00C3AE4C /* SettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingView.swift; sourceTree = ""; }; - D7C4A1AF2A0E522800C3AE4C /* SettingViewController+MailComposeViewControllerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingViewController+MailComposeViewControllerDelegate.swift"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -422,21 +533,13 @@ B50B1B0728596CB10080992C /* FSCalendar in Frameworks */, B53A35AD2A962E9C00B720BC /* MTNetwork in Frameworks */, B50B1B33285AC0970080992C /* Gifu in Frameworks */, + B5546A4B2AADFE5C004D9FE6 /* MTResource.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 333BF66428571C850039F77F /* Cell */ = { - isa = PBXGroup; - children = ( - 333BF66528571CF00039F77F /* FriendCollectionViewCell.swift */, - B55BCEB328D8449E00AF7E45 /* MemoryCollectionViewCell.swift */, - ); - path = Cell; - sourceTree = ""; - }; 3904F5012AA379CA00B6264F /* Presentation */ = { isa = PBXGroup; children = ( @@ -448,7 +551,6 @@ 3904F5022AA379F100B6264F /* DetailWait */ = { isa = PBXGroup; children = ( - 3904F5042AA37A2C00B6264F /* Service */, 3904F5032AA37A2000B6264F /* ViewModel */, ); path = DetailWait; @@ -462,12 +564,46 @@ path = ViewModel; sourceTree = ""; }; - 3904F5042AA37A2C00B6264F /* Service */ = { + 390E5CAA2AB89B9500EBE52E /* Domain */ = { isa = PBXGroup; children = ( - 3904F5052AA37A3C00B6264F /* MockDetailWaitService.swift */, + 39632A262AD81F1600A6E61D /* Usecase */, + 390E5CAB2AB89B9F00EBE52E /* Entity */, ); - path = Service; + path = Domain; + sourceTree = ""; + }; + 390E5CAB2AB89B9F00EBE52E /* Entity */ = { + isa = PBXGroup; + children = ( + 390E5CAC2AB89BCE00EBE52E /* RoomListItemTest.swift */, + 398CED072AB9E48F00F50CBD /* RoomInfoTest.swift */, + ); + path = Entity; + sourceTree = ""; + }; + 390E5CAE2AB8A6D100EBE52E /* Util */ = { + isa = PBXGroup; + children = ( + 390E5CAF2AB8A6D600EBE52E /* Extension */, + ); + path = Util; + sourceTree = ""; + }; + 390E5CAF2AB8A6D600EBE52E /* Extension */ = { + isa = PBXGroup; + children = ( + 390E5CB02AB8A6DB00EBE52E /* Type */, + ); + path = Extension; + sourceTree = ""; + }; + 390E5CB02AB8A6DB00EBE52E /* Type */ = { + isa = PBXGroup; + children = ( + 390E5CB12AB8A70600EBE52E /* Date+ExtensionTest.swift */, + ); + path = Type; sourceTree = ""; }; 391612D629E92308004AE982 /* View */ = { @@ -492,39 +628,51 @@ isa = PBXGroup; children = ( 395B5BC12A25E20000CE1420 /* DetailWaitViewModel.swift */, + 39632A722AF0DC2E00A6E61D /* DetailEditViewModel.swift */, ); path = ViewModel; sourceTree = ""; }; - 398B1CBA2A12415C00DEFCEC /* ManitoTests */ = { + 39632A262AD81F1600A6E61D /* Usecase */ = { isa = PBXGroup; children = ( - 3904F5012AA379CA00B6264F /* Presentation */, + 39632A272AD81F2E00A6E61D /* DetailWaitUsecaseTest.swift */, + 39632A832AFB24FD00A6E61D /* DetailEditUsecaseTest.swift */, ); - path = ManitoTests; + path = Usecase; sourceTree = ""; }; - 398B1CC82A13597600DEFCEC /* Frameworks */ = { + 39632A292AD8429000A6E61D /* DetailWait */ = { isa = PBXGroup; children = ( + 39632A2A2AD842A800A6E61D /* DetailWaitUsecaseError.swift */, ); - name = Frameworks; + path = DetailWait; sourceTree = ""; }; - 39BDDCC22A52BB34005E0A71 /* Services */ = { + 39632AC32B0B476100A6E61D /* DetailEdit */ = { isa = PBXGroup; children = ( - 39BDDCC32A52BB4A005E0A71 /* DetailWaitService.swift */, + 39632AC42B0B477700A6E61D /* DetailEditUsecaseError.swift */, ); - path = Services; + path = DetailEdit; sourceTree = ""; }; - 39C957CC2876E2BE00A04A2B /* Login */ = { + 398B1CBA2A12415C00DEFCEC /* ManitoTests */ = { isa = PBXGroup; children = ( - 39C957CD2876E2ED00A04A2B /* LoginViewController.swift */, + 390E5CAE2AB8A6D100EBE52E /* Util */, + 3904F5012AA379CA00B6264F /* Presentation */, + 390E5CAA2AB89B9500EBE52E /* Domain */, ); - path = Login; + path = ManitoTests; + sourceTree = ""; + }; + 398B1CC82A13597600DEFCEC /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; sourceTree = ""; }; 39C957D328799A8200A04A2B /* Foundation */ = { @@ -560,10 +708,19 @@ path = View; sourceTree = ""; }; + 39EF30F72AD3DE5600466A90 /* DetailWait */ = { + isa = PBXGroup; + children = ( + 39EF30F82AD3DE6700466A90 /* DetailWaitUseCase.swift */, + 39632A752AF0EE6600A6E61D /* DetailEditUsecase.swift */, + ); + path = DetailWait; + sourceTree = ""; + }; 7E0C5C342855B1EF00F698D1 /* NickName */ = { isa = PBXGroup; children = ( - D719395A2AA23EF200A73D6C /* Service */, + D719395A2AA23EF200A73D6C /* Usecase */, D719395B2AA23EFC00A73D6C /* ViewModel */, D719395C2AA23F0400A73D6C /* View */, D719395D2AA23F0900A73D6C /* ViewControllers */, @@ -583,49 +740,11 @@ path = UIComponent; sourceTree = ""; }; - B50B1B28285AB7550080992C /* Splash */ = { - isa = PBXGroup; - children = ( - B50B1B29285AB7650080992C /* SplashViewController.swift */, - ); - path = Splash; - sourceTree = ""; - }; - B50B1B36285AC28B0080992C /* GIFs */ = { - isa = PBXGroup; - children = ( - B55BCEC528F5AFB200AF7E45 /* capsule.gif */, - B55BCEC728F5AFD500AF7E45 /* joystick.gif */, - B50B1B3B285AD2B20080992C /* logo.gif */, - CB2F0FC828A5468C005F04C8 /* gifMa.gif */, - CB2F0FCA28A5468C005F04C8 /* gifNi.gif */, - CB2F0FC928A5468C005F04C8 /* gifTto.gif */, - ); - path = GIFs; - sourceTree = ""; - }; - B50B1B3F285B047C0080992C /* Interaction */ = { - isa = PBXGroup; - children = ( - B5AE11EE29D1E38D00F86FF8 /* View */, - B50B1B42285B14550080992C /* Cell */, - B50B1B40285B048A0080992C /* OpenManittoViewController.swift */, - B5E1F2B1285ECBDF006D880B /* SelectManitteeViewController.swift */, - ); - path = Interaction; - sourceTree = ""; - }; - B50B1B42285B14550080992C /* Cell */ = { - isa = PBXGroup; - children = ( - B50B1B43285B146A0080992C /* OpenManittoCollectionViewCell.swift */, - ); - path = Cell; - sourceTree = ""; - }; B53A35B22A9630FE00B720BC /* Service */ = { isa = PBXGroup; children = ( + B57D574C2AD9244600626BCD /* MailComposeManager */, + B577E4FE2AC373BB003CC102 /* PhotoPickerManager */, B5C7FC032AA8876700862021 /* ImageCacheManager */, B53A35B32A96310700B720BC /* UserDefaultService */, ); @@ -644,6 +763,7 @@ B53A35B82A9640EB00B720BC /* Data */ = { isa = PBXGroup; children = ( + D7C46A9C2AD57CFF00E5B2AC /* Error */, B53A35D12A971E4200B720BC /* DTO */, B53A35BE2A96F1F900B720BC /* Repository */, B5F525462855D00B00614FF7 /* Network */, @@ -654,13 +774,13 @@ B53A35BE2A96F1F900B720BC /* Repository */ = { isa = PBXGroup; children = ( - B53A35BF2A96F21A00B720BC /* MainRepository.swift */, - B53A35C32A96F50100B720BC /* DetailRoomRepository.swift */, - B53A35C52A96F6DB00B720BC /* RoomParticipationRepository.swift */, - B53A35C72A96F84700B720BC /* LetterRepository.swift */, - B53A35CB2A96F99D00B720BC /* SettingRepository.swift */, - B53A35CD2A96FA7A00B720BC /* LoginRepository.swift */, - B53A35CF2A97194D00B720BC /* TokenRepository.swift */, + B53A35BF2A96F21A00B720BC /* MainRepositoryImpl.swift */, + B53A35C32A96F50100B720BC /* DetailRoomRepositoryImpl.swift */, + B53A35C52A96F6DB00B720BC /* RoomParticipationRepositoryImpl.swift */, + B53A35C72A96F84700B720BC /* LetterRepositoryImpl.swift */, + B53A35CB2A96F99D00B720BC /* SettingRepositoryImpl.swift */, + B53A35CD2A96FA7A00B720BC /* LoginRepositoryImpl.swift */, + B53A35CF2A97194D00B720BC /* TokenRepositoryImpl.swift */, ); path = Repository; sourceTree = ""; @@ -714,14 +834,118 @@ path = Dev; sourceTree = ""; }; - B54741DE29A3A4BA00B75BA3 /* Views */ = { + B53CD0362ADF8EE300BE7353 /* UIComponent */ = { + isa = PBXGroup; + children = ( + B5AE11F129D28CB500F86FF8 /* OpenManittoPopupView.swift */, + ); + path = UIComponent; + sourceTree = ""; + }; + B5422CA32ADD16B60051A966 /* ViewModel */ = { + isa = PBXGroup; + children = ( + B5422CA42ADD16C40051A966 /* SplashViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + B5422CAC2ADD2AA10051A966 /* DetailScene */ = { + isa = PBXGroup; + children = ( + B5422CAD2ADD2AAD0051A966 /* Detail */, + ); + path = DetailScene; + sourceTree = ""; + }; + B5422CAD2ADD2AAD0051A966 /* Detail */ = { + isa = PBXGroup; + children = ( + B5422CAF2ADD2AC00051A966 /* View */, + B5422CAE2ADD2AB90051A966 /* ViewController */, + B5422CB02ADD2AC50051A966 /* ViewModel */, + ); + path = Detail; + sourceTree = ""; + }; + B5422CAE2ADD2AB90051A966 /* ViewController */ = { + isa = PBXGroup; + children = ( + B5E1F2B1285ECBDF006D880B /* SelectManitteeViewController.swift */, + B50B1B40285B048A0080992C /* OpenManittoViewController.swift */, + B5422CB12ADD2AE60051A966 /* MemoryViewController.swift */, + B5D3D2922AE0D0500016F1D5 /* FriendListViewController.swift */, + ); + path = ViewController; + sourceTree = ""; + }; + B5422CAF2ADD2AC00051A966 /* View */ = { + isa = PBXGroup; + children = ( + B5422CB72ADD2B280051A966 /* Cell */, + B53CD0362ADF8EE300BE7353 /* UIComponent */, + B5AE11F329D32EE700F86FF8 /* SelectManitteeView.swift */, + B5AE11EF29D1E43B00F86FF8 /* OpenManittoView.swift */, + B5422CB32ADD2B040051A966 /* MemoryView.swift */, + B5D3D2942AE0D0B60016F1D5 /* FriendListView.swift */, + ); + path = View; + sourceTree = ""; + }; + B5422CB02ADD2AC50051A966 /* ViewModel */ = { + isa = PBXGroup; + children = ( + B53CD03F2ADFC25C00BE7353 /* SelectManitteeViewModel.swift */, + B53CD0392ADF9B6100BE7353 /* OpenManittoViewModel.swift */, + B5422CB52ADD2B190051A966 /* MemoryViewModel.swift */, + B5D3D2962AE0D0CE0016F1D5 /* FriendListViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + B5422CB72ADD2B280051A966 /* Cell */ = { + isa = PBXGroup; + children = ( + B50B1B43285B146A0080992C /* OpenManittoCollectionViewCell.swift */, + B55BCEB328D8449E00AF7E45 /* MemoryCollectionViewCell.swift */, + 333BF66528571CF00039F77F /* FriendCollectionViewCell.swift */, + ); + path = Cell; + sourceTree = ""; + }; + B5422CBA2ADD3B5C0051A966 /* LetterScene */ = { + isa = PBXGroup; + children = ( + B5B3C15E2A41D6F400AABD6F /* LetterUsecase.swift */, + B5422CC92ADD3D1C0051A966 /* SendLetterUsecase.swift */, + ); + path = LetterScene; + sourceTree = ""; + }; + B5422CBB2ADD3B6B0051A966 /* SplashScene */ = { isa = PBXGroup; children = ( - B5B3C1592A41D69500AABD6F /* ViewControllers */, - B5B3C1582A41D67F00AABD6F /* Views */, - B5F5253328547EE000614FF7 /* UIComponents */, + B5422CA12ADD113C0051A966 /* SplashUsecase.swift */, ); - path = Views; + path = SplashScene; + sourceTree = ""; + }; + B5422CCD2ADD3DE00051A966 /* DetailScene */ = { + isa = PBXGroup; + children = ( + B5422CCE2ADD3DEC0051A966 /* MemoryUsecase.swift */, + B53CD0372ADF941200BE7353 /* OpenManittoUsecase.swift */, + B5D3D2982AE0D1270016F1D5 /* FriendListUsecase.swift */, + ); + path = DetailScene; + sourceTree = ""; + }; + B5422CD42ADD42C00051A966 /* DetailScene */ = { + isa = PBXGroup; + children = ( + B5422CD52ADD42CD0051A966 /* DetailUsecaseError.swift */, + ); + path = DetailScene; sourceTree = ""; }; B54B72A12AA3616600D60E4B /* App */ = { @@ -768,29 +992,65 @@ isa = PBXGroup; children = ( B5F524D728519AA100614FF7 /* Assets.xcassets */, - B50B1B36285AC28B0080992C /* GIFs */, - B5F5250228519F6A00614FF7 /* Fonts */, + B5C0BF842AAEE65500E9017C /* GIFs */, B5F524F928519BD600614FF7 /* Storyboards */, ); path = Resource; sourceTree = ""; }; - B55469C62AA97273004D9FE6 /* Error */ = { + B55469FD2AADB903004D9FE6 /* Error */ = { isa = PBXGroup; children = ( - B55469C72AA9727D004D9FE6 /* LetterScene */, + 39632AC32B0B476100A6E61D /* DetailEdit */, + CB93325D2AFCBD3C00B7F87A /* LoginScene */, + 39632A292AD8429000A6E61D /* DetailWait */, + B5422CD42ADD42C00051A966 /* DetailScene */, + B5546A092AADCAFD004D9FE6 /* LetterScene */, ); path = Error; sourceTree = ""; }; - B55469C72AA9727D004D9FE6 /* LetterScene */ = { + B5546A092AADCAFD004D9FE6 /* LetterScene */ = { isa = PBXGroup; children = ( - B54741E929A494E200B75BA3 /* LetterImageError.swift */, + B5546A0A2AADCB13004D9FE6 /* LetterUsecaseError.swift */, ); path = LetterScene; sourceTree = ""; }; + B5546A102AADD5F2004D9FE6 /* Modules */ = { + isa = PBXGroup; + children = ( + B5546A252AADD638004D9FE6 /* MTResource.xcodeproj */, + ); + path = Modules; + sourceTree = ""; + }; + B5546A262AADD638004D9FE6 /* Products */ = { + isa = PBXGroup; + children = ( + B5546A2A2AADD638004D9FE6 /* MTResource.framework */, + ); + name = Products; + sourceTree = ""; + }; + B577E4FE2AC373BB003CC102 /* PhotoPickerManager */ = { + isa = PBXGroup; + children = ( + B577E5032AC3799D003CC102 /* Error */, + B577E4FF2AC373CA003CC102 /* PhotoPickerManager.swift */, + ); + path = PhotoPickerManager; + sourceTree = ""; + }; + B577E5032AC3799D003CC102 /* Error */ = { + isa = PBXGroup; + children = ( + B577E5012AC37998003CC102 /* PHPickerError.swift */, + ); + path = Error; + sourceTree = ""; + }; B57CB3022A763BB200474042 /* Packages */ = { isa = PBXGroup; children = ( @@ -799,11 +1059,44 @@ path = Packages; sourceTree = ""; }; + B57D574C2AD9244600626BCD /* MailComposeManager */ = { + isa = PBXGroup; + children = ( + B57D574D2AD9248500626BCD /* MailComposeManager.swift */, + ); + path = MailComposeManager; + sourceTree = ""; + }; + B57D57652AD97D0200626BCD /* SplashScene */ = { + isa = PBXGroup; + children = ( + B57D57672AD97D3A00626BCD /* View */, + B57D57662AD97D1A00626BCD /* ViewController */, + B5422CA32ADD16B60051A966 /* ViewModel */, + ); + path = SplashScene; + sourceTree = ""; + }; + B57D57662AD97D1A00626BCD /* ViewController */ = { + isa = PBXGroup; + children = ( + B50B1B29285AB7650080992C /* SplashViewController.swift */, + ); + path = ViewController; + sourceTree = ""; + }; + B57D57672AD97D3A00626BCD /* View */ = { + isa = PBXGroup; + children = ( + B57D57682AD97DAE00626BCD /* SplashView.swift */, + ); + path = View; + sourceTree = ""; + }; B58CA2FC2A9CC29A00977592 /* Presentation */ = { isa = PBXGroup; children = ( B5C7FBE62AA75B9C00862021 /* Common */, - B55469C62AA97273004D9FE6 /* Error */, B58CA3052A9CC37000977592 /* Scene */, ); path = Presentation; @@ -812,6 +1105,11 @@ B58CA3052A9CC37000977592 /* Scene */ = { isa = PBXGroup; children = ( + CB9332572AFCAEDA00B7F87A /* LoginScene */, + D7AE5EBE2AC1964000A20D0A /* SettingScene */, + D7AE5EB72AC184A600A20D0A /* ParticipateRoomScene */, + B57D57652AD97D0200626BCD /* SplashScene */, + B5422CAC2ADD2AA10051A966 /* DetailScene */, B58CA3062A9CC37B00977592 /* LetterScene */, ); path = Scene; @@ -831,6 +1129,8 @@ isa = PBXGroup; children = ( B5F5250D2851A07700614FF7 /* LetterViewController.swift */, + B577E4F32ABE0B8A003CC102 /* SendLetterViewController.swift */, + 39F1C12D28D756E600585B83 /* LetterImageViewController.swift */, ); path = ViewController; sourceTree = ""; @@ -841,6 +1141,8 @@ B58CA3192A9D992F00977592 /* Cell */, B58CA3092A9CC3D700977592 /* UIComponent */, B5706BF329B710FC0093D198 /* LetterView.swift */, + B577E4FC2ABFDE6F003CC102 /* SendLetterView.swift */, + B54741DF29A3A4DB00B75BA3 /* LetterImageView.swift */, ); path = View; sourceTree = ""; @@ -849,6 +1151,9 @@ isa = PBXGroup; children = ( B5F5253428547F1900614FF7 /* LetterHeaderView.swift */, + B50B1AFA2856B5180080992C /* IndividualMissionView.swift */, + B50B1AFC2856C3500080992C /* SendLetterTextView.swift */, + B50B1AFE2856D3820080992C /* SendLetterPhotoView.swift */, ); path = UIComponent; sourceTree = ""; @@ -857,6 +1162,7 @@ isa = PBXGroup; children = ( B5B3C15C2A41D6EB00AABD6F /* LetterViewModel.swift */, + B577E4F52ABE0C95003CC102 /* SendLetterViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -864,19 +1170,19 @@ B58CA30B2A9CC43600977592 /* Usecase */ = { isa = PBXGroup; children = ( - B58CA3142A9D913600977592 /* LetterScene */, + CB9332542AFCAE2200B7F87A /* LoginScene */, + D7F1CC6F2AF4C13D007CFC97 /* Common */, + D7F1CC722AF4C35A007CFC97 /* CreateRoomScene */, + D7AE5EC42AC1966B00A20D0A /* SettingScene */, + D7AE5EBB2AC184F300A20D0A /* ParticipateRoomScene */, + B5422CBB2ADD3B6B0051A966 /* SplashScene */, + B5422CBA2ADD3B5C0051A966 /* LetterScene */, + 39EF30F72AD3DE5600466A90 /* DetailWait */, + B5422CCD2ADD3DE00051A966 /* DetailScene */, ); path = Usecase; sourceTree = ""; }; - B58CA3142A9D913600977592 /* LetterScene */ = { - isa = PBXGroup; - children = ( - B5B3C15E2A41D6F400AABD6F /* LetterUsecase.swift */, - ); - path = LetterScene; - sourceTree = ""; - }; B58CA3192A9D992F00977592 /* Cell */ = { isa = PBXGroup; children = ( @@ -905,6 +1211,8 @@ B5A085E72A97302900C8A98D /* Domain */ = { isa = PBXGroup; children = ( + D78B97E32AF11D3C009D9522 /* Repository */, + B55469FD2AADB903004D9FE6 /* Error */, B58CA30B2A9CC43600977592 /* Usecase */, B5A085E82A97303900C8A98D /* Entity */, ); @@ -914,6 +1222,7 @@ B5A085E82A97303900C8A98D /* Entity */ = { isa = PBXGroup; children = ( + CB9332592AFCAF3F00B7F87A /* LoginInfo.swift */, B5A085E92A97309900C8A98D /* MessageListItem.swift */, B5A086012A974B8100C8A98D /* RoomListItem.swift */, B5A085FB2A97470400C8A98D /* RoomInfo.swift */, @@ -923,38 +1232,27 @@ 3904F4FD2AA3743C00B6264F /* IndividualMission.swift */, 3904F4FF2AA3754400B6264F /* MessageCountInfo.swift */, B5F3F1052AA3516200FA1A32 /* RoomStatus.swift */, - B55469B52AA96F54004D9FE6 /* Character.swift */, + D7537F602AA70C40005AC78B /* ParticipatedRoomInfo.swift */, + B55469B52AA96F54004D9FE6 /* DefaultCharacterType.swift */, + B5422CA62ADD191A0051A966 /* EntryType.swift */, + B5422CD02ADD3E4B0051A966 /* Memory.swift */, + B5422CD22ADD418C0051A966 /* FriendList.swift */, + D78B97F42AF23CD0009D9522 /* CreateRoomInfo.swift */, ); path = Entity; sourceTree = ""; }; - B5AE11EE29D1E38D00F86FF8 /* View */ = { + B5C0BF842AAEE65500E9017C /* GIFs */ = { isa = PBXGroup; children = ( - B5AE11EF29D1E43B00F86FF8 /* OpenManittoView.swift */, - B5AE11F129D28CB500F86FF8 /* OpenManittoPopupView.swift */, - B5AE11F329D32EE700F86FF8 /* SelectManitteeView.swift */, - ); - path = View; - sourceTree = ""; - }; - B5B3C1582A41D67F00AABD6F /* Views */ = { - isa = PBXGroup; - children = ( - B5706BE029ADC20A0093D198 /* CreateLetterView.swift */, - B54741DF29A3A4DB00B75BA3 /* LetterImageView.swift */, - ); - path = Views; - sourceTree = ""; - }; - B5B3C1592A41D69500AABD6F /* ViewControllers */ = { - isa = PBXGroup; - children = ( - B5F31C4F28BF922E00F61D0F /* LetterViewController+MailCompose.swift */, - B50B1AF82856B2C60080992C /* CreateLetterViewController.swift */, - 39F1C12D28D756E600585B83 /* LetterImageViewController.swift */, + B5C0BF932AAEE6B000E9017C /* capsule.gif */, + B5C0BF942AAEE6B000E9017C /* gifMa.gif */, + B5C0BF962AAEE6B000E9017C /* gifNi.gif */, + B5C0BF982AAEE6B000E9017C /* gifTto.gif */, + B5C0BF952AAEE6B000E9017C /* joystick.gif */, + B5C0BF972AAEE6B000E9017C /* logo.gif */, ); - path = ViewControllers; + path = GIFs; sourceTree = ""; }; B5C7FBE62AA75B9C00862021 /* Common */ = { @@ -969,10 +1267,11 @@ B5C7FBEB2AA75DC700862021 /* Base */ = { isa = PBXGroup; children = ( - B55469B12AA96F2C004D9FE6 /* BaseViewController.swift */, B55469B22AA96F2C004D9FE6 /* BaseViewModelType.swift */, B5C7FBEC2AA7607300862021 /* BaseViewType.swift */, B5C7FBF42AA859E300862021 /* BaseViewControllerType.swift */, + 395402D12AAF0B4F003F3012 /* Navigationable.swift */, + 39833CFD2AB09A6C009D173A /* Keyboardable.swift */, ); path = Base; sourceTree = ""; @@ -983,6 +1282,7 @@ B5C7FC042AA887A400862021 /* Logger */, B5C7FC052AA887CC00862021 /* Extension */, B5C7FC072AA889BD00862021 /* Literal */, + B5EDF4A62AB05D1F0086230C /* Script */, ); path = Util; sourceTree = ""; @@ -1016,8 +1316,6 @@ B5C7FC062AA887E100862021 /* UI */ = { isa = PBXGroup; children = ( - B5F524FC28519EEC00614FF7 /* UIColor+Extension.swift */, - B5F524FE28519EF300614FF7 /* UIFont+Extension.swift */, B5C7FC082AA88ABB00862021 /* UIViewController */, B5C7FC092AA88AC800862021 /* UIView */, ); @@ -1027,7 +1325,8 @@ B5C7FC072AA889BD00862021 /* Literal */ = { isa = PBXGroup; children = ( - B5F5250028519EFB00614FF7 /* ImageLiteral.swift */, + B5EDF4A52AB056B50086230C /* Localizable.strings */, + B5E8502F2AAEE4CF00652AC3 /* GIFSet.swift */, 391B3002286198C200421F1D /* SizeLiteral.swift */, 7E15F67D28B35B3D00441305 /* TextLiteral.swift */, D777D2C828C7780E008655BD /* URLLiteral.swift */, @@ -1040,6 +1339,7 @@ children = ( B5C7FC0B2AA88AF500862021 /* UIViewController+Alert.swift */, B5C7FC0D2AA88B3500862021 /* UIViewController+Keyboard.swift */, + 395402D32AAF0BC5003F3012 /* UIViewController+Extension.swift */, ); path = UIViewController; sourceTree = ""; @@ -1050,7 +1350,8 @@ B5FEE9DC28C849B400DEA07E /* UIImageView+Cache.swift */, B5F525232851A25400614FF7 /* UICollectionView+Extension.swift */, B5F5253C2854B8CB00614FF7 /* UIView+Extension.swift */, - B5F525412855C97900614FF7 /* UILabel+Extension.swift */, + B55469EA2AAC3B5F004D9FE6 /* UIView+Animation.swift */, + B5F525412855C97900614FF7 /* UILabel+Config.swift */, 39018F4128C4708A00C78DBA /* UIButton+Extension.swift */, ); path = UIView; @@ -1061,8 +1362,7 @@ children = ( B5F525252851A26D00614FF7 /* NSObject+ClassName.swift */, B5E1F2AD285B2917006D880B /* Int+RandomNumber.swift */, - D77224DE285DDAB7008B760B /* Notification+Extension.swift */, - 39C957CF2879521400A04A2B /* String+Date.swift */, + 39C957CF2879521400A04A2B /* String+DateFormatter.swift */, 39C957D12879523200A04A2B /* Date+Extension.swift */, ); path = Type; @@ -1079,6 +1379,14 @@ path = Combine; sourceTree = ""; }; + B5EDF4A62AB05D1F0086230C /* Script */ = { + isa = PBXGroup; + children = ( + B5EDF4AD2AB078180086230C /* LocalizationScript.py */, + ); + path = Script; + sourceTree = ""; + }; B5F524C228519AA000614FF7 = { isa = PBXGroup; children = ( @@ -1087,6 +1395,7 @@ B5F524CD28519AA000614FF7 /* Manito */, 398B1CBA2A12415C00DEFCEC /* ManitoTests */, B57CB3022A763BB200474042 /* Packages */, + B5546A102AADD5F2004D9FE6 /* Modules */, B5F524CC28519AA000614FF7 /* Products */, 398B1CC82A13597600DEFCEC /* Frameworks */, ); @@ -1120,16 +1429,8 @@ isa = PBXGroup; children = ( CBBFF775287AE034006A5964 /* SettingDeveloperInfo */, - D724AF5B287088110003F280 /* Setting */, - B50B1B28285AB7550080992C /* Splash */, - 39C957CC2876E2BE00A04A2B /* Login */, 7E0C5C342855B1EF00F698D1 /* NickName */, B5F524E628519B0A00614FF7 /* Main */, - B50B1B3F285B047C0080992C /* Interaction */, - CBA15C91285CE1880051EDE2 /* ChooseCharacter */, - CB637A3828595BE400FF1240 /* ParticipateRoom */, - CB674C1F285969F10063A6B7 /* CheckRoom */, - B5F524EA28519B4B00614FF7 /* Letter */, B5F524E928519B3F00614FF7 /* Detail-Ing */, B5F524E828519B2F00614FF7 /* Detail-Wait */, B5F524E728519B0E00614FF7 /* InvitedCode */, @@ -1141,7 +1442,6 @@ B5F524E528519ACA00614FF7 /* CreateRoom */ = { isa = PBXGroup; children = ( - D7B83FD42A95A46F00BFD8FF /* Services */, D79E9D882A826D7900C031F0 /* ViewModel */, D70220212A7A22DC0024BACD /* View */, 7E3058C42854B45300489E6A /* UIComponent */, @@ -1163,6 +1463,8 @@ B5F524E728519B0E00614FF7 /* InvitedCode */ = { isa = PBXGroup; children = ( + D76E024C2AF5DD7800F079FB /* ViewModel */, + D76E024B2AF5DD6E00F079FB /* View */, B5F525072851A05E00614FF7 /* InvitedCodeViewController.swift */, ); path = InvitedCode; @@ -1171,7 +1473,6 @@ B5F524E828519B2F00614FF7 /* Detail-Wait */ = { isa = PBXGroup; children = ( - 39BDDCC22A52BB34005E0A71 /* Services */, 395B5BC02A25E1F400CE1420 /* ViewModel */, 391612D629E92308004AE982 /* View */, 392EC77F2855D107006918A9 /* UIComponent */, @@ -1186,103 +1487,85 @@ children = ( 39EE956B2A401E9600AF6857 /* View */, D75E8C7D28D76FB1004A6C41 /* UIcomponent */, - 333BF66428571C850039F77F /* Cell */, 435B17672913E71400212663 /* DetailingViewController.swift */, - 33BDF5612856E03800564211 /* FriendListViewController.swift */, - 333BF669285864CE0039F77F /* MemoryViewController.swift */, 39EE956E2A404A3800AF6857 /* MissionEditViewController.swift */, ); path = "Detail-Ing"; sourceTree = ""; }; - B5F524EA28519B4B00614FF7 /* Letter */ = { - isa = PBXGroup; - children = ( - B54741DE29A3A4BA00B75BA3 /* Views */, - ); - path = Letter; - sourceTree = ""; - }; B5F524F928519BD600614FF7 /* Storyboards */ = { isa = PBXGroup; children = ( - 7E0C5C372855B73100F698D1 /* Splash.storyboard */, - B5F524D428519AA000614FF7 /* Main.storyboard */, B5F524D928519AA100614FF7 /* LaunchScreen.storyboard */, - B5F525152851A0F600614FF7 /* DetailIng.storyboard */, ); path = Storyboards; sourceTree = ""; }; - B5F5250228519F6A00614FF7 /* Fonts */ = { + B5F525462855D00B00614FF7 /* Network */ = { isa = PBXGroup; children = ( - B5F5250328519F7A00614FF7 /* DungGeunMo.otf */, + 39C957D328799A8200A04A2B /* Foundation */, + 39C957EA287AE45900A04A2B /* EndPoint */, ); - path = Fonts; + path = Network; sourceTree = ""; }; - B5F5253328547EE000614FF7 /* UIComponents */ = { + CB66FD692AE7FC8200358CA2 /* ViewModel */ = { isa = PBXGroup; children = ( - B50B1AFA2856B5180080992C /* IndividualMissionView.swift */, - B50B1AFC2856C3500080992C /* CreateLetterTextView.swift */, - B50B1AFE2856D3820080992C /* CreateLetterPhotoView.swift */, + CB66FD6D2AE7FCE900358CA2 /* LoginViewModel.swift */, ); - path = UIComponents; + path = ViewModel; sourceTree = ""; }; - B5F525462855D00B00614FF7 /* Network */ = { + CB66FD6A2AE7FC8600358CA2 /* View */ = { isa = PBXGroup; children = ( - 39C957D328799A8200A04A2B /* Foundation */, - 39C957EA287AE45900A04A2B /* EndPoint */, + CB66FD6B2AE7FCDE00358CA2 /* LoginView.swift */, ); - path = Network; + path = View; sourceTree = ""; }; - CB5AE3E2285AAF2E00382EA3 /* UIComponent */ = { + CB85DE1D28A76F1A00399109 /* UIComponent */ = { isa = PBXGroup; children = ( - CB5AE3E3285AAF7400382EA3 /* RoomInfoView.swift */, - CB5AE3E5285AB76800382EA3 /* PeopleInfoView.swift */, + CB85DE1E28A76F3400399109 /* SettingDeveloperInfoHeaderView.swift */, ); path = UIComponent; sourceTree = ""; }; - CB637A3828595BE400FF1240 /* ParticipateRoom */ = { + CB9332542AFCAE2200B7F87A /* LoginScene */ = { isa = PBXGroup; children = ( - CB637A3928595BF900FF1240 /* UIComponent */, - CB637A3A28595C1200FF1240 /* ParticipateRoomViewController.swift */, - D7C4A1AA2A0D1AA400C3AE4C /* ParticipateRoomView.swift */, + CB9332552AFCAE3100B7F87A /* LoginUsecase.swift */, ); - path = ParticipateRoom; + path = LoginScene; sourceTree = ""; }; - CB637A3928595BF900FF1240 /* UIComponent */ = { + CB9332572AFCAEDA00B7F87A /* LoginScene */ = { isa = PBXGroup; children = ( - CB674C1B285966020063A6B7 /* InputInvitedCodeView.swift */, + CB66FD6A2AE7FC8600358CA2 /* View */, + CB9332582AFCAEF900B7F87A /* ViewController */, + CB66FD692AE7FC8200358CA2 /* ViewModel */, ); - path = UIComponent; + path = LoginScene; sourceTree = ""; }; - CB674C1F285969F10063A6B7 /* CheckRoom */ = { + CB9332582AFCAEF900B7F87A /* ViewController */ = { isa = PBXGroup; children = ( - CB5AE3E2285AAF2E00382EA3 /* UIComponent */, - CB674C2028596A310063A6B7 /* CheckRoomViewController.swift */, + 39C957CD2876E2ED00A04A2B /* LoginViewController.swift */, ); - path = CheckRoom; + path = ViewController; sourceTree = ""; }; - CB85DE1D28A76F1A00399109 /* UIComponent */ = { + CB93325D2AFCBD3C00B7F87A /* LoginScene */ = { isa = PBXGroup; children = ( - CB85DE1E28A76F3400399109 /* SettingDeveloperInfoHeaderView.swift */, + CB93325E2AFCBD4D00B7F87A /* LoginUsecaseError.swift */, ); - path = UIComponent; + path = LoginScene; sourceTree = ""; }; CB9592B02855BFDD00847751 /* Cell */ = { @@ -1303,24 +1586,6 @@ path = UIComponent; sourceTree = ""; }; - CBA15C91285CE1880051EDE2 /* ChooseCharacter */ = { - isa = PBXGroup; - children = ( - D7B83FD32A92F55200BFD8FF /* View */, - CBA15C92285CE18F0051EDE2 /* UIComponent */, - CBA15C93285CE1A80051EDE2 /* ChooseCharacterViewController.swift */, - ); - path = ChooseCharacter; - sourceTree = ""; - }; - CBA15C92285CE18F0051EDE2 /* UIComponent */ = { - isa = PBXGroup; - children = ( - CB21C33828C4C10200128D25 /* CharacterCollectionViewCell.swift */, - ); - path = UIComponent; - sourceTree = ""; - }; CBBFF775287AE034006A5964 /* SettingDeveloperInfo */ = { isa = PBXGroup; children = ( @@ -1347,133 +1612,271 @@ path = View; sourceTree = ""; }; - D71939502AA1C82100A73D6C /* Service */ = { + D719395A2AA23EF200A73D6C /* Usecase */ = { isa = PBXGroup; children = ( - D71939522AA1C84900A73D6C /* SettingService.swift */, + D71939602AA23F3B00A73D6C /* NicknameUsecase.swift */, ); - path = Service; + path = Usecase; sourceTree = ""; }; - D71939512AA1C82F00A73D6C /* ViewModel */ = { + D719395B2AA23EFC00A73D6C /* ViewModel */ = { isa = PBXGroup; children = ( - D71939542AA1C85300A73D6C /* SettingViewModel.swift */, + D719395E2AA23F2300A73D6C /* NicknameViewModel.swift */, ); path = ViewModel; sourceTree = ""; }; - D71939562AA1C86600A73D6C /* ViewControllers */ = { + D719395C2AA23F0400A73D6C /* View */ = { isa = PBXGroup; children = ( - D724AF5C287088310003F280 /* SettingViewController.swift */, - D7C4A1AF2A0E522800C3AE4C /* SettingViewController+MailComposeViewControllerDelegate.swift */, + D71939582AA2343E00A73D6C /* NicknameView.swift */, ); - path = ViewControllers; + path = View; sourceTree = ""; }; - D719395A2AA23EF200A73D6C /* Service */ = { + D719395D2AA23F0900A73D6C /* ViewControllers */ = { isa = PBXGroup; children = ( - D71939602AA23F3B00A73D6C /* NicknameService.swift */, + 7E0C5C352855B22700F698D1 /* CreateNicknameViewController.swift */, + 7EA25C2628C4FEA800746AEA /* ChangeNicknameViewController.swift */, ); - path = Service; + path = ViewControllers; sourceTree = ""; }; - D719395B2AA23EFC00A73D6C /* ViewModel */ = { + D75E8C7D28D76FB1004A6C41 /* UIcomponent */ = { isa = PBXGroup; children = ( - D719395E2AA23F2300A73D6C /* NicknameViewModel.swift */, + D75E8C7E28D76FFB004A6C41 /* LetterCountBadgeView.swift */, ); - path = ViewModel; + path = UIcomponent; sourceTree = ""; }; - D719395C2AA23F0400A73D6C /* View */ = { + D76E024B2AF5DD6E00F079FB /* View */ = { isa = PBXGroup; children = ( - D71939582AA2343E00A73D6C /* NicknameView.swift */, + D76E024E2AF5DD9800F079FB /* InvitedCodeView.swift */, ); path = View; sourceTree = ""; }; - D719395D2AA23F0900A73D6C /* ViewControllers */ = { + D76E024C2AF5DD7800F079FB /* ViewModel */ = { isa = PBXGroup; children = ( - 7E0C5C352855B22700F698D1 /* CreateNicknameViewController.swift */, - 7EA25C2628C4FEA800746AEA /* ChangeNicknameViewController.swift */, + D76E02502AF5DE5500F079FB /* InvitedCodeViewModel.swift */, ); - path = ViewControllers; + path = ViewModel; sourceTree = ""; }; - D724AF5B287088110003F280 /* Setting */ = { + D78B97DE2AEFD1E8009D9522 /* CreateRoomScene */ = { isa = PBXGroup; children = ( - D71939502AA1C82100A73D6C /* Service */, - D71939512AA1C82F00A73D6C /* ViewModel */, - D7C4A1AE2A0E51F500C3AE4C /* View */, - D75ECB6E28A5EDC400A5B271 /* UIComponent */, - D75ECB6D28A5EDBC00A5B271 /* Cell */, - D71939562AA1C86600A73D6C /* ViewControllers */, + D78B97DF2AEFD20A009D9522 /* CreateRoomError.swift */, ); - path = Setting; + path = CreateRoomScene; sourceTree = ""; }; - D75E8C7D28D76FB1004A6C41 /* UIcomponent */ = { + D78B97E32AF11D3C009D9522 /* Repository */ = { isa = PBXGroup; children = ( - D75E8C7E28D76FFB004A6C41 /* LetterCountBadgeView.swift */, + D78B97E42AF11DC1009D9522 /* MainRepository.swift */, + D78B97E62AF11DCD009D9522 /* DetailRoomRepository.swift */, + D78B97E82AF11DD7009D9522 /* RoomParticipationRepository.swift */, + D78B97EA2AF11DE2009D9522 /* LetterRepository.swift */, + D78B97EC2AF11DE8009D9522 /* SettingRepository.swift */, + D78B97EE2AF11DED009D9522 /* LoginRepository.swift */, + D78B97F02AF11DF0009D9522 /* TokenRepository.swift */, ); - path = UIcomponent; + path = Repository; + sourceTree = ""; + }; + D79E9D882A826D7900C031F0 /* ViewModel */ = { + isa = PBXGroup; + children = ( + D79E9D892A826E0200C031F0 /* CreateRoomViewModel.swift */, + ); + path = ViewModel; sourceTree = ""; }; - D75ECB6D28A5EDBC00A5B271 /* Cell */ = { + D7ADDA3A2AFBBD8B00755835 /* SettingScene */ = { isa = PBXGroup; children = ( - 7ED845B92876BE530075AC61 /* SettingViewTableCell.swift */, + D7ADDA3B2AFBBDA600755835 /* SettingError.swift */, ); - path = Cell; + path = SettingScene; sourceTree = ""; }; - D75ECB6E28A5EDC400A5B271 /* UIComponent */ = { + D7AE5EB72AC184A600A20D0A /* ParticipateRoomScene */ = { isa = PBXGroup; children = ( - D724AF5E28708D830003F280 /* TopCharacterImageView.swift */, + D7AE5EBA2AC184BC00A20D0A /* View */, + D7AE5EB92AC184B700A20D0A /* ViewController */, + D7AE5EB82AC184B300A20D0A /* ViewModel */, ); - path = UIComponent; + path = ParticipateRoomScene; sourceTree = ""; }; - D79E9D882A826D7900C031F0 /* ViewModel */ = { + D7AE5EB82AC184B300A20D0A /* ViewModel */ = { isa = PBXGroup; children = ( - D79E9D892A826E0200C031F0 /* CreateRoomViewModel.swift */, + D75DEEC62AA612DB00BD3AC7 /* ParticipateRoomViewModel.swift */, + D7537F682AA76DD0005AC78B /* ParticipationRoomDetailsViewModel.swift */, + D79938872ABEC80300711D5B /* ChooseCharacterViewModel.swift */, ); path = ViewModel; sourceTree = ""; }; - D7B83FD32A92F55200BFD8FF /* View */ = { + D7AE5EB92AC184B700A20D0A /* ViewController */ = { isa = PBXGroup; children = ( + CB637A3A28595C1200FF1240 /* ParticipateRoomViewController.swift */, + CB674C2028596A310063A6B7 /* ParticipationRoomDetailsViewController.swift */, + CBA15C93285CE1A80051EDE2 /* ChooseCharacterViewController.swift */, + ); + path = ViewController; + sourceTree = ""; + }; + D7AE5EBA2AC184BC00A20D0A /* View */ = { + isa = PBXGroup; + children = ( + D7AE5EBD2AC1956300A20D0A /* Cell */, + D7AE5EBC2AC1951100A20D0A /* UIComponent */, + D7C4A1AA2A0D1AA400C3AE4C /* ParticipateRoomView.swift */, + D7537F662AA75AFB005AC78B /* ParticipationRoomDetailsView.swift */, D7C4A1A82A0B895300C3AE4C /* ChooseCharacterView.swift */, ); path = View; sourceTree = ""; }; - D7B83FD42A95A46F00BFD8FF /* Services */ = { + D7AE5EBB2AC184F300A20D0A /* ParticipateRoomScene */ = { isa = PBXGroup; children = ( - D7B83FD52A95A48700BFD8FF /* CreateRoomService.swift */, + D75DEEC42AA612CE00BD3AC7 /* ParticipateRoomUsecase.swift */, ); - path = Services; + path = ParticipateRoomScene; sourceTree = ""; }; - D7C4A1AE2A0E51F500C3AE4C /* View */ = { + D7AE5EBC2AC1951100A20D0A /* UIComponent */ = { isa = PBXGroup; children = ( + CB674C1B285966020063A6B7 /* InputInvitedCodeView.swift */, + ); + path = UIComponent; + sourceTree = ""; + }; + D7AE5EBD2AC1956300A20D0A /* Cell */ = { + isa = PBXGroup; + children = ( + CB21C33828C4C10200128D25 /* CharacterCollectionViewCell.swift */, + ); + path = Cell; + sourceTree = ""; + }; + D7AE5EBE2AC1964000A20D0A /* SettingScene */ = { + isa = PBXGroup; + children = ( + D7AE5EC12AC1964E00A20D0A /* View */, + D7AE5EC02AC1964900A20D0A /* ViewController */, + D7AE5EBF2AC1964500A20D0A /* ViewModel */, + ); + path = SettingScene; + sourceTree = ""; + }; + D7AE5EBF2AC1964500A20D0A /* ViewModel */ = { + isa = PBXGroup; + children = ( + D71939542AA1C85300A73D6C /* SettingViewModel.swift */, + ); + path = ViewModel; + sourceTree = ""; + }; + D7AE5EC02AC1964900A20D0A /* ViewController */ = { + isa = PBXGroup; + children = ( + D724AF5C287088310003F280 /* SettingViewController.swift */, + ); + path = ViewController; + sourceTree = ""; + }; + D7AE5EC12AC1964E00A20D0A /* View */ = { + isa = PBXGroup; + children = ( + D7AE5EC32AC1965A00A20D0A /* Cell */, + D7AE5EC22AC1965100A20D0A /* UIComponent */, D7C4A1AC2A0DD8FA00C3AE4C /* SettingView.swift */, ); path = View; sourceTree = ""; }; + D7AE5EC22AC1965100A20D0A /* UIComponent */ = { + isa = PBXGroup; + children = ( + D724AF5E28708D830003F280 /* TopCharacterImageView.swift */, + ); + path = UIComponent; + sourceTree = ""; + }; + D7AE5EC32AC1965A00A20D0A /* Cell */ = { + isa = PBXGroup; + children = ( + 7ED845B92876BE530075AC61 /* SettingViewTableCell.swift */, + ); + path = Cell; + sourceTree = ""; + }; + D7AE5EC42AC1966B00A20D0A /* SettingScene */ = { + isa = PBXGroup; + children = ( + D71939522AA1C84900A73D6C /* SettingUsecase.swift */, + ); + path = SettingScene; + sourceTree = ""; + }; + D7AE5EC92AC1C30F00A20D0A /* ParticipateRoomScene */ = { + isa = PBXGroup; + children = ( + D7AE5ECA2AC1C32F00A20D0A /* ChooseCharacterError.swift */, + D76E025E2AF7D12600F079FB /* ParticipateRoomError.swift */, + ); + path = ParticipateRoomScene; + sourceTree = ""; + }; + D7BA95B32AF8AB8100455107 /* NicknameScene */ = { + isa = PBXGroup; + children = ( + D7BA95B42AF8AB9400455107 /* NicknameError.swift */, + ); + path = NicknameScene; + sourceTree = ""; + }; + D7C46A9C2AD57CFF00E5B2AC /* Error */ = { + isa = PBXGroup; + children = ( + D7ADDA3A2AFBBD8B00755835 /* SettingScene */, + D7BA95B32AF8AB8100455107 /* NicknameScene */, + D78B97DE2AEFD1E8009D9522 /* CreateRoomScene */, + D7AE5EC92AC1C30F00A20D0A /* ParticipateRoomScene */, + ); + path = Error; + sourceTree = ""; + }; + D7F1CC6F2AF4C13D007CFC97 /* Common */ = { + isa = PBXGroup; + children = ( + D7C0D9322AF4B67D00B5B9B1 /* TextFieldUsecase.swift */, + ); + path = Common; + sourceTree = ""; + }; + D7F1CC722AF4C35A007CFC97 /* CreateRoomScene */ = { + isa = PBXGroup; + children = ( + D7B83FD52A95A48700BFD8FF /* CreateRoomUsecase.swift */, + ); + path = CreateRoomScene; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -1508,6 +1911,7 @@ B5F524C928519AA000614FF7 /* Resources */, B53AD41E2A8C892800B83B33 /* GoogleService-Info.plist */, B5B6E4C62A962B2D0082FC7B /* Embed Frameworks */, + B5EDF4AA2AB069CE0086230C /* GoogleSheet-Localization */, ); buildRules = ( ); @@ -1563,6 +1967,12 @@ ); productRefGroup = B5F524CC28519AA000614FF7 /* Products */; projectDirPath = ""; + projectReferences = ( + { + ProductGroup = B5546A262AADD638004D9FE6 /* Products */; + ProjectRef = B5546A252AADD638004D9FE6 /* MTResource.xcodeproj */; + }, + ); projectRoot = ""; targets = ( B5F524CA28519AA000614FF7 /* Manito */, @@ -1571,6 +1981,16 @@ }; /* End PBXProject section */ +/* Begin PBXReferenceProxy section */ + B5546A2A2AADD638004D9FE6 /* MTResource.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = MTResource.framework; + remoteRef = B5546A292AADD638004D9FE6 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + /* Begin PBXResourcesBuildPhase section */ 398B1CB72A12415B00DEFCEC /* Resources */ = { isa = PBXResourcesBuildPhase; @@ -1583,20 +2003,18 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - CB2F0FCB28A5468C005F04C8 /* gifMa.gif in Resources */, - CB2F0FCD28A5468C005F04C8 /* gifNi.gif in Resources */, - B50B1B3C285AD2B20080992C /* logo.gif in Resources */, - 7E0C5C382855B73100F698D1 /* Splash.storyboard in Resources */, + B5EDF4AE2AB078180086230C /* LocalizationScript.py in Resources */, + B5C0BF9D2AAEE6B000E9017C /* logo.gif in Resources */, + B5C0BF9A2AAEE6B000E9017C /* gifMa.gif in Resources */, + B5C0BF9E2AAEE6B000E9017C /* gifTto.gif in Resources */, 3915D54B2A7516A2002D6C25 /* Key.plist in Resources */, B5F524DB28519AA100614FF7 /* LaunchScreen.storyboard in Resources */, B5F524D828519AA100614FF7 /* Assets.xcassets in Resources */, + B5C0BF992AAEE6B000E9017C /* capsule.gif in Resources */, + B5C0BF9B2AAEE6B000E9017C /* joystick.gif in Resources */, B55BCEB628D99F8600AF7E45 /* Settings.bundle in Resources */, - B5F525162851A0F600614FF7 /* DetailIng.storyboard in Resources */, - B55BCEC828F5AFD500AF7E45 /* joystick.gif in Resources */, - B5F5250428519F7A00614FF7 /* DungGeunMo.otf in Resources */, - CB2F0FCC28A5468C005F04C8 /* gifTto.gif in Resources */, - B55BCEC628F5AFB200AF7E45 /* capsule.gif in Resources */, - B5F524D628519AA000614FF7 /* Main.storyboard in Resources */, + B5EDF4A32AB056B50086230C /* Localizable.strings in Resources */, + B5C0BF9C2AAEE6B000E9017C /* gifNi.gif in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1622,6 +2040,24 @@ shellPath = /bin/sh; shellScript = "# Type a script or drag a script file from your workspace to insert its path.\n\ncase \"${CONFIGURATION}\" in\n \"Dev\" )\ncp -r \"$SRCROOT/Configuration/Dev/GoogleService-Info-Dev.plist\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist\" ;;\n \"Prod\" )\ncp -r \"$SRCROOT/Configuration/Prod/GoogleService-Info-Prod.plist\" \"${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist\" ;;\n*)\n;;\nesac\n"; }; + B5EDF4AA2AB069CE0086230C /* GoogleSheet-Localization */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 12; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "GoogleSheet-Localization"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\npython3 $TARGET_NAME/Util/Script/LocalizationScript.py $TARGET_NAME\n"; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1629,8 +2065,12 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 39632A842AFB24FD00A6E61D /* DetailEditUsecaseTest.swift in Sources */, + 390E5CAD2AB89BCE00EBE52E /* RoomListItemTest.swift in Sources */, + 390E5CB22AB8A70600EBE52E /* Date+ExtensionTest.swift in Sources */, + 398CED082AB9E48F00F50CBD /* RoomInfoTest.swift in Sources */, + 39632A282AD81F2E00A6E61D /* DetailWaitUsecaseTest.swift in Sources */, 395B5BCC2A2F6A9700CE1420 /* DetailWaitViewModelTest.swift in Sources */, - 3904F5062AA37A3C00B6264F /* MockDetailWaitService.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1638,6 +2078,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + B57D574E2AD9248500626BCD /* MailComposeManager.swift in Sources */, D75E8C7F28D76FFB004A6C41 /* LetterCountBadgeView.swift in Sources */, CBA4D7A62856D48C0018BDC2 /* RoomStateView.swift in Sources */, 333BF66628571CF00039F77F /* FriendCollectionViewCell.swift in Sources */, @@ -1645,74 +2086,102 @@ 39C957ED287AE4D100A04A2B /* MainEndPoint.swift in Sources */, B5F525292851A2A400614FF7 /* Logger.swift in Sources */, D79E9D8A2A826E0200C031F0 /* CreateRoomViewModel.swift in Sources */, - B50B1AFF2856D3820080992C /* CreateLetterPhotoView.swift in Sources */, + B50B1AFF2856D3820080992C /* SendLetterPhotoView.swift in Sources */, B5A086042A974D2000C8A98D /* ParticipantList.swift in Sources */, 397A241028BA494100454E4F /* APIEnvironment.swift in Sources */, CBBFF77C287AE491006A5964 /* DeveloperInfoViewCell.swift in Sources */, 392EC7812855D17D006918A9 /* DetailWaitTitleView.swift in Sources */, + B5422CCF2ADD3DEC0051A966 /* MemoryUsecase.swift in Sources */, + B577E5002AC373CA003CC102 /* PhotoPickerManager.swift in Sources */, B5A085E62A972EB700C8A98D /* LetterDTO.swift in Sources */, + 39632A732AF0DC2E00A6E61D /* DetailEditViewModel.swift in Sources */, 7E3058C82854B64D00489E6A /* InputCapacityView.swift in Sources */, - B53A35C62A96F6DB00B720BC /* RoomParticipationRepository.swift in Sources */, + B53A35C62A96F6DB00B720BC /* RoomParticipationRepositoryImpl.swift in Sources */, CBA15C94285CE1A80051EDE2 /* ChooseCharacterViewController.swift in Sources */, + D78B97E92AF11DD7009D9522 /* RoomParticipationRepository.swift in Sources */, 39EEF65E28CB3CFE00437654 /* LoginEndPoint.swift in Sources */, 392EC77E2855C388006918A9 /* SettingButton.swift in Sources */, - CB674C2128596A310063A6B7 /* CheckRoomViewController.swift in Sources */, + B5E850302AAEE4CF00652AC3 /* GIFSet.swift in Sources */, + CB674C2128596A310063A6B7 /* ParticipationRoomDetailsViewController.swift in Sources */, B5A085DF2A972D2A00C8A98D /* LetterRequestDTO.swift in Sources */, + D76E024F2AF5DD9800F079FB /* InvitedCodeView.swift in Sources */, 3904F5002AA3754400B6264F /* MessageCountInfo.swift in Sources */, + D7537F692AA76DD0005AC78B /* ParticipationRoomDetailsViewModel.swift in Sources */, B50B1AFB2856B5180080992C /* IndividualMissionView.swift in Sources */, + B577E5022AC37998003CC102 /* PHPickerError.swift in Sources */, + D78B97E02AEFD20A009D9522 /* CreateRoomError.swift in Sources */, B5F524FB28519C2A00614FF7 /* MainViewController.swift in Sources */, + D7537F672AA75AFB005AC78B /* ParticipationRoomDetailsView.swift in Sources */, + B5D3D2992AE0D1270016F1D5 /* FriendListUsecase.swift in Sources */, 7E3058C62854B47F00489E6A /* InputTitleView.swift in Sources */, - B5F5250128519EFB00614FF7 /* ImageLiteral.swift in Sources */, + B5422CD32ADD418C0051A966 /* FriendList.swift in Sources */, 39F1C12E28D756E600585B83 /* LetterImageViewController.swift in Sources */, 7E7542B32923744300D725CB /* ToastPopupView.swift in Sources */, D71939592AA2343E00A73D6C /* NicknameView.swift in Sources */, B5A085FE2A97493900C8A98D /* UserInfoDTO.swift in Sources */, + CB66FD6C2AE7FCDE00358CA2 /* LoginView.swift in Sources */, 39C957D22879523200A04A2B /* Date+Extension.swift in Sources */, - B55469B62AA96F54004D9FE6 /* Character.swift in Sources */, - B53A35C82A96F84700B720BC /* LetterRepository.swift in Sources */, + B5422CD62ADD42CD0051A966 /* DetailUsecaseError.swift in Sources */, + B55469B62AA96F54004D9FE6 /* DefaultCharacterType.swift in Sources */, + 39632AC52B0B477700A6E61D /* DetailEditUsecaseError.swift in Sources */, + B53A35C82A96F84700B720BC /* LetterRepositoryImpl.swift in Sources */, 39EE956F2A404A3800AF6857 /* MissionEditViewController.swift in Sources */, B5F5250A2851A06700614FF7 /* DetailWaitViewController.swift in Sources */, + D78B97F12AF11DF0009D9522 /* TokenRepository.swift in Sources */, B5A085F02A97326B00C8A98D /* FriendListDTO.swift in Sources */, + D78B97EF2AF11DED009D9522 /* LoginRepository.swift in Sources */, + B53CD0402ADFC25C00BE7353 /* SelectManitteeViewModel.swift in Sources */, D7C4A1AB2A0D1AA400C3AE4C /* ParticipateRoomView.swift in Sources */, - 39C957D02879521400A04A2B /* String+Date.swift in Sources */, + 39C957D02879521400A04A2B /* String+DateFormatter.swift in Sources */, 399D17AB2856C83B00F50D9D /* DetailEditViewController.swift in Sources */, B5A086002A97496900C8A98D /* MessageListItemDTO.swift in Sources */, 7E15F67E28B35B3D00441305 /* TextLiteral.swift in Sources */, + 39632A2B2AD842A800A6E61D /* DetailWaitUsecaseError.swift in Sources */, B5B3C16F2A42B37000AABD6F /* UIViewController+Combine.swift in Sources */, 3904F4FE2AA3743C00B6264F /* IndividualMission.swift in Sources */, - B54741EA29A494E200B75BA3 /* LetterImageError.swift in Sources */, - B5F525422855C97900614FF7 /* UILabel+Extension.swift in Sources */, - B53A35CE2A96FA7A00B720BC /* LoginRepository.swift in Sources */, + B5F525422855C97900614FF7 /* UILabel+Config.swift in Sources */, + B53A35CE2A96FA7A00B720BC /* LoginRepositoryImpl.swift in Sources */, CB21C33928C4C10200128D25 /* CharacterCollectionViewCell.swift in Sources */, + D78B97EB2AF11DE2009D9522 /* LetterRepository.swift in Sources */, + B577E4F42ABE0B8A003CC102 /* SendLetterViewController.swift in Sources */, B5C7FC0E2AA88B3500862021 /* UIViewController+Keyboard.swift in Sources */, 39C95804287DAD3000A04A2B /* LetterEndPoint.swift in Sources */, B50B1B44285B146A0080992C /* OpenManittoCollectionViewCell.swift in Sources */, B5F5253128545E4C00614FF7 /* BackButton.swift in Sources */, + B5422CA22ADD113C0051A966 /* SplashUsecase.swift in Sources */, 391612D829E9231B004AE982 /* DetailWaitView.swift in Sources */, + B5D3D2952AE0D0B60016F1D5 /* FriendListView.swift in Sources */, + B5422CA72ADD191A0051A966 /* EntryType.swift in Sources */, 7ED845BA2876BE530075AC61 /* SettingViewTableCell.swift in Sources */, B5B3C15F2A41D6F400AABD6F /* LetterUsecase.swift in Sources */, + 395402D42AAF0BC5003F3012 /* UIViewController+Extension.swift in Sources */, + B5D3D2932AE0D0500016F1D5 /* FriendListViewController.swift in Sources */, B5AE11F229D28CB500F86FF8 /* OpenManittoPopupView.swift in Sources */, B517C04A28D1F7EC0008BED0 /* TokenEndPoint.swift in Sources */, B5C7FC0C2AA88AF500862021 /* UIViewController+Alert.swift in Sources */, + B5422CB42ADD2B040051A966 /* MemoryView.swift in Sources */, 39EE956D2A401ED400AF6857 /* DetailingView.swift in Sources */, 39C95808287DB05C00A04A2B /* SettingEndPoint.swift in Sources */, B5A086062A977F1700C8A98D /* NetworkError.swift in Sources */, B5F524CF28519AA000614FF7 /* AppDelegate.swift in Sources */, + D7AE5ECB2AC1C32F00A20D0A /* ChooseCharacterError.swift in Sources */, 435B17682913E71400212663 /* DetailingViewController.swift in Sources */, B50CEE912A445EB700CF1C76 /* UIScrollView+Combine.swift in Sources */, + CB9332562AFCAE3100B7F87A /* LoginUsecase.swift in Sources */, + D76E02512AF5DE5500F079FB /* InvitedCodeViewModel.swift in Sources */, B5A085F22A9732E800C8A98D /* MemoryDTO.swift in Sources */, B5E1F2B2285ECBDF006D880B /* SelectManitteeViewController.swift in Sources */, D7C4A1A92A0B895300C3AE4C /* ChooseCharacterView.swift in Sources */, D724AF5F28708D830003F280 /* TopCharacterImageView.swift in Sources */, 7EA25C2728C4FEA800746AEA /* ChangeNicknameViewController.swift in Sources */, - 333BF66A285864CE0039F77F /* MemoryViewController.swift in Sources */, D719395F2AA23F2300A73D6C /* NicknameViewModel.swift in Sources */, B5C7FBF52AA859E300862021 /* BaseViewControllerType.swift in Sources */, - 39BDDCC42A52BB4A005E0A71 /* DetailWaitService.swift in Sources */, + B53CD0382ADF941300BE7353 /* OpenManittoUsecase.swift in Sources */, + B5422CB62ADD2B190051A966 /* MemoryViewModel.swift in Sources */, B5A086022A974B8100C8A98D /* RoomListItem.swift in Sources */, - B53A35CC2A96F99D00B720BC /* SettingRepository.swift in Sources */, + B53A35CC2A96F99D00B720BC /* SettingRepositoryImpl.swift in Sources */, B50CEEC02A6FF82600CF1C76 /* Publisher+Async.swift in Sources */, - B53A35C02A96F21A00B720BC /* MainRepository.swift in Sources */, + B53A35C02A96F21A00B720BC /* MainRepositoryImpl.swift in Sources */, D739C36728C7B82500161117 /* NicknameDTO.swift in Sources */, B5A085EA2A97309900C8A98D /* MessageListItem.swift in Sources */, B5AE11F029D1E43B00F86FF8 /* OpenManittoView.swift in Sources */, @@ -1721,59 +2190,73 @@ B5F525062851A05500614FF7 /* CreateRoomViewController.swift in Sources */, B5F524D128519AA000614FF7 /* SceneDelegate.swift in Sources */, B5A085E32A972DDC00C8A98D /* EditedMissionRequestDTO.swift in Sources */, + 395402D22AAF0B4F003F3012 /* Navigationable.swift in Sources */, CB637A3B28595C1200FF1240 /* ParticipateRoomViewController.swift in Sources */, - B5F524FF28519EF300614FF7 /* UIFont+Extension.swift in Sources */, D777D2C928C7780E008655BD /* URLLiteral.swift in Sources */, - D7B83FD62A95A48700BFD8FF /* CreateRoomService.swift in Sources */, + D7B83FD62A95A48700BFD8FF /* CreateRoomUsecase.swift in Sources */, 391B3003286198C200421F1D /* SizeLiteral.swift in Sources */, + B5D3D2972AE0D0CE0016F1D5 /* FriendListViewModel.swift in Sources */, B55BCEB428D8449E00AF7E45 /* MemoryCollectionViewCell.swift in Sources */, + B57D57692AD97DAE00626BCD /* SplashView.swift in Sources */, + CB93325A2AFCAF3F00B7F87A /* LoginInfo.swift in Sources */, + D78B97E72AF11DCD009D9522 /* DetailRoomRepository.swift in Sources */, + D75DEEC72AA612DB00BD3AC7 /* ParticipateRoomViewModel.swift in Sources */, B5F525262851A26D00614FF7 /* NSObject+ClassName.swift in Sources */, + 39632A762AF0EE6600A6E61D /* DetailEditUsecase.swift in Sources */, 3904F4FA2AA36F4F00B6264F /* UserInfo.swift in Sources */, 39FFE32828EEFFFE008442EE /* MoreButton.swift in Sources */, - B5706BE129ADC20A0093D198 /* CreateLetterView.swift in Sources */, 7E0C5C362855B22700F698D1 /* CreateNicknameViewController.swift in Sources */, B5B3C15D2A41D6EB00AABD6F /* LetterViewModel.swift in Sources */, - B55469B32AA96F2C004D9FE6 /* BaseViewController.swift in Sources */, B5A085FC2A97470400C8A98D /* RoomInfo.swift in Sources */, CB674C1C285966020063A6B7 /* InputInvitedCodeView.swift in Sources */, - B50B1AF92856B2C60080992C /* CreateLetterViewController.swift in Sources */, - B5F31C5028BF922E00F61D0F /* LetterViewController+MailCompose.swift in Sources */, 7E3058CA2854B7A900489E6A /* InputDateView.swift in Sources */, + D7C0D9332AF4B67D00B5B9B1 /* TextFieldUsecase.swift in Sources */, + D75DEEC52AA612CE00BD3AC7 /* ParticipateRoomUsecase.swift in Sources */, D7B6C97C2858B2D40024F326 /* CheckRoomInfoView.swift in Sources */, - D71939532AA1C84900A73D6C /* SettingService.swift in Sources */, + D78B97E52AF11DC1009D9522 /* MainRepository.swift in Sources */, + 39833CFE2AB09A6C009D173A /* Keyboardable.swift in Sources */, + D71939532AA1C84900A73D6C /* SettingUsecase.swift in Sources */, B5F31BB028BE1CA700F61D0F /* UserDefaultStorage.swift in Sources */, + D78B97ED2AF11DE8009D9522 /* SettingRepository.swift in Sources */, B5F31BB228BE1CD700F61D0F /* UserDefaultHandler.swift in Sources */, CB9592B52855D52700847751 /* CommonMissionView.swift in Sources */, B5C7FBED2AA7607300862021 /* BaseViewType.swift in Sources */, CBA4D7A42856CE9F0018BDC2 /* CreateRoomCollectionViewCell.swift in Sources */, D7C4A1A72A0B2EB000C3AE4C /* CharacterCollectionView.swift in Sources */, 39C95802287DACC300A04A2B /* RoomParticipationEndPoint.swift in Sources */, + B5422CCA2ADD3D1C0051A966 /* SendLetterUsecase.swift in Sources */, 395B5BC22A25E20000CE1420 /* DetailWaitViewModel.swift in Sources */, - B5F524FD28519EEC00614FF7 /* UIColor+Extension.swift in Sources */, + B577E4FD2ABFDE6F003CC102 /* SendLetterView.swift in Sources */, B5E1F2AE285B2917006D880B /* Int+RandomNumber.swift in Sources */, D7C4A1AD2A0DD8FA00C3AE4C /* SettingView.swift in Sources */, + D79938882ABEC80300711D5B /* ChooseCharacterViewModel.swift in Sources */, B55469B42AA96F2C004D9FE6 /* BaseViewModelType.swift in Sources */, + B5422CD12ADD3E4B0051A966 /* Memory.swift in Sources */, 39C957CE2876E2ED00A04A2B /* LoginViewController.swift in Sources */, B53A35D42A97266D00B720BC /* CreatedRoomRequestDTO.swift in Sources */, + B55469EB2AAC3B5F004D9FE6 /* UIView+Animation.swift in Sources */, B53A35B72A963F2100B720BC /* DetailRoomEndPoint.swift in Sources */, - D7C4A1B02A0E522800C3AE4C /* SettingViewController+MailComposeViewControllerDelegate.swift in Sources */, + B5546A0B2AADCB13004D9FE6 /* LetterUsecaseError.swift in Sources */, 3915D54D2A7516E9002D6C25 /* Key+Extension.swift in Sources */, B5A085EE2A9731F000C8A98D /* DailyMissionDTO.swift in Sources */, - 33BDF5622856E03800564211 /* FriendListViewController.swift in Sources */, D71939552AA1C85300A73D6C /* SettingViewModel.swift in Sources */, B5F5250E2851A07700614FF7 /* LetterViewController.swift in Sources */, B5F525082851A05E00614FF7 /* InvitedCodeViewController.swift in Sources */, 398B1C9B29F10B0300DEFCEC /* DetailEditView.swift in Sources */, - B50B1AFD2856C3500080992C /* CreateLetterTextView.swift in Sources */, - D77224DF285DDAB7008B760B /* Notification+Extension.swift in Sources */, + B50B1AFD2856C3500080992C /* SendLetterTextView.swift in Sources */, B5A085F42A9733A700C8A98D /* LoginDTO.swift in Sources */, - B53A35D02A97194D00B720BC /* TokenRepository.swift in Sources */, + B53A35D02A97194D00B720BC /* TokenRepositoryImpl.swift in Sources */, B5706BF629B9D4650093D198 /* GuideView.swift in Sources */, B50B1B41285B048A0080992C /* OpenManittoViewController.swift in Sources */, - CB5AE3E6285AB76800382EA3 /* PeopleInfoView.swift in Sources */, + B5422CA52ADD16C40051A966 /* SplashViewModel.swift in Sources */, B5B3C1652A427B5800AABD6F /* UIControl+Combine.swift in Sources */, B5A085F82A9744CC00C8A98D /* RoomListDTO.swift in Sources */, + B5422CB22ADD2AE60051A966 /* MemoryViewController.swift in Sources */, B5A085F62A9733F900C8A98D /* TokenDTO.swift in Sources */, + D7ADDA3C2AFBBDA600755835 /* SettingError.swift in Sources */, + B577E4F62ABE0C95003CC102 /* SendLetterViewModel.swift in Sources */, + CB93325F2AFCBD4D00B7F87A /* LoginUsecaseError.swift in Sources */, + B53CD03A2ADF9B6100BE7353 /* OpenManittoViewModel.swift in Sources */, B5F5253B2854ABF200614FF7 /* MainButton.swift in Sources */, 3904F4FC2AA3729300B6264F /* InvitationCode.swift in Sources */, B50B1B2A285AB7650080992C /* SplashViewController.swift in Sources */, @@ -1781,16 +2264,23 @@ B5F5253528547F1900614FF7 /* LetterHeaderView.swift in Sources */, B5A085FA2A9745DA00C8A98D /* RoomInfoDTO.swift in Sources */, B5F52537285481C800614FF7 /* LetterCollectionViewCell.swift in Sources */, - B53A35C42A96F50100B720BC /* DetailRoomRepository.swift in Sources */, + D7537F612AA70C40005AC78B /* ParticipatedRoomInfo.swift in Sources */, + D7BA95B52AF8AB9400455107 /* NicknameError.swift in Sources */, + B53A35C42A96F50100B720BC /* DetailRoomRepositoryImpl.swift in Sources */, + B53A35C42A96F50100B720BC /* DetailRoomRepositoryImpl.swift in Sources */, + B53A35C42A96F50100B720BC /* DetailRoomRepositoryImpl.swift in Sources */, + CB66FD6E2AE7FCE900358CA2 /* LoginViewModel.swift in Sources */, CB358C0228A564080084D001 /* SettingDeveloperInfoViewController.swift in Sources */, - D71939612AA23F3B00A73D6C /* NicknameService.swift in Sources */, + D71939612AA23F3B00A73D6C /* NicknameUsecase.swift in Sources */, B54741E029A3A4DB00B75BA3 /* LetterImageView.swift in Sources */, + 39EF30F92AD3DE6700466A90 /* DetailWaitUseCase.swift in Sources */, B5A085EC2A97314800C8A98D /* ParticipatedRoomInfoDTO.swift in Sources */, D724AF5D287088310003F280 /* SettingViewController.swift in Sources */, + D78B97F52AF23CD0009D9522 /* CreateRoomInfo.swift in Sources */, + D76E025F2AF7D12600F079FB /* ParticipateRoomError.swift in Sources */, B5F3F1062AA3516200FA1A32 /* RoomStatus.swift in Sources */, B5F5253D2854B8CB00614FF7 /* UIView+Extension.swift in Sources */, 39018F4228C4708A00C78DBA /* UIButton+Extension.swift in Sources */, - CB5AE3E4285AAF7400382EA3 /* RoomInfoView.swift in Sources */, B5FEE9DD28C849B400DEA07E /* UIImageView+Cache.swift in Sources */, CB9592B22855C09A00847751 /* ManitoRoomCollectionCell.swift in Sources */, D70220232A7A22EC0024BACD /* CreateRoomView.swift in Sources */, @@ -1811,12 +2301,12 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ - B5F524D428519AA000614FF7 /* Main.storyboard */ = { + B5EDF4A52AB056B50086230C /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( - B5F524D528519AA000614FF7 /* Base */, + B5EDF4A42AB056B50086230C /* en */, ); - name = Main.storyboard; + name = Localizable.strings; sourceTree = ""; }; B5F524D928519AA100614FF7 /* LaunchScreen.storyboard */ = { @@ -2006,8 +2496,9 @@ CODE_SIGN_ENTITLEMENTS = Manito/App/Manito.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HF8GHZB58X; + FRAMEWORK_SEARCH_PATHS = ""; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Manito/App/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "$(BUNDLE_DISPLAY_NAME)"; @@ -2017,7 +2508,6 @@ INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "‘애니또’가 사용자의 사진에 접근하려고 합니다. 쪽지에 사진을 업로드할 때 사용합니다."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UIUserInterfaceStyle = Dark; @@ -2025,7 +2515,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.6.0; + MARKETING_VERSION = 1.7.0; OTHER_SWIFT_FLAGS = "-DDEV"; PRODUCT_BUNDLE_IDENTIFIER = com.TeamFirefighter.Manito; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2046,9 +2536,10 @@ CODE_SIGN_ENTITLEMENTS = Manito/App/Manito.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 7; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HF8GHZB58X; ENABLE_TESTABILITY = YES; + FRAMEWORK_SEARCH_PATHS = ""; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Manito/App/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "$(BUNDLE_DISPLAY_NAME)"; @@ -2058,7 +2549,6 @@ INFOPLIST_KEY_NSPhotoLibraryUsageDescription = "‘애니또’가 사용자의 사진에 접근하려고 합니다. 쪽지에 사진을 업로드할 때 사용합니다."; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; - INFOPLIST_KEY_UIMainStoryboardFile = Main; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UIUserInterfaceStyle = Dark; @@ -2066,7 +2556,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.6.0; + MARKETING_VERSION = 1.7.0; OTHER_SWIFT_FLAGS = "-DPROD"; PRODUCT_BUNDLE_IDENTIFIER = com.TeamFirefighter.Manito; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/Manito/Manito/App/AppDelegate.swift b/Manito/Manito/App/AppDelegate.swift index cfe283b3c..1d358a25f 100644 --- a/Manito/Manito/App/AppDelegate.swift +++ b/Manito/Manito/App/AppDelegate.swift @@ -7,42 +7,43 @@ import AuthenticationServices import UIKit +import UserNotifications import Firebase -import UserNotifications +import MTResource @main class AppDelegate: UIResponder, UIApplicationDelegate { - - - + + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { getCredentialState() FirebaseApp.configure() - + Messaging.messaging().delegate = self UNUserNotificationCenter.current().delegate = self let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] - + UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { _, _ in } application.registerForRemoteNotifications() return true } - + // MARK: UISceneSession Lifecycle - + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { // Called when a new scene session is being created. // Use this method to select a configuration to create the new scene with. return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) } - + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { // Called when the user discards a scene session. // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. // Use this method to release any resources that were specific to the discarded scenes, as they will not return. } - + func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { return UIInterfaceOrientationMask.portrait } @@ -51,17 +52,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate { print("applicationWillEnterForeground") getCredentialState() } - + func applicationDidBecomeActive(_ application: UIApplication) { print("applicationDidBecomeActive") getCredentialState() } func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { - let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)}) - print("[Log] deviceToken :", deviceTokenString) + let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02X", $1)}) + print("[Log] deviceToken :", deviceTokenString) - Messaging.messaging().apnsToken = deviceToken + Messaging.messaging().apnsToken = deviceToken } } @@ -93,14 +94,14 @@ extension AppDelegate: MessagingDelegate { print("파이어베이스 토큰: \(fcmToken ?? "")") guard let token = fcmToken else { return } UserDefaultHandler.setFcmToken(fcmToken: token) - } + } } extension AppDelegate: UNUserNotificationCenterDelegate { func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { completionHandler([.badge, .sound]) - } - + } + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { guard let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate else { return } diff --git a/Manito/Manito/App/SceneDelegate.swift b/Manito/Manito/App/SceneDelegate.swift index 200ccf860..194a9898d 100644 --- a/Manito/Manito/App/SceneDelegate.swift +++ b/Manito/Manito/App/SceneDelegate.swift @@ -8,35 +8,37 @@ import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { - + var window: UIWindow? - + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let windowScene = (scene as? UIWindowScene) else { return } let window = UIWindow(windowScene: windowScene) - let storyboard = UIStoryboard(name: "Splash", bundle: nil) - guard let viewController = storyboard.instantiateViewController(withIdentifier: SplashViewController.className) as? SplashViewController else { return } - - window.rootViewController = viewController + let usecase = SplashUsecaseImpl() + let viewModel = SplashViewModel(usecase: usecase) + window.rootViewController = SplashViewController(viewModel: viewModel) self.window = window window.makeKeyAndVisible() } - + func sceneDidDisconnect(_ scene: UIScene) { } - + func sceneDidBecomeActive(_ scene: UIScene) { } - + func sceneWillResignActive(_ scene: UIScene) { } - + func sceneWillEnterForeground(_ scene: UIScene) { } - + func sceneDidEnterBackground(_ scene: UIScene) { } } extension SceneDelegate { func moveToLoginViewController() { - let viewController = UINavigationController(rootViewController: LoginViewController()) - window?.rootViewController = viewController + let repository = LoginRepositoryImpl() + let usecase = LoginUsecaseImpl(repository: repository) + let viewModel = LoginViewModel(usecase: usecase) + let viewController = LoginViewController(viewModel: viewModel) + window?.rootViewController = UINavigationController(rootViewController: viewController) } func changeRootViewWithLetterView(roomId: Int) { diff --git a/Manito/Manito/Data/DTO/Response/FriendListDTO.swift b/Manito/Manito/Data/DTO/Response/FriendListDTO.swift index 98f6bef61..09267a448 100644 --- a/Manito/Manito/Data/DTO/Response/FriendListDTO.swift +++ b/Manito/Manito/Data/DTO/Response/FriendListDTO.swift @@ -7,13 +7,28 @@ import Foundation +/// +/// 방에 참여한 멤버들의 정보를 반환하는 데이터 모델 DTO입니다. +/// + struct FriendListDTO: Decodable { + /// 멤버 수 let count: Int? + /// 멤버들 정보 let members: [MemberInfoDTO]? } +extension FriendListDTO { + func toFriendList() -> FriendList { + return FriendList(count: self.count ?? 0, + members: self.members?.compactMap { $0.toMemberInfo() } ?? []) + } +} + struct MemberInfoDTO: Decodable { + /// 닉네임 let nickname: String? + /// 선택한 컬러 인덱스 let colorIndex: Int? enum CodingKeys: String, CodingKey { @@ -21,3 +36,10 @@ struct MemberInfoDTO: Decodable { case colorIndex = "colorIdx" } } + +extension MemberInfoDTO { + func toMemberInfo() -> MemberInfo { + return MemberInfo(nickname: self.nickname ?? "", + colorIndex: self.colorIndex ?? 0) + } +} diff --git a/Manito/Manito/Data/DTO/Response/LoginDTO.swift b/Manito/Manito/Data/DTO/Response/LoginDTO.swift index dfeb6c1ab..4a75f52b7 100644 --- a/Manito/Manito/Data/DTO/Response/LoginDTO.swift +++ b/Manito/Manito/Data/DTO/Response/LoginDTO.swift @@ -14,3 +14,15 @@ struct LoginDTO: Decodable { let isNewMember: Bool? let userSettingDone: Bool? } + +extension LoginDTO { + func toLoginInfo() -> LoginInfo { + return LoginInfo( + accessToken: self.accessToken ?? "", + refreshToken: self.refreshToken ?? "", + nickname: self.nickname ?? "", + isNewMember: self.isNewMember ?? true, + userSettingDone: self.userSettingDone ?? false + ) + } +} diff --git a/Manito/Manito/Data/DTO/Response/MemoryDTO.swift b/Manito/Manito/Data/DTO/Response/MemoryDTO.swift index d347824c6..c58784180 100644 --- a/Manito/Manito/Data/DTO/Response/MemoryDTO.swift +++ b/Manito/Manito/Data/DTO/Response/MemoryDTO.swift @@ -7,12 +7,41 @@ import Foundation +/// +/// 함께 했던 기억 화면 관련 내용을 반환하는 +/// 데이터 모델 DTO입니다. +/// + struct MemoryDTO: Decodable { + /// 마니또와의 추억 let memoriesWithManitto: MemoryItemDTO? + /// 마니띠와의 추억 let memoriesWithManittee: MemoryItemDTO? } +extension MemoryDTO { + func toMemory() async -> Memory { + return await Memory(memoriesWithManitto: (self.memoriesWithManitto?.toMemoryItem())!, + memoriesWithManittee: (self.memoriesWithManittee?.toMemoryItem())!) + } +} + struct MemoryItemDTO: Decodable { + /// 마니또나 마니띠의 정보 let member: MemberInfoDTO? + /// 마니또, 마니띠와 주고 받았던 쪽지 내용 let messages: [MessageListItemDTO]? } + +extension MemoryItemDTO { + func toMemoryItem() async -> MemoryItem { + let memberInfo = MemberInfo(nickname: member?.nickname ?? "", + colorIndex: member?.colorIndex ?? 0) + var resultMessages: [MessageListItem] = [] + for message in self.messages ?? [] { + resultMessages.append(await message.toMessageListItem(canReport: false)) + } + return MemoryItem(member: memberInfo, + messages: resultMessages) + } +} diff --git a/Manito/Manito/Data/DTO/Response/MessageListItemDTO.swift b/Manito/Manito/Data/DTO/Response/MessageListItemDTO.swift index 560cfdced..d122bebda 100644 --- a/Manito/Manito/Data/DTO/Response/MessageListItemDTO.swift +++ b/Manito/Manito/Data/DTO/Response/MessageListItemDTO.swift @@ -26,12 +26,32 @@ struct MessageListItemDTO: Decodable { } extension MessageListItemDTO { - func toMessageListItem(canReport: Bool) -> MessageListItem { - return MessageListItem(id: self.id ?? 0, + func toMessageListItem(canReport: Bool) async -> MessageListItem { + return await MessageListItem(id: self.id ?? 0, content: self.content, - imageUrl: self.imageUrl, + imageUrl: self.verifiedImageURL(), createdDate: self.createdDate ?? "", missionInfo: self.missionInfo, canReport: canReport) } + + // MARK: - Private - func + + /// DB에 이미지가 없는 imageURL를 걸러내고 검증된 imageURL만 반환 + private func verifiedImageURL() async -> String? { + do { + guard let imageURL = self.imageUrl else { return nil } + guard let url = URL(string: imageURL) else { return nil } + + let (_, response) = try await URLSession.shared.data(from: url) + guard let urlResponse = response as? HTTPURLResponse else { return nil } + + switch urlResponse.statusCode { + case 200..<300: return self.imageUrl + default: return nil + } + } catch { + return nil + } + } } diff --git a/Manito/Manito/Data/DTO/Response/ParticipatedRoomInfoDTO.swift b/Manito/Manito/Data/DTO/Response/ParticipatedRoomInfoDTO.swift index 71bcc075a..96c89b1ba 100644 --- a/Manito/Manito/Data/DTO/Response/ParticipatedRoomInfoDTO.swift +++ b/Manito/Manito/Data/DTO/Response/ParticipatedRoomInfoDTO.swift @@ -7,11 +7,32 @@ import Foundation +/// +/// 방 참가시 그 방의 정보들을 반환하는 데이터 모델 DTO 입니다. +/// + struct ParticipatedRoomInfoDTO: Decodable { + /// 방 id let id: Int? + /// 방 제목 let title: String? + /// 방 참여 가능 인원 수 let capacity: Int? + /// 현재 방 참가 인원 수 let participatingCount: Int? + /// 방 시작 날짜 let startDate: String? + /// 방 종료 날짜 let endDate: String? } + +extension ParticipatedRoomInfoDTO { + func toParticipateRoomInfo() -> ParticipatedRoomInfo { + return ParticipatedRoomInfo(id: self.id ?? 0, + title: self.title ?? "", + capacity: self.capacity ?? 0, + participatingCount: self.participatingCount ?? 0, + startDate: self.startDate ?? "", + endDate: self.endDate ?? "") + } +} diff --git a/Manito/Manito/Data/DTO/Response/RoomInfoDTO.swift b/Manito/Manito/Data/DTO/Response/RoomInfoDTO.swift index 672992420..54e3db562 100644 --- a/Manito/Manito/Data/DTO/Response/RoomInfoDTO.swift +++ b/Manito/Manito/Data/DTO/Response/RoomInfoDTO.swift @@ -52,6 +52,20 @@ extension RoomInfoDTO { } } +extension RoomInfoDTO: Equatable { + static let testDummyRoomDTO = RoomInfoDTO( + roomInformation: RoomListItemDTO.testDummyRoomListItemDTO, + participants: ParticipantListDTO.testDummyParticipantListDTO, + manittee: UserInfoDTO.testDummyUserManittee, + manitto: UserInfoDTO.testDummyUserManitto, + invitation: InvitationCodeDTO.testDummyInvitationCodeDTO, + didViewRoulette: false, + mission: IndividualMissionDTO.testDummyIndividualMissionDTO, + admin: false, + messages: MessageCountInfoDTO.testDummyMessageInfoDTO + ) +} + struct ParticipantListDTO: Decodable { let count: Int? let members: [UserInfoDTO]? @@ -64,6 +78,13 @@ extension ParticipantListDTO { } } +extension ParticipantListDTO: Equatable { + static let testDummyParticipantListDTO = ParticipantListDTO( + count: 5, + members: UserInfoDTO.testDummyUserList + ) +} + struct InvitationCodeDTO: Decodable { let code: String? } @@ -74,6 +95,10 @@ extension InvitationCodeDTO { } } +extension InvitationCodeDTO: Equatable { + static let testDummyInvitationCodeDTO = InvitationCodeDTO(code: "ABCDEF") +} + /// 개별 미션에 대한 정보들을 반환하는 데이터 모델 DTO입니다. struct IndividualMissionDTO: Decodable, Hashable { /// 개별 미션 identifier @@ -88,12 +113,20 @@ extension IndividualMissionDTO { } } +extension IndividualMissionDTO: Equatable { + static let testDummyIndividualMissionDTO = IndividualMissionDTO(id: 1, content: "테스트미션") +} + struct MessageCountInfoDTO: Decodable { let count: Int? } -extension MessageCountInfoDTO: Equatable { +extension MessageCountInfoDTO { func toMessageCountInfo() -> MessageCountInfo { return MessageCountInfo(count: self.count ?? 0) } } + +extension MessageCountInfoDTO: Equatable { + static let testDummyMessageInfoDTO = MessageCountInfoDTO(count: 3) +} diff --git a/Manito/Manito/Data/DTO/Response/RoomListDTO.swift b/Manito/Manito/Data/DTO/Response/RoomListDTO.swift index 6dca36d12..59608934b 100644 --- a/Manito/Manito/Data/DTO/Response/RoomListDTO.swift +++ b/Manito/Manito/Data/DTO/Response/RoomListDTO.swift @@ -33,3 +33,15 @@ extension RoomListItemDTO { ) } } + +extension RoomListItemDTO: Equatable { + static let testDummyRoomListItemDTO = RoomListItemDTO( + id: 1, + title: "테스트타이틀", + state: "PRE", + participatingCount: 5, + capacity: 5, + startDate: "2023.01.01", + endDate: "2023.01.05" + ) +} diff --git a/Manito/Manito/Data/DTO/Response/UserInfoDTO.swift b/Manito/Manito/Data/DTO/Response/UserInfoDTO.swift index 33a733393..2922da36c 100644 --- a/Manito/Manito/Data/DTO/Response/UserInfoDTO.swift +++ b/Manito/Manito/Data/DTO/Response/UserInfoDTO.swift @@ -23,3 +23,16 @@ extension UserInfoDTO { return UserInfo(id: self.id ?? "" , nickname: self.nickname ?? "") } } + +extension UserInfoDTO: Equatable { + static let testDummyUserManittee = UserInfoDTO(id: "1", nickname: "테스트마니띠") + static let testDummyUserManitto = UserInfoDTO(id: "2", nickname: "테스트마니또") + + static let testDummyUserList = [ + UserInfoDTO(id: "100", nickname: "유저1"), + UserInfoDTO(id: "200", nickname: "유저2"), + UserInfoDTO(id: "300", nickname: "유저3"), + UserInfoDTO(id: "400", nickname: "유저4"), + UserInfoDTO(id: "500", nickname: "유저5") + ] +} diff --git a/Manito/Manito/Data/Error/CreateRoomScene/CreateRoomError.swift b/Manito/Manito/Data/Error/CreateRoomScene/CreateRoomError.swift new file mode 100644 index 000000000..edc3d2da5 --- /dev/null +++ b/Manito/Manito/Data/Error/CreateRoomScene/CreateRoomError.swift @@ -0,0 +1,20 @@ +// +// CreateRoomError.swift +// Manito +// +// Created by 이성호 on 10/30/23. +// + +import Foundation + +enum CreateRoomError: LocalizedError { + case failedToCreateRoom +} + +extension CreateRoomError { + var errorDescription: String? { + switch self { + case .failedToCreateRoom: return TextLiteral.Common.Error.networkServer.localized() + } + } +} diff --git a/Manito/Manito/Data/Error/NicknameScene/NicknameError.swift b/Manito/Manito/Data/Error/NicknameScene/NicknameError.swift new file mode 100644 index 000000000..c69d2338e --- /dev/null +++ b/Manito/Manito/Data/Error/NicknameScene/NicknameError.swift @@ -0,0 +1,20 @@ +// +// NicknameError.swift +// Manito +// +// Created by 이성호 on 11/6/23. +// + +import Foundation + +enum NicknameError: LocalizedError { + case clientError +} + +extension NicknameError { + var errorDescription: String? { + switch self { + case .clientError: return TextLiteral.Common.Error.networkServer.localized() + } + } +} diff --git a/Manito/Manito/Data/Error/ParticipateRoomScene/ChooseCharacterError.swift b/Manito/Manito/Data/Error/ParticipateRoomScene/ChooseCharacterError.swift new file mode 100644 index 000000000..535f255d7 --- /dev/null +++ b/Manito/Manito/Data/Error/ParticipateRoomScene/ChooseCharacterError.swift @@ -0,0 +1,24 @@ +// +// ChooseCharacterError.swift +// Manito +// +// Created by 이성호 on 2023/09/25. +// + +import Foundation + +enum ChooseCharacterError: LocalizedError { + case roomAlreadyParticipating + case clientError + case unknownError +} + +extension ChooseCharacterError { + var errorDescription: String? { + switch self { + case .roomAlreadyParticipating: return TextLiteral.ParticipateRoom.Error.alreadyJoinMessage.localized() + case .clientError: return TextLiteral.Common.Error.networkServer.localized() + case .unknownError: return TextLiteral.Common.Error.networkServer.localized() + } + } +} diff --git a/Manito/Manito/Data/Error/ParticipateRoomScene/ParticipateRoomError.swift b/Manito/Manito/Data/Error/ParticipateRoomScene/ParticipateRoomError.swift new file mode 100644 index 000000000..e6a88aee1 --- /dev/null +++ b/Manito/Manito/Data/Error/ParticipateRoomScene/ParticipateRoomError.swift @@ -0,0 +1,22 @@ +// +// ParticipateRoomError.swift +// Manito +// +// Created by 이성호 on 11/5/23. +// + +import Foundation + +enum ParticipateRoomError: LocalizedError { + case invailedCode + case clientError +} + +extension ParticipateRoomError { + var errorDescription: String? { + switch self { + case .invailedCode: return TextLiteral.ParticipateRoom.Error.message.localized() + case .clientError: return TextLiteral.Common.Error.networkServer.localized() + } + } +} diff --git a/Manito/Manito/Data/Error/SettingScene/SettingError.swift b/Manito/Manito/Data/Error/SettingScene/SettingError.swift new file mode 100644 index 000000000..ace02f186 --- /dev/null +++ b/Manito/Manito/Data/Error/SettingScene/SettingError.swift @@ -0,0 +1,20 @@ +// +// SettingError.swift +// Manito +// +// Created by 이성호 on 11/8/23. +// + +import Foundation + +enum SettingError: LocalizedError { + case clientError +} + +extension SettingError { + var errorDescription: String? { + switch self { + case .clientError: return TextLiteral.Common.Error.networkServer.localized() + } + } +} diff --git a/Manito/Manito/Data/Network/Foundation/Key+Extension.swift b/Manito/Manito/Data/Network/Foundation/Key+Extension.swift index 2377d8796..f6e9adfca 100644 --- a/Manito/Manito/Data/Network/Foundation/Key+Extension.swift +++ b/Manito/Manito/Data/Network/Foundation/Key+Extension.swift @@ -21,4 +21,11 @@ extension Bundle { guard let key = resource["Development URL"] as? String else { fatalError("Development URL을 입력해 주세요") } return key } + + var instagramAppID: String { + guard let file = self.path(forResource: "Key", ofType: "plist") else { return "Key 파일이 없습니다." } + guard let resource = NSDictionary(contentsOfFile: file) else { return "" } + guard let key = resource["instagramAppID"] as? String else { fatalError("instagramAppID를 입력해 주세요") } + return key + } } diff --git a/Manito/Manito/Data/Repository/DetailRoomRepository.swift b/Manito/Manito/Data/Repository/DetailRoomRepositoryImpl.swift similarity index 76% rename from Manito/Manito/Data/Repository/DetailRoomRepository.swift rename to Manito/Manito/Data/Repository/DetailRoomRepositoryImpl.swift index b8ecd1662..8fdbd1dd5 100644 --- a/Manito/Manito/Data/Repository/DetailRoomRepository.swift +++ b/Manito/Manito/Data/Repository/DetailRoomRepositoryImpl.swift @@ -9,18 +9,6 @@ import Foundation import MTNetwork -protocol DetailRoomRepository { - func fetchWithFriend(roomId: String) async throws -> FriendListDTO - func fetchRoomInfo(roomId: String) async throws -> RoomInfoDTO - func fetchResetMission(roomId: String) async throws -> IndividualMissionDTO - func fetchMemory(roomId: String) async throws -> MemoryDTO - func patchStartManitto(roomId: String) async throws -> UserInfoDTO - func patchEditMission(roomId: String, mission: EditedMissionRequestDTO) async throws -> IndividualMissionDTO - func putRoomInfo(roomId: String, roomInfo: CreatedRoomInfoRequestDTO) async throws -> Int - func deleteRoom(roomId: String) async throws -> Int - func deleteLeaveRoom(roomId: String) async throws -> Int -} - final class DetailRoomRepositoryImpl: DetailRoomRepository { private var provider = Provider() diff --git a/Manito/Manito/Data/Repository/LetterRepository.swift b/Manito/Manito/Data/Repository/LetterRepositoryImpl.swift similarity index 79% rename from Manito/Manito/Data/Repository/LetterRepository.swift rename to Manito/Manito/Data/Repository/LetterRepositoryImpl.swift index b5c8a9f0b..483426e71 100644 --- a/Manito/Manito/Data/Repository/LetterRepository.swift +++ b/Manito/Manito/Data/Repository/LetterRepositoryImpl.swift @@ -9,12 +9,6 @@ import Foundation import MTNetwork -protocol LetterRepository { - func dispatchLetter(roomId: String, image: Data?, letter: LetterRequestDTO, missionId: String) async throws -> Int - func fetchSendLetter(roomId: String) async throws -> LetterDTO - func fetchReceiveLetter(roomId: String) async throws -> LetterDTO -} - final class LetterRepositoryImpl: LetterRepository { private var provider = Provider() diff --git a/Manito/Manito/Data/Repository/LoginRepository.swift b/Manito/Manito/Data/Repository/LoginRepositoryImpl.swift similarity index 81% rename from Manito/Manito/Data/Repository/LoginRepository.swift rename to Manito/Manito/Data/Repository/LoginRepositoryImpl.swift index aa8cf0bf7..7633575e6 100644 --- a/Manito/Manito/Data/Repository/LoginRepository.swift +++ b/Manito/Manito/Data/Repository/LoginRepositoryImpl.swift @@ -9,10 +9,6 @@ import Foundation import MTNetwork -protocol LoginRepository { - func dispatchAppleLogin(login: LoginRequestDTO) async throws -> LoginDTO -} - final class LoginRepositoryImpl: LoginRepository { private var provider = Provider() diff --git a/Manito/Manito/Data/Repository/MainRepository.swift b/Manito/Manito/Data/Repository/MainRepositoryImpl.swift similarity index 80% rename from Manito/Manito/Data/Repository/MainRepository.swift rename to Manito/Manito/Data/Repository/MainRepositoryImpl.swift index 3a88b918b..3b8c3d026 100644 --- a/Manito/Manito/Data/Repository/MainRepository.swift +++ b/Manito/Manito/Data/Repository/MainRepositoryImpl.swift @@ -9,11 +9,6 @@ import Foundation import MTNetwork -protocol MainRepository { - func fetchCommonMission() async throws -> DailyMissionDTO - func fetchManittoList() async throws -> RoomListDTO -} - final class MainRepositoryImpl: MainRepository { private var provider = Provider() diff --git a/Manito/Manito/Data/Repository/RoomParticipationRepository.swift b/Manito/Manito/Data/Repository/RoomParticipationRepository.swift deleted file mode 100644 index 4fd9e053a..000000000 --- a/Manito/Manito/Data/Repository/RoomParticipationRepository.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// RoomParticipationRepository.swift -// Manito -// -// Created by SHIN YOON AH on 2023/08/24. -// - -import Foundation - -import MTNetwork - -protocol RoomParticipationRepository { - func dispatchCreateRoom(room: CreatedRoomRequestDTO) async throws -> Int - func dispatchVerifyCode(code: String) async throws -> ParticipatedRoomInfoDTO - func dispatchJoinRoom(roomId: String, member: MemberInfoRequestDTO) async throws -> Int -} - -final class RoomParticipationRepositoryImpl: RoomParticipationRepository { - - private var provider = Provider() - - func dispatchCreateRoom(room: CreatedRoomRequestDTO) async throws -> Int { - let response = try await self.provider - .request(.dispatchCreateRoom(room: room)) - let location = response.response?.allHeaderFields["Location"] as? String - let roomId = Int(location?.split(separator: "/").last ?? "-1") ?? -1 - return roomId - } - - func dispatchVerifyCode(code: String) async throws -> ParticipatedRoomInfoDTO { - let response = try await self.provider - .request(.dispatchVerifyCode(code: code)) - return try response.decode() - } - - func dispatchJoinRoom(roomId: String, member: MemberInfoRequestDTO) async throws -> Int { - let response = try await self.provider - .request(.dispatchJoinRoom(roomId: roomId, - member: member)) - return response.statusCode - } -} diff --git a/Manito/Manito/Data/Repository/RoomParticipationRepositoryImpl.swift b/Manito/Manito/Data/Repository/RoomParticipationRepositoryImpl.swift new file mode 100644 index 000000000..4c27dd87d --- /dev/null +++ b/Manito/Manito/Data/Repository/RoomParticipationRepositoryImpl.swift @@ -0,0 +1,50 @@ +// +// RoomParticipationRepository.swift +// Manito +// +// Created by SHIN YOON AH on 2023/08/24. +// + +import Foundation + +import MTNetwork + +final class RoomParticipationRepositoryImpl: RoomParticipationRepository { + + private var provider = Provider() + + func dispatchCreateRoom(room: CreatedRoomRequestDTO) async throws -> Int { + let response = try await self.provider + .request(.dispatchCreateRoom(room: room)) + let location = response.response?.allHeaderFields["Location"] as? String + let roomId = Int(location?.split(separator: "/").last ?? "-1") ?? -1 + return roomId + } + + func dispatchVerifyCode(code: String) async throws -> ParticipatedRoomInfoDTO { + do { + let response = try await self.provider + .request(.dispatchVerifyCode(code: code)) + return try response.decode() + } catch MTError.statusCode(reason: .clientError(let response)) { + switch response.statusCode { + case 404: throw ParticipateRoomError.invailedCode + default: throw ParticipateRoomError.clientError + } + } + } + + func dispatchJoinRoom(roomId: String, member: MemberInfoRequestDTO) async throws -> Int { + do { + let response = try await self.provider + .request(.dispatchJoinRoom(roomId: roomId, + member: member)) + return response.statusCode + } catch MTError.statusCode(reason: .clientError(let response)) { + switch response.statusCode { + case 409: throw ChooseCharacterError.roomAlreadyParticipating + default: throw ChooseCharacterError.clientError + } + } + } +} diff --git a/Manito/Manito/Data/Repository/SettingRepository.swift b/Manito/Manito/Data/Repository/SettingRepositoryImpl.swift similarity index 81% rename from Manito/Manito/Data/Repository/SettingRepository.swift rename to Manito/Manito/Data/Repository/SettingRepositoryImpl.swift index 7ddc9761f..63e779671 100644 --- a/Manito/Manito/Data/Repository/SettingRepository.swift +++ b/Manito/Manito/Data/Repository/SettingRepositoryImpl.swift @@ -9,11 +9,6 @@ import Foundation import MTNetwork -protocol SettingRepository { - func putUserInfo(nickname: NicknameDTO) async throws -> Int - func deleteMember() async throws -> Int -} - final class SettingRepositoryImpl: SettingRepository { private var provider = Provider() diff --git a/Manito/Manito/Data/Repository/TokenRepository.swift b/Manito/Manito/Data/Repository/TokenRepositoryImpl.swift similarity index 82% rename from Manito/Manito/Data/Repository/TokenRepository.swift rename to Manito/Manito/Data/Repository/TokenRepositoryImpl.swift index 60fc9a549..798e8730b 100644 --- a/Manito/Manito/Data/Repository/TokenRepository.swift +++ b/Manito/Manito/Data/Repository/TokenRepositoryImpl.swift @@ -9,10 +9,6 @@ import Foundation import MTNetwork -protocol TokenRepository { - func patchRefreshToken(token: TokenDTO) async throws -> TokenDTO -} - final class TokenRepositoryImpl: TokenRepository { private var provider = Provider() diff --git a/Manito/Manito/Domain/Entity/Character.swift b/Manito/Manito/Domain/Entity/Character.swift deleted file mode 100644 index dc7ea1613..000000000 --- a/Manito/Manito/Domain/Entity/Character.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// Character.swift -// Manito -// -// Created by COBY_PRO on 2022/09/04. -// - -import UIKit - -enum Character: CaseIterable { - case imgCharacterPink - case imgCharacterBrown - case imgCharacterBlue - case imgCharacterRed - case imgCharacterOrange - case imgCharacterYellow - case imgCharacterLightGreen - case imgCharacterHeavyPink - case imgCharacterPurple - - var color: UIColor { - switch self { - case .imgCharacterPink: - return UIColor.characterYellow - case .imgCharacterBrown: - return UIColor.characterRed - case .imgCharacterBlue: - return UIColor.characterOrange - case .imgCharacterRed: - return UIColor.characterBlue - case .imgCharacterOrange: - return UIColor.characterLightGreen - case .imgCharacterYellow: - return UIColor.characterPurple - case .imgCharacterLightGreen: - return UIColor.characterGreen - case .imgCharacterHeavyPink: - return UIColor.backgroundGrey - case .imgCharacterPurple: - return UIColor.characterPink - } - } - - var image: UIImage { - switch self { - case .imgCharacterPink: - return ImageLiterals.imgCharacterPink - case .imgCharacterBrown: - return ImageLiterals.imgCharacterBrown - case .imgCharacterBlue: - return ImageLiterals.imgCharacterBlue - case .imgCharacterRed: - return ImageLiterals.imgCharacterRed - case .imgCharacterOrange: - return ImageLiterals.imgCharacterOrange - case .imgCharacterYellow: - return ImageLiterals.imgCharacterYellow - case .imgCharacterLightGreen: - return ImageLiterals.imgCharacterLightGreen - case .imgCharacterHeavyPink: - return ImageLiterals.imgCharacterHeavyPink - case .imgCharacterPurple: - return ImageLiterals.imgCharacterPurple - } - } -} diff --git a/Manito/Manito/Domain/Entity/CreateRoomInfo.swift b/Manito/Manito/Domain/Entity/CreateRoomInfo.swift new file mode 100644 index 000000000..8e01a5da7 --- /dev/null +++ b/Manito/Manito/Domain/Entity/CreateRoomInfo.swift @@ -0,0 +1,31 @@ +// +// CreateRoom.swift +// Manito +// +// Created by 이성호 on 11/1/23. +// + +import Foundation + +struct CreateRoomInfo { + let title: String + let capacity: Int + let startDate: String + let endDate: String +} + +extension CreateRoomInfo { + static let emptyCreateInfo: CreateRoomInfo = { + return CreateRoomInfo(title: "", + capacity: 0, + startDate: "", + endDate: "") + }() + + func toCreateRoomInfoDTO() -> CreatedRoomInfoRequestDTO { + return CreatedRoomInfoRequestDTO(title: self.title, + capacity: self.capacity, + startDate: "20\(self.startDate)", + endDate: "20\(self.endDate)") + } +} diff --git a/Manito/Manito/Domain/Entity/DefaultCharacterType.swift b/Manito/Manito/Domain/Entity/DefaultCharacterType.swift new file mode 100644 index 000000000..62c0e352b --- /dev/null +++ b/Manito/Manito/Domain/Entity/DefaultCharacterType.swift @@ -0,0 +1,52 @@ +// +// DefaultCharacterType.swift +// Manito +// +// Created by COBY_PRO on 2022/09/04. +// + +import UIKit + +/// +/// 기본 캐릭터를 세팅할 때 사용되는 `enum` +/// + +enum DefaultCharacterType: CaseIterable { + case pink + case brown + case blue + case red + case orange + case yellow + case lightGreen + case heavyPink + case purple + + var backgroundColor: UIColor { + switch self { + case .pink: return UIColor.characterYellow + case .brown: return UIColor.characterRed + case .blue: return UIColor.characterOrange + case .red: return UIColor.characterBlue + case .orange: return UIColor.characterLightGreen + case .yellow: return UIColor.characterPurple + case .lightGreen: return UIColor.characterGreen + case .heavyPink: return UIColor.backgroundGrey + case .purple: return UIColor.characterPink + } + } + + var image: UIImage { + switch self { + case .pink: return UIImage.Image.characterPink + case .brown: return UIImage.Image.characterBrown + case .blue: return UIImage.Image.characterBlue + case .red: return UIImage.Image.characterRed + case .orange: return UIImage.Image.characterOrange + case .yellow: return UIImage.Image.characterYellow + case .lightGreen: return UIImage.Image.characterLightGreen + case .heavyPink: return UIImage.Image.characterHeavyPink + case .purple: return UIImage.Image.characterPurple + } + } +} diff --git a/Manito/Manito/Domain/Entity/EntryType.swift b/Manito/Manito/Domain/Entity/EntryType.swift new file mode 100644 index 000000000..394c42bfe --- /dev/null +++ b/Manito/Manito/Domain/Entity/EntryType.swift @@ -0,0 +1,18 @@ +// +// EntryType.swift +// Manito +// +// Created by SHIN YOON AH on 10/16/23. +// + +import Foundation + +/// +/// 앱 진입 시 띄워주는 뷰에 대한 `enum` +/// + +enum EntryType { + case login + case nickname + case main +} diff --git a/Manito/Manito/Domain/Entity/FriendList.swift b/Manito/Manito/Domain/Entity/FriendList.swift new file mode 100644 index 000000000..02c631b46 --- /dev/null +++ b/Manito/Manito/Domain/Entity/FriendList.swift @@ -0,0 +1,27 @@ +// +// FriendList.swift +// Manito +// +// Created by SHIN YOON AH on 10/16/23. +// + +import Foundation + +/// +/// 방에 참여하는 멤버 정보를 반환하는 +/// 데이터 모델 Entity입니다. +/// + +struct FriendList { + /// 방 참여 멤버 수 + let count: Int + /// 방 참여 멤버 정보 + let members: [MemberInfo] +} + +struct MemberInfo: Hashable { + /// 닉네임 + let nickname: String + /// 멤버 색깔 인덱스 + let colorIndex: Int +} diff --git a/Manito/Manito/Domain/Entity/IndividualMission.swift b/Manito/Manito/Domain/Entity/IndividualMission.swift index 2c2666088..9f0fa49d9 100644 --- a/Manito/Manito/Domain/Entity/IndividualMission.swift +++ b/Manito/Manito/Domain/Entity/IndividualMission.swift @@ -7,8 +7,13 @@ import Foundation +/// +/// 개별 미션에 대한 Entity +/// struct IndividualMission: Decodable, Hashable { + /// 개별 미션 id 값 let id: Int + /// 개별 미션 let content: String } diff --git a/Manito/Manito/Domain/Entity/InvitationCode.swift b/Manito/Manito/Domain/Entity/InvitationCode.swift index dae45b194..0cb207c5b 100644 --- a/Manito/Manito/Domain/Entity/InvitationCode.swift +++ b/Manito/Manito/Domain/Entity/InvitationCode.swift @@ -7,7 +7,11 @@ import Foundation +/// +/// 초대 코드에 대한 Entity +/// struct InvitationCode { + /// 초대 코드 let code: String } diff --git a/Manito/Manito/Domain/Entity/LoginInfo.swift b/Manito/Manito/Domain/Entity/LoginInfo.swift new file mode 100644 index 000000000..3fadcf667 --- /dev/null +++ b/Manito/Manito/Domain/Entity/LoginInfo.swift @@ -0,0 +1,21 @@ +// +// LoginInfo.swift +// Manito +// +// Created by COBY_PRO on 11/9/23. +// + +import Foundation + +/// +/// 로그인/회원가입 시에 유저 데이터를 반환하는 +/// 데이터 모델 Entity입니다. +/// + +struct LoginInfo { + let accessToken: String + let refreshToken: String + let nickname: String + let isNewMember: Bool + let userSettingDone: Bool +} diff --git a/Manito/Manito/Domain/Entity/Memory.swift b/Manito/Manito/Domain/Entity/Memory.swift new file mode 100644 index 000000000..4a8189e4d --- /dev/null +++ b/Manito/Manito/Domain/Entity/Memory.swift @@ -0,0 +1,27 @@ +// +// Memory.swift +// Manito +// +// Created by SHIN YOON AH on 10/16/23. +// + +import Foundation + +/// +/// 함께 했던 기억 화면 관련 내용을 반환하는 +/// 데이터 모델 Entity입니다. +/// + +struct Memory { + /// 마니또와의 추억 + let memoriesWithManitto: MemoryItem + /// 마니띠와의 추억 + let memoriesWithManittee: MemoryItem +} + +struct MemoryItem { + /// 마니또나 마니띠의 정보 + let member: MemberInfo + /// 마니또, 마니띠와 주고 받았던 쪽지 내용 + let messages: [MessageListItem] +} diff --git a/Manito/Manito/Domain/Entity/MessageCountInfo.swift b/Manito/Manito/Domain/Entity/MessageCountInfo.swift index c28d55b58..58c17465e 100644 --- a/Manito/Manito/Domain/Entity/MessageCountInfo.swift +++ b/Manito/Manito/Domain/Entity/MessageCountInfo.swift @@ -7,7 +7,11 @@ import Foundation +/// +/// 쪽지함 뱃지로 사용되는 Entity +/// struct MessageCountInfo { + /// 쪽지 수 (옵션) let count: Int? } diff --git a/Manito/Manito/Domain/Entity/MessageListItem.swift b/Manito/Manito/Domain/Entity/MessageListItem.swift index e19ff168f..1d95bdd63 100644 --- a/Manito/Manito/Domain/Entity/MessageListItem.swift +++ b/Manito/Manito/Domain/Entity/MessageListItem.swift @@ -30,12 +30,12 @@ struct MessageListItem: Hashable { /// 쪽지 생성 날짜와 오늘 날짜가 동일한지 var isToday: Bool { - return Date().letterDateToString == createdDate + return Date().toFullString == createdDate } /// 쪽지에 해당하는 개별 미션 반환(없으면 날짜 반환) var mission: String { - let date = self.isToday ? "오늘" : createdDate + let date = self.isToday ? TextLiteral.Letter.missionToday.localized() : createdDate guard let mission = missionInfo?.content else { return date } - return "\(date)의 개별미션\n[\(mission)]" + return TextLiteral.Letter.mission.localized(with: date, mission) } } diff --git a/Manito/Manito/Domain/Entity/ParticipantList.swift b/Manito/Manito/Domain/Entity/ParticipantList.swift index 6b29b4ea4..687cfbfda 100644 --- a/Manito/Manito/Domain/Entity/ParticipantList.swift +++ b/Manito/Manito/Domain/Entity/ParticipantList.swift @@ -7,8 +7,13 @@ import Foundation +/// +/// 마니또 참여 인원에 대한 Entity +/// struct ParticipantList { + /// 현재 참여한 총 인원 수 let count: Int + /// 참여한 유저들 리스트 let members: [UserInfo] } diff --git a/Manito/Manito/Domain/Entity/ParticipatedRoomInfo.swift b/Manito/Manito/Domain/Entity/ParticipatedRoomInfo.swift new file mode 100644 index 000000000..fe664bb79 --- /dev/null +++ b/Manito/Manito/Domain/Entity/ParticipatedRoomInfo.swift @@ -0,0 +1,34 @@ +// +// ParticipateRoomInfo.swift +// Manito +// +// Created by 이성호 on 2023/09/05. +// + +import Foundation + +/// 초대코드 입력 시 코드에 맞는 방 데이터 입니다. + +struct ParticipatedRoomInfo { + /// 방 id + let id: Int + /// 방 제목 + let title: String + /// 방 참여 가능 인원 수 + let capacity: Int + /// 현재 방 참가 인원 수 + let participatingCount: Int + /// 방 시작 날짜 + let startDate: String + /// 방 종료 날짜 + let endDate: String +} + +extension ParticipatedRoomInfo { + static let emptyInfo = ParticipatedRoomInfo(id: 0, + title: "", + capacity: 0, + participatingCount: 0, + startDate: "", + endDate: "") +} diff --git a/Manito/Manito/Domain/Entity/RoomInfo.swift b/Manito/Manito/Domain/Entity/RoomInfo.swift index 8f4522797..3584ea74d 100644 --- a/Manito/Manito/Domain/Entity/RoomInfo.swift +++ b/Manito/Manito/Domain/Entity/RoomInfo.swift @@ -7,32 +7,48 @@ import Foundation +/// +/// 대기방, 진행중, 완료 상태의 방의 상세 정보를 나타내는 +/// 데이터 모델 Entity 입니다. +/// struct RoomInfo { + /// 방의 이름과 상태, 참여 인원, 날짜가 포함된 Entity let roomInformation: RoomListItem + /// 참여 인원 Entity let participants: ParticipantList + /// 마니띠 Entity let manittee: UserInfo + /// 마니또 Entity (옵션) let manitto: UserInfo? + /// 초대 코드 Entity let invitation: InvitationCode + /// 마니또 시작시에 단 한번 표시되는 + /// 레버 GIF를 봤는지에 대한 Bool 값 (옵션) let didViewRoulette: Bool? + /// 개별 미션 Entity (옵션) let mission: IndividualMission? + /// 방장 여부 let admin: Bool + /// 진행중 상태일 때 쪽지함 뱃지 형식으로 표시 되는 Entity (옵션) let messages: MessageCountInfo? } extension RoomInfo { + /// 현재 인원 / 최대 인원 형식으로 반환 var userCount: String { return "\(participants.count)/\(roomInformation.capacity)" } - + + /// 마니또 시작 가능한 상태인지 var canStart: Bool { - if let date = roomInformation.startDate.stringToDate { + if let date = roomInformation.startDate.toDefaultDate { let isMinimumUserCount = participants.count >= 4 return isMinimumUserCount && date.isToday && admin } else { return false } } - + func toRoomListItem() -> RoomListItem { return RoomListItem(id: roomInformation.id, title: roomInformation.title, diff --git a/Manito/Manito/Domain/Entity/RoomListItem.swift b/Manito/Manito/Domain/Entity/RoomListItem.swift index c6051b0ed..8471304f1 100644 --- a/Manito/Manito/Domain/Entity/RoomListItem.swift +++ b/Manito/Manito/Domain/Entity/RoomListItem.swift @@ -7,13 +7,24 @@ import Foundation +/// +/// 메인 뷰에서 사용하는 방 정보 +/// 데이터 모델 Entity 입니다. +/// struct RoomListItem { + /// 방의 고유 id값 let id: Int + /// 방 제목 let title: String + /// 현재 진행 상태 let state: RoomStatus + /// 현재 참여한 인원 (옵션) let participatingCount: Int? + /// 방 참여 가능한 최대 인원 let capacity: Int + /// 시작 날짜 ex) 2023.09.12 let startDate: String + /// 종료 날짜 ex) 2023.09.16 let endDate: String init( @@ -36,40 +47,24 @@ struct RoomListItem { } extension RoomListItem { + /// 시작날짜와 종료날짜를 ~을 포함한 String 형식으로 반환 + /// ex) 2023.09.12 ~ 2023.09.16 var dateRangeText: String { - return startDate + " ~ " + endDate + return self.startDate + " ~ " + self.endDate } - - var isAlreadyPastDate: Bool { - if let date = startDate.stringToDate { - return date.distance(to: Date()) > 86400 - } else { - return false - } - } - - var isStart: Bool { - if let date = startDate.stringToDate { - let isStartDate = date.distance(to: Date()) < 86400 - let isPast = date.distance(to: Date()) > 86400 - return !isPast && isStartDate - } else { - return false - } - } - + + /// 시작 날짜가 오늘보다 과거인지 var isStartDatePast: Bool { - guard let startDate = self.startDate.stringToDate else { return true } + guard let startDate = self.startDate.toDefaultDate else { return true } return startDate.isPast } - - var dateRange: (startDate: String, endDate: String) { - let fiveDaysInterval: TimeInterval = 86400 * 4 - let startDate: String = isStartDatePast ? Date().dateToString : self.startDate - let endDate: String = isStartDatePast ? (Date() + fiveDaysInterval).dateToString : self.endDate - - return (startDate, endDate) - } + + static let emptyRoomListItem = RoomListItem(id: 0, + title: "", + state: "", + capacity: 0, + startDate: "", + endDate: "") } extension RoomListItem: Equatable { diff --git a/Manito/Manito/Domain/Entity/RoomStatus.swift b/Manito/Manito/Domain/Entity/RoomStatus.swift index c6be909fd..cb8ba2721 100644 --- a/Manito/Manito/Domain/Entity/RoomStatus.swift +++ b/Manito/Manito/Domain/Entity/RoomStatus.swift @@ -8,7 +8,7 @@ import UIKit /// -/// 방 상태에 대한 정보들을 반환하는 Enum 데이터 입니다. +/// 방 상태에 대한 정보들을 반환하는 `enum` /// enum RoomStatus: String { @@ -24,9 +24,9 @@ extension RoomStatus { /// 방 상태 뱃지에 들어가는 텍스트 var badgeTitle: String { switch self { - case .PRE: return TextLiteral.waiting - case .PROCESSING: return TextLiteral.doing - case .POST: return TextLiteral.done + case .PRE: return TextLiteral.Common.waiting.localized() + case .PROCESSING: return TextLiteral.Common.processing.localized() + case .POST: return TextLiteral.Common.done.localized() } } /// 방 상태 뱃지에 들어가는 배경 색상 diff --git a/Manito/Manito/Domain/Entity/UserInfo.swift b/Manito/Manito/Domain/Entity/UserInfo.swift index a6eeb9fb3..c48017247 100644 --- a/Manito/Manito/Domain/Entity/UserInfo.swift +++ b/Manito/Manito/Domain/Entity/UserInfo.swift @@ -7,8 +7,13 @@ import Foundation +/// +/// 유저 정보에 대한 Entity +/// struct UserInfo { + /// 유저 고유 id 값 let id: String + /// 유저 닉네임 let nickname: String } diff --git a/Manito/Manito/Domain/Error/DetailEdit/DetailEditUsecaseError.swift b/Manito/Manito/Domain/Error/DetailEdit/DetailEditUsecaseError.swift new file mode 100644 index 000000000..f0615332d --- /dev/null +++ b/Manito/Manito/Domain/Error/DetailEdit/DetailEditUsecaseError.swift @@ -0,0 +1,20 @@ +// +// DetailEditUsecaseError.swift +// Manito +// +// Created by Mingwan Choi on 11/20/23. +// + +import Foundation + +enum DetailEditUsecaseError: LocalizedError { + case failedToChangeRoomInfo +} + +extension DetailEditUsecaseError { + var errorDescription: String? { + switch self { + case .failedToChangeRoomInfo: return TextLiteral.DetailEdit.Error.message.localized() + } + } +} diff --git a/Manito/Manito/Domain/Error/DetailScene/DetailUsecaseError.swift b/Manito/Manito/Domain/Error/DetailScene/DetailUsecaseError.swift new file mode 100644 index 000000000..1144392d2 --- /dev/null +++ b/Manito/Manito/Domain/Error/DetailScene/DetailUsecaseError.swift @@ -0,0 +1,27 @@ +// +// DetailUsecaseError.swift +// Manito +// +// Created by SHIN YOON AH on 10/16/23. +// + +import Foundation + +enum DetailUsecaseError: LocalizedError { + case failedToFetchMemory + case failedToFetchManitto + case failedToFetchFriend +} + +extension DetailUsecaseError { + var errorDescription: String? { + switch self { + case .failedToFetchMemory: + return TextLiteral.Common.Error.networkServer.localized() + case .failedToFetchManitto: + return TextLiteral.DetailIng.Error.openManittoMessage.localized() + case .failedToFetchFriend: + return TextLiteral.DetailIng.Error.fetchFriendMessage.localized() + } + } +} diff --git a/Manito/Manito/Domain/Error/DetailWait/DetailWaitUsecaseError.swift b/Manito/Manito/Domain/Error/DetailWait/DetailWaitUsecaseError.swift new file mode 100644 index 000000000..b2dc86cba --- /dev/null +++ b/Manito/Manito/Domain/Error/DetailWait/DetailWaitUsecaseError.swift @@ -0,0 +1,26 @@ +// +// DetailWaitUsecaseError.swift +// Manito +// +// Created by Mingwan Choi on 10/13/23. +// + +import Foundation + +enum DetailWaitUsecaseError: LocalizedError { + case failedToFetchError + case failedToStartError + case failedToDeleteRoomError + case failedToLeaveRoomError +} + +extension DetailWaitUsecaseError { + var errorDescription: String? { + switch self { + case .failedToFetchError: return TextLiteral.DetailWait.Error.fetchRoom.localized() + case .failedToStartError: return TextLiteral.DetailWait.Error.startManitto.localized() + case .failedToDeleteRoomError: return TextLiteral.DetailWait.Error.deleteRoom.localized() + case .failedToLeaveRoomError: return TextLiteral.DetailWait.Error.leaveRoom.localized() + } + } +} diff --git a/Manito/Manito/Domain/Error/LetterScene/LetterUsecaseError.swift b/Manito/Manito/Domain/Error/LetterScene/LetterUsecaseError.swift new file mode 100644 index 000000000..da69a148f --- /dev/null +++ b/Manito/Manito/Domain/Error/LetterScene/LetterUsecaseError.swift @@ -0,0 +1,22 @@ +// +// LetterUsecaseError.swift +// Manito +// +// Created by SHIN YOON AH on 2023/09/10. +// + +import Foundation + +enum LetterUsecaseError: LocalizedError { + case failedToFetchLetter + case failedToSendLetter +} + +extension LetterUsecaseError { + var errorDescription: String? { + switch self { + case .failedToFetchLetter: return TextLiteral.Letter.Error.fetchMessage.localized() + case .failedToSendLetter: return TextLiteral.SendLetter.Error.sendMessage.localized() + } + } +} diff --git a/Manito/Manito/Domain/Error/LoginScene/LoginUsecaseError.swift b/Manito/Manito/Domain/Error/LoginScene/LoginUsecaseError.swift new file mode 100644 index 000000000..5ae27c923 --- /dev/null +++ b/Manito/Manito/Domain/Error/LoginScene/LoginUsecaseError.swift @@ -0,0 +1,30 @@ +// +// LoginUsecaseError.swift +// Manito +// +// Created by COBY_PRO on 11/9/23. +// + +import Foundation + +enum LoginUsecaseError: LocalizedError { + case failedToLogin + case failedCredential + case failedToken + case failedTokenToString +} + +extension LoginUsecaseError { + var errorDescription: String? { + switch self { + case .failedToLogin: + return TextLiteral.Common.Error.networkServer.localized() + case .failedCredential: + return TextLiteral.Sign.Error.credential.localized() + case .failedToken: + return TextLiteral.Sign.Error.token.localized() + case .failedTokenToString: + return TextLiteral.Sign.Error.tokenToString.localized() + } + } +} diff --git a/Manito/Manito/Domain/Repository/DetailRoomRepository.swift b/Manito/Manito/Domain/Repository/DetailRoomRepository.swift new file mode 100644 index 000000000..d125a749d --- /dev/null +++ b/Manito/Manito/Domain/Repository/DetailRoomRepository.swift @@ -0,0 +1,20 @@ +// +// DetailRoomRepository.swift +// Manito +// +// Created by 이성호 on 10/31/23. +// + +import Foundation + +protocol DetailRoomRepository { + func fetchWithFriend(roomId: String) async throws -> FriendListDTO + func fetchRoomInfo(roomId: String) async throws -> RoomInfoDTO + func fetchResetMission(roomId: String) async throws -> IndividualMissionDTO + func fetchMemory(roomId: String) async throws -> MemoryDTO + func patchStartManitto(roomId: String) async throws -> UserInfoDTO + func patchEditMission(roomId: String, mission: EditedMissionRequestDTO) async throws -> IndividualMissionDTO + func putRoomInfo(roomId: String, roomInfo: CreatedRoomInfoRequestDTO) async throws -> Int + func deleteRoom(roomId: String) async throws -> Int + func deleteLeaveRoom(roomId: String) async throws -> Int +} diff --git a/Manito/Manito/Domain/Repository/LetterRepository.swift b/Manito/Manito/Domain/Repository/LetterRepository.swift new file mode 100644 index 000000000..587b76a31 --- /dev/null +++ b/Manito/Manito/Domain/Repository/LetterRepository.swift @@ -0,0 +1,14 @@ +// +// LetterRepository.swift +// Manito +// +// Created by 이성호 on 10/31/23. +// + +import Foundation + +protocol LetterRepository { + func dispatchLetter(roomId: String, image: Data?, letter: LetterRequestDTO, missionId: String) async throws -> Int + func fetchSendLetter(roomId: String) async throws -> LetterDTO + func fetchReceiveLetter(roomId: String) async throws -> LetterDTO +} diff --git a/Manito/Manito/Domain/Repository/LoginRepository.swift b/Manito/Manito/Domain/Repository/LoginRepository.swift new file mode 100644 index 000000000..fa27d0834 --- /dev/null +++ b/Manito/Manito/Domain/Repository/LoginRepository.swift @@ -0,0 +1,12 @@ +// +// LoginRepository.swift +// Manito +// +// Created by 이성호 on 10/31/23. +// + +import Foundation + +protocol LoginRepository { + func dispatchAppleLogin(login: LoginRequestDTO) async throws -> LoginDTO +} diff --git a/Manito/Manito/Domain/Repository/MainRepository.swift b/Manito/Manito/Domain/Repository/MainRepository.swift new file mode 100644 index 000000000..41b1b36f2 --- /dev/null +++ b/Manito/Manito/Domain/Repository/MainRepository.swift @@ -0,0 +1,13 @@ +// +// MainRepository.swift +// Manito +// +// Created by 이성호 on 10/31/23. +// + +import Foundation + +protocol MainRepository { + func fetchCommonMission() async throws -> DailyMissionDTO + func fetchManittoList() async throws -> RoomListDTO +} diff --git a/Manito/Manito/Domain/Repository/RoomParticipationRepository.swift b/Manito/Manito/Domain/Repository/RoomParticipationRepository.swift new file mode 100644 index 000000000..ff436bc3d --- /dev/null +++ b/Manito/Manito/Domain/Repository/RoomParticipationRepository.swift @@ -0,0 +1,14 @@ +// +// RoomParticipationRepository.swift +// Manito +// +// Created by 이성호 on 10/31/23. +// + +import Foundation + +protocol RoomParticipationRepository { + func dispatchCreateRoom(room: CreatedRoomRequestDTO) async throws -> Int + func dispatchVerifyCode(code: String) async throws -> ParticipatedRoomInfoDTO + func dispatchJoinRoom(roomId: String, member: MemberInfoRequestDTO) async throws -> Int +} diff --git a/Manito/Manito/Domain/Repository/SettingRepository.swift b/Manito/Manito/Domain/Repository/SettingRepository.swift new file mode 100644 index 000000000..309d8b6fa --- /dev/null +++ b/Manito/Manito/Domain/Repository/SettingRepository.swift @@ -0,0 +1,13 @@ +// +// SettingRepository.swift +// Manito +// +// Created by 이성호 on 10/31/23. +// + +import Foundation + +protocol SettingRepository { + func putUserInfo(nickname: NicknameDTO) async throws -> Int + func deleteMember() async throws -> Int +} diff --git a/Manito/Manito/Domain/Repository/TokenRepository.swift b/Manito/Manito/Domain/Repository/TokenRepository.swift new file mode 100644 index 000000000..49a1dc7af --- /dev/null +++ b/Manito/Manito/Domain/Repository/TokenRepository.swift @@ -0,0 +1,12 @@ +// +// TokenRepository.swift +// Manito +// +// Created by 이성호 on 10/31/23. +// + +import Foundation + +protocol TokenRepository { + func patchRefreshToken(token: TokenDTO) async throws -> TokenDTO +} diff --git a/Manito/Manito/Domain/Usecase/Common/TextFieldUsecase.swift b/Manito/Manito/Domain/Usecase/Common/TextFieldUsecase.swift new file mode 100644 index 000000000..b2fea3a92 --- /dev/null +++ b/Manito/Manito/Domain/Usecase/Common/TextFieldUsecase.swift @@ -0,0 +1,29 @@ +// +// textFieldUsecase.swift +// Manito +// +// Created by 이성호 on 11/3/23. +// + +import Foundation + +protocol TextFieldUsecase { + func cutTextByMaxCount(text: String, maxCount: Int) -> String +} + +final class TextFieldUsecaseImpl: TextFieldUsecase { + func cutTextByMaxCount(text: String, maxCount: Int) -> String { + let isOverMaxCount = self.isOverMaxCount(titleCount: text.count, maxCount: maxCount) + + if isOverMaxCount { + let endIndex = text.index(text.startIndex, offsetBy: maxCount) + let fixedText = text[text.startIndex.. Bool { + return titleCount > maxCount + } +} diff --git a/Manito/Manito/Screens/CreateRoom/Services/CreateRoomService.swift b/Manito/Manito/Domain/Usecase/CreateRoomScene/CreateRoomUsecase.swift similarity index 63% rename from Manito/Manito/Screens/CreateRoom/Services/CreateRoomService.swift rename to Manito/Manito/Domain/Usecase/CreateRoomScene/CreateRoomUsecase.swift index ff231823a..80ef38977 100644 --- a/Manito/Manito/Screens/CreateRoom/Services/CreateRoomService.swift +++ b/Manito/Manito/Domain/Usecase/CreateRoomScene/CreateRoomUsecase.swift @@ -1,5 +1,5 @@ // -// CreateRoomService.swift +// CreateRoomUsecase.swift // Manito // // Created by 이성호 on 2023/08/23. @@ -7,11 +7,11 @@ import Foundation -protocol CreateRoomSevicable { +protocol CreateRoomUsecase { func dispatchCreateRoom(room: CreatedRoomRequestDTO) async throws -> Int } -final class CreateRoomService: CreateRoomSevicable { +final class CreateRoomUsecaseImpl: CreateRoomUsecase { private let repository: RoomParticipationRepository @@ -23,10 +23,8 @@ final class CreateRoomService: CreateRoomSevicable { do { let roomId = try await self.repository.dispatchCreateRoom(room: room) return roomId - } catch NetworkError.serverError { - throw NetworkError.serverError - } catch NetworkError.clientError(let message) { - throw NetworkError.clientError(message: message) + } catch { + throw CreateRoomError.failedToCreateRoom } } } diff --git a/Manito/Manito/Domain/Usecase/DetailScene/FriendListUsecase.swift b/Manito/Manito/Domain/Usecase/DetailScene/FriendListUsecase.swift new file mode 100644 index 000000000..e0e37e527 --- /dev/null +++ b/Manito/Manito/Domain/Usecase/DetailScene/FriendListUsecase.swift @@ -0,0 +1,38 @@ +// +// FriendListUsecase.swift +// Manito +// +// Created by SHIN YOON AH on 10/19/23. +// + +import Combine +import Foundation + +protocol FriendListUsecase { + func fetchFriendList(roomId: String) async throws -> FriendList +} + +final class FriendListUsecaseImpl: FriendListUsecase { + + // MARK: - property + + private let repository: DetailRoomRepository + + // MARK: - init + + init(repository: DetailRoomRepository) { + self.repository = repository + } + + // MARK: - Public - func + + func fetchFriendList(roomId: String) async throws -> FriendList { + do { + let friendListData = try await self.repository.fetchWithFriend(roomId: roomId) + return friendListData.toFriendList() + } catch { + throw DetailUsecaseError.failedToFetchFriend + } + } +} + diff --git a/Manito/Manito/Domain/Usecase/DetailScene/MemoryUsecase.swift b/Manito/Manito/Domain/Usecase/DetailScene/MemoryUsecase.swift new file mode 100644 index 000000000..6b7faa6da --- /dev/null +++ b/Manito/Manito/Domain/Usecase/DetailScene/MemoryUsecase.swift @@ -0,0 +1,41 @@ +// +// MemoryUsecase.swift +// Manito +// +// Created by SHIN YOON AH on 10/16/23. +// + +import Combine +import Foundation + +protocol MemoryUsecase { + var memory: Memory? { get set } + + func fetchMemory(roomId: String) async throws +} + +final class MemoryUsecaseImpl: MemoryUsecase { + + // MARK: - property + + @Published var memory: Memory? + + private let repository: DetailRoomRepository + + // MARK: - init + + init(repository: DetailRoomRepository) { + self.repository = repository + } + + // MARK: - Public - func + + func fetchMemory(roomId: String) async throws { + do { + let memoryData = try await self.repository.fetchMemory(roomId: roomId) + self.memory = await memoryData.toMemory() + } catch { + throw DetailUsecaseError.failedToFetchMemory + } + } +} diff --git a/Manito/Manito/Domain/Usecase/DetailScene/OpenManittoUsecase.swift b/Manito/Manito/Domain/Usecase/DetailScene/OpenManittoUsecase.swift new file mode 100644 index 000000000..b9426e42b --- /dev/null +++ b/Manito/Manito/Domain/Usecase/DetailScene/OpenManittoUsecase.swift @@ -0,0 +1,42 @@ +// +// OpenManittoUsecase.swift +// Manito +// +// Created by SHIN YOON AH on 10/18/23. +// + +import Combine +import Foundation + +protocol OpenManittoUsecase { + func fetchFriendList(roomId: String) async throws -> FriendList + func loadNickname() -> String +} + +final class OpenManittoUsecaseImpl: OpenManittoUsecase { + + // MARK: - property + + private let repository: DetailRoomRepository + + // MARK: - init + + init(repository: DetailRoomRepository) { + self.repository = repository + } + + // MARK: - Public - func + + func fetchFriendList(roomId: String) async throws -> FriendList { + do { + let data = try await self.repository.fetchWithFriend(roomId: roomId) + return data.toFriendList() + } catch { + throw DetailUsecaseError.failedToFetchManitto + } + } + + func loadNickname() -> String { + return UserDefaultStorage.nickname + } +} diff --git a/Manito/Manito/Domain/Usecase/DetailWait/DetailEditUsecase.swift b/Manito/Manito/Domain/Usecase/DetailWait/DetailEditUsecase.swift new file mode 100644 index 000000000..f2768475a --- /dev/null +++ b/Manito/Manito/Domain/Usecase/DetailWait/DetailEditUsecase.swift @@ -0,0 +1,48 @@ +// +// DetailEditUsecase.swift +// Manito +// +// Created by Mingwan Choi on 10/31/23. +// + +import Foundation + +protocol DetailEditUsecase { + var roomInformation: RoomInfo { get set } + + func vaildStartDateIsNotPast(startDate: String) -> Bool + func vaildMemberCountIsUnder(capacity: Int) -> Bool + func changeRoomInformation(roomDto: CreatedRoomInfoRequestDTO) async throws -> Int +} + +final class DetailEditUsecaseImpl: DetailEditUsecase { + @Published var roomInformation: RoomInfo + + let repository: DetailRoomRepository + + init(roomInformation: RoomInfo, repository: DetailRoomRepository) { + self.roomInformation = roomInformation + self.repository = repository + } + + func vaildStartDateIsNotPast(startDate: String) -> Bool { + guard let startDate = startDate.toDefaultDate else { return false } + let isPast = startDate.isPast + return !isPast + } + + func vaildMemberCountIsUnder(capacity: Int) -> Bool { + let isUnderMember = self.roomInformation.participants.count <= capacity + return isUnderMember + } + + func changeRoomInformation(roomDto: CreatedRoomInfoRequestDTO) async throws -> Int { + do { + let statusCode = try await self.repository.putRoomInfo(roomId: self.roomInformation.roomInformation.id.description, + roomInfo: roomDto) + return statusCode + } catch { + throw DetailEditUsecaseError.failedToChangeRoomInfo + } + } +} diff --git a/Manito/Manito/Domain/Usecase/DetailWait/DetailWaitUseCase.swift b/Manito/Manito/Domain/Usecase/DetailWait/DetailWaitUseCase.swift new file mode 100644 index 000000000..88710fd73 --- /dev/null +++ b/Manito/Manito/Domain/Usecase/DetailWait/DetailWaitUseCase.swift @@ -0,0 +1,80 @@ +// +// DetailWaitUseCase.swift +// Manito +// +// Created by Mingwan Choi on 10/9/23. +// + +import Combine +import Foundation + +protocol DetailWaitUseCase { + var roomInformation: RoomInfo { get set } + + func fetchRoomInformaion(roomId: String) async throws -> RoomInfoDTO + func patchStartManitto(roomId: String) async throws -> UserInfoDTO + func deleteRoom(roomId: String) async throws -> Int + func deleteLeaveRoom(roomId: String) async throws -> Int +} + +final class DetailWaitUseCaseImpl: DetailWaitUseCase { + + // MARK: - property + + @Published var roomInformation: RoomInfo = RoomInfo.emptyRoom + + private let repository: DetailRoomRepository + + // MARK: - init + + init(repository: DetailRoomRepository) { + self.repository = repository + } + + // MARK: - func + + func fetchRoomInformaion(roomId: String) async throws -> RoomInfoDTO { + do { + let roomInformation = try await self.repository.fetchRoomInfo(roomId: roomId) + self.roomInformation = roomInformation.toRoomInfo() + return roomInformation + } catch { + throw DetailWaitUsecaseError.failedToFetchError + } + } + + func patchStartManitto(roomId: String) async throws -> UserInfoDTO { + do { + let userInformation = try await self.repository.patchStartManitto(roomId: roomId) + return userInformation + } catch { + throw DetailWaitUsecaseError.failedToStartError + } + } + + func deleteRoom(roomId: String) async throws -> Int { + do { + let statusCode = try await self.repository.deleteRoom(roomId: roomId) + if statusCode == 204 { + return statusCode + } else { + throw DetailWaitUsecaseError.failedToDeleteRoomError + } + } catch { + throw DetailWaitUsecaseError.failedToDeleteRoomError + } + } + + func deleteLeaveRoom(roomId: String) async throws -> Int { + do { + let statusCode = try await self.repository.deleteLeaveRoom(roomId: roomId) + if statusCode == 204 { + return statusCode + } else { + throw DetailWaitUsecaseError.failedToLeaveRoomError + } + } catch { + throw DetailWaitUsecaseError.failedToLeaveRoomError + } + } +} diff --git a/Manito/Manito/Domain/Usecase/LetterScene/LetterUsecase.swift b/Manito/Manito/Domain/Usecase/LetterScene/LetterUsecase.swift index fa80db544..1e141ef6a 100644 --- a/Manito/Manito/Domain/Usecase/LetterScene/LetterUsecase.swift +++ b/Manito/Manito/Domain/Usecase/LetterScene/LetterUsecase.swift @@ -39,10 +39,8 @@ final class LetterUsecaseImpl: LetterUsecase { let letterData = try await self.repository.fetchSendLetter(roomId: roomId) self.setManitteeId(letterData.manittee?.id) return letterData.messages - } catch NetworkError.serverError { - throw NetworkError.serverError - } catch NetworkError.clientError(let message) { - throw NetworkError.clientError(message: message) + } catch { + throw LetterUsecaseError.failedToFetchLetter } } @@ -51,10 +49,8 @@ final class LetterUsecaseImpl: LetterUsecase { let letterData = try await self.repository.fetchReceiveLetter(roomId: roomId) self.setManitteeId(letterData.manittee?.id) return letterData.messages - } catch NetworkError.serverError { - throw NetworkError.serverError - } catch NetworkError.clientError(let message) { - throw NetworkError.clientError(message: message) + } catch { + throw LetterUsecaseError.failedToFetchLetter } } diff --git a/Manito/Manito/Domain/Usecase/LetterScene/SendLetterUsecase.swift b/Manito/Manito/Domain/Usecase/LetterScene/SendLetterUsecase.swift new file mode 100644 index 000000000..b0fa8fb24 --- /dev/null +++ b/Manito/Manito/Domain/Usecase/LetterScene/SendLetterUsecase.swift @@ -0,0 +1,44 @@ +// +// SendLetterUsecase.swift +// Manito +// +// Created by SHIN YOON AH on 2023/09/24. +// + +import Combine +import Foundation + +protocol SendLetterUsecase { + func dispatchLetter(roomId: String, image: Data?, letter: LetterRequestDTO, missionId: String) async throws -> Int +} + +final class SendLetterUsecaseImpl: SendLetterUsecase { + + // MARK: - property + + private let repository: LetterRepository + + // MARK: - init + + init(repository: LetterRepository) { + self.repository = repository + } + + // MARK: - Public - func + + func dispatchLetter(roomId: String, image: Data?, letter: LetterRequestDTO, missionId: String) async throws -> Int { + do { + guard image != nil || letter.messageContent != nil else { throw LetterUsecaseError.failedToSendLetter } + let statusCode = try await self.repository.dispatchLetter(roomId: roomId, + image: image, + letter: letter, + missionId: missionId) + switch statusCode { + case 200..<300: return statusCode + default: throw LetterUsecaseError.failedToSendLetter + } + } catch { + throw LetterUsecaseError.failedToSendLetter + } + } +} diff --git a/Manito/Manito/Domain/Usecase/LoginScene/LoginUsecase.swift b/Manito/Manito/Domain/Usecase/LoginScene/LoginUsecase.swift new file mode 100644 index 000000000..fde278373 --- /dev/null +++ b/Manito/Manito/Domain/Usecase/LoginScene/LoginUsecase.swift @@ -0,0 +1,36 @@ +// +// LoginUsecase.swift +// Manito +// +// Created by COBY_PRO on 11/9/23. +// + +import Foundation + +protocol LoginUsecase { + func dispatchAppleLogin(login: LoginRequestDTO) async throws -> LoginInfo +} + +final class LoginUsecaseImpl: LoginUsecase { + + // MARK: - property + + private let repository: LoginRepository + + // MARK: - init + + init(repository: LoginRepository) { + self.repository = repository + } + + // MARK: - Public - func + + func dispatchAppleLogin(login: LoginRequestDTO) async throws -> LoginInfo { + do { + let loginDTO = try await self.repository.dispatchAppleLogin(login: login) + return loginDTO.toLoginInfo() + } catch { + throw LoginUsecaseError.failedToLogin + } + } +} diff --git a/Manito/Manito/Domain/Usecase/ParticipateRoomScene/ParticipateRoomUsecase.swift b/Manito/Manito/Domain/Usecase/ParticipateRoomScene/ParticipateRoomUsecase.swift new file mode 100644 index 000000000..94c47d55b --- /dev/null +++ b/Manito/Manito/Domain/Usecase/ParticipateRoomScene/ParticipateRoomUsecase.swift @@ -0,0 +1,46 @@ +// +// ParticipateRoomUsecase.swift +// Manito +// +// Created by 이성호 on 2023/09/04. +// + +import Foundation + +protocol ParticipateRoomUsecase { + func dispatchVerifyCode(code: String) async throws -> ParticipatedRoomInfoDTO + func dispatchJoinRoom(roomId: String, member: MemberInfoRequestDTO) async throws -> Int +} + +final class ParticipateRoomUsecaseImpl: ParticipateRoomUsecase { + + // MARK: - property + + private let repository: RoomParticipationRepository + + // MARK: - init + + init(repository: RoomParticipationRepository) { + self.repository = repository + } + + // MARK: - Public - func + + func dispatchVerifyCode(code: String) async throws -> ParticipatedRoomInfoDTO { + do { + let roomInfo = try await self.repository.dispatchVerifyCode(code: code) + return roomInfo + } catch ParticipateRoomError.invailedCode { + throw ParticipateRoomError.invailedCode + } catch ParticipateRoomError.clientError { + throw ParticipateRoomError.clientError + } + } + + func dispatchJoinRoom(roomId: String, member: MemberInfoRequestDTO) async throws -> Int { + do { + let statusCode = try await self.repository.dispatchJoinRoom(roomId: roomId, member: member) + return statusCode + } + } +} diff --git a/Manito/Manito/Screens/Setting/Service/SettingService.swift b/Manito/Manito/Domain/Usecase/SettingScene/SettingUsecase.swift similarity index 59% rename from Manito/Manito/Screens/Setting/Service/SettingService.swift rename to Manito/Manito/Domain/Usecase/SettingScene/SettingUsecase.swift index d0075c814..15ebe2666 100644 --- a/Manito/Manito/Screens/Setting/Service/SettingService.swift +++ b/Manito/Manito/Domain/Usecase/SettingScene/SettingUsecase.swift @@ -1,5 +1,5 @@ // -// SettingService.swift +// SettingUsecase.swift // Manito // // Created by 이성호 on 2023/09/01. @@ -7,26 +7,30 @@ import Foundation -protocol SettingServicable { +protocol SettingUsecase { func deleteUser() async throws -> Int } -final class SettingService: SettingServicable { +final class SettingUsecaseImpl: SettingUsecase { + + // MARK: - property private let repository: SettingRepository + // MARK: - init + init(repository: SettingRepository) { self.repository = repository } + // MARK: - Public - func + func deleteUser() async throws -> Int { do { let statusCode = try await self.repository.deleteMember() return statusCode - } catch NetworkError.serverError { - throw NetworkError.serverError - } catch NetworkError.clientError(let message) { - throw NetworkError.clientError(message: message) + } catch { + throw SettingError.clientError } } } diff --git a/Manito/Manito/Domain/Usecase/SplashScene/SplashUsecase.swift b/Manito/Manito/Domain/Usecase/SplashScene/SplashUsecase.swift new file mode 100644 index 000000000..4cb0bca33 --- /dev/null +++ b/Manito/Manito/Domain/Usecase/SplashScene/SplashUsecase.swift @@ -0,0 +1,35 @@ +// +// SplashUsecase.swift +// Manito +// +// Created by SHIN YOON AH on 10/16/23. +// + +import Combine +import Foundation + +protocol SplashUsecase { + var entryType: EntryType { get set } +} + +final class SplashUsecaseImpl: SplashUsecase { + + // MARK: - property + + lazy var entryType: EntryType = self.initEntryType() + + private let isLogin: Bool = UserDefaultStorage.isLogin + private let isSetNickname: Bool = (UserDefaultStorage.nickname != "") + private let isSetFCMToken: Bool = UserDefaultStorage.isSetFcmToken + + // MARK: - Private - func + + private func initEntryType() -> EntryType { + if self.isLogin { + if !self.isSetNickname { return .nickname } + return .main + } else { + return .login + } + } +} diff --git a/Manito/Manito/Global/Literal/ValueLiteral.swift b/Manito/Manito/Global/Literal/ValueLiteral.swift deleted file mode 100644 index 619cfee68..000000000 --- a/Manito/Manito/Global/Literal/ValueLiteral.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// ValueLiteral.swift -// Manito -// -// Created by Mingwan Choi on 2022/06/21. -// - -import Foundation - -enum ValueLiteral { - static let leadingTrailingPadding = 21 -} diff --git a/Manito/Manito/Presentation/Common/Base/BaseViewController.swift b/Manito/Manito/Presentation/Common/Base/BaseViewController.swift deleted file mode 100644 index 762846497..000000000 --- a/Manito/Manito/Presentation/Common/Base/BaseViewController.swift +++ /dev/null @@ -1,104 +0,0 @@ -// -// BaseViewController.swift -// Manito -// -// Created by SHIN YOON AH on 2022/06/09. -// - -import UIKit - -class BaseViewController: UIViewController { - - // MARK: - property - - private lazy var backButton: UIButton = { - let button = BackButton() - let buttonAction = UIAction { [weak self] _ in - self?.navigationController?.popViewController(animated: true) - } - button.addAction(buttonAction, for: .touchUpInside) - return button - }() - - // MARK: - init - - init() { - super.init(nibName: nil, bundle: nil) - } - - deinit { - print("success deallocation") - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - } - - // MARK: - life cycle - - override func viewDidLoad() { - super.viewDidLoad() - setupBackButton() - hidekeyboardWhenTappedAround() - setupNavigationBar() - } - - override func viewDidDisappear(_ animated: Bool) { - super.viewDidDisappear(animated) - NotificationCenter.default.removeObserver(self) - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - setupInteractivePopGestureRecognizer() - } - - func setupNavigationBar() { - guard let navigationBar = navigationController?.navigationBar else { return } - let appearance = UINavigationBarAppearance() - let font = UIFont.font(.regular, ofSize: 14) - let largeFont = UIFont.font(.regular, ofSize: 34) - - appearance.titleTextAttributes = [.font: font] - appearance.largeTitleTextAttributes = [.font: largeFont] - appearance.shadowColor = .clear - appearance.backgroundColor = .backgroundGrey - - navigationBar.standardAppearance = appearance - navigationBar.compactAppearance = appearance - navigationBar.scrollEdgeAppearance = appearance - } - - // MARK: - helper func - - func makeBarButtonItem(with view: T) -> UIBarButtonItem { - return UIBarButtonItem(customView: view) - } - - func removeBarButtonItemOffset(with button: UIButton, offsetX: CGFloat = 0, offsetY: CGFloat = 0) -> UIView { - let offsetView = UIView(frame: CGRect(x: 0, y: 0, width: 45, height: 45)) - offsetView.bounds = offsetView.bounds.offsetBy(dx: offsetX, dy: offsetY) - offsetView.addSubview(button) - return offsetView - } - - // MARK: - private func - - private func setupBackButton() { - let leftOffsetBackButton = removeBarButtonItemOffset(with: backButton, offsetX: 10) - let backButton = makeBarButtonItem(with: leftOffsetBackButton) - - navigationItem.leftBarButtonItem = backButton - } - - private func setupInteractivePopGestureRecognizer() { - self.navigationController?.interactivePopGestureRecognizer?.delegate = self - } -} - -extension BaseViewController: UIGestureRecognizerDelegate { - func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - guard let count = self.navigationController?.viewControllers.count else { return false } - return count > 1 - } -} diff --git a/Manito/Manito/Presentation/Common/Base/Keyboardable.swift b/Manito/Manito/Presentation/Common/Base/Keyboardable.swift new file mode 100644 index 000000000..1842f06e7 --- /dev/null +++ b/Manito/Manito/Presentation/Common/Base/Keyboardable.swift @@ -0,0 +1,18 @@ +// +// Keyboardable.swift +// Manito +// +// Created by Mingwan Choi on 2023/09/12. +// + +import UIKit + +protocol Keyboardable { + func setupKeyboardGesture() +} + +extension Keyboardable where Self: UIViewController { + func setupKeyboardGesture() { + self.hidekeyboardWhenTappedAround() + } +} diff --git a/Manito/Manito/Presentation/Common/Base/Navigationable.swift b/Manito/Manito/Presentation/Common/Base/Navigationable.swift new file mode 100644 index 000000000..68f397fd1 --- /dev/null +++ b/Manito/Manito/Presentation/Common/Base/Navigationable.swift @@ -0,0 +1,62 @@ +// +// Navigationable.swift +// Manito +// +// Created by Mingwan Choi on 2023/09/11. +// + +import UIKit + +protocol Navigationable: UIGestureRecognizerDelegate { + func setupNavigation() +} + +extension Navigationable where Self: UIViewController { + func setupNavigation() { + self.setupNavigationBar() + self.setupBackButton() + self.setDragPopGesture(self) + } + + private func backButtonItem() -> UIBarButtonItem { + let button = BackButton() + let buttonAction = UIAction { [weak self] _ in + self?.back() + } + button.addAction(buttonAction, for: .touchUpInside) + let leftOffsetBackButton = self.removeBarButtonItemOffset(with: button, offsetX: 10) + let backButton = self.makeBarButtonItem(with: leftOffsetBackButton) + return backButton + } + + private func setupBackButton() { + let backButton = self.backButtonItem() + self.navigationItem.leftBarButtonItem = backButton + } + + private func back() { + if let navigation = self.navigationController { + navigation.popViewController(animated: true) + } + } + + private func setupNavigationBar() { + guard let navigationBar = navigationController?.navigationBar else { return } + let appearance = UINavigationBarAppearance() + let font = UIFont.font(.regular, ofSize: 14) + let largeFont = UIFont.font(.regular, ofSize: 34) + + appearance.titleTextAttributes = [.font: font] + appearance.largeTitleTextAttributes = [.font: largeFont] + appearance.shadowColor = .clear + appearance.backgroundColor = .backgroundGrey + + navigationBar.standardAppearance = appearance + navigationBar.compactAppearance = appearance + navigationBar.scrollEdgeAppearance = appearance + } + + func setDragPopGesture(_ viewController: Navigationable) { + self.navigationController?.interactivePopGestureRecognizer?.delegate = viewController + } +} diff --git a/Manito/Manito/Presentation/Common/Component/Button/BackButton.swift b/Manito/Manito/Presentation/Common/Component/Button/BackButton.swift index 07f5bfe43..367930b3c 100644 --- a/Manito/Manito/Presentation/Common/Component/Button/BackButton.swift +++ b/Manito/Manito/Presentation/Common/Component/Button/BackButton.swift @@ -23,7 +23,7 @@ final class BackButton: UIButton { // MARK: - func private func configUI() { - self.setImage(ImageLiterals.btnBack, for: .normal) + self.setImage(UIImage.Button.back, for: .normal) self.tintColor = .white } } diff --git a/Manito/Manito/Presentation/Common/Component/Button/MoreButton.swift b/Manito/Manito/Presentation/Common/Component/Button/MoreButton.swift index a2b590c5a..88e3b6336 100644 --- a/Manito/Manito/Presentation/Common/Component/Button/MoreButton.swift +++ b/Manito/Manito/Presentation/Common/Component/Button/MoreButton.swift @@ -18,7 +18,7 @@ final class MoreButton: UIButton { } private func configUI() { - self.setImage(ImageLiterals.icMore, for: .normal) + self.setImage(UIImage.Icon.more, for: .normal) self.tintColor = .white } } diff --git a/Manito/Manito/Presentation/Common/Component/Button/SettingButton.swift b/Manito/Manito/Presentation/Common/Component/Button/SettingButton.swift index db37dd249..848d1be07 100644 --- a/Manito/Manito/Presentation/Common/Component/Button/SettingButton.swift +++ b/Manito/Manito/Presentation/Common/Component/Button/SettingButton.swift @@ -21,7 +21,7 @@ final class SettingButton: UIButton { } private func configUI() { - self.setImage(ImageLiterals.btnSetting, for: .normal) + self.setImage(UIImage.Button.setting, for: .normal) self.tintColor = .white } } diff --git a/Manito/Manito/Presentation/Common/Component/View/GuideView.swift b/Manito/Manito/Presentation/Common/Component/View/GuideView.swift index 3f6bd5bdb..9da5518f9 100644 --- a/Manito/Manito/Presentation/Common/Component/View/GuideView.swift +++ b/Manito/Manito/Presentation/Common/Component/View/GuideView.swift @@ -18,16 +18,16 @@ final class GuideView: UIView { var text: String { switch self { - case .letter: return TextLiteral.letterViewControllerGuideText - case .main: return TextLiteral.mainViewControllerGuideText - case .detailing: return TextLiteral.detailIngViewControllerGuideText + case .letter: return TextLiteral.Letter.guide.localized() + case .main: return TextLiteral.Main.guide.localized() + case .detailing: return TextLiteral.DetailIng.guide.localized() } } var image: UIImage { switch self { - case .letter: return ImageLiterals.icLetterMissionInfo - default: return ImageLiterals.icMissionInfo + case .letter: return UIImage.Icon.letterMissionInfo + default: return UIImage.Icon.missionInfo } } } @@ -35,7 +35,7 @@ final class GuideView: UIView { // MARK: - ui component private let guideButton: UIButton = UIButton() - private let guideBoxImageView: UIImageView = UIImageView(image: ImageLiterals.imgGuideBox) + private let guideBoxImageView: UIImageView = UIImageView(image: UIImage.Image.guideBox) private let guideLabel: UILabel = { let label = UILabel() label.numberOfLines = 0 @@ -117,7 +117,7 @@ final class GuideView: UIView { view.addSubview(self.guideBoxImageView) self.guideBoxImageView.snp.makeConstraints { $0.top.equalTo(view.safeAreaLayoutGuide.snp.top).inset(35) - $0.trailing.equalTo(view.snp.trailing).inset(Size.leadingTrailingPadding + 8) + $0.trailing.equalTo(view.snp.trailing).inset(SizeLiteral.leadingTrailingPadding + 8) $0.width.equalTo(270) $0.height.equalTo(90) } diff --git a/Manito/Manito/Presentation/Common/Component/View/ToastPopupView.swift b/Manito/Manito/Presentation/Common/Component/View/ToastPopupView.swift index 73a5c6fa9..3aee50576 100644 --- a/Manito/Manito/Presentation/Common/Component/View/ToastPopupView.swift +++ b/Manito/Manito/Presentation/Common/Component/View/ToastPopupView.swift @@ -42,6 +42,6 @@ struct ToastView { static func showToast(code: String, message: String, controller: UIViewController) { UIPasteboard.general.string = code - showToast(message: TextLiteral.detailWaitViewControllerCopyCode, controller: controller) + showToast(message: TextLiteral.DetailWait.toastCopyMessage.localized(), controller: controller) } } diff --git a/Manito/Manito/Presentation/Error/LetterScene/LetterImageError.swift b/Manito/Manito/Presentation/Error/LetterScene/LetterImageError.swift deleted file mode 100644 index 5c443f5c8..000000000 --- a/Manito/Manito/Presentation/Error/LetterScene/LetterImageError.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// LetterImageError.swift -// Manito -// -// Created by SHIN YOON AH on 2023/02/21. -// - -import Foundation - -enum LetterImageError: LocalizedError { - case invalidImage - case invalidPhotoLibrary -} - -extension LetterImageError { - var errorDescription: String? { - switch self { - case .invalidImage: return TextLiteral.letterImageViewControllerErrorMessage - case .invalidPhotoLibrary: return TextLiteral.letterImageViewControllerErrorMessage - } - } -} diff --git a/Manito/Manito/Presentation/Scene/DetailScene/Detail/View/Cell/FriendCollectionViewCell.swift b/Manito/Manito/Presentation/Scene/DetailScene/Detail/View/Cell/FriendCollectionViewCell.swift new file mode 100644 index 000000000..38249f101 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/DetailScene/Detail/View/Cell/FriendCollectionViewCell.swift @@ -0,0 +1,82 @@ +// +// FriendCollectionViewCell.swift +// Manito +// +// Created by 최성희 on 2022/06/13. +// + +import UIKit + +import SnapKit + +final class FriendCollectionViewCell: UICollectionViewCell, BaseViewType { + + // MARK: - ui component + + private let friendBackView: UIView = { + let view = UIView() + view.makeBorderLayer(color: .white) + view.layer.cornerRadius = 49 + return view + }() + private let friendImageView: UIImageView = { + let imageView = UIImageView() + imageView.layer.cornerRadius = 45 + imageView.contentMode = .scaleAspectFit + return imageView + }() + private let friendNicknameLabel: UILabel = { + let label = UILabel() + label.font = .font(.regular, ofSize: 15) + label.textAlignment = .center + return label + }() + + // MARK: - init + + override init(frame: CGRect) { + super.init(frame: frame) + self.baseInit() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - base view + + func setupLayout() { + self.addSubview(self.friendBackView) + self.friendBackView.snp.makeConstraints { + $0.top.equalToSuperview().inset(16) + $0.centerX.equalToSuperview() + } + + self.friendBackView.addSubview(self.friendImageView) + self.friendImageView.snp.makeConstraints { + $0.edges.equalToSuperview().inset(4) + $0.width.height.equalTo(90) + } + + self.addSubview(self.friendNicknameLabel) + self.friendNicknameLabel.snp.makeConstraints { + $0.top.equalTo(self.friendBackView.snp.bottom).offset(14) + $0.leading.trailing.equalToSuperview().inset(10) + $0.height.equalTo(16) + } + } + + func configureUI() { + self.backgroundColor = .white.withAlphaComponent(0.3) + self.makeBorderLayer(color: .white) + } + + // MARK: - func + + func configureCell(name: String, colorIndex: Int) { + self.friendNicknameLabel.text = name + self.friendBackView.backgroundColor = DefaultCharacterType.allCases[colorIndex].backgroundColor + self.friendImageView.image = DefaultCharacterType.allCases[colorIndex].image + } +} diff --git a/Manito/Manito/Screens/Detail-Ing/Cell/MemoryCollectionViewCell.swift b/Manito/Manito/Presentation/Scene/DetailScene/Detail/View/Cell/MemoryCollectionViewCell.swift similarity index 55% rename from Manito/Manito/Screens/Detail-Ing/Cell/MemoryCollectionViewCell.swift rename to Manito/Manito/Presentation/Scene/DetailScene/Detail/View/Cell/MemoryCollectionViewCell.swift index 6dac5a356..7fbb73893 100644 --- a/Manito/Manito/Screens/Detail-Ing/Cell/MemoryCollectionViewCell.swift +++ b/Manito/Manito/Presentation/Scene/DetailScene/Detail/View/Cell/MemoryCollectionViewCell.swift @@ -9,10 +9,13 @@ import UIKit import SnapKit +protocol MemoryCollectionViewCellDelegate: AnyObject { + func didTapPhotoImage(_ imageURL: String) +} + final class MemoryCollectionViewCell: UICollectionViewCell, BaseViewType { - var didTappedImage: ((UIImage) -> ())? - // MARK: - properties + // MARK: - ui component private let photoImageView: UIImageView = { let imageView = UIImageView() @@ -28,14 +31,21 @@ final class MemoryCollectionViewCell: UICollectionViewCell, BaseViewType { label.lineBreakMode = .byCharWrapping return label }() - + + // MARK: - property + + private var imageURL: String? + + weak var delegate: MemoryCollectionViewCellDelegate? + // MARK: - init - + override init(frame: CGRect) { super.init(frame: frame) self.baseInit() + self.setupImageTapGesture() } - + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") @@ -45,51 +55,60 @@ final class MemoryCollectionViewCell: UICollectionViewCell, BaseViewType { override func prepareForReuse() { super.prepareForReuse() - photoImageView.image = nil - contentLabel.text = "" + self.photoImageView.image = nil + self.contentLabel.text = "" } // MARK: - base func func setupLayout() { - addSubview(photoImageView) - photoImageView.snp.makeConstraints { + self.addSubview(self.photoImageView) + self.photoImageView.snp.makeConstraints { $0.edges.equalToSuperview() } - addSubview(contentLabel) - contentLabel.snp.makeConstraints { + self.addSubview(self.contentLabel) + self.contentLabel.snp.makeConstraints { $0.edges.equalToSuperview().inset(10) } } func configureUI() { - backgroundColor = .darkGrey002 - makeBorderLayer(color: .white) - layer.masksToBounds = true - setupImageTapGesture() + self.backgroundColor = .darkGrey002 + self.makeBorderLayer(color: .white) + self.layer.masksToBounds = true } + // MARK: - func + func setData(imageUrl: String? = nil, content: String? = nil) { if let imageUrl = imageUrl { - photoImageView.loadImageUrl(imageUrl) + self.photoImageView.loadImageUrl(imageUrl) + self.imageURL = imageUrl return } if let content = content { - contentLabel.text = content - contentLabel.addLabelSpacing() - contentLabel.textAlignment = .center + self.contentLabel.text = content + self.contentLabel.addLabelSpacing() + self.contentLabel.textAlignment = .center } } - +} + +// MARK: - Helper +extension MemoryCollectionViewCell { private func setupImageTapGesture() { - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapPhoto)) - photoImageView.addGestureRecognizer(tapGesture) + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.didTapPhoto)) + self.photoImageView.addGestureRecognizer(tapGesture) } - @objc private func didTapPhoto() { - guard let image = photoImageView.image else { return } - didTappedImage?(image) + // MARK: - selector + + @objc + private func didTapPhoto() { + if let imageURL { + self.delegate?.didTapPhotoImage(imageURL) + } } } diff --git a/Manito/Manito/Screens/Interaction/Cell/OpenManittoCollectionViewCell.swift b/Manito/Manito/Presentation/Scene/DetailScene/Detail/View/Cell/OpenManittoCollectionViewCell.swift similarity index 87% rename from Manito/Manito/Screens/Interaction/Cell/OpenManittoCollectionViewCell.swift rename to Manito/Manito/Presentation/Scene/DetailScene/Detail/View/Cell/OpenManittoCollectionViewCell.swift index b5d80ad59..3d0f4c600 100644 --- a/Manito/Manito/Screens/Interaction/Cell/OpenManittoCollectionViewCell.swift +++ b/Manito/Manito/Presentation/Scene/DetailScene/Detail/View/Cell/OpenManittoCollectionViewCell.swift @@ -49,8 +49,8 @@ final class OpenManittoCollectionViewCell: UICollectionViewCell, BaseViewType { // MARK: - func func configureCell(colorIndex: Int) { - self.characterImageView.image = Character.allCases[colorIndex].image - self.contentView.backgroundColor = Character.allCases[colorIndex].color + self.characterImageView.image = DefaultCharacterType.allCases[colorIndex].image + self.contentView.backgroundColor = DefaultCharacterType.allCases[colorIndex].backgroundColor self.contentView.alpha = 0.5 } diff --git a/Manito/Manito/Presentation/Scene/DetailScene/Detail/View/FriendListView.swift b/Manito/Manito/Presentation/Scene/DetailScene/Detail/View/FriendListView.swift new file mode 100644 index 000000000..4ba19235f --- /dev/null +++ b/Manito/Manito/Presentation/Scene/DetailScene/Detail/View/FriendListView.swift @@ -0,0 +1,98 @@ +// +// FriendListView.swift +// Manito +// +// Created by SHIN YOON AH on 10/19/23. +// + +import UIKit + +import SnapKit + +final class FriendListView: UIView, BaseViewType { + + private enum ConstantSize { + static let groupInterItemSpacing: CGFloat = 14 + static let sectionContentInset: NSDirectionalEdgeInsets = NSDirectionalEdgeInsets( + top: 18.0, leading: 28.0, bottom: 18.0, trailing: 28.0 + ) + static let itemSpacing: CGFloat = 28.0 * 2 + groupInterItemSpacing + } + + // MARK: - ui component + + private lazy var friendListCollectionView: UICollectionView = { + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: self.createLayout()) + collectionView.backgroundColor = .clear + collectionView.showsVerticalScrollIndicator = false + collectionView.register(cell: FriendCollectionViewCell.self, + forCellWithReuseIdentifier: FriendCollectionViewCell.className) + return collectionView + }() + + // MARK: - init + + override init(frame: CGRect) { + super.init(frame: frame) + self.baseInit() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - base func + + func setupLayout() { + self.addSubview(self.friendListCollectionView) + self.friendListCollectionView.snp.makeConstraints { + $0.top.equalTo(self.safeAreaLayoutGuide) + $0.leading.trailing.bottom.equalToSuperview() + } + } + + func configureUI() { + self.backgroundColor = .backgroundGrey + } + + // MARK: - func + + func configureNavigationBar(_ viewController: UIViewController) { + viewController.title = TextLiteral.Detail.togetherFriendTitle.localized() + } + + func collectionView() -> UICollectionView { + return self.friendListCollectionView + } +} + +// MARK: - UICollectionViewLayout +extension FriendListView { + private func createLayout() -> UICollectionViewLayout { + let layout = UICollectionViewCompositionalLayout { [weak self] index, environment -> NSCollectionLayoutSection? in + let itemWidth = ((self?.window?.windowScene?.screen.bounds.width ?? 0) - ConstantSize.itemSpacing) / 2 + let itemSize = NSCollectionLayoutSize( + widthDimension: .absolute(itemWidth), + heightDimension: .absolute(itemWidth) + ) + let item = NSCollectionLayoutItem(layoutSize: itemSize) + + let groupSize = NSCollectionLayoutSize( + widthDimension: .fractionalWidth(1.0), + heightDimension: .absolute(itemWidth) + ) + let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) + let groupSpacing = NSCollectionLayoutSpacing.fixed(ConstantSize.groupInterItemSpacing) + group.interItemSpacing = groupSpacing + + let section = NSCollectionLayoutSection(group: group) + section.contentInsets = ConstantSize.sectionContentInset + section.interGroupSpacing = ConstantSize.groupInterItemSpacing + + return section + } + + return layout + } +} diff --git a/Manito/Manito/Presentation/Scene/DetailScene/Detail/View/MemoryView.swift b/Manito/Manito/Presentation/Scene/DetailScene/Detail/View/MemoryView.swift new file mode 100644 index 000000000..88aeaf026 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/DetailScene/Detail/View/MemoryView.swift @@ -0,0 +1,212 @@ +// +// MemoryView.swift +// Manito +// +// Created by SHIN YOON AH on 10/16/23. +// + +import Combine +import UIKit + +import SnapKit + +final class MemoryView: UIView, BaseViewType { + + typealias MemoryDetail = (announcingText: String, nickname: String, backgroundColor: UIColor, image: UIImage) + + private enum Size { + static let lineSpacing: CGFloat = 10.0 + static let margin: CGFloat = 16.0 + static let cellWidth: CGFloat = (UIScreen.main.bounds.size.width - (margin * 2 + lineSpacing)) / 2 + static let cellHeight: CGFloat = cellWidth * 0.9 + static let collectionViewHeight: CGFloat = cellHeight * 2 + lineSpacing + } + + // MARK: - ui component + + private lazy var segmentControl: UISegmentedControl = { + let control = UISegmentedControl(items: [TextLiteral.Letter.manitteTitle.localized(), + TextLiteral.Letter.manittoTitle.localized()]) + let font = UIFont.font(.regular, ofSize: 14) + let normalTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white, .font: font] + let selectedTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black, .font: font] + + control.setTitleTextAttributes(normalTextAttributes, for: .normal) + control.setTitleTextAttributes(selectedTextAttributes, for: .selected) + control.selectedSegmentTintColor = .white + control.backgroundColor = .darkGrey004 + control.selectedSegmentIndex = 0 + + return control + }() + private lazy var memoryCollectionView: UICollectionView = { + let flowLayout = UICollectionViewFlowLayout() + flowLayout.itemSize = CGSize(width: Size.cellWidth, height: Size.cellHeight) + flowLayout.minimumLineSpacing = Size.lineSpacing + flowLayout.minimumInteritemSpacing = Size.lineSpacing + flowLayout.sectionInset = UIEdgeInsets(top: 0, left: Size.margin, bottom: 0, right: Size.margin) + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) + collectionView.backgroundColor = .clear + collectionView.register(MemoryCollectionViewCell.self, forCellWithReuseIdentifier: MemoryCollectionViewCell.className) + return collectionView + }() + private let announcementLabel: UILabel = { + let label = UILabel() + label.font = .font(.regular, ofSize: 15) + return label + }() + private let nicknameLabel: UILabel = { + let label = UILabel() + label.font = .font(.regular, ofSize: 30) + return label + }() + private let shareButton: UIButton = { + let button = UIButton() + button.setImage(UIImage.Icon.insta, for: .normal) + return button + }() + private let manittoTopImageView = UIImageView(image: UIImage.Image.characters) + private let manittoBottomImageView = UIImageView(image: UIImage.Image.characters) + private let characterImageView = UIImageView() + private let instaShareBoundView = UIView() + private let characterBackView: UIView = { + let view = UIView() + view.makeBorderLayer(color: .white) + view.layer.cornerRadius = 49.5 + return view + }() + + // MARK: - property + + var segmentControlPublisher: AnyPublisher { + return self.segmentControl.tapPublisher + .compactMap { [weak self] in self?.segmentControl.selectedSegmentIndex } + .eraseToAnyPublisher() + } + var shareButtonPublisher: AnyPublisher { + return self.shareButton.tapPublisher + .compactMap { [weak self] in + self?.convert(self?.instaShareBoundView) + } + .eraseToAnyPublisher() + } + + // MARK: - init + + override init(frame: CGRect) { + super.init(frame: frame) + self.baseInit() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - base func + + func setupLayout() { + self.addSubview(self.segmentControl) + self.segmentControl.snp.makeConstraints { + $0.top.equalTo(self.safeAreaLayoutGuide).inset(30) + $0.centerX.equalToSuperview() + $0.height.equalTo(36) + $0.width.equalTo(294) + } + + self.addSubview(self.instaShareBoundView) + self.instaShareBoundView.snp.makeConstraints { + $0.top.equalTo(self.segmentControl.snp.bottom).offset(32) + $0.leading.trailing.equalToSuperview() + $0.bottom.equalTo(self.safeAreaLayoutGuide).inset(31) + } + + self.instaShareBoundView.addSubview(self.manittoTopImageView) + self.manittoTopImageView.snp.makeConstraints { + $0.top.equalToSuperview() + $0.leading.trailing.equalToSuperview().inset(15) + $0.height.equalTo(40) + } + + self.instaShareBoundView.addSubview(self.announcementLabel) + self.announcementLabel.snp.makeConstraints { + $0.top.equalTo(self.manittoTopImageView.snp.bottom).offset(30) + $0.centerX.equalToSuperview() + } + + self.instaShareBoundView.addSubview(self.memoryCollectionView) + self.memoryCollectionView.snp.makeConstraints { + $0.top.equalTo(self.announcementLabel.snp.bottom).offset(30) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(Size.collectionViewHeight) + } + + self.instaShareBoundView.addSubview(self.manittoBottomImageView) + self.manittoBottomImageView.snp.makeConstraints { + $0.bottom.equalToSuperview() + $0.leading.trailing.equalToSuperview().inset(15) + $0.height.equalTo(40) + } + + self.instaShareBoundView.addSubview(self.nicknameLabel) + self.nicknameLabel.snp.makeConstraints { + $0.bottom.equalTo(self.manittoBottomImageView.snp.top).offset(-30) + $0.centerX.equalToSuperview() + } + + self.instaShareBoundView.addSubview(self.characterBackView) + self.characterBackView.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.centerY.equalTo(self.memoryCollectionView.snp.centerY) + $0.width.height.equalTo(99) + } + + self.characterBackView.addSubview(self.characterImageView) + self.characterImageView.snp.makeConstraints { + $0.center.equalToSuperview() + $0.width.height.equalTo(90) + } + } + + func configureUI() { + self.backgroundColor = .backgroundGrey + } + + // MARK: - func + + func configureDelegation(_ delegate: UICollectionViewDataSource) { + self.memoryCollectionView.dataSource = delegate + } + + func configureNavigationBar(of viewController: UIViewController) { + guard let navigationController = viewController.navigationController else { return } + let shareButton = UIBarButtonItem(customView: self.shareButton) + viewController.navigationItem.rightBarButtonItem = shareButton + viewController.title = TextLiteral.Memory.title.localized() + navigationController.navigationBar.prefersLargeTitles = false + navigationController.navigationItem.largeTitleDisplayMode = .automatic + } + + func updateMemoryView(_ detail: MemoryDetail) { + self.announcementLabel.text = detail.announcingText + self.nicknameLabel.text = detail.nickname + self.characterBackView.backgroundColor = detail.backgroundColor + self.characterImageView.image = detail.image + } + + func updateCollectionView() { + self.memoryCollectionView.reloadData() + } +} + +// MARK: - Helper +extension MemoryView { + private func convert(_ inputView: UIView?) -> Data? { + guard let view = inputView else { return nil } + let renderer = UIGraphicsImageRenderer(size: view.bounds.size) + let renderImage = renderer.image { _ in + view.drawHierarchy(in: view.bounds, afterScreenUpdates: true) + } + return renderImage.pngData() + } +} diff --git a/Manito/Manito/Screens/Interaction/View/OpenManittoView.swift b/Manito/Manito/Presentation/Scene/DetailScene/Detail/View/OpenManittoView.swift similarity index 59% rename from Manito/Manito/Screens/Interaction/View/OpenManittoView.swift rename to Manito/Manito/Presentation/Scene/DetailScene/Detail/View/OpenManittoView.swift index fe7fad7af..41e74d432 100644 --- a/Manito/Manito/Screens/Interaction/View/OpenManittoView.swift +++ b/Manito/Manito/Presentation/Scene/DetailScene/Detail/View/OpenManittoView.swift @@ -5,14 +5,11 @@ // Created by SHIN YOON AH on 2023/03/27. // +import Combine import UIKit import SnapKit -protocol OpenManittoViewDelegate: AnyObject { - func confirmButtonTapped() -} - final class OpenManittoView: UIView, BaseViewType { private enum InternalSize { @@ -51,20 +48,17 @@ final class OpenManittoView: UIView, BaseViewType { private let titleLabel: UILabel = { let label = UILabel() label.font = .font(.regular, ofSize: 34) - label.text = TextLiteral.openManittoViewControllerTitle + label.text = TextLiteral.DetailIng.openManittoTitle.localized() return label }() private let popupView: OpenManittoPopupView = OpenManittoPopupView() - - // MARK: - property - - private let totalCount: Double = 10.0 - private(set) var randomIndex: Int = -1 { - didSet { - self.manittoCollectionView.reloadData() - } + + // MARK: - init + + var confirmPublisher: AnyPublisher { + return self.popupView.confirmButtonPublisher } - + // MARK: - init override init(frame: CGRect) { @@ -104,56 +98,18 @@ final class OpenManittoView: UIView, BaseViewType { // MARK: - func - private func animateManittoCollectionView(with friendList: FriendListDTO, - _ manittoIndex: Int, - _ manittoNickname: String) { - let timeInterval: Double = 0.3 - let durationTime: Double = timeInterval * self.totalCount - let delay: Double = 1.0 - - DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: { - UIView.animate(withDuration: durationTime, animations: { - self.performRandomShuffleAnimation(with: timeInterval, friendList) - }, completion: { _ in - let deadline: DispatchTime = .now() + delay + durationTime - self.performOpenManittoAnimation(with: deadline, manittoIndex, manittoNickname) - }) - }) - } - - private func performRandomShuffleAnimation(with timeInterval: TimeInterval, _ friendList: FriendListDTO) { - guard let count = friendList.count else { return } - var countNumber: Int = 0 - - Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: true) { [weak self] _ in - guard let self = self, - countNumber != Int(self.totalCount) else { return } - - countNumber += 1 - self.randomIndex = Int.random(in: 0...count-1, excluding: self.randomIndex) - } - } - - private func performOpenManittoAnimation(with deadline: DispatchTime, - _ manittoIndex: Int, - _ manittoNickname: String) { - DispatchQueue.main.asyncAfter(deadline: deadline, execute: { - self.randomIndex = manittoIndex - }) - - DispatchQueue.main.asyncAfter(deadline: deadline + 1.0, execute: { - self.popupView.fadeIn(duration: 0.2) - self.popupView.setupTypingAnimation(user: UserDefaultStorage.nickname, manitto: manittoNickname) - }) + func configureDelegation(_ delegate: UICollectionViewDataSource) { + self.manittoCollectionView.dataSource = delegate } - - func setupManittoAnimation(friendList: FriendListDTO, manittoIndex: Int, manittoNickname: String) { - self.animateManittoCollectionView(with: friendList, manittoIndex, manittoNickname) + + func updateCollectionView() { self.manittoCollectionView.reloadData() } - - func configureDelegation(_ delegate: UICollectionViewDataSource & OpenManittoViewDelegate) { - self.manittoCollectionView.dataSource = delegate - self.popupView.configureDelegation(delegate) + + func updatePopupView(text: String) { + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + self.popupView.fadeIn(duration: 0.2) + self.popupView.setupTypingAnimation(text) + } } } diff --git a/Manito/Manito/Screens/Interaction/View/SelectManitteeView.swift b/Manito/Manito/Presentation/Scene/DetailScene/Detail/View/SelectManitteeView.swift similarity index 72% rename from Manito/Manito/Screens/Interaction/View/SelectManitteeView.swift rename to Manito/Manito/Presentation/Scene/DetailScene/Detail/View/SelectManitteeView.swift index b4c909de3..25a7bb5ad 100644 --- a/Manito/Manito/Screens/Interaction/View/SelectManitteeView.swift +++ b/Manito/Manito/Presentation/Scene/DetailScene/Detail/View/SelectManitteeView.swift @@ -5,16 +5,12 @@ // Created by SHIN YOON AH on 2023/03/28. // +import Combine import UIKit import Gifu import SnapKit -protocol SelectManitteeViewDelegate: AnyObject { - func confirmButtonDidTap() - func moveToNextStep() -} - final class SelectManitteeView: UIView, BaseViewType { // MARK: - ui component @@ -22,7 +18,7 @@ final class SelectManitteeView: UIView, BaseViewType { private let informationLabel: UILabel = { let label = UILabel() label.font = .font(.regular, ofSize: 20) - label.text = TextLiteral.selectManitteeViewControllerInformationText + label.text = TextLiteral.DetailIng.selectManitteeTitle.localized() label.numberOfLines = 2 label.addLabelSpacing() label.textAlignment = .center @@ -36,7 +32,7 @@ final class SelectManitteeView: UIView, BaseViewType { }() private let confirmButton: MainButton = { let button = MainButton() - button.title = TextLiteral.confirm + button.title = TextLiteral.Common.confirm.localized() return button }() private let joystickBackgroundView: UIView = UIView() @@ -45,14 +41,17 @@ final class SelectManitteeView: UIView, BaseViewType { // MARK: - property - private weak var delegate: SelectManitteeViewDelegate? - + var nextStepSubject: PassthroughSubject = PassthroughSubject() + + var confirmButtonPublisher: AnyPublisher { + return self.confirmButton.tapPublisher + } + // MARK: - init override init(frame: CGRect) { super.init(frame: frame) self.baseInit() - self.setupButtonAction() self.setupSwipeGesture() } @@ -108,74 +107,63 @@ final class SelectManitteeView: UIView, BaseViewType { } // MARK: - func - - private func setupButtonAction() { - let confirmAction = UIAction { [weak self] _ in - self?.delegate?.confirmButtonDidTap() - } - self.confirmButton.addAction(confirmAction, for: .touchUpInside) - } - - private func setupSwipeGesture() { - let swipeLeftGesture = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture(_:))) - let swipeRightGesture = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture(_:))) - swipeLeftGesture.direction = UISwipeGestureRecognizer.Direction.left - swipeRightGesture.direction = UISwipeGestureRecognizer.Direction.right - self.joystickBackgroundView.addGestureRecognizer(swipeLeftGesture) - self.joystickBackgroundView.addGestureRecognizer(swipeRightGesture) - } - - private func setupShowJoystickConfiguration() { - self.joystickImageView.animate(withGIFNamed: ImageLiterals.gifJoystick) + + func showJoystick() { + self.hideStepView(at: 0) + self.joystickImageView.animate(withGIFNamed: GIFSet.joystick) } - private func setupShowCapsuleConfiguration() { + func showCapsule() { + self.hideStepView(at: 1) self.joystickImageView.stopAnimatingGIF() - self.openCapsuleImageView.animate(withGIFNamed: ImageLiterals.gifCapsule, loopCount: 1, animationBlock: { [weak self] in - self?.delegate?.moveToNextStep() + self.openCapsuleImageView.animate(withGIFNamed: GIFSet.capsule, loopCount: 1, animationBlock: { [weak self] in + self?.nextStepSubject.send(()) }) } - private func setupOpenNameConfiguration() { + func showManitteeName() { + self.hideStepView(at: 2) self.nameLabel.fadeIn() self.openCapsuleImageView.stopAnimatingGIF() DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: { [weak self] in - self?.delegate?.moveToNextStep() + self?.nextStepSubject.send(()) }) } + + func showConfirmButton() { + self.hideStepView(at: 3) + } + + func setupManitteeNickname(_ nickname: String) { + self.nameLabel.text = nickname + } +} - private func setupHiddenStepView(at step: Int) { +// MARK: - Helper +extension SelectManitteeView { + private func hideStepView(at step: Int) { self.joystickBackgroundView.isHidden = !(step == 0) self.openCapsuleImageView.isHidden = !(step == 1 || step == 2 || step == 3) self.nameLabel.isHidden = !(step == 2 || step == 3) self.confirmButton.isHidden = !(step == 3) } - - func configureDelegation(_ delegate: SelectManitteeViewDelegate) { - self.delegate = delegate - } - - func configureUI(manitteeNickname: String) { - self.nameLabel.text = manitteeNickname - } - - func manageStepView(step: Int) { - self.setupHiddenStepView(at: step) - switch step { - case 0: self.setupShowJoystickConfiguration() - case 1: self.setupShowCapsuleConfiguration() - case 2: self.setupOpenNameConfiguration() - default: break - } + + private func setupSwipeGesture() { + let swipeLeftGesture = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture(_:))) + let swipeRightGesture = UISwipeGestureRecognizer(target: self, action: #selector(self.respondToSwipeGesture(_:))) + swipeLeftGesture.direction = UISwipeGestureRecognizer.Direction.left + swipeRightGesture.direction = UISwipeGestureRecognizer.Direction.right + self.joystickBackgroundView.addGestureRecognizer(swipeLeftGesture) + self.joystickBackgroundView.addGestureRecognizer(swipeRightGesture) } - + // MARK: - selector @objc private func respondToSwipeGesture(_ gesture: UIGestureRecognizer) { if let swipeGesture = gesture as? UISwipeGestureRecognizer { switch swipeGesture.direction { - case .left, .right: self.delegate?.moveToNextStep() + case .left, .right: self.nextStepSubject.send(()) default: break } } diff --git a/Manito/Manito/Screens/Interaction/View/OpenManittoPopupView.swift b/Manito/Manito/Presentation/Scene/DetailScene/Detail/View/UIComponent/OpenManittoPopupView.swift similarity index 67% rename from Manito/Manito/Screens/Interaction/View/OpenManittoPopupView.swift rename to Manito/Manito/Presentation/Scene/DetailScene/Detail/View/UIComponent/OpenManittoPopupView.swift index fc52eef7d..ecc7cb23e 100644 --- a/Manito/Manito/Screens/Interaction/View/OpenManittoPopupView.swift +++ b/Manito/Manito/Presentation/Scene/DetailScene/Detail/View/UIComponent/OpenManittoPopupView.swift @@ -5,11 +5,12 @@ // Created by SHIN YOON AH on 2023/03/28. // +import Combine import UIKit import SnapKit -final class OpenManittoPopupView: UIView { +final class OpenManittoPopupView: UIView, BaseViewType { // MARK: - ui component @@ -24,7 +25,7 @@ final class OpenManittoPopupView: UIView { private let informationLabel: UILabel = { let label = UILabel() label.font = .font(.regular, ofSize: 18) - label.text = TextLiteral.openManittoViewControllerPopupDescription + label.text = TextLiteral.DetailIng.openManittoPopupHelperContent.localized() label.numberOfLines = 2 label.addLabelSpacing() label.textAlignment = .center @@ -36,22 +37,22 @@ final class OpenManittoPopupView: UIView { }() private let confirmButton: MainButton = { let button = MainButton() - button.title = TextLiteral.confirm + button.title = TextLiteral.Common.confirm.localized() return button }() - private let popupImageView: UIImageView = UIImageView(image: ImageLiterals.imgEnterRoom) - - // MARK: - property - - private weak var delegate: OpenManittoViewDelegate? + private let popupImageView: UIImageView = UIImageView(image: UIImage.Image.enterRoom) + + // MARK: - init + + var confirmButtonPublisher: AnyPublisher { + return self.confirmButton.tapPublisher + } // MARK: - init override init(frame: CGRect) { super.init(frame: frame) - self.setupLayout() - self.configureUI() - self.setupButtonAction() + self.baseInit() } @available(*, unavailable) @@ -59,29 +60,29 @@ final class OpenManittoPopupView: UIView { fatalError("init(coder:) has not been implemented") } - // MARK: - func + // MARK: - base func - private func setupLayout() { + func setupLayout() { self.addSubview(self.popupImageView) self.popupImageView.snp.makeConstraints { $0.top.equalTo(self.safeAreaLayoutGuide).inset(UIScreen.main.bounds.size.height * 0.15) $0.leading.trailing.equalToSuperview().inset(21) $0.height.equalTo(self.popupImageView.snp.width).multipliedBy(1.16) } - + self.addSubview(self.confirmButton) self.confirmButton.snp.makeConstraints { $0.bottom.equalTo(self.safeAreaLayoutGuide).inset(31) $0.centerX.equalToSuperview() } - + self.popupImageView.addSubview(self.typingLabel) self.typingLabel.snp.makeConstraints { $0.centerY.equalToSuperview().offset(-30) $0.centerX.equalToSuperview() $0.leading.trailing.equalToSuperview().inset(24) } - + self.popupImageView.addSubview(self.informationLabel) self.informationLabel.snp.makeConstraints { $0.bottom.equalToSuperview().inset(51) @@ -89,24 +90,34 @@ final class OpenManittoPopupView: UIView { } } - private func configureUI() { + func configureUI() { self.backgroundColor = .black.withAlphaComponent(0.8) self.alpha = 0.0 } + + // MARK: - func - private func setupButtonAction() { - let confirmAction = UIAction { [weak self] _ in - self?.delegate?.confirmButtonTapped() - } - self.confirmButton.addAction(confirmAction, for: .touchUpInside) + func setupTypingAnimation(_ text: String) { + self.animateTyping(in: self.typingLabel, text: text) + self.typingLabel.addLabelSpacing() } +} - func configureDelegation(_ delegate: OpenManittoViewDelegate) { - self.delegate = delegate - } +// MARK: - Helper +extension OpenManittoPopupView { + private func animateTyping(in label: UILabel, text: String, delay: TimeInterval = 5.0) { + label.text = "" + + let writingTask = DispatchWorkItem { + text.forEach { char in + DispatchQueue.main.async { + label.text?.append(char) + } + Thread.sleep(forTimeInterval: delay/100) + } + } - func setupTypingAnimation(user: String, manitto: String) { - self.typingLabel.setTyping(text: "\(user)의 마니또는\n\(manitto)입니다.") - self.typingLabel.addLabelSpacing() + let queue: DispatchQueue = .init(label: "typespeed", qos: .userInteractive) + queue.asyncAfter(deadline: .now() + 0.7, execute: writingTask) } } diff --git a/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewController/FriendListViewController.swift b/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewController/FriendListViewController.swift new file mode 100644 index 000000000..6887e8ddf --- /dev/null +++ b/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewController/FriendListViewController.swift @@ -0,0 +1,141 @@ +// +// FriendListViewController.swift +// Manito +// +// Created by SHIN YOON AH on 10/19/23. +// + +import Combine +import UIKit + +final class FriendListViewController: UIViewController, Navigationable { + + enum Section: CaseIterable { + case main + } + + // MARK: - ui component + + private let friendListView: FriendListView = FriendListView() + + private var dataSource: UICollectionViewDiffableDataSource? + private var snapShot: NSDiffableDataSourceSnapshot? + + // MARK: - property + + private var cancelBag: Set = Set() + + private var viewModel: any BaseViewModelType + + // MARK: - init + + init(viewModel: any BaseViewModelType) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - life cycle + + override func loadView() { + self.view = self.friendListView + } + + override func viewDidLoad() { + super.viewDidLoad() + self.setupNavigation() + self.configureNavigationBar() + self.configureDataSource() + self.bindViewModel() + } + + // MARK: - func + + private func configureNavigationBar() { + self.friendListView.configureNavigationBar(self) + } + + private func bindViewModel() { + let output = self.transformedOutput() + self.bindOutputToViewModel(output) + } + + private func transformedOutput() -> FriendListViewModel.Output? { + guard let viewModel = self.viewModel as? FriendListViewModel else { return nil } + let input = FriendListViewModel.Input( + viewDidLoad: self.viewDidLoadPublisher + ) + return viewModel.transform(from: input) + } + + private func bindOutputToViewModel(_ output: FriendListViewModel.Output?) { + guard let output else { return } + + output.friendList + .receive(on: DispatchQueue.main) + .sink(receiveCompletion: { [weak self] completion in + switch completion { + case .finished: break + case .failure(let error): + self?.showErrorAlert(message: error.localizedDescription) + } + }, receiveValue: { [weak self] list in + self?.reloadMemberList(list) + }) + .store(in: &self.cancelBag) + } + + private func showErrorAlert(message: String) { + self.makeAlert(title: TextLiteral.Common.Error.title.localized(), + message: message, + okAction: { [weak self] _ in self?.navigationController?.popViewController(animated: true) }) + } +} + +// MARK: - DataSource +extension FriendListViewController { + private func configureDataSource() { + self.dataSource = self.friendListCollectionViewDataSource() + self.configureSnapshot() + } + + private func friendListCollectionViewDataSource() -> UICollectionViewDiffableDataSource { + let friendCellRegistration = UICollectionView.CellRegistration { cell, indexPath, item in + cell.configureCell(name: item.nickname, + colorIndex: item.colorIndex) + } + + return UICollectionViewDiffableDataSource( + collectionView: self.friendListView.collectionView(), + cellProvider: { collectionView, indexPath, item in + return collectionView.dequeueConfiguredReusableCell( + using: friendCellRegistration, + for: indexPath, + item: item) + } + ) + } +} + +// MARK: - Snapshot +extension FriendListViewController { + private func configureSnapshot() { + self.snapShot = NSDiffableDataSourceSnapshot() + self.snapShot?.appendSections([.main]) + if let snapShot { + self.dataSource?.apply(snapShot, animatingDifferences: true) + } + } + + private func reloadMemberList(_ items: [MemberInfo]) { + guard var snapShot else { return } + let previousMemberData = snapShot.itemIdentifiers(inSection: .main) + snapShot.deleteItems(previousMemberData) + snapShot.appendItems(items, toSection: .main) + self.dataSource?.apply(snapShot, animatingDifferences: true) + } +} diff --git a/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewController/MemoryViewController.swift b/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewController/MemoryViewController.swift new file mode 100644 index 000000000..f1b487164 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewController/MemoryViewController.swift @@ -0,0 +1,170 @@ +// +// MemoryViewController.swift +// Manito +// +// Created by SHIN YOON AH on 10/16/23. +// + +import Combine +import UIKit + +import SnapKit + +final class MemoryViewController: UIViewController, Navigationable { + + // MARK: - ui component + + private let memoryView = MemoryView() + + // MARK: - property + + private var messages: [MessageListItem] = [] + + private var cancelBag: Set = Set() + + private var viewModel: any BaseViewModelType + + // MARK: - init + + init(viewModel: any BaseViewModelType) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - life cycle + + override func loadView() { + self.view = self.memoryView + } + + override func viewDidLoad() { + super.viewDidLoad() + self.configureUI() + self.bindViewModel() + self.bindUI() + self.setupNavigation() + } + + // MARK: - func + + private func configureUI() { + self.memoryView.configureNavigationBar(of: self) + self.memoryView.configureDelegation(self) + } + + private func bindViewModel() { + let output = self.transformedOutput() + self.bindOutputToViewModel(output) + } + + private func transformedOutput() -> MemoryViewModel.Output? { + guard let viewModel = self.viewModel as? MemoryViewModel else { return nil } + let input = MemoryViewModel.Input( + viewDidLoad: self.viewDidLoadPublisher, + segmentControlValueChanged: self.memoryView.segmentControlPublisher + ) + return viewModel.transform(from: input) + } + + private func bindOutputToViewModel(_ output: MemoryViewModel.Output?) { + guard let output = output else { return } + + output.member + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] result in + switch result { + case .success(let data): + self?.updateMemoryView(announcingText: data.announcingText, + nickname: data.memberInfo.nickname, + colorIndex: data.memberInfo.colorIndex) + case .failure(let error): + self?.showErrorAlert(message: error.localizedDescription) + } + }) + .store(in: &self.cancelBag) + + output.messages + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] result in + switch result { + case .success(let data): + self?.updateMessages(data) + case .failure(let error): + self?.showErrorAlert(message: error.localizedDescription) + } + }) + .store(in: &self.cancelBag) + } + + private func bindUI() { + self.memoryView.shareButtonPublisher + .map { [URLLiteral.Memory.instagramBundle: $0] } + .sink(receiveValue: { [weak self] in + self?.handleInstagramShare($0) + }) + .store(in: &self.cancelBag) + } +} + +// MARK: - Helper +extension MemoryViewController { + private func updateMemoryView(announcingText: String, nickname: String, colorIndex: Int) { + let backgroundColor = DefaultCharacterType.allCases[colorIndex].backgroundColor + let image = DefaultCharacterType.allCases[colorIndex].image + let detail = (announcingText: announcingText, nickname: nickname, backgroundColor: backgroundColor, image: image) + self.memoryView.updateMemoryView(detail) + } + + private func updateMessages(_ messages: [MessageListItem]) { + self.messages = messages + self.memoryView.updateCollectionView() + } + + private func handleInstagramShare(_ items: [String: Any]) { + if let shareURL = URL(string: URLLiteral.Memory.instagram) { + if UIApplication.shared.canOpenURL(shareURL) { + let options = [UIPasteboard.OptionsKey.expirationDate: Date().addingTimeInterval(300)] + UIPasteboard.general.setItems([items], options: options) + UIApplication.shared.open(shareURL) + } else { + self.showErrorAlert(title: TextLiteral.Memory.Error.instaTitle.localized(), + message: TextLiteral.Memory.Error.instaMessage.localized()) + } + } + } + + private func showErrorAlert(title: String = TextLiteral.Common.Error.title.localized(), + message: String) { + self.makeAlert(title: title, message: message) + } +} + +// MARK: - UICollectionViewDataSource +extension MemoryViewController: UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return self.messages.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell: MemoryCollectionViewCell = collectionView.dequeueReusableCell(forIndexPath: indexPath) + let message = self.messages[indexPath.item] + cell.setData(imageUrl: message.imageUrl, content: message.content) + cell.delegate = self + return cell + } +} + +// MARK: - MemoryCollectionViewCellDelegate +extension MemoryViewController: MemoryCollectionViewCellDelegate { + func didTapPhotoImage(_ imageURL: String) { + let viewController = LetterImageViewController(imageUrl: imageURL) + viewController.modalPresentationStyle = .fullScreen + viewController.modalTransitionStyle = .crossDissolve + self.present(viewController, animated: true) + } +} diff --git a/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewController/OpenManittoViewController.swift b/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewController/OpenManittoViewController.swift new file mode 100644 index 000000000..78f7259f6 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewController/OpenManittoViewController.swift @@ -0,0 +1,155 @@ +// +// OpenManittoViewController.swift +// Manito +// +// Created by SHIN YOON AH on 2022/06/16. +// + +import Combine +import UIKit + +import SnapKit + +final class OpenManittoViewController: UIViewController, Navigationable { + + // MARK: - ui component + + private let openManittoView: OpenManittoView = OpenManittoView() + + // MARK: - property + + private let randomCompletionSubject: PassthroughSubject = PassthroughSubject() + + private var cancelBag: Set = Set() + + private var memberList: [MemberInfo] = [] + private var randomIndex: Int = 0 + + private let viewModel: any BaseViewModelType + + // MARK: - init + + init(viewModel: any BaseViewModelType) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - life cycle + + override func loadView() { + self.view = self.openManittoView + } + + override func viewDidLoad() { + super.viewDidLoad() + self.configureDelegation() + self.bindViewModel() + self.bindUI() + self.setupNavigation() + } + + // MARK: - func + + private func configureDelegation() { + self.openManittoView.configureDelegation(self) + } + + private func bindViewModel() { + let output = self.transformedOutput() + self.bindOutputToViewModel(output) + } + + private func transformedOutput() -> OpenManittoViewModel.Output? { + guard let viewModel = self.viewModel as? OpenManittoViewModel else { return nil } + let input = OpenManittoViewModel.Input( + viewDidLoad: self.viewDidLoadPublisher, + openManitto: self.randomCompletionSubject.eraseToAnyPublisher() + ) + return viewModel.transform(from: input) + } + + private func bindOutputToViewModel(_ output: OpenManittoViewModel.Output?) { + guard let output = output else { return } + + output.memberInfo + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] result in + switch result { + case .success(let list): + self?.updateMemberList(list) + case .failure(let error): + self?.makeAlert(title: TextLiteral.Common.Error.title.localized(), + message: error.localizedDescription) + self?.dismiss(animated: true) + } + }) + .store(in: &self.cancelBag) + + output.randomIndex + .receive(on: DispatchQueue.main) + .handleEvents(receiveCompletion: { [weak self] _ in + self?.randomCompletionSubject.send(()) + }) + .sink(receiveValue: { [weak self] index in + self?.updateRandomIndex(to: index) + }) + .store(in: &self.cancelBag) + + Publishers.Zip(output.manittoIndex, output.popupText) + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] index, text in + self?.updateManittoView(index: index, openText: text) + }) + .store(in: &self.cancelBag) + } + + private func bindUI() { + self.openManittoView.confirmPublisher + .sink(receiveValue: { [weak self] in + self?.dismiss(animated: true) + }) + .store(in: &self.cancelBag) + } +} + +// MARK: - Helper +extension OpenManittoViewController { + private func updateMemberList(_ list: [MemberInfo]) { + self.memberList = list + self.openManittoView.updateCollectionView() + } + + private func updateRandomIndex(to index: Int) { + self.randomIndex = index + self.openManittoView.updateCollectionView() + } + + private func updateManittoView(index: Int, openText: String) { + self.updateRandomIndex(to: index) + self.openManittoView.updatePopupView(text: openText) + } +} + +// MARK: - UICollectionViewDataSource +extension OpenManittoViewController: UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return self.memberList.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell: OpenManittoCollectionViewCell = collectionView.dequeueReusableCell(forIndexPath: indexPath) + let member = self.memberList[indexPath.item] + cell.configureCell(colorIndex: member.colorIndex) + + if indexPath.item == self.randomIndex { + cell.highlightCell() + } + + return cell + } +} diff --git a/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewController/SelectManitteeViewController.swift b/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewController/SelectManitteeViewController.swift new file mode 100644 index 000000000..5cfb34889 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewController/SelectManitteeViewController.swift @@ -0,0 +1,102 @@ +// +// SelectManitteeViewController.swift +// Manito +// +// Created by SHIN YOON AH on 2022/06/19. +// + +import Combine +import UIKit + +final class SelectManitteeViewController: UIViewController, Navigationable { + + // MARK: - ui component + + private let selectManitteeView: SelectManitteeView = SelectManitteeView() + + // MARK: - property + + private var cancelBag: Set = Set() + + private let viewModel: any BaseViewModelType + + // MARK: - init + + init(viewModel: any BaseViewModelType) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - life cycle + + override func loadView() { + self.view = self.selectManitteeView + } + + override func viewDidLoad() { + super.viewDidLoad() + self.bindViewModel() + self.setupNavigation() + } + + // MARK: - func + + private func bindViewModel() { + let output = self.transformedOutput() + self.bindOutputToViewModel(output) + } + + private func transformedOutput() -> SelectManitteeViewModel.Output? { + guard let viewModel = self.viewModel as? SelectManitteeViewModel else { return nil } + let input = SelectManitteeViewModel.Input( + viewDidLoad: self.viewDidLoadPublisher, + swapView: self.selectManitteeView.nextStepSubject.eraseToAnyPublisher(), + confirmButtonDidTap: self.selectManitteeView.confirmButtonPublisher + ) + return viewModel.transform(from: input) + } + + private func bindOutputToViewModel(_ output: SelectManitteeViewModel.Output?) { + guard let output = output else { return } + + output.currentType + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] type in + switch type { + case (.showJoystick, _): + self?.selectManitteeView.showJoystick() + case (.showCapsule, _): + self?.selectManitteeView.showCapsule() + case (.openName, let nickname): + self?.selectManitteeView.setupManitteeNickname(nickname) + self?.selectManitteeView.showManitteeName() + case (.openButton, _): + self?.selectManitteeView.showConfirmButton() + } + }) + .store(in: &self.cancelBag) + + output.roomId + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] in + self?.presentDetailIngViewController(roomId: $0) + }) + .store(in: &self.cancelBag) + } +} + +// MARK: - Helper +extension SelectManitteeViewController { + private func presentDetailIngViewController(roomId: String) { + guard let presentingViewController = self.presentingViewController as? UINavigationController else { return } + let detailingViewController = DetailingViewController(roomId: roomId) + presentingViewController.popViewController(animated: true) + presentingViewController.pushViewController(detailingViewController, animated: false) + self.dismiss(animated: true) + } +} diff --git a/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewModel/FriendListViewModel.swift b/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewModel/FriendListViewModel.swift new file mode 100644 index 000000000..c74b3eb63 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewModel/FriendListViewModel.swift @@ -0,0 +1,44 @@ +// +// FriendListViewModel.swift +// Manito +// +// Created by SHIN YOON AH on 10/19/23. +// + +import Combine +import Foundation + +final class FriendListViewModel: BaseViewModelType { + + struct Input { + let viewDidLoad: AnyPublisher + } + + struct Output { + let friendList: AnyPublisher<[MemberInfo], Error> + } + + // MARK: - property + + private let usecase: FriendListUsecase + private let roomId: String + + // MARK: - init + + init(usecase: FriendListUsecase, + roomId: String) { + self.usecase = usecase + self.roomId = roomId + } + + // MARK: - Public - func + + func transform(from input: Input) -> Output { + let friendList = input.viewDidLoad + .compactMap { [weak self] in return self } + .asyncMap { try await $0.usecase.fetchFriendList(roomId: $0.roomId).members } + .eraseToAnyPublisher() + + return Output(friendList: friendList) + } +} diff --git a/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewModel/MemoryViewModel.swift b/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewModel/MemoryViewModel.swift new file mode 100644 index 000000000..2dca12da7 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewModel/MemoryViewModel.swift @@ -0,0 +1,99 @@ +// +// MemoryViewModel.swift +// Manito +// +// Created by SHIN YOON AH on 10/16/23. +// + +import Combine +import Foundation + +final class MemoryViewModel: BaseViewModelType { + + enum MemberType: Int { + case manitte = 0 + case manitto = 1 + } + + struct Input { + let viewDidLoad: AnyPublisher + let segmentControlValueChanged: AnyPublisher + } + + struct Output { + let member: AnyPublisher, Never> + let messages: AnyPublisher, Never> + } + + // MARK: - property + + private let memberSubject: PassthroughSubject, Never> = PassthroughSubject() + private let messageSubject: PassthroughSubject, Never> = PassthroughSubject() + + private var cancelBag: Set = Set() + + private let usecase: MemoryUsecase + private let roomId: String + + // MARK: - init + + init(usecase: MemoryUsecase, + roomId: String) { + self.usecase = usecase + self.roomId = roomId + } + + // MARK: - Public - func + + func transform(from input: Input) -> Output { + input.viewDidLoad + .sink(receiveValue: { [weak self] in + self?.fetchMemory() + }) + .store(in: &self.cancelBag) + + input.segmentControlValueChanged + .compactMap { MemberType(rawValue: $0) } + .sink(receiveValue: { [weak self] in + self?.sendInformation(with: $0) + }) + .store(in: &self.cancelBag) + + return Output( + member: self.memberSubject.eraseToAnyPublisher(), + messages: self.messageSubject.eraseToAnyPublisher() + ) + } + + // MARK: - Private - func + + private func fetchMemory() { + Task { + do { + try await self.usecase.fetchMemory(roomId: self.roomId) + self.sendInformation(with: .manitte) + } catch(let error) { + self.memberSubject.send(.failure(error)) + } + } + } + + private func sendInformation(with type: MemberType) { + guard let data = self.usecase.memory else { return } + + switch type { + case .manitte: + let memberInfo = MemberInfo(nickname: data.memoriesWithManittee.member.nickname, + colorIndex: data.memoriesWithManittee.member.colorIndex) + let message = data.memoriesWithManittee.messages.filter { $0.imageUrl != nil || $0.content != nil } + self.memberSubject.send(.success((TextLiteral.Memory.manitteContent.localized(), memberInfo))) + self.messageSubject.send(.success(message)) + case .manitto: + let memberInfo = MemberInfo(nickname: data.memoriesWithManitto.member.nickname, + colorIndex: data.memoriesWithManitto.member.colorIndex) + let message = data.memoriesWithManitto.messages.filter { $0.imageUrl != nil || $0.content != nil } + self.memberSubject.send(.success((TextLiteral.Memory.manittoContent.localized(), memberInfo))) + self.messageSubject.send(.success(message)) + } + } +} diff --git a/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewModel/OpenManittoViewModel.swift b/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewModel/OpenManittoViewModel.swift new file mode 100644 index 000000000..f64e0dee5 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewModel/OpenManittoViewModel.swift @@ -0,0 +1,116 @@ +// +// OpenManittoViewModel.swift +// Manito +// +// Created by SHIN YOON AH on 10/18/23. +// + +import Combine +import Foundation + +final class OpenManittoViewModel: BaseViewModelType { + + struct Input { + let viewDidLoad: AnyPublisher + let openManitto: AnyPublisher + } + + struct Output { + let memberInfo: AnyPublisher, Never> + let randomIndex: AnyPublisher + let manittoIndex: AnyPublisher + let popupText: AnyPublisher + } + + // MARK: - property + + private var cancelBag: Set = Set() + + private var currentIndex: Int = 0 + + private let usecase: OpenManittoUsecase + private let roomId: String + private let manittoNickname: String + + // MARK: - init + + init(usecase: OpenManittoUsecase, + roomId: String, + manittoNickname: String) { + self.usecase = usecase + self.roomId = roomId + self.manittoNickname = manittoNickname + } + + // MARK: - Public - func + + func transform(from input: Input) -> Output { + let memberInfo = input.viewDidLoad + .compactMap { [weak self] in return self } + .asyncMap { await $0.fetchMemberInfo() } + .eraseToAnyPublisher() + + let timerPublisher = Timer.publish(every: 0.3, on: .main, in: .default) + .autoconnect() + + let randomIndex = Publishers.CombineLatest(memberInfo, timerPublisher) + .delay(for: .seconds(0.5), scheduler: DispatchQueue.global()) + .map { result, _ -> [MemberInfo] in + switch result { + case .success(let list): return list + case .failure: return [] + } + } + .prefix(10) + .compactMap { [weak self] in self?.randomIndex(in: $0.count) } + .eraseToAnyPublisher() + + let manittoIndex = Publishers.CombineLatest(memberInfo, input.openManitto) + .delay(for: .seconds(1), scheduler: DispatchQueue.global()) + .compactMap { [weak self] result, _ -> Int? in + switch result { + case .success(let list): return self?.manittoIndex(in: list) + case .failure: return nil + } + } + .eraseToAnyPublisher() + + let popupText = input.openManitto + .compactMap { [weak self] in self?.manittoPopupText() } + .eraseToAnyPublisher() + + return Output( + memberInfo: memberInfo, + randomIndex: randomIndex, + manittoIndex: manittoIndex, + popupText: popupText + ) + } + + // MARK: - Private - func + + private func fetchMemberInfo() async -> Result<[MemberInfo], Error> { + do { + let list = try await self.usecase.fetchFriendList(roomId: self.roomId) + let memberInfo = list.members + return .success(memberInfo) + } catch(let error) { + return .failure(error) + } + } + + private func randomIndex(in count: Int) -> Int { + let randomIndex = Int.random(in: 0...count-1, excluding: self.currentIndex) + self.currentIndex = randomIndex + return randomIndex + } + + private func manittoIndex(in list: [MemberInfo]) -> Int? { + return list.firstIndex(where: { $0.nickname == self.manittoNickname }) + } + + private func manittoPopupText() -> String { + let userNickname = self.usecase.loadNickname() + return TextLiteral.DetailIng.openManittoPopupContent.localized(with: userNickname, self.manittoNickname) + } +} diff --git a/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewModel/SelectManitteeViewModel.swift b/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewModel/SelectManitteeViewModel.swift new file mode 100644 index 000000000..8c1afec28 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/DetailScene/Detail/ViewModel/SelectManitteeViewModel.swift @@ -0,0 +1,80 @@ +// +// SelectManitteeViewModel.swift +// Manito +// +// Created by SHIN YOON AH on 10/18/23. +// + +import Combine +import Foundation + +final class SelectManitteeViewModel: BaseViewModelType { + + enum Step: Int { + case showJoystick = 0, showCapsule, openName, openButton + } + + struct Input { + let viewDidLoad: AnyPublisher + let swapView: AnyPublisher + let confirmButtonDidTap: AnyPublisher + } + + struct Output { + let currentType: AnyPublisher<(step: Step, nickname: String), Never> + let roomId: AnyPublisher + } + + // MARK: - property + + private var cancelBag: Set = Set() + + private var currentType: Step = .showJoystick + + private let roomId: String + private let manitteeNickname: String + + // MARK: - init + + init(roomId: String, + manitteeNickname: String) { + self.roomId = roomId + self.manitteeNickname = manitteeNickname + } + + // MARK: - Public - func + + func transform(from input: Input) -> Output { + let viewDidLoadPublisher = input.viewDidLoad + .compactMap { [weak self] in self?.initType() } + .eraseToAnyPublisher() + + let swapPublisher = input.swapView + .compactMap { [weak self] in self?.nextType() } + .eraseToAnyPublisher() + + let currentType = Publishers.Merge(viewDidLoadPublisher, swapPublisher) + .eraseToAnyPublisher() + + let roomId = input.confirmButtonDidTap + .compactMap { [weak self] in self?.roomId } + .eraseToAnyPublisher() + + return Output( + currentType: currentType, + roomId: roomId + ) + } + + // MARK: - Private - func + + private func initType() -> (step: Step, nickname: String) { + return (step: self.currentType, nickname: self.manitteeNickname) + } + + private func nextType() -> (step: Step, nickname: String) { + guard let currentType = Step(rawValue: self.currentType.rawValue + 1) else { return (.showJoystick, "") } + self.currentType = currentType + return (step: currentType, nickname: self.manitteeNickname) + } +} diff --git a/Manito/Manito/Presentation/Scene/LetterScene/View/Cell/LetterCollectionViewCell.swift b/Manito/Manito/Presentation/Scene/LetterScene/View/Cell/LetterCollectionViewCell.swift index 745324d3a..9284167cc 100644 --- a/Manito/Manito/Presentation/Scene/LetterScene/View/Cell/LetterCollectionViewCell.swift +++ b/Manito/Manito/Presentation/Scene/LetterScene/View/Cell/LetterCollectionViewCell.swift @@ -66,7 +66,7 @@ final class LetterCollectionViewCell: UICollectionViewCell, BaseViewType { }() private let reportButton: UIButton = { let button = UIButton() - button.setImage(ImageLiterals.icReport, for: .normal) + button.setImage(UIImage.Icon.report, for: .normal) return button }() diff --git a/Manito/Manito/Screens/Letter/Views/Views/LetterImageView.swift b/Manito/Manito/Presentation/Scene/LetterScene/View/LetterImageView.swift similarity index 83% rename from Manito/Manito/Screens/Letter/Views/Views/LetterImageView.swift rename to Manito/Manito/Presentation/Scene/LetterScene/View/LetterImageView.swift index 5cbbd2fa6..4a54f919f 100644 --- a/Manito/Manito/Screens/Letter/Views/Views/LetterImageView.swift +++ b/Manito/Manito/Presentation/Scene/LetterScene/View/LetterImageView.swift @@ -5,15 +5,11 @@ // Created by SHIN YOON AH on 2023/02/20. // +import Combine import UIKit import SnapKit -protocol LetterImageViewDelegate: AnyObject { - func downloadImageAsset(_ imageAsset: UIImage?) - func closeButtonTapped() -} - final class LetterImageView: UIView, BaseViewType { // MARK: - ui component @@ -31,7 +27,7 @@ final class LetterImageView: UIView, BaseViewType { }() private let closeButton: UIButton = { let button = UIButton(type: .system) - button.setImage(ImageLiterals.btnXmark, for: .normal) + button.setImage(UIImage.Button.xmark, for: .normal) button.tintColor = .grey001 return button }() @@ -44,20 +40,26 @@ final class LetterImageView: UIView, BaseViewType { }() private let downloadButton: UIButton = { let button = UIButton() - button.setImage(ImageLiterals.icSave, for: .normal) + button.setImage(UIImage.Icon.save, for: .normal) return button }() - + // MARK: - property - - private weak var delegate: LetterImageViewDelegate? + + var closeButtonPublisher: AnyPublisher { + return self.closeButton.tapPublisher + } + var downloadButtonPublisher: AnyPublisher { + return self.downloadButton.tapPublisher + .compactMap { self.imageView.image } + .eraseToAnyPublisher() + } // MARK: - init override init(frame: CGRect) { super.init(frame: frame) self.baseInit() - self.setupAction() self.setupImagePinchGesture() } @@ -92,35 +94,18 @@ final class LetterImageView: UIView, BaseViewType { // MARK: - func - private func setupAction() { - let downloadAction = UIAction { [weak self] _ in - let downloadImage = self?.imageView.image - self?.delegate?.downloadImageAsset(downloadImage) - } - self.downloadButton.addAction(downloadAction, for: .touchUpInside) - - let closeAction = UIAction { [weak self] _ in - self?.delegate?.closeButtonTapped() - } - self.closeButton.addAction(closeAction, for: .touchUpInside) - } - - private func setupImagePinchGesture() { - let pinch = UIPinchGestureRecognizer(target: self, action: #selector(self.didPinchImage(_:))) - self.addGestureRecognizer(pinch) - } - func configureImageFrame() { self.scrollView.frame = self.bounds self.imageView.frame = self.scrollView.bounds } - + func configureImage(_ imageUrl: String) { self.imageView.loadImageUrl(imageUrl) } - - func configureDelegate(_ delegate: LetterImageViewDelegate) { - self.delegate = delegate + + private func setupImagePinchGesture() { + let pinch = UIPinchGestureRecognizer(target: self, action: #selector(self.didPinchImage(_:))) + self.addGestureRecognizer(pinch) } // MARK: - selector @@ -132,7 +117,7 @@ final class LetterImageView: UIView, BaseViewType { } } - +// MARK: - UIScrollViewDelegate extension LetterImageView: UIScrollViewDelegate { func viewForZooming(in scrollView: UIScrollView) -> UIView? { return self.imageView diff --git a/Manito/Manito/Presentation/Scene/LetterScene/View/LetterView.swift b/Manito/Manito/Presentation/Scene/LetterScene/View/LetterView.swift index 2a21216e1..c046c9969 100644 --- a/Manito/Manito/Presentation/Scene/LetterScene/View/LetterView.swift +++ b/Manito/Manito/Presentation/Scene/LetterScene/View/LetterView.swift @@ -19,9 +19,9 @@ final class LetterView: UIView, BaseViewType { static let headerContentInset: NSDirectionalEdgeInsets = NSDirectionalEdgeInsets.zero static let sectionContentInset: NSDirectionalEdgeInsets = NSDirectionalEdgeInsets( top: 18.0, - leading: Size.leadingTrailingPadding, + leading: SizeLiteral.leadingTrailingPadding, bottom: 18.0, - trailing: Size.leadingTrailingPadding + trailing: SizeLiteral.leadingTrailingPadding ) static let stackMargins: UIEdgeInsets = UIEdgeInsets( top: 0, @@ -49,14 +49,14 @@ final class LetterView: UIView, BaseViewType { }() private let sendLetterButton: UIButton = { let button = MainButton() - button.title = TextLiteral.sendLetterViewSendLetterButton + button.title = TextLiteral.Letter.buttonSend.localized() return button }() private let emptyLabel: UILabel = { let label = UILabel() label.numberOfLines = 2 label.font = .font(.regular, ofSize: 16) - label.text = TextLiteral.letterViewControllerEmptyViewTo + label.text = TextLiteral.Letter.emptyToContent.localized() label.isHidden = true label.textColor = .grey003 label.addLabelSpacing(lineSpacing: 16) @@ -131,7 +131,9 @@ final class LetterView: UIView, BaseViewType { } extension LetterView { + // MARK: - base func + func setupLayout() { self.addSubview(self.wholeStackView) self.wholeStackView.snp.makeConstraints { @@ -160,7 +162,7 @@ extension LetterView { self.backgroundColor = .backgroundGrey } - // MARK: - private func + // MARK: - Private - func private func bindUI() { self.listCollectionView.scrollPublisher @@ -172,7 +174,7 @@ extension LetterView { private func setupNavigationTitle(in viewController: UIViewController) { guard let navigationController = viewController.navigationController else { return } - viewController.title = TextLiteral.letterViewControllerTitle + viewController.title = TextLiteral.Letter.title.localized() navigationController.navigationBar.prefersLargeTitles = true navigationController.navigationItem.largeTitleDisplayMode = .automatic } diff --git a/Manito/Manito/Presentation/Scene/LetterScene/View/SendLetterView.swift b/Manito/Manito/Presentation/Scene/LetterScene/View/SendLetterView.swift new file mode 100644 index 000000000..4cd0acedb --- /dev/null +++ b/Manito/Manito/Presentation/Scene/LetterScene/View/SendLetterView.swift @@ -0,0 +1,210 @@ +// +// SendLetterView.swift +// Manito +// +// Created by SHIN YOON AH on 2023/09/24. +// + +import Combine +import UIKit + +import SnapKit + +final class SendLetterView: UIView, BaseViewType { + + typealias Message = (content: String?, image: UIImage?) + typealias ActionDetail = (message: String, + titles: [String], + styles: [UIAlertAction.Style], + actions: [((UIAlertAction) -> Void)?]) + + // MARK: - ui component + + private let indicatorView: UIView = { + let view = UIView() + view.backgroundColor = .white.withAlphaComponent(0.8) + view.layer.cornerRadius = 2 + return view + }() + private let cancelButton: UIButton = { + let button = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: 44, height: 44))) + button.titleLabel?.font = .font(.regular, ofSize: 16) + button.setTitle(TextLiteral.Common.cancel.localized(), for: .normal) + button.setTitleColor(.white, for: .normal) + button.setTitleColor(.white.withAlphaComponent(0.5), for: .highlighted) + return button + }() + private let sendButton: UIButton = { + let button = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: 50, height: 44))) + button.titleLabel?.font = .font(.regular, ofSize: 16) + button.setTitle(TextLiteral.SendLetter.buttonSend.localized(), for: .normal) + button.setTitleColor(.subBlue, for: .normal) + button.setTitleColor(.subBlue.withAlphaComponent(0.5), for: .highlighted) + button.setTitleColor(.subBlue.withAlphaComponent(0.5), for: .disabled) + return button + }() + private let scrollView: UIScrollView = { + let scrollView = UIScrollView() + scrollView.showsVerticalScrollIndicator = false + scrollView.showsHorizontalScrollIndicator = false + return scrollView + }() + private let scrollContentView: UIView = UIView() + private let missionView: IndividualMissionView = IndividualMissionView() + private let letterTextView: SendLetterTextView = SendLetterTextView() + private let letterPhotoView: SendLetterPhotoView = SendLetterPhotoView() + + // MARK: - property + + var textViewChangedPublisher: AnyPublisher { + return self.letterTextView.textSubject.eraseToAnyPublisher() + } + + var photoButtonTapPublisher: AnyPublisher { + return self.letterPhotoView.photoButtonPublisher + } + + var openCameraMenuTapPublisher: AnyPublisher { + return self.letterPhotoView.openCameraMenuSubject.eraseToAnyPublisher() + } + + var openPhotosMenuTapPublisher: AnyPublisher { + return self.letterPhotoView.openPhotosMenuSubject.eraseToAnyPublisher() + } + + var cancelButtonTapPublisher: AnyPublisher { + return self.cancelButton.tapPublisher + .map { [weak self] in + guard let self else { return false } + return self.letterTextView.hasTextSubject.value || self.letterPhotoView.hasImageSubject.value + } + .eraseToAnyPublisher() + } + + var sendButtonTapPublisher: AnyPublisher { + return self.sendButton.tapPublisher + .map { [weak self] in + guard let self else { return (nil, nil) } + return (self.letterTextView.textSubject.value, self.letterPhotoView.imageSubject.value) + } + .eraseToAnyPublisher() + } + + private var cancelBag: Set = Set() + + // MARK: - init + + override init(frame: CGRect) { + super.init(frame: frame) + self.baseInit() + self.bindUI() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - func + + func configureNavigationController(of viewController: UIViewController) { + self.setupNavigationController(of: viewController) + self.setupNavigationItem(in: viewController.navigationController ?? UINavigationController()) + } + + func setupMission(to mission: String) { + self.missionView.setupMission(to: mission) + } + + func updateSendButtonIsEnabled(to isEnabled: Bool) { + self.sendButton.isEnabled = isEnabled + } + + func updateTextView(count: Int, maxCount: Int) { + self.letterTextView.updateCounter(count, maxCount: maxCount) + } + + func updateTextView(content: String) { + self.letterTextView.updateTextViewContent(to: content) + } + + func updatePhotoView(image: UIImage) { + self.letterPhotoView.updatePhoto(to: image) + } +} + +extension SendLetterView { + + // MARK: - base func + + func setupLayout() { + self.addSubview(self.indicatorView) + self.indicatorView.snp.makeConstraints { + $0.top.equalToSuperview().inset(9) + $0.centerX.equalToSuperview() + $0.height.equalTo(3) + $0.width.equalTo(40) + } + + self.addSubview(self.scrollView) + self.scrollView.snp.makeConstraints { + $0.top.equalTo(self.safeAreaLayoutGuide) + $0.leading.trailing.bottom.equalToSuperview() + } + + self.scrollView.addSubview(self.scrollContentView) + self.scrollContentView.snp.makeConstraints { + $0.edges.equalToSuperview() + $0.width.equalTo(self.scrollView.snp.width) + } + + self.scrollContentView.addSubview(self.missionView) + self.missionView.snp.makeConstraints { + $0.top.equalToSuperview().offset(25) + $0.leading.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) + $0.height.equalTo(100) + } + + self.scrollContentView.addSubview(self.letterTextView) + self.letterTextView.snp.makeConstraints { + $0.top.equalTo(self.missionView.snp.bottom).offset(32) + $0.leading.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) + } + + self.scrollContentView.addSubview(self.letterPhotoView) + self.letterPhotoView.snp.makeConstraints { + $0.top.equalTo(self.letterTextView.snp.bottom).offset(22) + $0.leading.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) + $0.bottom.equalToSuperview().inset(105) + } + } + + func configureUI() { + self.backgroundColor = .backgroundGrey + } + + // MARK: - Private - func + + private func setupNavigationController(of viewController: UIViewController) { + viewController.title = TextLiteral.SendLetter.title.localized() + viewController.isModalInPresentation = true + } + + private func setupNavigationItem(in navigationController: UINavigationController) { + let navigationItem = navigationController.topViewController?.navigationItem + let cancelButton = UIBarButtonItem(customView: self.cancelButton) + let sendButton = UIBarButtonItem(customView: self.sendButton) + + sendButton.isEnabled = false + + navigationItem?.leftBarButtonItem = cancelButton + navigationItem?.rightBarButtonItem = sendButton + } + + private func bindUI() { + Publishers.CombineLatest(self.letterTextView.hasTextSubject, self.letterPhotoView.hasImageSubject) + .map { $0 || $1 } + .assign(to: \.isEnabled, on: self.sendButton) + .store(in: &self.cancelBag) + } +} diff --git a/Manito/Manito/Screens/Letter/Views/UIComponents/IndividualMissionView.swift b/Manito/Manito/Presentation/Scene/LetterScene/View/UIComponent/IndividualMissionView.swift similarity index 82% rename from Manito/Manito/Screens/Letter/Views/UIComponents/IndividualMissionView.swift rename to Manito/Manito/Presentation/Scene/LetterScene/View/UIComponent/IndividualMissionView.swift index 3d20d1653..5f5f67aaf 100644 --- a/Manito/Manito/Screens/Letter/Views/UIComponents/IndividualMissionView.swift +++ b/Manito/Manito/Presentation/Scene/LetterScene/View/UIComponent/IndividualMissionView.swift @@ -9,13 +9,13 @@ import UIKit import SnapKit -final class IndividualMissionView: UIView { +final class IndividualMissionView: UIView, BaseViewType { // MARK: - ui component private let titleLabel: UILabel = { let label = UILabel() - label.text = TextLiteral.individualMissionViewTitleLabel + label.text = TextLiteral.DetailIng.individualMissionTitle.localized() label.font = .font(.regular, ofSize: 14) label.textColor = .grey002 return label @@ -33,17 +33,17 @@ final class IndividualMissionView: UIView { override init(frame: CGRect) { super.init(frame: frame) - self.setupLayout() - self.configureUI() + self.baseInit() } + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - // MARK: - func + // MARK: - base func - private func setupLayout() { + func setupLayout() { self.addSubview(self.titleLabel) self.titleLabel.snp.makeConstraints { $0.top.equalToSuperview().inset(12) @@ -59,12 +59,14 @@ final class IndividualMissionView: UIView { } } - private func configureUI() { + func configureUI() { self.backgroundColor = .darkGrey004 self.makeBorderLayer(color: .subOrange) } - func setupMission(with mission: String) { + // MARK: - func + + func setupMission(to mission: String) { self.missionLabel.text = mission } } diff --git a/Manito/Manito/Presentation/Scene/LetterScene/View/UIComponent/LetterHeaderView.swift b/Manito/Manito/Presentation/Scene/LetterScene/View/UIComponent/LetterHeaderView.swift index ddc993a37..f0bb9066a 100644 --- a/Manito/Manito/Presentation/Scene/LetterScene/View/UIComponent/LetterHeaderView.swift +++ b/Manito/Manito/Presentation/Scene/LetterScene/View/UIComponent/LetterHeaderView.swift @@ -16,8 +16,8 @@ final class LetterHeaderView: UICollectionReusableView { private let segmentedControl: UISegmentedControl = { let font = UIFont.font(.regular, ofSize: 14) - let control = UISegmentedControl(items: [TextLiteral.letterHeaderViewSegmentControlManitti, - TextLiteral.letterHeaderViewSegmentControlManitto]) + let control = UISegmentedControl(items: [TextLiteral.Letter.manitteTitle.localized(), + TextLiteral.Letter.manittoTitle.localized()]) let normalTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white, NSAttributedString.Key.font: font] let selectedTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black, @@ -44,7 +44,8 @@ final class LetterHeaderView: UICollectionReusableView { self.setupLayout() self.configureUI() } - + + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -59,7 +60,7 @@ final class LetterHeaderView: UICollectionReusableView { self.addSubview(self.segmentedControl) self.segmentedControl.snp.makeConstraints { $0.top.bottom.equalToSuperview().inset(13) - $0.leading.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.height.equalTo(40) } } diff --git a/Manito/Manito/Presentation/Scene/LetterScene/View/UIComponent/SendLetterPhotoView.swift b/Manito/Manito/Presentation/Scene/LetterScene/View/UIComponent/SendLetterPhotoView.swift new file mode 100644 index 000000000..7162880db --- /dev/null +++ b/Manito/Manito/Presentation/Scene/LetterScene/View/UIComponent/SendLetterPhotoView.swift @@ -0,0 +1,135 @@ +// +// SendLetterPhotoView.swift +// Manito +// +// Created by SHIN YOON AH on 2022/06/13. +// + +import Combine +import UIKit + +import SnapKit + +final class SendLetterPhotoView: UIView { + + typealias AlertAction = ((UIAlertAction) -> ()) + typealias ActionDetail = (message: String, + titles: [String], + styles: [UIAlertAction.Style], + actions: [((UIAlertAction) -> Void)?]) + + // MARK: - ui component + + private let importPhotosButton: UIButton = { + let button = UIButton() + button.makeBorderLayer(color: .white) + button.clipsToBounds = true + button.setImage(UIImage.Button.camera, for: .normal) + button.imageView?.contentMode = .scaleAspectFill + button.setPreferredSymbolConfiguration(.init(pointSize: 25), forImageIn: .normal) + button.tintColor = .white + button.backgroundColor = .darkGrey004 + return button + }() + private let titleLabel: UILabel = { + let label = UILabel() + label.text = TextLiteral.SendLetter.photoTitle.localized() + label.font = .font(.regular, ofSize: 16) + return label + }() + + // MARK: - property + + var photoButtonPublisher: AnyPublisher { + return self.importPhotosButton.tapPublisher + .map { [weak self] _ -> ActionDetail in + guard let self else { return (message: "", titles: [], styles: [], actions: []) } + return ( + message: TextLiteral.SendLetter.photoMenuTitle.localized(), + titles: self.actionTitles(), + styles: self.actionStyles(), + actions: self.alertActions() + ) + } + .eraseToAnyPublisher() + } + + var openCameraMenuSubject: PassthroughSubject = PassthroughSubject() + var openPhotosMenuSubject: PassthroughSubject = PassthroughSubject() + var imageSubject: CurrentValueSubject = CurrentValueSubject(nil) + var hasImageSubject: CurrentValueSubject = CurrentValueSubject(false) + + private var cancelBag: Set = Set() + + // MARK: - init + + override init(frame: CGRect) { + super.init(frame: frame) + self.setupLayout() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - func + + private func setupLayout() { + self.addSubview(self.titleLabel) + self.titleLabel.snp.makeConstraints { + $0.top.leading.equalToSuperview() + } + + self.addSubview(self.importPhotosButton) + self.importPhotosButton.snp.makeConstraints { + $0.top.equalTo(self.titleLabel.snp.bottom).offset(17) + $0.leading.trailing.bottom.equalToSuperview() + $0.height.equalTo(209) + } + } + + private func actionTitles() -> [String] { + return self.hasImageSubject.value ? [TextLiteral.SendLetter.photoMenuTakePhoto.localized(), + TextLiteral.SendLetter.photoMenuChoosePhoto.localized(), + TextLiteral.SendLetter.photoMenuDeletePhoto.localized(), + TextLiteral.Common.cancel.localized()] + : [TextLiteral.SendLetter.photoMenuTakePhoto.localized(), + TextLiteral.SendLetter.photoMenuChoosePhoto.localized(), + TextLiteral.Common.cancel.localized()] + } + + private func actionStyles() -> [UIAlertAction.Style] { + return self.hasImageSubject.value ? [.default, .default, .default, .cancel] + : [.default, .default, .cancel] + } + + private func alertActions() -> [AlertAction?] { + let openCameraAction: AlertAction = { [weak self] _ in + self?.openCameraMenuSubject.send(()) + } + let openPhotosAction: AlertAction = { [weak self] _ in + self?.openPhotosMenuSubject.send(()) + } + let removePhotoAction: AlertAction = { [weak self] _ in + self?.removePhoto() + } + + return self.hasImageSubject.value ? [openCameraAction, openPhotosAction, removePhotoAction, nil] + : [openCameraAction, openPhotosAction, nil] + } + + private func removePhoto() { + self.importPhotosButton.setImage(UIImage.Button.camera, for: .normal) + self.imageSubject.send(nil) + self.hasImageSubject.send(false) + } + + func updatePhoto(to image: UIImage) { + DispatchQueue.main.async { + self.importPhotosButton.setImage(image, for: .normal) + self.imageSubject.send(image) + self.hasImageSubject.send(true) + } + } +} diff --git a/Manito/Manito/Screens/Letter/Views/UIComponents/CreateLetterTextView.swift b/Manito/Manito/Presentation/Scene/LetterScene/View/UIComponent/SendLetterTextView.swift similarity index 67% rename from Manito/Manito/Screens/Letter/Views/UIComponents/CreateLetterTextView.swift rename to Manito/Manito/Presentation/Scene/LetterScene/View/UIComponent/SendLetterTextView.swift index 03f48e016..cfbbf9da9 100644 --- a/Manito/Manito/Screens/Letter/Views/UIComponents/CreateLetterTextView.swift +++ b/Manito/Manito/Presentation/Scene/LetterScene/View/UIComponent/SendLetterTextView.swift @@ -1,21 +1,22 @@ // -// CreateLetterTextView.swift +// SendLetterTextView.swift // Manito // // Created by SHIN YOON AH on 2022/06/13. // +import Combine import UIKit import SnapKit -final class CreateLetterTextView: UIView { +final class SendLetterTextView: UIView { // MARK: - ui component private let titleLabel: UILabel = { let label = UILabel() - label.text = TextLiteral.letterTextViewTitleLabel + label.text = TextLiteral.SendLetter.textTitle.localized() label.font = .font(.regular, ofSize: 16) return label }() @@ -43,24 +44,17 @@ final class CreateLetterTextView: UIView { // MARK: - property - var sendHasTextValue: ((_ hasText: Bool) -> ())? - - var text: String? { - guard self.letterTextView.text != "" && self.letterTextView.text != nil else { return nil } - return self.letterTextView.text - } - private let maximumCount: Int = 100 - - + var textSubject: CurrentValueSubject = CurrentValueSubject("") + var hasTextSubject: CurrentValueSubject = CurrentValueSubject(false) // MARK: - init override init(frame: CGRect) { super.init(frame: frame) self.setupLayout() - self.setCounter(0, maximumCount: self.maximumCount) } - + + @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } @@ -88,22 +82,19 @@ final class CreateLetterTextView: UIView { } } - private func setCounter(_ count: Int, maximumCount: Int) { - self.countLabel.text = "\(count)/\(maximumCount)" + func updateCounter(_ count: Int, maxCount: Int) { + self.countLabel.text = "\(count)/\(maxCount)" } - private func textViewReachedMaximumCount(_ textView: UITextView, maximumCount: Int) { - if (textView.text?.count ?? 0 > maximumCount) { - textView.deleteBackward() - } + func updateTextViewContent(to content: String) { + self.letterTextView.text = content } } // MARK: - UITextViewDelegate -extension CreateLetterTextView: UITextViewDelegate { +extension SendLetterTextView: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { - self.setCounter(textView.text?.count ?? 0, maximumCount: self.maximumCount) - self.textViewReachedMaximumCount(self.letterTextView, maximumCount: self.maximumCount) - self.sendHasTextValue?(textView.hasText) + self.hasTextSubject.send(textView.hasText) + self.textSubject.send(textView.text) } } diff --git a/Manito/Manito/Presentation/Scene/LetterScene/ViewController/LetterImageViewController.swift b/Manito/Manito/Presentation/Scene/LetterScene/ViewController/LetterImageViewController.swift new file mode 100644 index 000000000..c9e3efe5e --- /dev/null +++ b/Manito/Manito/Presentation/Scene/LetterScene/ViewController/LetterImageViewController.swift @@ -0,0 +1,79 @@ +// +// LetterImageViewController.swift +// Manito +// +// Created by Mingwan Choi on 2022/09/18. +// + +import Combine +import UIKit + +final class LetterImageViewController: UIViewController, Navigationable { + + // MARK: - ui component + + private lazy var letterImageView: LetterImageView = LetterImageView() + + // MARK: - property + + private let photoPickerManager = PhotoPickerManager() + + private var cancelBag: Set = Set() + + private let imageUrl: String + + // MARK: - init + + init(imageUrl: String) { + self.imageUrl = imageUrl + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - life cycle + + override func loadView() { + self.view = self.letterImageView + } + + override func viewDidLoad() { + super.viewDidLoad() + self.setupNavigation() + self.setupPhotoPickerManager() + self.bindUI() + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + self.configureImageView() + } + + // MARK: - func + + private func configureImageView() { + self.letterImageView.configureImageFrame() + self.letterImageView.configureImage(self.imageUrl) + } + + private func setupPhotoPickerManager() { + self.photoPickerManager.viewController = self + } + + private func bindUI() { + self.letterImageView.closeButtonPublisher + .sink(receiveValue: { [weak self] in + self?.dismiss(animated: true) + }) + .store(in: &self.cancelBag) + + self.letterImageView.downloadButtonPublisher + .sink(receiveValue: { [weak self] image in + self?.photoPickerManager.savePhoto(image: image) + }) + .store(in: &self.cancelBag) + } +} diff --git a/Manito/Manito/Presentation/Scene/LetterScene/ViewController/LetterViewController.swift b/Manito/Manito/Presentation/Scene/LetterScene/ViewController/LetterViewController.swift index a80e7b456..8cba28a51 100644 --- a/Manito/Manito/Presentation/Scene/LetterScene/ViewController/LetterViewController.swift +++ b/Manito/Manito/Presentation/Scene/LetterScene/ViewController/LetterViewController.swift @@ -8,7 +8,9 @@ import Combine import UIKit -final class LetterViewController: BaseViewController { +final class LetterViewController: UIViewController, Navigationable { + + typealias MessageDetail = (roomId: String, mission: String, missionId: String, manitteeId: String) enum Section: CaseIterable { case main @@ -22,6 +24,8 @@ final class LetterViewController: BaseViewController { private var snapShot: NSDiffableDataSourceSnapshot! // MARK: - property + + private let mailManager: MailComposeManager = MailComposeManager() private let segmentValueSubject: PassthroughSubject = PassthroughSubject() private let reportSubject: PassthroughSubject = PassthroughSubject() @@ -37,7 +41,7 @@ final class LetterViewController: BaseViewController { init(viewModel: any BaseViewModelType) { self.viewModel = viewModel - super.init() + super.init(nibName: nil, bundle: nil) } @available(*, unavailable) @@ -55,6 +59,8 @@ final class LetterViewController: BaseViewController { super.viewDidLoad() self.configureDataSource() self.bindViewModel() + self.setupMailManager() + self.setupNavigation() } override func viewWillAppear(_ animated: Bool) { @@ -66,6 +72,12 @@ final class LetterViewController: BaseViewController { super.viewWillDisappear(animated) self.letterView.removeGuideView() } + + // MARK: - func + + private func setupMailManager() { + self.mailManager.viewController = self + } // MARK: - func - bind @@ -89,41 +101,37 @@ final class LetterViewController: BaseViewController { } private func bindOutputToViewModel(_ output: LetterViewModel.Output?) { - guard let output = output else { return } + guard let output else { return } output.messages .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { [weak self] completion in - switch completion { - case .failure(_): - self?.showErrorAlert() - case .finished: return + .sink(receiveValue: { [weak self] result in + switch result { + case .success(let items): + self?.handleMessageList(items) + case .failure(let error): + self?.showErrorAlert(error.localizedDescription) + self?.handleMessageList([]) } - }, receiveValue: { [weak self] items in - self?.handleMessageList(items) }) .store(in: &self.cancelBag) output.messageDetails - .sink(receiveValue: { [weak self] details in - guard let self = self else { return } - let viewController = CreateLetterViewController(manitteeId: details.manitteeId, - roomId: details.roomId, - mission: details.mission, - missionId: details.missionId) - let navigationController = UINavigationController(rootViewController: viewController) - viewController.configureDelegation(self) - self.present(navigationController, animated: true) + .sink(receiveValue: { [weak self] detail in + self?.presentSendLetterViewController(detail: detail) }) .store(in: &self.cancelBag) output.reportDetails .sink(receiveValue: { [weak self] details in - self?.sendReportMail(userNickname: details.nickname, content: details.content) + let content = TextLiteral.Mail.reportMessage.localized(with: details.nickname, + details.content, + Date().description) + self?.sendReportMail(content: content) }) .store(in: &self.cancelBag) - Publishers.CombineLatest(output.roomState, output.index) + Publishers.CombineLatest(output.roomStatus, output.index) .map { (state: $0, index: $1) } .sink(receiveValue: { [weak self] result in self?.updateLetterViewBottomArea(with: result.state, result.index) @@ -137,7 +145,7 @@ final class LetterViewController: BaseViewController { if let content { self?.reportSubject.send(content) } else { - self?.reportSubject.send("쪽지 내용 없음") + self?.reportSubject.send(TextLiteral.Letter.emailEmptyContent.localized()) } }) .store(in: &self.cancelBag) @@ -171,38 +179,51 @@ final class LetterViewController: BaseViewController { // MARK: - Helper extension LetterViewController { - private func showErrorAlert() { - self.makeAlert(title: TextLiteral.letterViewControllerErrorTitle, - message: TextLiteral.letterViewControllerErrorDescription) + private func showErrorAlert(_ message: String) { + self.makeAlert(title: TextLiteral.Common.Error.title.localized(), + message: message) } - private func handleMessageList(_ messages: [MessageListItem]?) { - guard let messages else { - self.showErrorAlert() - return - } - + private func handleMessageList(_ messages: [MessageListItem]) { self.reloadMessageList(messages) self.letterView.updateEmptyAreaStatus(to: !messages.isEmpty) } + + private func sendReportMail(content: String) { + let title = TextLiteral.Mail.reportTitle.localized() + self.mailManager.sendMail(title: title, content: content) + } private func updateLetterViewEmptyArea(with index: Int) { switch index { case 0: - self.letterView.updateEmptyArea(with: TextLiteral.letterViewControllerEmptyViewTo) + self.letterView.updateEmptyArea(with: TextLiteral.Letter.emptyToContent.localized()) default: - self.letterView.updateEmptyArea(with: TextLiteral.letterViewControllerEmptyViewFrom) + self.letterView.updateEmptyArea(with: TextLiteral.Letter.emptyFromContent.localized()) } } - private func updateLetterViewBottomArea(with state: LetterViewModel.RoomState, _ index: Int) { + private func updateLetterViewBottomArea(with state: RoomStatus, _ index: Int) { switch (state, index) { - case (.processing, 0): + case (.PROCESSING, 0): self.letterView.showBottomArea() default: self.letterView.hideBottomArea() } } + + private func presentSendLetterViewController(detail: MessageDetail) { + let usecase = SendLetterUsecaseImpl(repository: LetterRepositoryImpl()) + let viewModel = SendLetterViewModel(usecase: usecase, + mission: detail.mission, + manitteeId: detail.manitteeId, + roomId: detail.roomId, + missionId: detail.missionId) + let viewController = SendLetterViewController(viewModel: viewModel) + let navigationController = UINavigationController(rootViewController: viewController) + viewController.configureDelegation(self) + self.present(navigationController, animated: true) + } } // MARK: - DataSource @@ -275,8 +296,8 @@ extension LetterViewController { } } -// MARK: - CreateLetterViewControllerDelegate -extension LetterViewController: CreateLetterViewControllerDelegate { +// MARK: - SendLetterViewControllerDelegate +extension LetterViewController: SendLetterViewControllerDelegate { func refreshLetterData() { self.refreshSubject.send(()) } diff --git a/Manito/Manito/Presentation/Scene/LetterScene/ViewController/SendLetterViewController.swift b/Manito/Manito/Presentation/Scene/LetterScene/ViewController/SendLetterViewController.swift new file mode 100644 index 000000000..e474a9a07 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/LetterScene/ViewController/SendLetterViewController.swift @@ -0,0 +1,239 @@ +// +// SendLetterViewController.swift +// Manito +// +// Created by SHIN YOON AH on 2023/09/23. +// + +import Combine +import UIKit + +protocol SendLetterViewControllerDelegate: AnyObject { + func refreshLetterData() +} + +final class SendLetterViewController: UIViewController, Navigationable, Keyboardable { + + typealias AlertAction = ((UIAlertAction) -> Void)? + + // MARK: - ui component + + private let sendLetterView: SendLetterView = SendLetterView() + + // MARK: - property + + private let photoPickerManager: PhotoPickerManager = PhotoPickerManager() + + private var cancelBag: Set = Set() + + private let viewModel: any BaseViewModelType + + private weak var delegate: SendLetterViewControllerDelegate? + + // MARK: - init + + init(viewModel: any BaseViewModelType) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - life cycle + + override func loadView() { + self.view = self.sendLetterView + } + + override func viewDidLoad() { + super.viewDidLoad() + self.setupNavigation() + self.setupKeyboardGesture() + self.setupPhotoPickerManager() + self.configureNavigationController() + self.bindUI() + self.bindViewModel() + } + + // MARK: - func + + func configureDelegation(_ delegate: SendLetterViewControllerDelegate) { + self.delegate = delegate + self.navigationController?.presentationController?.delegate = self + } + + private func setupPhotoPickerManager() { + self.photoPickerManager.viewController = self + } + + private func configureNavigationController() { + self.sendLetterView.configureNavigationController(of: self) + } + + // MARK: - func - bind + + private func bindUI() { + self.sendLetterView.cancelButtonTapPublisher + .sink(receiveValue: { [weak self] hasChanged in + if hasChanged { + self?.showActionSheet(actionTitles: [TextLiteral.Common.discardChanges.localized(), + TextLiteral.Common.cancel.localized()], + actionStyle: [.destructive, .cancel], + actions: [self?.dismissAction(), nil]) + } else { + self?.presentationControllerDidDismiss() + } + }) + .store(in: &self.cancelBag) + + self.sendLetterView.photoButtonTapPublisher + .sink(receiveValue: { [weak self] detail in + self?.showActionSheet(message: detail.message, + actionTitles: detail.titles, + actionStyle: detail.styles, + actions: detail.actions) + }) + .store(in: &self.cancelBag) + + self.sendLetterView.openCameraMenuTapPublisher + .sink(receiveValue: { [weak self] in + self?.photoPickerManager.openCamera() + self?.updatePhoto() + }) + .store(in: &self.cancelBag) + + self.sendLetterView.openPhotosMenuTapPublisher + .sink(receiveValue: { [weak self] in + self?.photoPickerManager.openPhotos() + self?.updatePhoto() + }) + .store(in: &self.cancelBag) + } + + private func bindViewModel() { + let output = self.transformedOutput() + self.bindOutputToViewModel(output) + } + + private func transformedOutput() -> SendLetterViewModel.Output? { + guard let viewModel = self.viewModel as? SendLetterViewModel else { return nil } + let input = SendLetterViewModel.Input( + viewDidLoad: self.viewDidLoadPublisher, + sendLetterButtonDidTap: self.sendButtonTapPublisher(), + letterTextDidChange: self.sendLetterView.textViewChangedPublisher + ) + + return viewModel.transform(from: input) + } + + private func sendButtonTapPublisher() -> AnyPublisher<(content: String?, image: Data?), Never> { + return self.sendLetterView.sendButtonTapPublisher + .handleEvents(receiveOutput: { [weak self] _ in + self?.sendLetterView.updateSendButtonIsEnabled(to: false) + }) + .map { [weak self] (content, image) in + let data = self?.convertToData(image: image) + return (content, data) + } + .eraseToAnyPublisher() + } + + private func bindOutputToViewModel(_ output: SendLetterViewModel.Output?) { + guard let output else { return } + + output.mission + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] mission in + self?.sendLetterView.setupMission(to: mission) + }) + .store(in: &self.cancelBag) + + output.letterResponse + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] result in + switch result { + case .success: + self?.refreshLetter() + case .failure(let error): + self?.sendLetterView.updateSendButtonIsEnabled(to: true) + self?.showErrorAlert(error.localizedDescription) + } + }) + .store(in: &self.cancelBag) + + output.textCount + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] text in + self?.sendLetterView.updateTextView(count: text.count, maxCount: text.maxCount) + }) + .store(in: &self.cancelBag) + + output.text + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] text in + self?.sendLetterView.updateTextView(content: text) + }) + .store(in: &self.cancelBag) + } +} + +// MARK: - Helper +extension SendLetterViewController { + private func dismissAction() -> AlertAction { + return { [weak self] _ in + self?.resignFirstResponder() + self?.dismiss(animated: true) + } + } + + private func showActionSheet(message: String? = nil, + actionTitles: [String], + actionStyle: [UIAlertAction.Style], + actions: [((UIAlertAction) -> Void)?]) { + self.makeActionSheet(message: message, + actionTitles: actionTitles, + actionStyle: actionStyle, + actions: actions) + } + + private func updatePhoto() { + self.photoPickerManager.loadImage = { [weak self] result in + switch result { + case .success(let image): + self?.sendLetterView.updatePhotoView(image: image) + case .failure(let error): + DispatchQueue.main.async { + self?.showErrorAlert(error.localizedDescription) + } + } + } + } + + private func presentationControllerDidDismiss() { + self.dismiss(animated: true) + } + + private func convertToData(image: UIImage?) -> Data? { + return image?.jpegData(compressionQuality: 0.3) + } + + private func showErrorAlert(_ message: String) { + self.makeAlert(title: TextLiteral.Common.Error.title.localized(), + message: message) + } + + private func refreshLetter() { + self.delegate?.refreshLetterData() + self.dismiss(animated: true) + } +} + +// MARK: - UIAdaptivePresentationControllerDelegate +extension SendLetterViewController: UIAdaptivePresentationControllerDelegate { + func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) { + self.presentationControllerDidDismiss() + } +} diff --git a/Manito/Manito/Presentation/Scene/LetterScene/ViewModel/LetterViewModel.swift b/Manito/Manito/Presentation/Scene/LetterScene/ViewModel/LetterViewModel.swift index 177be8c81..311e44c0d 100644 --- a/Manito/Manito/Presentation/Scene/LetterScene/ViewModel/LetterViewModel.swift +++ b/Manito/Manito/Presentation/Scene/LetterScene/ViewModel/LetterViewModel.swift @@ -10,19 +10,14 @@ import Foundation final class LetterViewModel: BaseViewModelType { - typealias MessageDetails = (roomId: String, mission: String, missionId: String, manitteeId: String) - typealias ReportDetails = (nickname: String, content: String) + typealias MessageDetail = (roomId: String, mission: String, missionId: String, manitteeId: String) + typealias ReportDetail = (nickname: String, content: String) enum MessageType: Int { case sent = 0 case received = 1 } - enum RoomState: String { - case processing = "PROCESSING" - case post = "POST" - } - struct Input { let viewDidLoad: AnyPublisher let segmentControlValueChanged: PassthroughSubject @@ -32,11 +27,11 @@ final class LetterViewModel: BaseViewModelType { } struct Output { - let messages: AnyPublisher<[MessageListItem]?, Error> + let messages: AnyPublisher, Never> let index: AnyPublisher - let messageDetails: AnyPublisher - let reportDetails: AnyPublisher - let roomState: AnyPublisher + let messageDetails: AnyPublisher + let reportDetails: AnyPublisher + let roomStatus: AnyPublisher } // MARK: - property @@ -44,8 +39,8 @@ final class LetterViewModel: BaseViewModelType { private var cancelBag: Set = Set() private let usecase: LetterUsecase - private var messageDetails: MessageDetails - private let roomState: RoomState + private var messageDetail: MessageDetail + private let roomStatus: RoomStatus private let messageType: MessageType // MARK: - init @@ -54,11 +49,11 @@ final class LetterViewModel: BaseViewModelType { roomId: String, mission: String, missionId: String, - roomState: String, + roomStatus: RoomStatus, messageType: MessageType) { self.usecase = usecase - self.messageDetails = MessageDetails(roomId, mission, missionId, "") - self.roomState = RoomState(rawValue: roomState)! + self.messageDetail = MessageDetail(roomId, mission, missionId, "") + self.roomStatus = roomStatus self.messageType = messageType } @@ -77,8 +72,13 @@ final class LetterViewModel: BaseViewModelType { let mergePublisher = Publishers.Merge3(viewDidLoadType, segmentValueType, refreshWithType) let messagesPublisher = mergePublisher - .asyncMap { [weak self] type in - try await self?.fetchMessages(with: type) + .asyncMap { [weak self] type -> Result<[MessageListItem], Error> in + do { + let messages = try await self?.fetchMessages(with: type) + return .success(messages ?? []) + } catch (let error) { + return .failure(error) + } } .eraseToAnyPublisher() @@ -89,21 +89,21 @@ final class LetterViewModel: BaseViewModelType { .eraseToAnyPublisher() let messageDetailsPublisher = input.sendLetterButtonDidTap - .map { [weak self] _ -> MessageDetails in - self?.loadMessageDetails() - return (self?.messageDetails)! + .map { [weak self] _ -> MessageDetail in + self?.loadMessageDetail() + return (self?.messageDetail)! } .eraseToAnyPublisher() let reportPublisher = input.reportButtonDidTap - .map { [weak self] content -> ReportDetails in + .map { [weak self] content -> ReportDetail in self?.usecase.loadNickname() return ((self?.usecase.nickname)!, content) } .eraseToAnyPublisher() - let roomStatePublisher = input.viewDidLoad - .compactMap { [weak self] in self?.roomState } + let roomStatusPublisher = input.viewDidLoad + .compactMap { [weak self] in self?.roomStatus } .eraseToAnyPublisher() return Output( @@ -111,7 +111,7 @@ final class LetterViewModel: BaseViewModelType { index: currentIndexPublisher, messageDetails: messageDetailsPublisher, reportDetails: reportPublisher, - roomState: roomStatePublisher + roomStatus: roomStatusPublisher ) } @@ -123,7 +123,7 @@ final class LetterViewModel: BaseViewModelType { } private func fetchMessages(with type: MessageType) async throws -> [MessageListItem] { - let roomId = self.messageDetails.roomId + let roomId = self.messageDetail.roomId switch type { case .sent: @@ -142,20 +142,24 @@ extension LetterViewModel { private func fetchSendMessages(roomId: String, type: MessageType) async throws -> [MessageListItem] { let messages = try await self.usecase.fetchSendLetter(roomId: roomId) - return self.insertReportState(type, in: messages) + return await self.insertReportState(type, in: messages) } private func fetchReceivedMessages(roomId: String, type: MessageType) async throws -> [MessageListItem] { let messages = try await self.usecase.fetchReceiveLetter(roomId: roomId) - return self.insertReportState(type, in: messages) + return await self.insertReportState(type, in: messages) } - private func loadMessageDetails() { + private func loadMessageDetail() { guard let manitteeId = self.usecase.manitteeId else { return } - self.messageDetails.manitteeId = manitteeId + self.messageDetail.manitteeId = manitteeId } - private func insertReportState(_ type: MessageType, in messages: [MessageListItemDTO]) -> [MessageListItem] { - return messages.map { $0.toMessageListItem(canReport: (type == .received)) } + private func insertReportState(_ type: MessageType, in messages: [MessageListItemDTO]) async -> [MessageListItem] { + var resultMessages: [MessageListItem] = [] + for message in messages { + resultMessages.append(await message.toMessageListItem(canReport: (type == .received))) + } + return resultMessages } } diff --git a/Manito/Manito/Presentation/Scene/LetterScene/ViewModel/SendLetterViewModel.swift b/Manito/Manito/Presentation/Scene/LetterScene/ViewModel/SendLetterViewModel.swift new file mode 100644 index 000000000..bbdd32bed --- /dev/null +++ b/Manito/Manito/Presentation/Scene/LetterScene/ViewModel/SendLetterViewModel.swift @@ -0,0 +1,108 @@ +// +// SendLetterViewModel.swift +// Manito +// +// Created by SHIN YOON AH on 2023/09/23. +// + +import Combine +import Foundation + +final class SendLetterViewModel: BaseViewModelType { + + typealias Message = (content: String?, image: Data?) + typealias MessageDetail = (roomId: String, missionId: String, manitteeId: String) + + struct Input { + let viewDidLoad: AnyPublisher + let sendLetterButtonDidTap: AnyPublisher + let letterTextDidChange: AnyPublisher + } + + struct Output { + let mission: AnyPublisher + let letterResponse: AnyPublisher, Never> + let textCount: AnyPublisher<(count: Int, maxCount: Int), Never> + let text: AnyPublisher + } + + // MARK: - property + + private var cancelBag: Set = Set() + + private let maximumTextCount: Int = 100 + + private let usecase: SendLetterUsecase + private let mission: String + private let messageDetail: MessageDetail + + // MARK: - init + + init(usecase: SendLetterUsecase, + mission: String, + manitteeId: String, + roomId: String, + missionId: String) { + self.usecase = usecase + self.mission = mission + self.messageDetail = (roomId, missionId, manitteeId) + } + + // MARK: - Public - func + + func transform(from input: Input) -> Output { + let mission = input.viewDidLoad + .compactMap { [weak self] in self?.mission } + .eraseToAnyPublisher() + + let letterResponse = input.sendLetterButtonDidTap + .asyncMap { [weak self] data -> Result in + do { + let _ = try await self?.dispatchLetter(content: data.content, image: data.image) + return .success(()) + } catch(let error) { + return .failure(error) + } + } + .eraseToAnyPublisher() + + let truncatedText = input.letterTextDidChange + .map { [weak self] in + guard let self else { return $0 } + return self.truncateText($0) + } + .eraseToAnyPublisher() + + let textCount = truncatedText + .map { [weak self] in + guard let self else { return (count: 0, maxCount: 0) } + return (count: $0.count, maxCount: self.maximumTextCount) + } + .eraseToAnyPublisher() + + return Output(mission: mission, + letterResponse: letterResponse, + textCount: textCount, + text: truncatedText) + } +} + +// MARK: - Helper +extension SendLetterViewModel { + private func dispatchLetter(content: String?, image: Data?) async throws -> Int { + let letter = LetterRequestDTO(manitteeId: self.messageDetail.manitteeId, messageContent: content) + return try await self.usecase.dispatchLetter(roomId: self.messageDetail.roomId, + image: image, + letter: letter, + missionId: self.messageDetail.missionId) + } + + private func truncateText(_ text: String) -> String { + if text.count > self.maximumTextCount { + let prefixText = text[text.startIndex.. { + return self.appleLoginButton.buttonTapPublisher + } + + // MARK: - init + + override init(frame: CGRect) { + super.init(frame: frame) + self.baseInit() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - base func + + func setupLayout() { + self.addSubview(self.logoImageView) + self.logoImageView.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.centerY.equalToSuperview().offset(-92) + $0.width.height.equalTo(130) + } + + self.addSubview(self.logoTextImageView) + self.logoTextImageView.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.top.equalTo(self.logoImageView.snp.bottom).offset(7) + } + + self.addSubview(self.appleLoginButton) + self.appleLoginButton.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.leading.trailing.equalToSuperview().inset(65) + $0.height.equalTo(50) + $0.bottom.equalTo(self.safeAreaLayoutGuide).inset(35) + } + } + + func configureUI() { + self.backgroundColor = .backgroundGrey + } +} diff --git a/Manito/Manito/Presentation/Scene/LoginScene/ViewController/LoginViewController.swift b/Manito/Manito/Presentation/Scene/LoginScene/ViewController/LoginViewController.swift new file mode 100644 index 000000000..3ec395d87 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/LoginScene/ViewController/LoginViewController.swift @@ -0,0 +1,129 @@ +// +// LoginViewController.swift +// Manito +// +// Created by Mingwan Choi on 2022/07/07. +// + +import Combine +import UIKit + +import SnapKit + +final class LoginViewController: UIViewController { + + // MARK: - ui component + + private let loginView: LoginView = LoginView() + + // MARK: - property + + private var cancellable: Set = Set() + private let viewModel: any BaseViewModelType + + // MARK: - init + + init(viewModel: any BaseViewModelType) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + print("\(#file) is dead") + } + + // MARK: - life cycle + + override func loadView() { + self.view = self.loginView + } + + override func viewDidLoad() { + super.viewDidLoad() + self.bindViewModel() + } + + // MARK: - func + + private func bindViewModel() { + let output = self.transformedOutput() + self.bindOutputToViewModel(output) + } + + private func transformedOutput() -> LoginViewModel.Output? { + guard let viewModel = self.viewModel as? LoginViewModel else { return nil } + let input = LoginViewModel.Input( + appleSignButtonDidTap: self.loginView.appleSignButtonTapPublisher + ) + return viewModel.transform(from: input) + } + + private func bindOutputToViewModel(_ output: LoginViewModel.Output?) { + guard let output else { return } + + output.isNewLogin + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] result in + switch result { + case .success(let isNewLogin): + if isNewLogin { + self?.presentCreateNicknameViewController() + } else { + self?.presentMainViewController() + } + case .failure(let error): + switch error { + case .failedToLogin: self?.makeAlertWhenLoginError(error: error.localizedDescription) + case .failedCredential: self?.makeAlertWhenCredentialError(error: error.localizedDescription) + case .failedToken: self?.makeAlertWhenTokenError(error: error.localizedDescription) + case .failedTokenToString: self?.makeAlertWhenTokenToStringError(error: error.localizedDescription) + } + } + }) + .store(in: &self.cancellable) + } +} + +// MARK: - Helper +extension LoginViewController { + private func presentCreateNicknameViewController() { + let nicknameUsecase = NicknameUsecaseImpl(repository: SettingRepositoryImpl()) + let textFieldUsecase = TextFieldUsecaseImpl() + let viewModel = NicknameViewModel(nicknameUsecase: nicknameUsecase, + textFieldUsecase: textFieldUsecase) + let viewController = CreateNicknameViewController(viewModel: viewModel) + self.navigationController?.pushViewController(viewController, animated: true) + } + + private func presentMainViewController() { + let navigationController = UINavigationController(rootViewController: MainViewController()) + navigationController.modalPresentationStyle = .fullScreen + navigationController.modalTransitionStyle = .crossDissolve + self.present(navigationController, animated: true) + } + + private func makeAlertWhenLoginError(error: String) { + self.makeAlert(title: TextLiteral.Common.Error.title.localized(), + message: error) + } + + private func makeAlertWhenCredentialError(error: String) { + self.makeAlert(title: TextLiteral.Sign.Error.credential.localized(), + message: error) + } + + private func makeAlertWhenTokenError(error: String) { + self.makeAlert(title: TextLiteral.Sign.Error.token.localized(), + message: error) + } + + private func makeAlertWhenTokenToStringError(error: String) { + self.makeAlert(title: TextLiteral.Sign.Error.tokenToString.localized(), + message: error) + } +} diff --git a/Manito/Manito/Presentation/Scene/LoginScene/ViewModel/LoginViewModel.swift b/Manito/Manito/Presentation/Scene/LoginScene/ViewModel/LoginViewModel.swift new file mode 100644 index 000000000..6531a0049 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/LoginScene/ViewModel/LoginViewModel.swift @@ -0,0 +1,100 @@ +// +// LoginViewModel.swift +// Manito +// +// Created by COBY_PRO on 10/24/23. +// + +import AuthenticationServices +import Combine +import Foundation + +final class LoginViewModel: NSObject, BaseViewModelType { + + // MARK: - property + + private let usecase: LoginUsecase + private var cancellable: Set = Set() + + private let isNewLoginSubject: PassthroughSubject, Never> = PassthroughSubject() + + struct Input { + let appleSignButtonDidTap: AnyPublisher + } + + struct Output { + let isNewLogin: AnyPublisher, Never> + } + + func transform(from input: Input) -> Output { + input.appleSignButtonDidTap + .sink(receiveValue: { [weak self] _ in + self?.didTapAppleSignButton() + }) + .store(in: &self.cancellable) + return Output(isNewLogin: self.isNewLoginSubject.eraseToAnyPublisher()) + } + + // MARK: - init + + init(usecase: LoginUsecase) { + self.usecase = usecase + } + + // MARK: - func + + private func didTapAppleSignButton() { + let provider = ASAuthorizationAppleIDProvider() + + let request = provider.createRequest() + request.requestedScopes = [.fullName, .email] + + let controller = ASAuthorizationController(authorizationRequests: [request]) + controller.delegate = self + controller.performRequests() + } + + // MARK: - network + + private func dispatchLogin(login: LoginRequestDTO) { + Task { + do { + let login = try await self.usecase.dispatchAppleLogin(login: login) + + UserDefaultHandler.setIsLogin(isLogin: true) + UserDefaultHandler.setAccessToken(accessToken: login.accessToken) + UserDefaultHandler.setRefreshToken(refreshToken: login.refreshToken) + + if !login.isNewMember { + UserDefaultHandler.setNickname(nickname: login.nickname) + UserDefaultHandler.setIsSetFcmToken(isSetFcmToken: true) + } + + self.isNewLoginSubject.send(.success(login.isNewMember)) + } catch { + self.isNewLoginSubject.send(.failure(LoginUsecaseError.failedToLogin)) + } + } + } +} + +extension LoginViewModel: ASAuthorizationControllerDelegate { + func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { + guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else { + self.isNewLoginSubject.send(.failure(LoginUsecaseError.failedCredential)) + return + } + guard let token = credential.identityToken else { + self.isNewLoginSubject.send(.failure(LoginUsecaseError.failedToken)) + return + } + guard let tokenToString = String(data: token, encoding: .utf8) else { + self.isNewLoginSubject.send(.failure(LoginUsecaseError.failedTokenToString)) + return + } + + let loginDTO = LoginRequestDTO(identityToken: tokenToString, + fcmToken: UserDefaultStorage.fcmToken) + self.dispatchLogin(login: loginDTO) + } +} diff --git a/Manito/Manito/Screens/ChooseCharacter/UIComponent/CharacterCollectionViewCell.swift b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/View/Cell/CharacterCollectionViewCell.swift similarity index 97% rename from Manito/Manito/Screens/ChooseCharacter/UIComponent/CharacterCollectionViewCell.swift rename to Manito/Manito/Presentation/Scene/ParticipateRoomScene/View/Cell/CharacterCollectionViewCell.swift index a5e87c32d..63b2a213d 100644 --- a/Manito/Manito/Screens/ChooseCharacter/UIComponent/CharacterCollectionViewCell.swift +++ b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/View/Cell/CharacterCollectionViewCell.swift @@ -16,7 +16,7 @@ final class CharacterCollectionViewCell: UICollectionViewCell, BaseViewType { private let characterImageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFit - imageView.image = ImageLiterals.imgMa + imageView.image = UIImage.Image.ma return imageView }() diff --git a/Manito/Manito/Screens/ChooseCharacter/View/ChooseCharacterView.swift b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/View/ChooseCharacterView.swift similarity index 58% rename from Manito/Manito/Screens/ChooseCharacter/View/ChooseCharacterView.swift rename to Manito/Manito/Presentation/Scene/ParticipateRoomScene/View/ChooseCharacterView.swift index afd2d7640..9df419336 100644 --- a/Manito/Manito/Screens/ChooseCharacter/View/ChooseCharacterView.swift +++ b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/View/ChooseCharacterView.swift @@ -5,60 +5,61 @@ // Created by 이성호 on 2023/05/10. // +import Combine import UIKit import SnapKit -protocol ChooseCharacterViewDelegate: AnyObject { - func backButtonDidTap() - func closeButtonDidTap() - func joinButtonDidTap(characterIndex: Int) -} - final class ChooseCharacterView: UIView, BaseViewType { // MARK: - ui component private let closeButton: UIButton = { let button = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: 44, height: 44))) - button.setImage(ImageLiterals.btnXmark, for: .normal) + button.setImage(UIImage.Button.xmark, for: .normal) return button }() private let backButton: UIButton = { let button = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: 44, height: 44))) - button.setImage(ImageLiterals.icBack, for: .normal) + button.setImage(UIImage.Button.back, for: .normal) return button }() private let titleLabel: UILabel = { let label = UILabel() - label.text = TextLiteral.chooseCharacterViewControllerTitleLabel + label.text = TextLiteral.ParticipateRoom.chooseCharacterTitle.localized() label.font = .font(.regular, ofSize: 34) return label }() private let subTitleLabel: UILabel = { let label = UILabel() - label.text = TextLiteral.chooseCharacterViewControllerSubTitleLabel + label.text = TextLiteral.ParticipateRoom.chooseCharacterSubTitle.localized() label.font = .font(.regular, ofSize: 18) label.textColor = .grey002 return label }() - private let manittoCollectionView: CharacterCollectionView = CharacterCollectionView() + let manittoCollectionView: CharacterCollectionView = CharacterCollectionView() private let joinButton: MainButton = { let button = MainButton() - button.title = TextLiteral.enterRoom + button.title = TextLiteral.Common.enterRoom.localized() return button }() // MARK: - property - private weak var delegate: ChooseCharacterViewDelegate? + var backButtonTapPublisher: AnyPublisher { + return self.backButton.tapPublisher + } + var closeButtonTapPublisher: AnyPublisher { + return self.closeButton.tapPublisher + } + let joinButtonTapPublisher: PassthroughSubject = PassthroughSubject() // MARK: - init override init(frame: CGRect) { super.init(frame: frame) self.baseInit() - self.setupButtonAction() + self.setupAction() } @available(*, unavailable) @@ -66,24 +67,32 @@ final class ChooseCharacterView: UIView, BaseViewType { fatalError("init(coder:) has not been implemented") } - // MARK: - base func + // MARK: - func + + private func setupAction() { + let didTapJoinButton = UIAction { [weak self] _ in + guard let self = self else { return } + self.joinButtonTapPublisher.send(self.manittoCollectionView.characterIndexTapPublisher.value) + } + self.joinButton.addAction(didTapJoinButton, for: .touchUpInside) + } func setupLayout() { self.addSubview(self.titleLabel) self.titleLabel.snp.makeConstraints { $0.top.equalTo(self.safeAreaLayoutGuide).inset(20) - $0.leading.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) } self.addSubview(self.subTitleLabel) self.subTitleLabel.snp.makeConstraints { $0.top.equalTo(self.titleLabel.snp.bottom).offset(10) - $0.leading.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) } self.addSubview(self.joinButton) self.joinButton.snp.makeConstraints { - $0.leading.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.bottom.equalToSuperview().inset(57) $0.height.equalTo(60) } @@ -91,43 +100,20 @@ final class ChooseCharacterView: UIView, BaseViewType { self.addSubview(self.manittoCollectionView) self.manittoCollectionView.snp.makeConstraints { $0.top.equalTo(self.subTitleLabel.snp.bottom).offset(37) - $0.leading.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.bottom.equalTo(self.joinButton.snp.top) } } - - func configureUI() { - self.backgroundColor = .backgroundGrey - } - - // MARK: - func - - private func setupButtonAction() { - let didTapBackButton = UIAction { [weak self] _ in - self?.delegate?.backButtonDidTap() - } - let didTapCloseButton = UIAction { [weak self] _ in - self?.delegate?.closeButtonDidTap() - } - let didTapJoinButton = UIAction { [weak self] _ in - self?.delegate?.joinButtonDidTap(characterIndex: self?.manittoCollectionView.characterIndexTapPublisher.value ?? 0) - } - - self.backButton.addAction(didTapBackButton, for: .touchUpInside) - self.closeButton.addAction(didTapCloseButton, for: .touchUpInside) - self.joinButton.addAction(didTapJoinButton, for: .touchUpInside) - } - - func configureDelegate(_ delegate: ChooseCharacterViewDelegate) { - self.delegate = delegate - } - func configureNavigationItem(_ navigationController: UINavigationController) { + func configureNavigationBarItem(_ navigationController: UINavigationController) { let navigationItem = navigationController.topViewController?.navigationItem - let backButton = UIBarButtonItem(customView: self.backButton) let closeButton = UIBarButtonItem(customView: self.closeButton) - navigationItem?.leftBarButtonItem = backButton navigationItem?.rightBarButtonItem = closeButton + navigationItem?.leftBarButtonItem = nil + } + + func configureUI() { + self.backgroundColor = .backgroundGrey } } diff --git a/Manito/Manito/Presentation/Scene/ParticipateRoomScene/View/ParticipateRoomView.swift b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/View/ParticipateRoomView.swift new file mode 100644 index 000000000..b13532b4d --- /dev/null +++ b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/View/ParticipateRoomView.swift @@ -0,0 +1,124 @@ +// +// ParticipateRoomView.swift +// Manito +// +// Created by 이성호 on 2023/05/11. +// + +import Combine +import UIKit + +import SnapKit + +final class ParticipateRoomView: UIView, BaseViewType { + + // MARK: - ui component + + private let titleLabel: UILabel = { + let label = UILabel() + label.text = TextLiteral.Common.enterRoom.localized() + label.font = .font(.regular, ofSize: 34) + return label + }() + private let closeButton: UIButton = { + let button = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: 44, height: 44))) + button.setImage(UIImage.Button.xmark, for: .normal) + return button + }() + private let nextButton: MainButton = { + let button = MainButton() + button.title = TextLiteral.Common.searchRoom.localized() + button.isDisabled = true + return button + }() + private let inputInvitedCodeView: InputInvitedCodeView = InputInvitedCodeView() + + // MARK: - property + + let nextButtonTapPublisher: PassthroughSubject = PassthroughSubject() + var closeButtonTapPublisher: AnyPublisher { + self.closeButton.tapPublisher + } + var textFieldDidChangedPublisher: PassthroughSubject { + return self.inputInvitedCodeView.textFieldDidChangedPublisher + } + + // MARK: - init + + override init(frame: CGRect) { + super.init(frame: frame) + self.baseInit() + self.setupButtonAction() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - base func + + func setupLayout() { + self.addSubview(self.titleLabel) + self.titleLabel.snp.makeConstraints { + $0.top.equalTo(self.safeAreaLayoutGuide).inset(20) + $0.leading.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) + } + + self.addSubview(self.nextButton) + self.nextButton.snp.makeConstraints { + $0.leading.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) + $0.bottom.equalTo(self.keyboardLayoutGuide.snp.top).inset(-23) + $0.height.equalTo(60) + } + + self.addSubview(self.inputInvitedCodeView) + self.inputInvitedCodeView.snp.makeConstraints { + $0.top.equalTo(self.titleLabel.snp.bottom).offset(66) + $0.leading.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) + $0.bottom.equalTo(self.nextButton.snp.top) + } + + self.bringSubviewToFront(self.nextButton) + } + + func configureUI() { + self.backgroundColor = .backgroundGrey + } + + // MARK: - func + + private func setupButtonAction() { + let didTapNextButton = UIAction { [weak self] _ in + guard let code = self?.inputInvitedCodeView.code() else { return } + self?.nextButtonTapPublisher.send(code) + } + self.nextButton.addAction(didTapNextButton, for: .touchUpInside) + } + + func configureNavigationBarItem(_ navigationController: UINavigationController) { + let navigationItem = navigationController.topViewController?.navigationItem + let closeButton = UIBarButtonItem(customView: self.closeButton) + + navigationItem?.rightBarButtonItem = closeButton + navigationItem?.leftBarButtonItem = nil + } + + func endEditing() { + if !self.nextButton.isTouchInside { + self.endEditing(true) + } + } + + func toggleDoneButton(isEnabled: Bool) { + self.nextButton.isDisabled = !isEnabled + } + + func updateTextCount(count: Int, maxLength: Int) { + self.inputInvitedCodeView.updateTextCount(count: count, maxLength: maxLength) + } + + func updateTextFieldText(fixedText: String) { + self.inputInvitedCodeView.updateTextFieldText(fixedText: fixedText) + } +} diff --git a/Manito/Manito/Presentation/Scene/ParticipateRoomScene/View/ParticipationRoomDetailsView.swift b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/View/ParticipationRoomDetailsView.swift new file mode 100644 index 000000000..6f750dfa8 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/View/ParticipationRoomDetailsView.swift @@ -0,0 +1,162 @@ +// +// ParticipationRoomDetails.swift +// Manito +// +// Created by 이성호 on 2023/09/05. +// + +import Combine +import UIKit + +import SnapKit + +final class ParticipationRoomDetailsView: UIView, BaseViewType { + + // MARK: - ui component + + private let roomInfoImageView: UIImageView = { + let image = UIImageView() + image.image = UIImage.Image.enterRoom + image.isUserInteractionEnabled = true + return image + }() + private let roomLabel: UILabel = { + let label = UILabel() + label.font = .font(.regular, ofSize: 34) + return label + }() + private let dateLabel: UILabel = { + let label = UILabel() + label.font = .font(.regular, ofSize: 18) + return label + }() + private let peopleImageView = UIImageView(image: UIImage.Image.ni) + private let peopleLabel: UILabel = { + let label = UILabel() + label.font = .font(.regular, ofSize: 24) + return label + }() + private let questionLabel: UILabel = { + let label = UILabel() + label.text = TextLiteral.ParticipateRoom.title.localized() + label.font = .font(.regular, ofSize: 18) + label.makeShadow(color: .black, opacity: 0.25, offset: CGSize(width: 0, height: 3), radius: 0) + return label + }() + private let noButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle(TextLiteral.ParticipateRoom.buttonNo.localized(), for: .normal) + button.setTitleColor(.black, for: .normal) + button.titleLabel?.font = .font(.regular, ofSize: 35) + button.backgroundColor = .yellow001 + button.makeShadow(color: .shadowYellow, opacity: 1.0, offset: CGSize(width: 0, height: 4), radius: 1) + button.layer.cornerRadius = 22 + return button + }() + private let yesButton: UIButton = { + let button = UIButton(type: .system) + button.setTitle(TextLiteral.ParticipateRoom.buttonYes.localized(), for: .normal) + button.setTitleColor(.black, for: .normal) + button.titleLabel?.font = .font(.regular, ofSize: 35) + button.backgroundColor = .yellow001 + button.makeShadow(color: .shadowYellow, opacity: 1.0, offset: CGSize(width: 0, height: 4), radius: 1) + button.layer.cornerRadius = 22 + return button + }() + + // MARK: - property + + var noButtonDidTapPublisher: AnyPublisher { + return self.noButton.tapPublisher + } + var yesButtonDidTapPublisher: AnyPublisher { + return self.yesButton.tapPublisher + } + + // MARK: - init + + override init(frame: CGRect) { + super.init(frame: frame) + self.baseInit() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - func + + func setupLayout() { + self.addSubview(self.roomInfoImageView) + self.roomInfoImageView.snp.makeConstraints { + $0.center.equalToSuperview() + $0.leading.trailing.equalToSuperview().inset(24) + $0.height.equalTo(self.roomInfoImageView.snp.width).multipliedBy(1.15) + } + + self.roomInfoImageView.addSubview(self.roomLabel) + self.roomLabel.snp.makeConstraints { + $0.top.equalToSuperview().inset(120) + $0.height.equalTo(35) + $0.centerX.equalToSuperview() + } + + self.roomInfoImageView.addSubview(self.dateLabel) + self.dateLabel.snp.makeConstraints { + $0.top.equalTo(self.roomLabel.snp.bottom).offset(8) + $0.height.equalTo(20) + $0.centerX.equalToSuperview() + } + + self.roomInfoImageView.addSubview(self.peopleImageView) + self.peopleImageView.snp.makeConstraints { + $0.top.equalTo(self.dateLabel.snp.bottom).offset(7) + $0.width.height.equalTo(60) + $0.trailing.equalTo(self.snp.centerX).offset(-10) + } + + self.roomInfoImageView.addSubview(self.peopleLabel) + self.peopleLabel.snp.makeConstraints { + $0.leading.equalTo(self.peopleImageView.snp.trailing).offset(5) + $0.centerY.equalTo(self.peopleImageView.snp.centerY) + } + + self.roomInfoImageView.addSubview(self.noButton) + self.noButton.snp.makeConstraints { + $0.bottom.equalToSuperview().inset(15) + $0.trailing.equalTo(self.snp.centerX).offset(-15) + $0.width.equalTo(110) + $0.height.equalTo(44) + } + + self.roomInfoImageView.addSubview(self.yesButton) + self.yesButton.snp.makeConstraints { + $0.bottom.equalToSuperview().inset(15) + $0.leading.equalTo(self.snp.centerX).offset(15) + $0.width.equalTo(110) + $0.height.equalTo(44) + } + + self.roomInfoImageView.addSubview(self.questionLabel) + self.questionLabel.snp.makeConstraints { + $0.bottom.equalTo(self.noButton.snp.top).offset(-15) + $0.centerX.equalToSuperview() + } + } + + func configureUI() { + self.backgroundColor = .black.withAlphaComponent(0.7) + } + + func updateRoomInfo(roomInfo: ParticipatedRoomInfo) { + let title = roomInfo.title + let capacity = roomInfo.capacity + let startDate = roomInfo.startDate + let endDate = roomInfo.endDate + + self.roomLabel.text = title + self.dateLabel.text = "\(startDate) ~ \(endDate)" + self.peopleLabel.text = "X \(capacity)인" + } +} diff --git a/Manito/Manito/Presentation/Scene/ParticipateRoomScene/View/UIComponent/InputInvitedCodeView.swift b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/View/UIComponent/InputInvitedCodeView.swift new file mode 100644 index 000000000..10fd39551 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/View/UIComponent/InputInvitedCodeView.swift @@ -0,0 +1,102 @@ +// +// InputInvitedCodeView.swift +// Manito +// +// Created by 이성호 on 2022/06/15. +// + +import Combine +import UIKit + +import SnapKit + +final class InputInvitedCodeView: UIView, BaseViewType { + + // MARK: - ui component + + private lazy var roomCodeTextField: UITextField = { + let textField = UITextField() + let attributes = [ + NSAttributedString.Key.font : UIFont.font(.regular, ofSize: 18) + ] + textField.backgroundColor = .darkGrey002 + textField.attributedPlaceholder = NSAttributedString(string: TextLiteral.ParticipateRoom.inputCodePlaceholder.localized(), + attributes: attributes) + textField.textAlignment = .center + textField.makeBorderLayer(color: .white) + textField.font = .font(.regular, ofSize: 18) + textField.returnKeyType = .done + textField.delegate = self + textField.autocorrectionType = .no + textField.autocapitalizationType = .allCharacters + textField.becomeFirstResponder() + return textField + }() + private let limitLabel : UILabel = { + let label = UILabel() + label.font = .font(.regular, ofSize: 20) + label.textColor = .grey002 + return label + }() + + // MARK: - property + + let textFieldDidChangedPublisher: PassthroughSubject = PassthroughSubject() + + // MARK: - life cycle + + override init(frame: CGRect) { + super.init(frame: frame) + self.baseInit() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - func + + func setupLayout() { + self.addSubview(self.roomCodeTextField) + self.roomCodeTextField.snp.makeConstraints { + $0.top.leading.trailing.equalToSuperview() + $0.height.equalTo(60) + } + + self.addSubview(self.limitLabel) + self.limitLabel.snp.makeConstraints { + $0.top.equalTo(self.roomCodeTextField.snp.bottom).offset(10) + $0.trailing.equalToSuperview() + } + } + + func configureUI() { + self.backgroundColor = .backgroundGrey + } + + func code() -> String { + guard let code = self.roomCodeTextField.text else { return "" } + return code + } + + func updateTextCount(count: Int, maxLength: Int) { + self.limitLabel.text = "\(count)/\(maxLength)" + } + + func updateTextFieldText(fixedText: String) { + DispatchQueue.main.async { + self.roomCodeTextField.text = String(fixedText) + } + } +} + +extension InputInvitedCodeView: UITextFieldDelegate { + func textFieldShouldReturn(_ textField: UITextField) -> Bool { + roomCodeTextField.resignFirstResponder() + } + + func textFieldDidChangeSelection(_ textField: UITextField) { + self.textFieldDidChangedPublisher.send(textField.text ?? "") + } +} diff --git a/Manito/Manito/Presentation/Scene/ParticipateRoomScene/ViewController/ChooseCharacterViewController.swift b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/ViewController/ChooseCharacterViewController.swift new file mode 100644 index 000000000..707c915fc --- /dev/null +++ b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/ViewController/ChooseCharacterViewController.swift @@ -0,0 +1,133 @@ +// +// ChooseRoomViewController.swift +// Manito +// +// Created by COBY_PRO on 2022/06/18. +// + +import Combine +import UIKit + +import SnapKit + +final class ChooseCharacterViewController: UIViewController, Navigationable { + + // MARK: - ui component + + private let chooseCharacterView: ChooseCharacterView = ChooseCharacterView() + + // MARK: - property + + private let viewModel: any BaseViewModelType + private var cancellable: Set = Set() + + // MARK: - init + + init(viewModel: any BaseViewModelType) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + print("\(#file) is dead") + } + + // MARK: - life cycle + + override func loadView() { + self.view = self.chooseCharacterView + } + + override func viewDidLoad() { + super.viewDidLoad() + self.configureNavigation() + self.setupNavigation() + self.bindToViewModel() + self.bindUI() + } + + // MARK: - func + + private func configureNavigation() { + guard let navigationController = self.navigationController else { return } + self.chooseCharacterView.configureNavigationBarItem(navigationController) + } + + private func bindToViewModel() { + let output = self.transformedOutput() + self.bindOutputToViewModel(output) + } + + private func transformedOutput() -> ChooseCharacterViewModel.Output? { + guard let viewModel = self.viewModel as? ChooseCharacterViewModel else { return nil } + let input = ChooseCharacterViewModel.Input(joinButtonTapPublisher: self.chooseCharacterView.joinButtonTapPublisher.eraseToAnyPublisher()) + return viewModel.transform(from: input) + } + + private func bindOutputToViewModel(_ output: ChooseCharacterViewModel.Output?) { + guard let output else { return } + + output.roomId + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] result in + switch result { + case .success(let roomId): + self?.pushDetailWaitViewController(roomId: roomId) + case .failure(let error): + switch error { + case .roomAlreadyParticipating: self?.makeAlertWhenAlreadyJoin(error: error.localizedDescription) + default: self?.makeAlertWhenNetworkError(error: error.localizedDescription) + } + } + }) + .store(in: &self.cancellable) + } + + private func bindUI() { + self.chooseCharacterView.backButtonTapPublisher + .sink { [weak self] _ in + self?.navigationController?.popViewController(animated: true) + } + .store(in: &self.cancellable) + + self.chooseCharacterView.closeButtonTapPublisher + .sink { [weak self] _ in + self?.dismiss(animated: true) + } + .store(in: &self.cancellable) + } + + private func makeAlertWhenAlreadyJoin(error: String) { + self.makeAlert(title: TextLiteral.ParticipateRoom.Error.alreadyJoinTitle.localized(), + message: error, + okAction: { [weak self] _ in + self?.dismiss(animated: true) + }) + } + + private func makeAlertWhenNetworkError(error: String) { + self.makeAlert(title: TextLiteral.Common.Error.title.localized(), + message: error, + okAction: nil) + } +} + +// MARK: - Helper + +extension ChooseCharacterViewController { + private func pushDetailWaitViewController(roomId: Int) { + guard let navigationController = self.presentingViewController as? UINavigationController else { return } + let repository = DetailRoomRepositoryImpl() + let usecase = DetailWaitUseCaseImpl(repository: repository) + let viewModel = DetailWaitViewModel(roomId: roomId.description, usecase: usecase) + let viewController = DetailWaitViewController(viewModel: viewModel) + self.dismiss(animated: true) { + navigationController.pushViewController(viewController, animated: true) + } + } +} diff --git a/Manito/Manito/Presentation/Scene/ParticipateRoomScene/ViewController/ParticipateRoomViewController.swift b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/ViewController/ParticipateRoomViewController.swift new file mode 100644 index 000000000..a7611cb47 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/ViewController/ParticipateRoomViewController.swift @@ -0,0 +1,132 @@ +// +// ParticipateRoomViewController.swift +// Manito +// +// Created by 이성호 on 2022/06/15. +// + +import Combine +import UIKit + +import SnapKit + +final class ParticipateRoomViewController: UIViewController, Keyboardable { + + // MARK: - ui component + + private let participateRoomView: ParticipateRoomView = ParticipateRoomView() + + // MARK: - property + + private let viewModel: any BaseViewModelType + private var cancellable: Set = Set() + + // MARK: - init + + init(viewModel: any BaseViewModelType) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + print("\(#file) is dead") + } + + // MARK: - life cycle + + override func loadView() { + self.view = self.participateRoomView + } + + override func viewDidLoad() { + super.viewDidLoad() + self.configureNavigation() + self.bindToViewModel() + self.bindUI() + self.setupKeyboardGesture() + } + + // MARK: - override + + override func endEditingView() { + self.participateRoomView.endEditing() + } + + // MARK: - func + + private func configureNavigation() { + guard let navigationController = self.navigationController else { return } + self.participateRoomView.configureNavigationBarItem(navigationController) + } + + private func bindToViewModel() { + let output = self.transfromedOutput() + self.bindOutputToViewModel(output) + } + + private func transfromedOutput() -> ParticipateRoomViewModel.Output? { + guard let viewModel = self.viewModel as? ParticipateRoomViewModel else { return nil } + let input = ParticipateRoomViewModel.Input(viewDidLoad: self.viewDidLoadPublisher, + textFieldDidChanged: self.participateRoomView.textFieldDidChangedPublisher.eraseToAnyPublisher(), + nextButtonDidTap: self.participateRoomView.nextButtonTapPublisher.eraseToAnyPublisher()) + return viewModel.transform(from: input) + } + + private func bindOutputToViewModel(_ output: ParticipateRoomViewModel.Output?) { + guard let output else { return } + + output.counts + .sink(receiveValue: { [weak self] (textCount, maxCount) in + self?.participateRoomView.updateTextCount(count: textCount, maxLength: maxCount) + }) + .store(in: &self.cancellable) + + output.fixedTitleByMaxCount + .sink { [weak self] fixedTitle in + self?.participateRoomView.updateTextFieldText(fixedText: fixedTitle) + } + .store(in: &self.cancellable) + + output.isEnabled + .sink(receiveValue: { [weak self] isEnable in + self?.participateRoomView.toggleDoneButton(isEnabled: isEnable) + }) + .store(in: &self.cancellable) + + output.roomInfo + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] result in + switch result { + case .success(let roomInfo): + self?.presentParticipationRoomDetailsView(roomInfo: roomInfo) + case .failure(let error): + self?.makeAlert(title: error.localizedDescription) + } + }) + .store(in: &self.cancellable) + } + + private func bindUI() { + self.participateRoomView.closeButtonTapPublisher + .sink { [weak self] _ in + self?.dismiss(animated: true) + } + .store(in: &self.cancellable) + } +} + +// MARK: - Helper + +extension ParticipateRoomViewController { + private func presentParticipationRoomDetailsView(roomInfo: ParticipatedRoomInfo) { + let viewController = ParticipationRoomDetailsViewController(viewModel: ParticipationRoomDetailsViewModel(roomInfo: roomInfo)) + viewController.modalPresentationStyle = .overFullScreen + viewController.modalTransitionStyle = .crossDissolve + self.present(viewController, animated: true) + } +} diff --git a/Manito/Manito/Presentation/Scene/ParticipateRoomScene/ViewController/ParticipationRoomDetailsViewController.swift b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/ViewController/ParticipationRoomDetailsViewController.swift new file mode 100644 index 000000000..9b955ee13 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/ViewController/ParticipationRoomDetailsViewController.swift @@ -0,0 +1,107 @@ +// +// ParticipationRoomDetailsViewController.swift +// Manito +// +// Created by 이성호 on 2022/06/15. +// + +import Combine +import UIKit + +import SnapKit + +final class ParticipationRoomDetailsViewController: UIViewController { + + // MARK: - ui component + + private let participationRoomDetailsView: ParticipationRoomDetailsView = ParticipationRoomDetailsView() + + // MARK: - property + + private let viewModel: any BaseViewModelType + private var cancellable: Set = Set() + + // MARK: - init + + init(viewModel: any BaseViewModelType) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + print("\(#file) is dead") + } + + // MARK: - life cycle + + override func loadView() { + self.view = self.participationRoomDetailsView + } + + override func viewDidLoad() { + super.viewDidLoad() + self.bindToViewModel() + self.bindUI() + } + + // MARK: - func + + private func bindToViewModel() { + let output = self.transformedOutput() + self.bindOutputToViewModel(output) + } + + private func transformedOutput() -> ParticipationRoomDetailsViewModel.Output? { + guard let viewModel = self.viewModel as? ParticipationRoomDetailsViewModel else { return nil } + let input = ParticipationRoomDetailsViewModel.Input(viewDidLoad: self.viewDidLoadPublisher, + yesButtonDidTap: self.participationRoomDetailsView.yesButtonDidTapPublisher) + return viewModel.transform(from: input) + } + + private func bindOutputToViewModel(_ output: ParticipationRoomDetailsViewModel.Output?) { + guard let output else { return } + + output.roomInfo + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] roomInfo in + self?.participationRoomDetailsView.updateRoomInfo(roomInfo: roomInfo) + }) + .store(in: &self.cancellable) + + output.yesButtonDidTap + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] roomId in + guard let self else { return } + self.presentChooseCharacterViewController(roomId: roomId) + }) + .store(in: &self.cancellable) + } + + private func bindUI() { + self.participationRoomDetailsView.noButtonDidTapPublisher + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] _ in + self?.dismiss(animated: true) + }) + .store(in: &self.cancellable) + } +} + +// MARK: - Helper + +extension ParticipationRoomDetailsViewController { + private func presentChooseCharacterViewController(roomId: Int) { + guard let presentingViewController = self.presentingViewController as? UINavigationController else { return } + self.dismiss(animated: true, completion: { + let repository = RoomParticipationRepositoryImpl() + let usecase = ParticipateRoomUsecaseImpl(repository: repository) + let viewModel = ChooseCharacterViewModel(usecase: usecase, roomId: roomId) + presentingViewController.pushViewController(ChooseCharacterViewController(viewModel: viewModel), animated: true) + }) + } +} diff --git a/Manito/Manito/Presentation/Scene/ParticipateRoomScene/ViewModel/ChooseCharacterViewModel.swift b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/ViewModel/ChooseCharacterViewModel.swift new file mode 100644 index 000000000..ff5a5724f --- /dev/null +++ b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/ViewModel/ChooseCharacterViewModel.swift @@ -0,0 +1,61 @@ +// +// ChooseCharacterViewModel.swift +// Manito +// +// Created by 이성호 on 2023/09/23. +// + +import Combine +import Foundation + +final class ChooseCharacterViewModel: BaseViewModelType { + + struct Input { + let joinButtonTapPublisher: AnyPublisher + } + + struct Output { + let roomId: AnyPublisher, Never> + } + + // MARK: - property + + private let roomId: Int + + private let usecase: ParticipateRoomUsecase + private var cancellable: Set = Set() + + // MARK: - init + + init(usecase: ParticipateRoomUsecase, roomId: Int) { + self.usecase = usecase + self.roomId = roomId + } + + // MARK: - func + + func transform(from input: Input) -> Output { + let roomId = input.joinButtonTapPublisher + .asyncMap { [weak self] characterIndex -> Result in + do { + let _ = try await self?.dispatchJoinRoom(roomId: self?.roomId ?? 0, colorIndex: characterIndex) + return .success(self?.roomId ?? 0) + } catch (let error) { + guard let error = error as? ChooseCharacterError else { return .failure(.unknownError) } + return .failure(error) + } + } + .eraseToAnyPublisher() + + return Output(roomId: roomId) + } +} + +// MARK: - Helper + +extension ChooseCharacterViewModel { + private func dispatchJoinRoom(roomId: Int, colorIndex: Int) async throws -> Int { + return try await self.usecase.dispatchJoinRoom(roomId: roomId.description, member: MemberInfoRequestDTO(colorIndex: colorIndex)) + } +} + diff --git a/Manito/Manito/Presentation/Scene/ParticipateRoomScene/ViewModel/ParticipateRoomViewModel.swift b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/ViewModel/ParticipateRoomViewModel.swift new file mode 100644 index 000000000..54c08803d --- /dev/null +++ b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/ViewModel/ParticipateRoomViewModel.swift @@ -0,0 +1,97 @@ +// +// ParticipateRoomViewModel.swift +// Manito +// +// Created by 이성호 on 2023/09/04. +// + +import Combine +import Foundation + +final class ParticipateRoomViewModel: BaseViewModelType { + + typealias Counts = (textCount: Int, maxCount: Int) + + struct Input { + let viewDidLoad: AnyPublisher + let textFieldDidChanged: AnyPublisher + let nextButtonDidTap: AnyPublisher + } + + struct Output { + let counts: AnyPublisher + let fixedTitleByMaxCount: AnyPublisher + let isEnabled: AnyPublisher + let roomInfo: AnyPublisher, Never> + } + + // MARK: - property + + private let maxCount: Int = 6 + + private let usecase: ParticipateRoomUsecase + private let textFieldUsecase: TextFieldUsecase + + private var cancellable: Set = Set() + + // MARK: - init + + init(usecase: ParticipateRoomUsecase, + textFieldUsecase: TextFieldUsecase) { + self.usecase = usecase + self.textFieldUsecase = textFieldUsecase + } + + // MARK: - func + + func transform(from input: Input) -> Output { + let countViewDidLoadType = input.viewDidLoad + .map { [weak self] _ -> Counts in + (0, self?.maxCount ?? 0) + } + + let countTextFieldDidChangedType = input.textFieldDidChanged + .map { [weak self] text -> Counts in + (text.count, self?.maxCount ?? 0) + } + + let mergeCount = Publishers.Merge(countViewDidLoadType, countTextFieldDidChangedType) + .eraseToAnyPublisher() + + let fixedTitle = input.textFieldDidChanged + .map { [weak self] text in + guard let self else { return "" } + let code = self.textFieldUsecase.cutTextByMaxCount(text: text, maxCount: self.maxCount) + return code + } + .eraseToAnyPublisher() + + let isEnabled = input.textFieldDidChanged + .map { $0.count == 6 } + .eraseToAnyPublisher() + + let roomInfo = input.nextButtonDidTap + .asyncMap({ [weak self] code -> Result in + do { + let roomInfo = try await self?.dispatchVerifyCode(code) + return .success(roomInfo ?? ParticipatedRoomInfo.emptyInfo) + } catch (let error) { + return .failure(error) + } + }) + .eraseToAnyPublisher() + + return Output(counts: mergeCount, + fixedTitleByMaxCount: fixedTitle, + isEnabled: isEnabled, + roomInfo: roomInfo) + } +} + +// MARK: - Helper + +extension ParticipateRoomViewModel { + private func dispatchVerifyCode(_ code: String) async throws -> ParticipatedRoomInfo { + return try await self.usecase.dispatchVerifyCode(code: code).toParticipateRoomInfo() + } +} diff --git a/Manito/Manito/Presentation/Scene/ParticipateRoomScene/ViewModel/ParticipationRoomDetailsViewModel.swift b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/ViewModel/ParticipationRoomDetailsViewModel.swift new file mode 100644 index 000000000..5b030ff07 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/ParticipateRoomScene/ViewModel/ParticipationRoomDetailsViewModel.swift @@ -0,0 +1,48 @@ +// +// ParticipationRoomDetailsViewModel.swift +// Manito +// +// Created by 이성호 on 2023/09/05. +// + +import Combine +import Foundation + +final class ParticipationRoomDetailsViewModel: BaseViewModelType { + + struct Input { + let viewDidLoad: AnyPublisher + let yesButtonDidTap: AnyPublisher + } + + struct Output { + let roomInfo: AnyPublisher + let yesButtonDidTap: AnyPublisher + } + + // MARK: - property + + private let roomInfo: ParticipatedRoomInfo + private var cancellable: Set = Set() + + // MARK: - init + + init(roomInfo: ParticipatedRoomInfo) { + self.roomInfo = roomInfo + } + + // MARK: - func + + func transform(from input: Input) -> Output { + let roomInfo = input.viewDidLoad + .compactMap { [weak self] in self?.roomInfo } + .eraseToAnyPublisher() + + let yesButtonDidTap = input.yesButtonDidTap + .compactMap { [weak self] in self?.roomInfo.id } + .eraseToAnyPublisher() + + return Output(roomInfo: roomInfo, + yesButtonDidTap: yesButtonDidTap) + } +} diff --git a/Manito/Manito/Screens/Setting/Cell/SettingViewTableCell.swift b/Manito/Manito/Presentation/Scene/SettingScene/View/Cell/SettingViewTableCell.swift similarity index 100% rename from Manito/Manito/Screens/Setting/Cell/SettingViewTableCell.swift rename to Manito/Manito/Presentation/Scene/SettingScene/View/Cell/SettingViewTableCell.swift diff --git a/Manito/Manito/Screens/Setting/View/SettingView.swift b/Manito/Manito/Presentation/Scene/SettingScene/View/SettingView.swift similarity index 51% rename from Manito/Manito/Screens/Setting/View/SettingView.swift rename to Manito/Manito/Presentation/Scene/SettingScene/View/SettingView.swift index 3c6cfc000..2a07a4d27 100644 --- a/Manito/Manito/Screens/Setting/View/SettingView.swift +++ b/Manito/Manito/Presentation/Scene/SettingScene/View/SettingView.swift @@ -10,21 +10,35 @@ import UIKit import SnapKit -protocol SettingViewDelegate: AnyObject { - func changNicknameButtonDidTap() - func personalInfomationButtonDidTap() - func termsOfServiceButtonDidTap() - func developerInfoButtonDidTap() - func helpButtonDidTap() - func logoutButtonDidTap() - func withdrawalButtonDidTap() -} - final class SettingView: UIView, BaseViewType { - struct Option { - let title: String - let handler: () -> Void + enum SettingActions: CaseIterable { + case changeNickname + case personInfomation + case termsOfService + case developerInfo + case help + case logout + case withdrawal + + var title: String { + switch self { + case .changeNickname: + return TextLiteral.Setting.changeNickname.localized() + case .personInfomation: + return TextLiteral.Setting.personalInformation.localized() + case .termsOfService: + return TextLiteral.Setting.termsOfService.localized() + case .developerInfo: + return TextLiteral.Setting.developerInfo.localized() + case .help: + return TextLiteral.Setting.inquiry.localized() + case .logout: + return TextLiteral.Setting.logout.localized() + case .withdrawal: + return "" + } + } } // MARK: - ui component @@ -41,29 +55,25 @@ final class SettingView: UIView, BaseViewType { }() private let withdrawalButton: UIButton = { let button = UIButton() - button.setTitle(TextLiteral.settingViewControllerWithdrawalTitle, for: .normal) + button.setTitle(TextLiteral.Setting.withdrawal.localized(), for: .normal) button.titleLabel?.font = .font(.regular, ofSize: 15) button.setUnderLine() return button }() - private let imageRow: TopCharacterImageView = TopCharacterImageView() // MARK: - property - private var options: [Option] = [] - private weak var delegate: SettingViewDelegate? - - let withdrawalButtonPublisher = PassthroughSubject() - let logoutButtonPublisher = PassthroughSubject() + private let settingActions: [SettingActions] = SettingActions.allCases + + let buttonDidTapPublisher: PassthroughSubject = PassthroughSubject() // MARK: - init override init(frame: CGRect) { super.init(frame: frame) self.baseInit() - self.setupButtonAction() - self.configureModels() + self.setupAction() } @available(*, unavailable) @@ -85,7 +95,7 @@ final class SettingView: UIView, BaseViewType { self.tableView.snp.makeConstraints { $0.top.equalTo(self.imageRow.snp.bottom) $0.centerX.equalToSuperview() - $0.width.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.width.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.bottom.equalToSuperview().inset(50) } @@ -102,49 +112,19 @@ final class SettingView: UIView, BaseViewType { // MARK: - func - private func setupButtonAction() { - let withdrawalButtonDidTap = UIAction { [weak self] _ in - self?.delegate?.withdrawalButtonDidTap() + private func setupAction() { + let didTapWithdrawalButton = UIAction { [weak self] _ in + self?.buttonDidTapPublisher.send(.withdrawal) } - - self.withdrawalButton.addAction(withdrawalButtonDidTap, for: .touchUpInside) - } - - private func configureModels() { - self.options.append(Option(title: TextLiteral.settingViewControllerChangeNickNameTitle, handler: { [weak self] in - self?.delegate?.changNicknameButtonDidTap() - })) - - self.options.append(Option(title: TextLiteral.settingViewControllerPersonalInfomationTitle, handler: { [weak self] in - self?.delegate?.personalInfomationButtonDidTap() - })) - - self.options.append(Option(title: TextLiteral.settingViewControllerTermsOfServiceTitle, handler: { [weak self] in - self?.delegate?.termsOfServiceButtonDidTap() - })) - - self.options.append(Option(title: TextLiteral.settingViewControllerDeveloperInfoTitle, handler: { [weak self] in - self?.delegate?.developerInfoButtonDidTap() - })) - - self.options.append(Option(title: TextLiteral.settingViewControllerHelpTitle, handler: { [weak self] in - self?.delegate?.helpButtonDidTap() - })) - - self.options.append(Option(title: TextLiteral.settingViewControllerLogoutTitle, handler: { [weak self] in - self?.delegate?.logoutButtonDidTap() - })) - } - - func configureDelegate(_ delegate: SettingViewDelegate) { - self.delegate = delegate + self.withdrawalButton.addAction(didTapWithdrawalButton, for: .touchUpInside) } } +// MARK: - UITableViewDelegate + extension SettingView: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let model = self.options[indexPath.row] - model.handler() + self.buttonDidTapPublisher.send(self.settingActions[indexPath.row]) } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { @@ -152,17 +132,16 @@ extension SettingView: UITableViewDelegate { } } +// MARK: - UITableViewDataSource + extension SettingView: UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return self.options.count + return self.settingActions.count - 1 } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let model = self.options[indexPath.row] - guard let cell = tableView.dequeueReusableCell(withIdentifier: SettingViewTableCell.className ,for: indexPath) as? SettingViewTableCell else { - return UITableViewCell() - } - cell.configureCell(title: model.title) + guard let cell = tableView.dequeueReusableCell(withIdentifier: SettingViewTableCell.className ,for: indexPath) as? SettingViewTableCell else { return UITableViewCell() } + cell.configureCell(title: settingActions[indexPath.row].title) return cell } } diff --git a/Manito/Manito/Screens/Setting/UIComponent/TopCharacterImageView.swift b/Manito/Manito/Presentation/Scene/SettingScene/View/UIComponent/TopCharacterImageView.swift similarity index 65% rename from Manito/Manito/Screens/Setting/UIComponent/TopCharacterImageView.swift rename to Manito/Manito/Presentation/Scene/SettingScene/View/UIComponent/TopCharacterImageView.swift index 95de43240..7cc73ddbe 100644 --- a/Manito/Manito/Screens/Setting/UIComponent/TopCharacterImageView.swift +++ b/Manito/Manito/Presentation/Scene/SettingScene/View/UIComponent/TopCharacterImageView.swift @@ -11,79 +11,78 @@ import SnapKit final class TopCharacterImageView: UIView { - // MARK: - Property + // MARK: - ui component private let imgCharacterOrange: UIImageView = { let imageView = UIImageView() - imageView.image = ImageLiterals.imgCharacterOrange + imageView.image = UIImage.Image.characterOrange return imageView }() private let imgCharacterPurple: UIImageView = { let imageView = UIImageView() - imageView.image = ImageLiterals.imgCharacterPurple + imageView.image = UIImage.Image.characterPurple return imageView }() private let imgNi: UIImageView = { let imageView = UIImageView() - imageView.image = ImageLiterals.imgNi + imageView.image = UIImage.Image.ni return imageView }() private let imgCharacterLightGreen: UIImageView = { let imageView = UIImageView() - imageView.image = ImageLiterals.imgCharacterLightGreen + imageView.image = UIImage.Image.characterLightGreen return imageView }() private let imgCharacterRed: UIImageView = { let imageView = UIImageView() - imageView.image = ImageLiterals.imgCharacterRed + imageView.image = UIImage.Image.characterRed return imageView }() - // MARK: - Init + // MARK: - init override init(frame: CGRect) { super.init(frame: frame) - render() + self.setupLayout() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - // MARK: - Function + // MARK: - func - private func render() { - - self.addSubview(imgCharacterOrange) - imgCharacterOrange.snp.makeConstraints { + private func setupLayout() { + self.addSubview(self.imgCharacterOrange) + self.imgCharacterOrange.snp.makeConstraints { $0.top.equalToSuperview() $0.centerX.equalToSuperview().offset(-80) $0.height.width.equalTo(40) } - self.addSubview(imgCharacterPurple) - imgCharacterPurple.snp.makeConstraints { + self.addSubview(self.imgCharacterPurple) + self.imgCharacterPurple.snp.makeConstraints { $0.top.equalToSuperview() $0.centerX.equalToSuperview().offset(-40) $0.height.width.equalTo(40) } - self.addSubview(imgNi) - imgNi.snp.makeConstraints { + self.addSubview(self.imgNi) + self.imgNi.snp.makeConstraints { $0.top.equalToSuperview() $0.centerX.equalToSuperview() $0.height.width.equalTo(40) } - self.addSubview(imgCharacterLightGreen) - imgCharacterLightGreen.snp.makeConstraints { + self.addSubview(self.imgCharacterLightGreen) + self.imgCharacterLightGreen.snp.makeConstraints { $0.top.equalToSuperview() $0.centerX.equalToSuperview().offset(40) $0.height.width.equalTo(40) } - self.addSubview(imgCharacterRed) - imgCharacterRed.snp.makeConstraints { + self.addSubview(self.imgCharacterRed) + self.imgCharacterRed.snp.makeConstraints { $0.top.equalToSuperview() $0.centerX.equalToSuperview().offset(80) $0.height.width.equalTo(40) diff --git a/Manito/Manito/Presentation/Scene/SettingScene/ViewController/SettingViewController.swift b/Manito/Manito/Presentation/Scene/SettingScene/ViewController/SettingViewController.swift new file mode 100644 index 000000000..5304dfd55 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/SettingScene/ViewController/SettingViewController.swift @@ -0,0 +1,194 @@ +// +// SettingViewController.swift +// Manito +// +// Created by 이성호 on 2022/07/02. +// + +import Combine +import UIKit + +import SnapKit + +final class SettingViewController: UIViewController, Navigationable { + + // MARK: - ui component + + private let settingView: SettingView = SettingView() + + // MARK: - property + + private let mailManager: MailComposeManager = MailComposeManager() + + private let viewModel: any BaseViewModelType + private var cancellable: Set = Set() + + private let withdrawalPublisher: PassthroughSubject = PassthroughSubject() + private let logoutPublisher: PassthroughSubject = PassthroughSubject() + + // MARK: - init + + init(viewModel: any BaseViewModelType) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + print("\(#file) is dead") + } + + // MARK: - life cycle + + override func loadView() { + self.view = self.settingView + } + + override func viewDidLoad() { + super.viewDidLoad() + self.setupMailManager() + self.bindViewModel() + self.bindUI() + self.setupNavigation() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.configureNavigationBar() + } + + // MARK: - func + + private func configureNavigationBar() { + self.navigationController?.navigationBar.prefersLargeTitles = false + } + + private func setupMailManager() { + self.mailManager.viewController = self + } + + private func bindViewModel() { + let output = self.transformedOutput() + self.bindOutputToViewModel(output) + } + + private func transformedOutput() -> SettingViewModel.Output? { + guard let viewModel = self.viewModel as? SettingViewModel else { return nil } + let input = SettingViewModel.Input(logoutButtonDidTap: self.logoutPublisher.eraseToAnyPublisher(), + withdrawalButtonDidTap: self.withdrawalPublisher.eraseToAnyPublisher()) + return viewModel.transform(from: input) + } + + private func bindOutputToViewModel(_ output: SettingViewModel.Output?) { + guard let output else { return } + + output.logout + .receive(on: DispatchQueue.main) + .sink { [weak self] in self?.logout() } + .store(in: &self.cancellable) + + output.deleteUser + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + switch result { + case .success(): self?.deleteUser() + case .failure(let error): self?.makeAlert(title: error.localizedDescription) + } + } + .store(in: &self.cancellable) + } + + private func bindUI() { + self.settingView.buttonDidTapPublisher + .sink { [weak self] type in + switch type { + case .changeNickname: + self?.pushChangeNicknameViewController() + case .personInfomation: + self?.openUrlBySettingButton(type: .personInfomation) + case .termsOfService: + self?.openUrlBySettingButton(type: .termsOfService) + case .developerInfo: + self?.pushDeveloperInfoViewController() + case .help: + self?.sendReportMail() + case .logout: + self?.makeAlertBySettingButton(type: .logout) + case .withdrawal: + self?.makeAlertBySettingButton(type: .withdrawal) + } + } + .store(in: &self.cancellable) + } + + private func makeAlertBySettingButton(type: SettingView.SettingActions) { + switch type { + case .logout: + self.makeRequestAlert(title: TextLiteral.Setting.logoutAlertTitle.localized(), + message: "", + okAction: { [weak self] _ in + self?.logoutPublisher.send() + }) + case .withdrawal: + self.makeRequestAlert(title: TextLiteral.Common.warningTitle.localized(), + message: TextLiteral.Setting.withdrawalAlertMessage.localized(), + okAction: { [weak self] _ in + self?.withdrawalPublisher.send() + }) + default: return + } + } + + private func openUrlBySettingButton(type: SettingView.SettingActions) { + switch type { + case .personInfomation: self.openUrl(url: URLLiteral.Setting.personalInformation) + case .termsOfService: self.openUrl(url: URLLiteral.Setting.termsOfService) + default: return + } + } + + private func sendReportMail() { + let title = TextLiteral.Mail.inquiryTitle.localized() + let content = TextLiteral.Mail.inquiryMessage.localized(with: UserDefaultStorage.nickname, Date().description) + self.mailManager.sendMail(title: title, content: content) + } +} + +// MARK: - Helper + +extension SettingViewController { + private func pushChangeNicknameViewController() { + let repository = SettingRepositoryImpl() + let nicknameUsecase = NicknameUsecaseImpl(repository: repository) + let textFieldUsecase = TextFieldUsecaseImpl() + let viewMdoel = NicknameViewModel(nicknameUsecase: nicknameUsecase, + textFieldUsecase: textFieldUsecase) + self.navigationController?.pushViewController(ChangeNicknameViewController(viewModel: viewMdoel), animated: true) + } + + private func pushDeveloperInfoViewController() { + self.navigationController?.pushViewController(SettingDeveloperInfoViewController(), animated: true) + } + + private func deleteUser() { + guard let sceneDelgate = UIApplication.shared.connectedScenes.first?.delegate + as? SceneDelegate else { return } + sceneDelgate.moveToLoginViewController() + } + + private func logout() { + guard let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate + as? SceneDelegate else { return } + sceneDelegate.moveToLoginViewController() + } + + private func openUrl(url: String) { + if let url = URL(string: url) { + UIApplication.shared.open(url, options: [:]) + } + } +} diff --git a/Manito/Manito/Presentation/Scene/SettingScene/ViewModel/SettingViewModel.swift b/Manito/Manito/Presentation/Scene/SettingScene/ViewModel/SettingViewModel.swift new file mode 100644 index 000000000..228b92156 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/SettingScene/ViewModel/SettingViewModel.swift @@ -0,0 +1,64 @@ +// +// SettingViewModel.swift +// Manito +// +// Created by 이성호 on 2023/09/01. +// + +import Combine +import Foundation + +final class SettingViewModel: BaseViewModelType { + + struct Input { + let logoutButtonDidTap: AnyPublisher + let withdrawalButtonDidTap: AnyPublisher + } + + struct Output { + let logout: AnyPublisher + let deleteUser: AnyPublisher, Never> + } + + // MARK: - property + + private let usecase: SettingUsecase + private var cancellable: Set = Set() + + // MARK: - init + + init(usecase: SettingUsecase) { + self.usecase = usecase + } + + // MARK: - func + + func transform(from input: Input) -> Output { + let logout = input.logoutButtonDidTap + .map { UserDefaultHandler.clearAllDataExcludingFcmToken() } + .eraseToAnyPublisher() + + let deleteUser = input.withdrawalButtonDidTap + .asyncMap { [weak self] _ -> Result in + do { + let _ = try await self?.deleteUser() + return .success(()) + } catch(let error) { + return .failure(error) + } + } + .eraseToAnyPublisher() + + return Output(logout: logout, + deleteUser: deleteUser) + } +} + +// MARK: - Helper + +extension SettingViewModel { + private func deleteUser() async throws { + let _ = try await self.usecase.deleteUser() + UserDefaultHandler.clearAllDataExcludingFcmToken() + } +} diff --git a/Manito/Manito/Presentation/Scene/SplashScene/View/SplashView.swift b/Manito/Manito/Presentation/Scene/SplashScene/View/SplashView.swift new file mode 100644 index 000000000..57bdd35d3 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/SplashScene/View/SplashView.swift @@ -0,0 +1,63 @@ +// +// SplashView.swift +// Manito +// +// Created by SHIN YOON AH on 10/13/23. +// + +import UIKit + +import Gifu +import SnapKit + +final class SplashView: UIView, BaseViewType { + + // MARK: - ui component + + private let logoImageView: UIImageView = UIImageView(image: UIImage.Image.textLogo) + private let logoGIFImageView: GIFImageView = GIFImageView(image: UIImage(named: GIFSet.logo)) + + // MARK: - init + + override init(frame: CGRect) { + super.init(frame: frame) + self.baseInit() + self.setupGifImage() + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - base func + + func setupLayout() { + self.addSubview(self.logoImageView) + self.logoImageView.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.centerY.equalToSuperview().offset(30) + $0.width.equalTo(196) + $0.height.equalTo(44) + } + + self.addSubview(self.logoGIFImageView) + self.logoGIFImageView.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.bottom.equalTo(self.logoImageView.snp.top).offset(-3) + $0.width.height.equalTo(130) + } + } + + func configureUI() { + self.backgroundColor = .backgroundGrey + } + + // MARK: - func + + private func setupGifImage() { + DispatchQueue.main.async { + self.logoGIFImageView.animate(withGIFNamed: GIFSet.logo) + } + } +} diff --git a/Manito/Manito/Presentation/Scene/SplashScene/ViewController/SplashViewController.swift b/Manito/Manito/Presentation/Scene/SplashScene/ViewController/SplashViewController.swift new file mode 100644 index 000000000..42d99f7f9 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/SplashScene/ViewController/SplashViewController.swift @@ -0,0 +1,107 @@ +// +// SplashViewController.swift +// Manito +// +// Created by SHIN YOON AH on 2022/06/16. +// + +import Combine +import UIKit + +final class SplashViewController: UIViewController { + + // MARK: - ui component + + private let splashView: SplashView = SplashView() + + // MARK: - property + + private var cancelBag: Set = Set() + + private let viewModel: any BaseViewModelType + + // MARK: - init + + init(viewModel: any BaseViewModelType) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - life cycle + + override func loadView() { + self.view = self.splashView + } + + override func viewDidLoad() { + super.viewDidLoad() + self.bindViewModel() + } + + // MARK: - func + + private func bindViewModel() { + let output = self.transformedOutput() + self.bindOutputToViewModel(output) + } + + private func transformedOutput() -> SplashViewModel.Output? { + guard let viewModel = self.viewModel as? SplashViewModel else { return nil } + let input = SplashViewModel.Input( + viewDidLoad: self.viewDidLoadPublisher + ) + return viewModel.transform(from: input) + } + + private func bindOutputToViewModel(_ output: SplashViewModel.Output?) { + guard let output else { return } + + output.entryType + .sink(receiveValue: { [weak self] type in + switch type { + case .login: self?.presentLoginViewConroller() + case .nickname: self?.presentCreateNicknameViewController() + case .main: self?.presentMainViewController() + } + }) + .store(in: &self.cancelBag) + } +} + +// MARK: - Helper +extension SplashViewController { + private func presentLoginViewConroller() { + let repository = LoginRepositoryImpl() + let usecase = LoginUsecaseImpl(repository: repository) + let viewModel = LoginViewModel(usecase: usecase) + let viewController = LoginViewController(viewModel: viewModel) + let navigtionViewController = UINavigationController(rootViewController: viewController) + navigtionViewController.setNavigationBarHidden(true, animated: true) + navigtionViewController.modalPresentationStyle = .fullScreen + navigtionViewController.modalTransitionStyle = .crossDissolve + self.present(navigtionViewController, animated: true) + } + + private func presentCreateNicknameViewController() { + let nicknameUsecase = NicknameUsecaseImpl(repository: SettingRepositoryImpl()) + let textFieldUsecase = TextFieldUsecaseImpl() + let viewModel = NicknameViewModel(nicknameUsecase: nicknameUsecase, + textFieldUsecase: textFieldUsecase) + let viewController = CreateNicknameViewController(viewModel: viewModel) + viewController.modalPresentationStyle = .fullScreen + viewController.modalTransitionStyle = .crossDissolve + self.present(viewController, animated: true) + } + + private func presentMainViewController() { + let navigationController = UINavigationController(rootViewController: MainViewController()) + navigationController.modalPresentationStyle = .fullScreen + navigationController.modalTransitionStyle = .crossDissolve + self.present(navigationController, animated: true) + } +} diff --git a/Manito/Manito/Presentation/Scene/SplashScene/ViewModel/SplashViewModel.swift b/Manito/Manito/Presentation/Scene/SplashScene/ViewModel/SplashViewModel.swift new file mode 100644 index 000000000..7276a3b97 --- /dev/null +++ b/Manito/Manito/Presentation/Scene/SplashScene/ViewModel/SplashViewModel.swift @@ -0,0 +1,45 @@ +// +// SplashViewModel.swift +// Manito +// +// Created by SHIN YOON AH on 10/16/23. +// + +import Combine +import Foundation + +final class SplashViewModel: BaseViewModelType { + + struct Input { + let viewDidLoad: AnyPublisher + } + + struct Output { + let entryType: AnyPublisher + } + + // MARK: - property + + private var cancelBag: Set = Set() + + private let usecase: SplashUsecase + + // MARK: - init + + init(usecase: SplashUsecase) { + self.usecase = usecase + } + + // MARK: - Public - func + + func transform(from input: Input) -> Output { + let entryType = input.viewDidLoad + .delay(for: .seconds(1.5), scheduler: DispatchQueue.main) + .compactMap { [weak self] _ in self?.usecase.entryType } + .eraseToAnyPublisher() + + return Output( + entryType: entryType + ) + } +} diff --git a/Manito/Manito/Resource/Storyboards/Base.lproj/Main.storyboard b/Manito/Manito/Resource/Storyboards/Base.lproj/Main.storyboard deleted file mode 100644 index 020699b1f..000000000 --- a/Manito/Manito/Resource/Storyboards/Base.lproj/Main.storyboard +++ /dev/null @@ -1,66 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Manito/Manito/Resource/Storyboards/DetailIng.storyboard b/Manito/Manito/Resource/Storyboards/DetailIng.storyboard deleted file mode 100644 index 181b17967..000000000 --- a/Manito/Manito/Resource/Storyboards/DetailIng.storyboard +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Manito/Manito/Resource/Storyboards/Splash.storyboard b/Manito/Manito/Resource/Storyboards/Splash.storyboard deleted file mode 100644 index 0630b4144..000000000 --- a/Manito/Manito/Resource/Storyboards/Splash.storyboard +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Manito/Manito/Screens/CheckRoom/CheckRoomViewController.swift b/Manito/Manito/Screens/CheckRoom/CheckRoomViewController.swift index e92adde5a..0bdd163da 100644 --- a/Manito/Manito/Screens/CheckRoom/CheckRoomViewController.swift +++ b/Manito/Manito/Screens/CheckRoom/CheckRoomViewController.swift @@ -98,7 +98,7 @@ class CheckRoomViewController: BaseViewController, BaseViewControllerType { roomInfoImageView.addSubview(noButton) noButton.snp.makeConstraints { - $0.top.equalTo(questionLabel.snp.bottom).offset(Size.leadingTrailingPadding) + $0.top.equalTo(questionLabel.snp.bottom).offset(SizeLiteral.leadingTrailingPadding) $0.width.equalTo(110) $0.height.equalTo(44) $0.leading.equalToSuperview().inset(48) @@ -106,7 +106,7 @@ class CheckRoomViewController: BaseViewController, BaseViewControllerType { roomInfoImageView.addSubview(yesButton) yesButton.snp.makeConstraints { - $0.top.equalTo(questionLabel.snp.bottom).offset(Size.leadingTrailingPadding) + $0.top.equalTo(questionLabel.snp.bottom).offset(SizeLiteral.leadingTrailingPadding) $0.width.equalTo(110) $0.height.equalTo(44) $0.trailing.equalToSuperview().inset(48) @@ -126,6 +126,6 @@ class CheckRoomViewController: BaseViewController, BaseViewControllerType { let capacity = roomInfo?.capacity else { return } roomInfoView.roomLabel.text = title roomInfoView.dateLabel.text = "\(startDate) ~ \(endDate)" - roomInfoView.peopleInfoView.peopleLabel.text = "X \(capacity)인" + roomInfoView.peopleInfoView.peopleLabel.text = TextLiteral.person(capacity) } } diff --git a/Manito/Manito/Screens/CheckRoom/UIComponent/PeopleInfoView.swift b/Manito/Manito/Screens/CheckRoom/UIComponent/PeopleInfoView.swift deleted file mode 100644 index fb238c479..000000000 --- a/Manito/Manito/Screens/CheckRoom/UIComponent/PeopleInfoView.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// PeopleInfoView.swift -// Manito -// -// Created by COBY_PRO on 2022/06/16. -// - -import UIKit - -import SnapKit - -final class PeopleInfoView: UIView { - - // MARK: - property - - private let peopleImageView = UIImageView(image: ImageLiterals.imgNi) - let peopleLabel: UILabel = { - let label = UILabel() - label.font = .font(.regular, ofSize: 24) - return label - }() - - // MARK: - init - - override init(frame: CGRect) { - super.init(frame: frame) - render() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - life cycle - - private func render() { - self.addSubview(peopleImageView) - peopleImageView.snp.makeConstraints { - $0.leading.top.bottom.equalToSuperview() - $0.width.height.equalTo(60) - } - - self.addSubview(peopleLabel) - peopleLabel.snp.makeConstraints { - $0.trailing.top.bottom.equalToSuperview() - $0.leading.equalTo(self.peopleImageView.snp.trailing).offset(5) - } - } -} diff --git a/Manito/Manito/Screens/CheckRoom/UIComponent/RoomInfoView.swift b/Manito/Manito/Screens/CheckRoom/UIComponent/RoomInfoView.swift deleted file mode 100644 index 1137c5c4a..000000000 --- a/Manito/Manito/Screens/CheckRoom/UIComponent/RoomInfoView.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// RoomInfoView.swift -// Manito -// -// Created by COBY_PRO on 2022/06/16. -// - -import UIKit - -import SnapKit - -final class RoomInfoView: UIView { - - // MARK: - property - - let roomLabel: UILabel = { - let label = UILabel() - label.font = .font(.regular, ofSize: 34) - return label - }() - let dateLabel: UILabel = { - let label = UILabel() - label.font = .font(.regular, ofSize: 18) - return label - }() - let peopleInfoView = PeopleInfoView() - - // MARK: - init - - override init(frame: CGRect) { - super.init(frame: frame) - render() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - life cycle - - private func render() { - self.addSubview(roomLabel) - roomLabel.snp.makeConstraints { - $0.top.centerX.equalToSuperview() - } - - self.addSubview(dateLabel) - dateLabel.snp.makeConstraints { - $0.top.equalTo(roomLabel.snp.bottom).offset(8) - $0.centerX.equalToSuperview() - } - - self.addSubview(peopleInfoView) - peopleInfoView.snp.makeConstraints { - $0.top.equalTo(dateLabel.snp.bottom).offset(10) - $0.centerX.bottom.equalToSuperview() - } - } -} diff --git a/Manito/Manito/Screens/ChooseCharacter/ChooseCharacterViewController.swift b/Manito/Manito/Screens/ChooseCharacter/ChooseCharacterViewController.swift deleted file mode 100644 index 8a13b4566..000000000 --- a/Manito/Manito/Screens/ChooseCharacter/ChooseCharacterViewController.swift +++ /dev/null @@ -1,120 +0,0 @@ -// -// ChooseRoomViewController.swift -// Manito -// -// Created by COBY_PRO on 2022/06/18. -// - -import UIKit - -import SnapKit - -final class ChooseCharacterViewController: BaseViewController { - - // MARK: - ui component - - private let chooseCharacterView: ChooseCharacterView = ChooseCharacterView() - - // MARK: - property - - private let roomParticipationRepository: RoomParticipationRepository = RoomParticipationRepositoryImpl() - private let roomId: Int? - - // MARK: - init - - init(roomId: Int?) { - self.roomId = roomId - super.init() - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - print("\(#file) is dead") - } - - // MARK: - life cycle - - override func loadView() { - self.view = self.chooseCharacterView - } - - override func viewDidLoad() { - super.viewDidLoad() - self.configureDelegation() - self.configureNavigationController() - } - - // MARK: - override - - override func setupNavigationBar() { - super.setupNavigationBar() - // FIXME: ParticipateRoomVC 수정 시 삭제 예정 - navigationController?.navigationBar.isHidden = false - } - - // MARK: - func - - private func configureDelegation() { - self.chooseCharacterView.configureDelegate(self) - } - - private func configureNavigationController() { - guard let navigationController = self.navigationController else { return } - self.chooseCharacterView.configureNavigationItem(navigationController) - } - - private func pushDetailWaitViewController(roomId: Int) { - guard let navigationController = self.presentingViewController as? UINavigationController else { return } - let viewModel = DetailWaitViewModel(roomIndex: roomId, detailWaitService: DetailWaitService(repository: DetailRoomRepositoryImpl())) - let viewController = DetailWaitViewController(viewModel: viewModel) - self.dismiss(animated: true) { - navigationController.pushViewController(viewController, animated: true) - } - } - - private func makeAlertWhenAlreadyJoin() { - self.makeAlert(title: "이미 참여중인 방입니다", message: "참여중인 애니또 리스트를 확인해 보세요", okAction: { [weak self] _ in - self?.dismiss(animated: true) - }) - } - - // MARK: - network - - private func requestJoinRoom(characterIndex: Int) { - Task { - do { - guard let roomId = self.roomId else { return } - let status = try await self.roomParticipationRepository.dispatchJoinRoom(roomId: roomId.description, - member: MemberInfoRequestDTO(colorIndex: characterIndex)) - if status == 201 { - self.pushDetailWaitViewController(roomId: roomId) - } - } catch NetworkError.serverError { - print("server Error") - } catch NetworkError.encodingError { - print("encoding Error") - } catch NetworkError.clientError(let message) { - print("client Error: \(String(describing: message))") - self.makeAlertWhenAlreadyJoin() - } - } - } -} - -extension ChooseCharacterViewController: ChooseCharacterViewDelegate { - func backButtonDidTap() { - self.navigationController?.popViewController(animated: true) - } - - func closeButtonDidTap() { - self.dismiss(animated: true) - } - - func joinButtonDidTap(characterIndex: Int) { - self.requestJoinRoom(characterIndex: characterIndex) - } -} diff --git a/Manito/Manito/Screens/CreateRoom/CreateRoomViewController.swift b/Manito/Manito/Screens/CreateRoom/CreateRoomViewController.swift index a15ea76bb..feda16029 100644 --- a/Manito/Manito/Screens/CreateRoom/CreateRoomViewController.swift +++ b/Manito/Manito/Screens/CreateRoom/CreateRoomViewController.swift @@ -10,7 +10,7 @@ import UIKit import SnapKit -final class CreateRoomViewController: BaseViewController { +final class CreateRoomViewController: UIViewController, Navigationable, Keyboardable { // MARK: - ui component @@ -18,14 +18,14 @@ final class CreateRoomViewController: BaseViewController { // MARK: - property - private var cancellable = Set() - private let createRoomViewModel: CreateRoomViewModel + private var cancellable: Set = Set() + private let viewModel: any BaseViewModelType // MARK: - init - init(viewModel: CreateRoomViewModel) { - self.createRoomViewModel = viewModel - super.init() + init(viewModel: any BaseViewModelType) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) } @available(*, unavailable) @@ -46,8 +46,10 @@ final class CreateRoomViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() self.setupNavigationBarHiddenState() - self.configureDelegation() self.bindViewModel() + self.bindUI() + self.setupNavigation() + self.setupKeyboardGesture() } // MARK: - override @@ -62,65 +64,42 @@ final class CreateRoomViewController: BaseViewController { self.navigationController?.navigationBar.isHidden = true } - private func configureDelegation() { - self.createRoomView.configureDelegate(self) - } - - private func pushDetailWaitViewController(roomId: Int) { - guard let navigationController = self.presentingViewController as? UINavigationController else { return } - let viewModel = DetailWaitViewModel(roomIndex: roomId, - detailWaitService: DetailWaitService(repository: DetailRoomRepositoryImpl())) - let viewController = DetailWaitViewController(viewModel: viewModel) - - navigationController.popViewController(animated: true) - navigationController.pushViewController(viewController, animated: false) - - self.dismiss(animated: true) { - NotificationCenter.default.post(name: .createRoomInvitedCode, object: nil) - } - } - private func bindViewModel() { let output = self.transformedOutput() self.bindOutputToViewModel(output) } - private func transformedOutput() -> CreateRoomViewModel.Output { - let input = CreateRoomViewModel.Input(textFieldTextDidChanged: self.createRoomView.roomTitleView.textFieldPublisher.eraseToAnyPublisher(), - sliderValueDidChanged: self.createRoomView.roomCapacityView.sliderPublisher.eraseToAnyPublisher(), - startDateDidTap: self.createRoomView.roomDateView.calendarView.startDateTapPublisher.eraseToAnyPublisher(), - endDateDidTap: self.createRoomView.roomDateView.calendarView.endDateTapPublisher.eraseToAnyPublisher(), - characterIndexDidTap: self.createRoomView.characterCollectionView.characterIndexTapPublisher.eraseToAnyPublisher(), - nextButtonDidTap: self.createRoomView.nextButtonDidTapPublisher.eraseToAnyPublisher(), - backButtonDidTap: self.createRoomView.backButtonDidTapPublisher.eraseToAnyPublisher()) - return self.createRoomViewModel.transform(from: input) + private func transformedOutput() -> CreateRoomViewModel.Output? { + guard let viewModel = self.viewModel as? CreateRoomViewModel else { return nil } + let input = CreateRoomViewModel.Input(viewDidLoad: self.viewDidLoadPublisher, + textFieldTextDidChanged: self.createRoomView.textFieldPublisher.eraseToAnyPublisher(), + sliderValueDidChanged: self.createRoomView.sliderPublisher.eraseToAnyPublisher(), + startDateDidTap: self.createRoomView.startDateTapPublisher.eraseToAnyPublisher(), + endDateDidTap: self.createRoomView.endDateTapPublisher.eraseToAnyPublisher(), + characterIndexDidTap: self.createRoomView.characterIndexTapPublisher.eraseToAnyPublisher(), + nextButtonDidTap: self.createRoomView.nextButtonDidTapPublisher, + backButtonDidTap: self.createRoomView.backButtonDidTapPublisher) + return viewModel.transform(from: input) } - private func bindOutputToViewModel(_ output: CreateRoomViewModel.Output) { + private func bindOutputToViewModel(_ output: CreateRoomViewModel.Output?) { + guard let output else { return } - output.title - .sink(receiveValue: { [weak self] title in - self?.createRoomView.roomInfoView.updateRoomTitle(title: title) - self?.createRoomView.roomTitleView.updateTitleCount(count: title.count, maxLength: self?.createRoomViewModel.maxCount ?? 0) - }) + output.counts + .sink { [weak self] (textCount, maxCount) in + self?.createRoomView.updateTitleCount(count: textCount, maxLength: maxCount) + } .store(in: &self.cancellable) output.fixedTitleByMaxCount .sink(receiveValue: { [weak self] fixedTitle in - self?.createRoomView.roomTitleView.updateTextFieldText(fixedTitle: fixedTitle) + self?.createRoomView.updateTextFieldText(fixedTitle: fixedTitle) }) .store(in: &self.cancellable) output.capacity .sink(receiveValue: { [weak self] capacity in - self?.createRoomView.roomCapacityView.updateCapacity(capacity: capacity) - self?.createRoomView.roomInfoView.updateRoomCapacity(capacity: capacity) - }) - .store(in: &self.cancellable) - - output.dateRange - .sink(receiveValue: { [weak self] dateRange in - self?.createRoomView.roomInfoView.updateRoomDateRange(range: dateRange) + self?.createRoomView.updateCapacity(capacity: capacity) }) .store(in: &self.cancellable) @@ -130,35 +109,52 @@ final class CreateRoomViewController: BaseViewController { }) .store(in: &self.cancellable) - output.currentNextStep - .sink(receiveValue: { [weak self] step in - self?.createRoomView.nextButtonDidTap(currentStep: step.0, nextStep: step.1) - }) - .store(in: &self.cancellable) - - output.previousStep - .sink(receiveValue: { [weak self] step in - self?.createRoomView.backButtonDidTap(previousStep: step) - }) - .store(in: &self.cancellable) - output.roomId .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { [weak self] result in + .sink { [weak self] result in switch result { - case .finished: return - case .failure(_): - self?.makeAlert(title: "에러발생") + case .success(let roomId): self?.pushDetailWaitViewController(roomId: roomId.description) + case .failure(let error): self?.makeAlert(title: error.localizedDescription) } - }, receiveValue: { [weak self] roomid in - self?.pushDetailWaitViewController(roomId: roomid) - }) + } + .store(in: &self.cancellable) + + output.roomInfo + .sink { [weak self] roomInfo in + self?.createRoomView.updateRoomInfo(title: roomInfo.title, + capacity: roomInfo.capacity, + range: roomInfo.dateRange) + } + .store(in: &self.cancellable) + + output.currentStep + .sink { [weak self] (currentStep, isEnabled) in + self?.createRoomView.manageViewByStep(at: currentStep, isEnabled: isEnabled) + } + .store(in: &self.cancellable) + } + + private func bindUI() { + self.createRoomView.closeButtonDidTapPublisher + .sink { [weak self] _ in + self?.dismiss(animated: true) + } .store(in: &self.cancellable) } } -extension CreateRoomViewController: CreateRoomViewDelegate { - func didTapCloseButton() { - self.dismiss(animated: true) +// MARK: - Helper +extension CreateRoomViewController { + private func pushDetailWaitViewController(roomId: String) { + guard let navigationController = self.presentingViewController as? UINavigationController else { return } + let viewModel = DetailWaitViewModel(roomId: roomId, usecase: DetailWaitUseCaseImpl(repository: DetailRoomRepositoryImpl())) + let viewController = DetailWaitViewController(viewModel: viewModel) + + navigationController.popViewController(animated: true) + navigationController.pushViewController(viewController, animated: false) + + self.dismiss(animated: true) { + viewController.sendCreateRoomEvent() + } } } diff --git a/Manito/Manito/Screens/CreateRoom/UIComponent/CharacterCollectionView.swift b/Manito/Manito/Screens/CreateRoom/UIComponent/CharacterCollectionView.swift index 954be5495..b5926a12d 100644 --- a/Manito/Manito/Screens/CreateRoom/UIComponent/CharacterCollectionView.swift +++ b/Manito/Manito/Screens/CreateRoom/UIComponent/CharacterCollectionView.swift @@ -46,7 +46,7 @@ final class CharacterCollectionView: UIView { // MARK: - property - let characterIndexTapPublisher = CurrentValueSubject(0) + let characterIndexTapPublisher: CurrentValueSubject = CurrentValueSubject(0) // MARK: - init @@ -72,14 +72,14 @@ final class CharacterCollectionView: UIView { extension CharacterCollectionView: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return Character.allCases.count + return DefaultCharacterType.allCases.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CharacterCollectionViewCell.className, for: indexPath) as? CharacterCollectionViewCell else { return UICollectionViewCell() } - cell.configureBackgroundColor(color: Character.allCases[indexPath.item].color) - cell.configureImage(image: Character.allCases[indexPath.item].image) + cell.configureBackgroundColor(color: DefaultCharacterType.allCases[indexPath.item].backgroundColor) + cell.configureImage(image: DefaultCharacterType.allCases[indexPath.item].image) if indexPath.item == 0 { cell.isSelected = true @@ -92,6 +92,7 @@ extension CharacterCollectionView: UICollectionViewDataSource { extension CharacterCollectionView: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - self.characterIndexTapPublisher.send(indexPath.item) + let index = indexPath.item + self.characterIndexTapPublisher.send(index) } } diff --git a/Manito/Manito/Screens/CreateRoom/UIComponent/CheckRoomInfoView.swift b/Manito/Manito/Screens/CreateRoom/UIComponent/CheckRoomInfoView.swift index 0fcb3b5ae..4e8ade515 100644 --- a/Manito/Manito/Screens/CreateRoom/UIComponent/CheckRoomInfoView.swift +++ b/Manito/Manito/Screens/CreateRoom/UIComponent/CheckRoomInfoView.swift @@ -1,5 +1,5 @@ // -// CheckRoomView.swift +// CheckRoomInfoView.swift // Manito // // Created by 이성호 on 2022/06/14. @@ -27,7 +27,7 @@ final class CheckRoomInfoView: UIView { }() private let imageView: UIImageView = { let imageView = UIImageView() - imageView.image = ImageLiterals.imgNi + imageView.image = UIImage.Image.ni return imageView }() private var personLabel: UILabel = { @@ -78,15 +78,9 @@ final class CheckRoomInfoView: UIView { } } - func updateRoomTitle(title: String) { + func updateRoomInfo(title: String, capacity: Int, range: String) { self.nameLabel.text = title - } - - func updateRoomCapacity(capacity: Int) { - self.personLabel.text = "\(capacity)" + TextLiteral.per - } - - func updateRoomDateRange(range: String) { + self.personLabel.text = TextLiteral.Common.people.localized(with: capacity) self.dateLabel.text = range } } diff --git a/Manito/Manito/Screens/CreateRoom/UIComponent/InputCapacityView.swift b/Manito/Manito/Screens/CreateRoom/UIComponent/InputCapacityView.swift index e412a34ea..f5eb3753c 100644 --- a/Manito/Manito/Screens/CreateRoom/UIComponent/InputCapacityView.swift +++ b/Manito/Manito/Screens/CreateRoom/UIComponent/InputCapacityView.swift @@ -16,7 +16,7 @@ final class InputCapacityView: UIView { private let titleLabel: UILabel = { let label = UILabel() - label.text = TextLiteral.inputPersonViewTitle + label.text = TextLiteral.CreateRoom.inputPersonTitle.localized() label.font = .font(.regular, ofSize: 18) return label }() @@ -28,13 +28,13 @@ final class InputCapacityView: UIView { }() private let imageView: UIImageView = { let imageView = UIImageView() - imageView.image = ImageLiterals.imgNi + imageView.image = UIImage.Image.ni imageView.backgroundColor = .darkGray return imageView }() private lazy var personLabel: UILabel = { let label = UILabel() - label.text = TextLiteral.x + " \(Int(self.personSlider.value))인" + label.text = TextLiteral.Common.xPeople.localized(with: Int(self.personSlider.value)) label.font = .font(.regular, ofSize: 24) return label }() @@ -44,8 +44,7 @@ final class InputCapacityView: UIView { slider.minimumValue = 4 slider.maximumValue = 15 slider.tintColor = .mainRed - slider.setThumbImage(ImageLiterals.imageSliderThumb, for: .normal) - slider.addTarget(self, action: #selector(self.didSlideSlider(_:)), for: .valueChanged) + slider.setThumbImage(UIImage.Image.sliderThumb, for: .normal) return slider }() private lazy var minLabel: UILabel = { @@ -61,7 +60,11 @@ final class InputCapacityView: UIView { return label }() - let sliderPublisher = PassthroughSubject() + // MARK: - property + + var sliderPublisher: AnyPublisher { + return self.personSlider.valuePublisher + } // MARK: - init @@ -105,13 +108,13 @@ final class InputCapacityView: UIView { self.addSubview(self.minLabel) self.minLabel.snp.makeConstraints { - $0.leading.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.top.equalTo(self.personBackView.snp.bottom).offset(49) } self.addSubview(self.maxLabel) self.maxLabel.snp.makeConstraints { - $0.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.top.equalTo(self.minLabel.snp.top) } @@ -124,14 +127,6 @@ final class InputCapacityView: UIView { } func updateCapacity(capacity: Int) { - self.personLabel.text = TextLiteral.x + " \(capacity)인" - } - - // MARK: - selector - - @objc - private func didSlideSlider(_ slider: UISlider) { - let value = Int(slider.value) - self.sliderPublisher.send(value) + self.personLabel.text = TextLiteral.Common.xPeople.localized(with: capacity) } } diff --git a/Manito/Manito/Screens/CreateRoom/UIComponent/InputDateView.swift b/Manito/Manito/Screens/CreateRoom/UIComponent/InputDateView.swift index 35073c558..202b26dd0 100644 --- a/Manito/Manito/Screens/CreateRoom/UIComponent/InputDateView.swift +++ b/Manito/Manito/Screens/CreateRoom/UIComponent/InputDateView.swift @@ -5,6 +5,7 @@ // Created by LeeSungHo on 2022/06/11. // +import Combine import UIKit import SnapKit @@ -15,18 +16,27 @@ final class InputDateView: UIView { private let titleLabel: UILabel = { let label = UILabel() - label.text = TextLiteral.inputDateViewTitle + label.text = TextLiteral.CreateRoom.inputDateTitle.localized() label.font = .font(.regular, ofSize: 18) return label }() - let calendarView: CalendarView = CalendarView() + private let calendarView: CalendarView = CalendarView() private let dateInfoLabel: UILabel = { let label = UILabel() - label.text = TextLiteral.maxMessage + label.text = TextLiteral.Common.Calendar.maxDateContent.localized() label.font = .font(.regular, ofSize: 16) label.textColor = .grey002 return label }() + + // MARK: - property + + var startDateTapPublisher: PassthroughSubject { + return self.calendarView.startDateTapPublisher + } + var endDateTapPublisher: PassthroughSubject { + return self.calendarView.endDateTapPublisher + } // MARK: - init diff --git a/Manito/Manito/Screens/CreateRoom/UIComponent/InputTitleView.swift b/Manito/Manito/Screens/CreateRoom/UIComponent/InputTitleView.swift index 11c8fd461..c42af3fe7 100644 --- a/Manito/Manito/Screens/CreateRoom/UIComponent/InputTitleView.swift +++ b/Manito/Manito/Screens/CreateRoom/UIComponent/InputTitleView.swift @@ -20,7 +20,7 @@ final class InputTitleView: UIView { NSAttributedString.Key.font : UIFont.font(.regular, ofSize: 18) ] textField.backgroundColor = .darkGrey002 - textField.attributedPlaceholder = NSAttributedString(string: TextLiteral.inputNameViewRoomNameText, + textField.attributedPlaceholder = NSAttributedString(string: TextLiteral.CreateRoom.inputNameTitle.localized(), attributes:attributes) textField.textAlignment = .center textField.makeBorderLayer(color: .white) @@ -32,9 +32,8 @@ final class InputTitleView: UIView { textField.becomeFirstResponder() return textField }() - private lazy var roomsTextLimitLabel : UILabel = { + private let roomsTextLimitLabel : UILabel = { let label = UILabel() - label.text = "0/\(self.maxLength)" label.font = .font(.regular, ofSize: 20) label.textColor = .grey002 return label @@ -42,8 +41,7 @@ final class InputTitleView: UIView { // MARK: - property - private var maxLength: Int = 8 - let textFieldPublisher = PassthroughSubject() + let textFieldPublisher: PassthroughSubject = PassthroughSubject() // MARK: - init diff --git a/Manito/Manito/Screens/CreateRoom/View/CreateRoomView.swift b/Manito/Manito/Screens/CreateRoom/View/CreateRoomView.swift index c03fa89f6..59baf6081 100644 --- a/Manito/Manito/Screens/CreateRoom/View/CreateRoomView.swift +++ b/Manito/Manito/Screens/CreateRoom/View/CreateRoomView.swift @@ -10,57 +10,68 @@ import UIKit import SnapKit -protocol CreateRoomViewDelegate: AnyObject { - func didTapCloseButton() -} - final class CreateRoomView: UIView, BaseViewType { // MARK: - ui component private let titleLabel: UILabel = { let label = UILabel() - label.text = TextLiteral.createRoom + label.text = TextLiteral.Common.createRoom.localized() label.font = .font(.regular, ofSize: 34) return label }() private let closeButton: UIButton = { let button = UIButton(type: .system) - button.setImage(ImageLiterals.btnXmark, for: .normal) + button.setImage(UIImage.Button.xmark, for: .normal) button.tintColor = .grey001 return button }() private let nextButton: MainButton = { let button = MainButton() - button.title = TextLiteral.next + button.title = TextLiteral.CreateRoom.next.localized() button.isDisabled = true return button }() private let backButton: UIButton = { let button = UIButton() - button.setImage(ImageLiterals.icBack, for: .normal) - button.setTitle(" " + TextLiteral.previous, for: .normal) + button.setImage(UIImage.Button.back, for: .normal) + button.setTitle(" " + TextLiteral.CreateRoom.previous.localized(), for: .normal) button.titleLabel?.font = .font(.regular, ofSize: 14) button.tintColor = .white button.isHidden = true return button }() - let roomTitleView: InputTitleView = InputTitleView() - let roomCapacityView: InputCapacityView = InputCapacityView() - let roomDateView: InputDateView = InputDateView() - let roomInfoView: CheckRoomInfoView = CheckRoomInfoView() - let characterCollectionView: CharacterCollectionView = CharacterCollectionView() - - let nextButtonDidTapPublisher = PassthroughSubject() - let backButtonDidTapPublisher = PassthroughSubject() + private let roomTitleView: InputTitleView = InputTitleView() + private let roomCapacityView: InputCapacityView = InputCapacityView() + private let roomDateView: InputDateView = InputDateView() + private let roomInfoView: CheckRoomInfoView = CheckRoomInfoView() + private let characterCollectionView: CharacterCollectionView = CharacterCollectionView() // MARK: - property - private weak var delegate: CreateRoomViewDelegate? - private var roomStep: CreateRoomStep = .inputTitle { - willSet(step) { - self.manageStepView(step: step) - } + var closeButtonDidTapPublisher: AnyPublisher { + return self.closeButton.tapPublisher + } + var textFieldPublisher: PassthroughSubject { + return self.roomTitleView.textFieldPublisher + } + var sliderPublisher: AnyPublisher { + return self.roomCapacityView.sliderPublisher + } + var startDateTapPublisher: PassthroughSubject { + return self.roomDateView.startDateTapPublisher + } + var endDateTapPublisher: PassthroughSubject { + return self.roomDateView.endDateTapPublisher + } + var characterIndexTapPublisher: CurrentValueSubject { + return self.characterCollectionView.characterIndexTapPublisher + } + var nextButtonDidTapPublisher: AnyPublisher { + return self.nextButton.tapPublisher + } + var backButtonDidTapPublisher: AnyPublisher { + return self.backButton.tapPublisher } // MARK: - init @@ -68,8 +79,6 @@ final class CreateRoomView: UIView, BaseViewType { override init(frame: CGRect) { super.init(frame: frame) self.baseInit() - self.setupAction() - self.setupNotificationCenter() } @available(*, unavailable) @@ -83,13 +92,13 @@ final class CreateRoomView: UIView, BaseViewType { self.addSubview(self.titleLabel) self.titleLabel.snp.makeConstraints { $0.top.equalTo(self.safeAreaLayoutGuide).inset(66) - $0.leading.equalTo(self.safeAreaLayoutGuide).inset(Size.leadingTrailingPadding) + $0.leading.equalTo(self.safeAreaLayoutGuide).inset(SizeLiteral.leadingTrailingPadding) } self.addSubview(self.closeButton) self.closeButton.snp.makeConstraints { $0.top.equalTo(self.safeAreaLayoutGuide).inset(9) - $0.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) } self.addSubview(self.backButton) @@ -101,8 +110,8 @@ final class CreateRoomView: UIView, BaseViewType { self.addSubview(self.nextButton) self.nextButton.snp.makeConstraints { - $0.leading.trailing.equalTo(self.safeAreaLayoutGuide).inset(Size.leadingTrailingPadding) - $0.bottom.equalTo(self.safeAreaLayoutGuide).inset(23) + $0.leading.trailing.equalTo(self.safeAreaLayoutGuide).inset(SizeLiteral.leadingTrailingPadding) + $0.bottom.equalTo(self.keyboardLayoutGuide.snp.top).inset(-23) $0.height.equalTo(60) } @@ -116,7 +125,7 @@ final class CreateRoomView: UIView, BaseViewType { .forEach { $0.snp.makeConstraints { $0.top.equalTo(self.titleLabel.snp.bottom).offset(66) - $0.leading.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.bottom.equalTo(self.nextButton.snp.top) } } @@ -126,51 +135,20 @@ final class CreateRoomView: UIView, BaseViewType { func configureUI() { self.backgroundColor = .backgroundGrey - self.roomStep = .inputTitle } // MARK: - func - - private func setupAction() { - let closeAction = UIAction { [weak self] _ in - self?.delegate?.didTapCloseButton() - } - self.closeButton.addAction(closeAction, for: .touchUpInside) - - let nextAction = UIAction { [weak self] _ in - guard let currentStep = self?.roomStep else { return } - self?.nextButtonDidTapPublisher.send(currentStep) - } - self.nextButton.addAction(nextAction, for: .touchUpInside) - - let backAction = UIAction { [weak self] _ in - guard let currentStep = self?.roomStep else { return } - self?.backButtonDidTapPublisher.send(currentStep) - } - self.backButton.addAction(backAction, for: .touchUpInside) - } - private func setupNotificationCenter() { - NotificationCenter.default.addObserver(self, - selector: #selector(keyboardWillShow), - name: UIResponder.keyboardWillShowNotification, - object: nil) - NotificationCenter.default.addObserver(self, - selector: #selector(keyboardWillHide), - name: UIResponder.keyboardWillHideNotification, - object: nil) + private func updateHiddenView(at step: CreateRoomViewModel.Step) { + self.roomTitleView.isHidden = !(step == .title) + self.roomCapacityView.isHidden = !(step == .capacity) + self.roomDateView.isHidden = !(step == .date) + self.roomInfoView.isHidden = !(step == .roomInfo) + self.characterCollectionView.isHidden = !(step == .character) } - private func updateHiddenStepView(at step: CreateRoomStep) { - self.roomTitleView.isHidden = !(step == .inputTitle) - self.roomCapacityView.isHidden = !(step == .inputCapacity) - self.roomDateView.isHidden = !(step == .inputDate) - self.roomInfoView.isHidden = !(step == .checkRoom) - self.characterCollectionView.isHidden = !(step == .chooseCharacter) - } - - private func updateHiddenBackButton(at step: CreateRoomStep) { - self.backButton.isHidden = !(step == .inputCapacity || step == .inputDate || step == .checkRoom || step == .chooseCharacter) + private func updateHiddenBackButton(at step: CreateRoomViewModel.Step) { + self.backButton.isHidden = !(step == .capacity || step == .date || step == .roomInfo || step == .character) } private func updateRoomTitleViewAnimation() { @@ -201,112 +179,53 @@ final class CreateRoomView: UIView, BaseViewType { self.characterCollectionView.fadeIn() } - private func runActionAtStep(at step: CreateRoomStep) { + private func manageViewByNextStep(at step: CreateRoomViewModel.Step, isEnabled: Bool) { switch step { - case .inputTitle: + case .title: + self.updateRoomTitleViewAnimation() + case .capacity: + self.updateRoomCapacityViewAnimation() + self.toggleNextButton(isEnable: isEnabled) self.endEditing(true) - case .inputCapacity: - self.nextButton(isEnable: false) - default: - break + case .date: + self.updateRoomDateViewAnimation() + self.toggleNextButton(isEnable: isEnabled) + case .roomInfo: self.updateRoomDataCheckViewAnimation() + case .character: self.updateChooseCharacterViewAnimation() } } - private func nextButton(isEnable: Bool) { - self.nextButton.isDisabled = !isEnable - } - - func configureDelegate(_ delegate: CreateRoomViewDelegate) { - self.delegate = delegate - } - func endEditingView() { if !self.nextButton.isTouchInside { self.endEditing(true) } } - func backButtonDidTap(previousStep: CreateRoomStep) { - self.roomStep = previousStep - } - - func nextButtonDidTap(currentStep: CreateRoomStep, nextStep: CreateRoomStep) { - self.runActionAtStep(at: currentStep) - self.roomStep = nextStep - } - func toggleNextButton(isEnable: Bool) { self.nextButton.isDisabled = !isEnable } - private func manageStepView(step: CreateRoomStep) { - self.updateHiddenStepView(at: step) - self.updateHiddenBackButton(at: step) - switch step { - case .inputTitle: - self.updateRoomTitleViewAnimation() - case .inputCapacity: - self.updateRoomCapacityViewAnimation() - self.nextButton(isEnable: true) - case .inputDate: - self.updateRoomDateViewAnimation() - case .checkRoom: - self.updateRoomDataCheckViewAnimation() - case .chooseCharacter: - self.updateChooseCharacterViewAnimation() - } + func updateTitleCount(count: Int, maxLength: Int) { + self.roomTitleView.updateTitleCount(count: count, maxLength: maxLength) } - // MARK: - selector - - @objc - private func keyboardWillShow(notification: NSNotification) { - if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { - UIView.animate(withDuration: 0.2, animations: { - self.nextButton.transform = CGAffineTransform(translationX: 0, y: -keyboardSize.height + 30) - }) - } + func updateTextFieldText(fixedTitle: String) { + self.roomTitleView.updateTextFieldText(fixedTitle: fixedTitle) } - @objc - private func keyboardWillHide(notification: NSNotification) { - UIView.animate(withDuration: 0.2, animations: { - self.nextButton.transform = .identity - }) + func updateCapacity(capacity: Int) { + self.roomCapacityView.updateCapacity(capacity: capacity) } -} - -enum CreateRoomStep { - case inputTitle, inputCapacity, inputDate, checkRoom, chooseCharacter - func next() -> Self { - switch self { - case .inputTitle: - return .inputCapacity - case .inputCapacity: - return .inputDate - case .inputDate: - return .checkRoom - case .checkRoom: - return .chooseCharacter - case .chooseCharacter: - return .chooseCharacter - } + func updateRoomInfo(title: String, capacity: Int, range: String) { + self.roomInfoView.updateRoomInfo(title: title, + capacity: capacity, + range: range) } - func previous() -> Self { - switch self { - case .inputTitle: - return .inputTitle - case .inputCapacity: - return .inputTitle - case .inputDate: - return .inputCapacity - case .checkRoom: - return .inputDate - case .chooseCharacter: - return .checkRoom - } + func manageViewByStep(at step: CreateRoomViewModel.Step, isEnabled: Bool) { + self.updateHiddenView(at: step) + self.updateHiddenBackButton(at: step) + self.manageViewByNextStep(at: step, isEnabled: isEnabled) } } - diff --git a/Manito/Manito/Screens/CreateRoom/ViewModel/CreateRoomViewModel.swift b/Manito/Manito/Screens/CreateRoom/ViewModel/CreateRoomViewModel.swift index e26cb01c0..158655ad5 100644 --- a/Manito/Manito/Screens/CreateRoom/ViewModel/CreateRoomViewModel.swift +++ b/Manito/Manito/Screens/CreateRoom/ViewModel/CreateRoomViewModel.swift @@ -10,58 +10,121 @@ import Foundation final class CreateRoomViewModel: BaseViewModelType { - typealias CurrentNextStep = (current: CreateRoomStep, next: CreateRoomStep) + typealias Counts = (textCount: Int, maxCount: Int) + typealias StepButtonState = (step: Step, isEnabled: Bool) + + struct RoomInfo { + let title: String + let capacity: Int + let dateRange: String + } + + enum Step: Int { + case title = 0, capacity, date, roomInfo, character + + func next() -> Self { + switch self { + case .title: + return .capacity + case .capacity: + return .date + case .date: + return .roomInfo + case .roomInfo: + return .character + case .character: + return .character + } + } + + func previous() -> Self { + switch self { + case .title: + return .title + case .capacity: + return .title + case .date: + return .capacity + case .roomInfo: + return .date + case .character: + return .roomInfo + } + } + } // MARK: - property - let maxCount: Int = 8 + private let maxCount: Int = 8 + private var currentStep: Step = .title - private let createRoomService: CreateRoomService - private var cancellable = Set() + private let usecase: CreateRoomUsecase + private let textFieldUsecase: TextFieldUsecase + private var cancellable: Set = Set() - private let titleSubject = CurrentValueSubject("") - private let capacitySubject = CurrentValueSubject(4) - private let startDateSubject = CurrentValueSubject("") - private let endDateSubject = CurrentValueSubject("") - private let dateRangeSubject = PassthroughSubject() - private let characterIndexSubject = CurrentValueSubject(0) - private let roomIdSubject = PassthroughSubject() + private let titleSubject: CurrentValueSubject = CurrentValueSubject("") + private let capacitySubject:CurrentValueSubject = CurrentValueSubject(4) + private let startDateSubject: CurrentValueSubject = CurrentValueSubject("") + private let endDateSubject: CurrentValueSubject = CurrentValueSubject("") + private let dateRangeSubject: PassthroughSubject = PassthroughSubject() + private let characterIndexSubject: CurrentValueSubject = CurrentValueSubject(0) + private let roomIdSubject: PassthroughSubject, Never> = PassthroughSubject() struct Input { + let viewDidLoad: AnyPublisher let textFieldTextDidChanged: AnyPublisher let sliderValueDidChanged: AnyPublisher let startDateDidTap: AnyPublisher let endDateDidTap: AnyPublisher let characterIndexDidTap: AnyPublisher - let nextButtonDidTap: AnyPublisher - let backButtonDidTap: AnyPublisher + let nextButtonDidTap: AnyPublisher + let backButtonDidTap: AnyPublisher } struct Output { - let title: CurrentValueSubject + let currentStep: AnyPublisher + let counts: AnyPublisher let fixedTitleByMaxCount: AnyPublisher - let capacity: CurrentValueSubject - let dateRange: PassthroughSubject + let capacity: AnyPublisher let isEnabled: AnyPublisher - let currentNextStep: AnyPublisher - let previousStep: AnyPublisher - let roomId: PassthroughSubject + let roomId: AnyPublisher, Never> + let roomInfo: AnyPublisher } func transform(from input: Input) -> Output { + let viewDidLoad = input.viewDidLoad + .compactMap { [weak self] in self?.initStep() } + .eraseToAnyPublisher() + + let nextButtonDidTap = input.nextButtonDidTap + .compactMap { [weak self] in self?.nextStep() } + .eraseToAnyPublisher() + + let previousStep = input.backButtonDidTap + .compactMap { [weak self] in self?.previousStep() } + .eraseToAnyPublisher() + + let currentStep = Publishers.Merge3(viewDidLoad, nextButtonDidTap, previousStep) + .eraseToAnyPublisher() + + let countViewDidLoadType = input.viewDidLoad + .map { [weak self] _ -> Counts in + (0, self?.maxCount ?? 0) + } + + let countTextFieldDidChangedType = input.textFieldTextDidChanged + .map { [weak self] text -> Counts in + (text.count, self?.maxCount ?? 0) + } + + let mergeCount = Publishers.Merge(countViewDidLoadType, countTextFieldDidChangedType).eraseToAnyPublisher() + let fixedTitle = input.textFieldTextDidChanged .map { [weak self] text -> String in - let isOverMaxCount = self?.isOverMaxCount(titleCount: text.count, maxCount: self?.maxCount ?? 0) ?? false - - if isOverMaxCount { - let endIndex = text.index(text.startIndex, offsetBy: self?.maxCount ?? 0) - let fixedText = text[text.startIndex.. Bool in - return !title.isEmpty - } + .map { !$0.isEmpty } let isEnabledDateType = input.endDateDidTap - .map { endDate -> Bool in - return !endDate.isEmpty - } + .map { !$0.isEmpty } let isEnabled = Publishers.Merge(isEnabledTitleType, isEnabledDateType) .eraseToAnyPublisher() @@ -106,84 +165,78 @@ final class CreateRoomViewModel: BaseViewModelType { }) .store(in: &self.cancellable) - let currentNextStep = input.nextButtonDidTap - .map { [weak self] step -> CurrentNextStep in - guard let self = self else { return (step, step.next()) } - return self.runActionByStep(step: step) - } - .eraseToAnyPublisher() - - let previousStep = input.backButtonDidTap - .map { [weak self] step -> CreateRoomStep in - guard let self = self else { return step } - return self.previous(step: step) + let roomInfo = Publishers.CombineLatest3(self.titleSubject, + self.capacitySubject, + self.dateRangeSubject) + .map { title, capacity, date in + return RoomInfo(title: title, + capacity: capacity, + dateRange: date) } .eraseToAnyPublisher() - return Output(title: self.titleSubject, + return Output(currentStep: currentStep, + counts: mergeCount, fixedTitleByMaxCount: fixedTitle, - capacity: self.capacitySubject, - dateRange: self.dateRangeSubject, + capacity: self.capacitySubject.eraseToAnyPublisher(), isEnabled: isEnabled, - currentNextStep: currentNextStep, previousStep: previousStep, - roomId: self.roomIdSubject) + roomId: self.roomIdSubject.eraseToAnyPublisher(), + roomInfo: roomInfo) } // MARK: - init - init(createRoomService: CreateRoomService) { - self.createRoomService = createRoomService + init(usecase: CreateRoomUsecase, + textFieldUsecase: TextFieldUsecase) { + self.usecase = usecase + self.textFieldUsecase = textFieldUsecase } // MARK: - func - private func isOverMaxCount(titleCount: Int, maxCount: Int) -> Bool { - if titleCount > maxCount { return true } - else { return false } + private func initStep() -> StepButtonState { + return (self.currentStep, false) } - private func runActionByStep(step: CreateRoomStep) -> CurrentNextStep { - switch step { - case .chooseCharacter: - self.requestCreateRoom(roomInfo: CreatedRoomInfoRequestDTO(title: self.titleSubject.value, - capacity: self.capacitySubject.value, - startDate: self.startDateSubject.value, - endDate: self.endDateSubject.value)) - return (step, step.next()) + private func nextStep() -> StepButtonState { + switch self.currentStep { + case .capacity: + self.currentStep = currentStep.next() + return (self.currentStep, self.dateIsEmpty()) + case .character: + let roomInfo = CreateRoomInfo(title: self.titleSubject.value, + capacity: self.capacitySubject.value, + startDate: self.startDateSubject.value, + endDate: self.endDateSubject.value) + self.dispatchCreateRoom(roomInfo: roomInfo) + return (self.currentStep, true) default: - return (step, step.next()) + self.currentStep = currentStep.next() + return (self.currentStep, true) } } - private func previous(step: CreateRoomStep) -> CreateRoomStep { - switch step { - case .inputTitle: - return .inputTitle - case .inputCapacity: - return .inputTitle - case .inputDate: - return .inputCapacity - case .checkRoom: - return .inputDate - case .chooseCharacter: - return .checkRoom - } + private func previousStep() -> StepButtonState { + self.currentStep = currentStep.previous() + return (self.currentStep, true) + } + + private func dateIsEmpty() -> Bool { + return !self.endDateSubject.value.isEmpty } // MARK: - network - private func requestCreateRoom(roomInfo: CreatedRoomInfoRequestDTO) { + private func dispatchCreateRoom(roomInfo: CreateRoomInfo) { Task { do { - let roomId = try await self.createRoomService.dispatchCreateRoom(room: CreatedRoomRequestDTO(room: CreatedRoomInfoRequestDTO(title: roomInfo.title, - capacity: roomInfo.capacity, - startDate: "20\(roomInfo.startDate)", - endDate: "20\(roomInfo.endDate)"), - member: MemberInfoRequestDTO(colorIndex: self.characterIndexSubject.value))) - self.roomIdSubject.send(roomId) + let roomInfoDTO = roomInfo.toCreateRoomInfoDTO() + let memberInfoDTO = MemberInfoRequestDTO(colorIndex: self.characterIndexSubject.value) + let roomId = try await self.usecase.dispatchCreateRoom(room: CreatedRoomRequestDTO(room: roomInfoDTO, + member: memberInfoDTO)) + self.roomIdSubject.send(.success(roomId)) } catch(let error) { - guard let error = error as? NetworkError else { return } - self.roomIdSubject.send(completion: .failure(error)) + self.roomIdSubject.send(.failure(error)) } } } diff --git a/Manito/Manito/Screens/Detail-Ing/Cell/FriendCollectionViewCell.swift b/Manito/Manito/Screens/Detail-Ing/Cell/FriendCollectionViewCell.swift deleted file mode 100644 index 6174e1772..000000000 --- a/Manito/Manito/Screens/Detail-Ing/Cell/FriendCollectionViewCell.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// FriendCollectionViewCell.swift -// Manito -// -// Created by 최성희 on 2022/06/13. -// - -import UIKit - -class FriendCollectionViewCell: UICollectionViewCell { - @IBOutlet weak var friendListBackView: UIView! - @IBOutlet weak var friendListImageView: UIImageView! - @IBOutlet weak var friendListNameLabel: UILabel! - - func setupFont() { - friendListNameLabel.font = .font(.regular, ofSize: 15) - } - - func setupViewLayer() { - friendListBackView.makeBorderLayer(color: .white) - friendListImageView.layer.cornerRadius = 45 - friendListBackView.layer.cornerRadius = 49 - } - - func setFriendName(name: String) { - friendListNameLabel.text = name - } - - func setFriendImage(index: Int) { - friendListBackView.backgroundColor = Character.allCases[index].color - friendListImageView.image = Character.allCases[index].image - } -} diff --git a/Manito/Manito/Screens/Detail-Ing/DetailingViewController.swift b/Manito/Manito/Screens/Detail-Ing/DetailingViewController.swift index b2f344b88..cd2605e20 100644 --- a/Manito/Manito/Screens/Detail-Ing/DetailingViewController.swift +++ b/Manito/Manito/Screens/Detail-Ing/DetailingViewController.swift @@ -9,7 +9,7 @@ import UIKit import SnapKit -final class DetailingViewController: BaseViewController { +final class DetailingViewController: UIViewController, Navigationable { // MARK: - property @@ -25,7 +25,7 @@ final class DetailingViewController: BaseViewController { init(roomId: String) { self.roomId = roomId - super.init() + super.init(nibName: nil, bundle: nil) } @available(*, unavailable) @@ -47,6 +47,7 @@ final class DetailingViewController: BaseViewController { super.viewDidLoad() self.configureDelegation() self.configureNavigationController() + self.setupNavigation() } override func viewWillAppear(_ animated: Bool) { @@ -72,17 +73,17 @@ final class DetailingViewController: BaseViewController { } private func openManittee(manitteeName: String) { - let viewController = SelectManitteeViewController(roomId: self.roomId, manitteeNickname: manitteeName) + let viewModel = SelectManitteeViewModel(roomId: self.roomId, manitteeNickname: manitteeName) + let viewController = SelectManitteeViewController(viewModel: viewModel) viewController.modalTransitionStyle = .crossDissolve viewController.modalPresentationStyle = .fullScreen self.present(viewController, animated: true) } private func pushFriendListViewController() { - let storyboard = UIStoryboard(name: "DetailIng", bundle: nil) - guard let viewController = storyboard.instantiateViewController(withIdentifier: FriendListViewController.className) as? FriendListViewController else { return } - guard let roomId = Int(roomId) else { return } - viewController.roomIndex = roomId + let usecase = FriendListUsecaseImpl(repository: DetailRoomRepositoryImpl()) + let viewModel = FriendListViewModel(usecase: usecase, roomId: self.roomId) + let viewController = FriendListViewController(viewModel: viewModel) self.navigationController?.pushViewController(viewController, animated: true) } @@ -95,9 +96,9 @@ final class DetailingViewController: BaseViewController { } private func resetMission() { - self.makeRequestAlert(title: TextLiteral.detailIngViewControllerResetMissionAlertTitle, - message: TextLiteral.detailIngViewControllerResetMissionAlertMessage, - okTitle: TextLiteral.detailIngViewControllerResetMissionAlertOkTitle, + self.makeRequestAlert(title: TextLiteral.DetailIng.resetAlertTitle.localized(), + message: TextLiteral.DetailIng.resetAlertMessage.localized(), + okTitle: TextLiteral.DetailIng.resetAlertOk.localized(), okStyle: .default, okAction: { [weak self] _ in guard let roomId = self?.roomId else { return } @@ -106,8 +107,8 @@ final class DetailingViewController: BaseViewController { case .success(): self?.requestRoomInformation() case .failure: - self?.makeAlert(title: TextLiteral.detailIngViewControllerResetMissionErrorAlertOkTitle, - message: TextLiteral.detailIngViewControllerResetMissionErrorAlertOkMessage) + self?.makeAlert(title: TextLiteral.Common.Error.title.localized(), + message: TextLiteral.DetailIng.Error.resetMessage.localized()) } } }) @@ -154,7 +155,7 @@ final class DetailingViewController: BaseViewController { roomId: self.roomId, mission: mission, missionId: missionId.description, - roomState: self.detailingView.roomType.rawValue, + roomStatus: self.detailingView.roomType, messageType: .received) let letterViewController = LetterViewController(viewModel: viewModel) self.navigationController?.pushViewController(letterViewController, animated: true) @@ -176,7 +177,8 @@ final class DetailingViewController: BaseViewController { print("encoding Error") } catch NetworkError.clientError(let message) { print("client Error: \(String(describing: message))") - makeAlert(title: TextLiteral.detailIngViewControllerDoneExitAlertAdmin) + // FIXME: - Exit, Delete가 각각 누구에게 쓰이는지가 불분명하고 에러 처리가 잘 되어 있지 않아서 작성하지 않았습니다. + // 담당하시는 분께 맡기겠습니다.🙇‍♂️ } } } @@ -195,6 +197,8 @@ final class DetailingViewController: BaseViewController { print("encoding Error") } catch NetworkError.clientError(let message) { print("client Error: \(String(describing: message))") + // FIXME: - Exit, Delete가 각각 누구에게 쓰이는지가 불분명하고 에러 처리가 잘 되어 있지 않아서 작성하지 않았습니다. + // 담당하시는 분께 맡기겠습니다.🙇‍♂️ } } } @@ -224,11 +228,11 @@ extension DetailingViewController: DetailingDelegate { self?.resetMission() } - self.makeActionSheet(title: TextLiteral.detailIngViewControllerMissionEditTitle, + self.makeActionSheet(title: TextLiteral.DetailIng.missionMenuTitle.localized(), actionTitles: [ - TextLiteral.detailIngViewControllerSelfEditMissionTitle, - TextLiteral.detailIngViewControllerResetMissionTitle, - TextLiteral.cancel], + TextLiteral.DetailIng.missionMenuSetting.localized(), + TextLiteral.DetailIng.missionMenuReset.localized(), + TextLiteral.Common.cancel.localized()], actionStyle: [.default, .default, .cancel], actions: [editMissionAction, resetAction, nil]) } @@ -246,19 +250,25 @@ extension DetailingViewController: DetailingDelegate { roomId: self.roomId, mission: mission, missionId: missionId.description, - roomState: self.detailingView.roomType.rawValue, + roomStatus: self.detailingView.roomType, messageType: .sent) let letterViewController = LetterViewController(viewModel: viewModel) self.navigationController?.pushViewController(letterViewController, animated: true) } func manittoMemoryButtonDidTap() { - let viewController = MemoryViewController(roomId: self.roomId) + let usecase = MemoryUsecaseImpl(repository: DetailRoomRepositoryImpl()) + let viewModel = MemoryViewModel(usecase: usecase, roomId: self.roomId) + let viewController = MemoryViewController(viewModel: viewModel) self.navigationController?.pushViewController(viewController, animated: true) } func manittoOpenButtonDidTap(nickname: String) { - let viewController = OpenManittoViewController(roomId: self.roomId, manittoNickname: nickname) + let usecase = OpenManittoUsecaseImpl(repository: DetailRoomRepositoryImpl()) + let viewModel = OpenManittoViewModel(usecase: usecase, + roomId: self.roomId, + manittoNickname: nickname) + let viewController = OpenManittoViewController(viewModel: viewModel) viewController.modalTransitionStyle = .crossDissolve viewController.modalPresentationStyle = .fullScreen self.present(viewController, animated: true) diff --git a/Manito/Manito/Screens/Detail-Ing/FriendListViewController.swift b/Manito/Manito/Screens/Detail-Ing/FriendListViewController.swift deleted file mode 100644 index 32ac2bda8..000000000 --- a/Manito/Manito/Screens/Detail-Ing/FriendListViewController.swift +++ /dev/null @@ -1,109 +0,0 @@ -// -// FriendListViewController.swift -// Manito -// -// Created by 최성희 on 2022/06/13. -// - -import UIKit - -final class FriendListViewController: BaseViewController, BaseViewControllerType { - var friendArray: [MemberInfoDTO] = [] { - didSet { - friendListCollectionView.reloadData() - } - } - var detailRoomRepository: DetailRoomRepository = DetailRoomRepositoryImpl() - - var roomIndex: Int = 0 - - @IBOutlet weak var friendListCollectionView: UICollectionView! - - // MARK: - init - - deinit { - print("\(#file) is dead") - } - - // MARK: - life cycle - - override func viewDidAppear(_ animated: Bool) { - requestWithFriends() - } - - override func viewDidLoad() { - super.viewDidLoad() - self.baseViewDidLoad() - setupDelegation() - } - - // MARK: - base func - - func setupLayout() { - // FIXME: - 스토리보드를 코드 베이스로 바꿔야 하는 화면입니다. - } - - func configureUI() { - self.view.backgroundColor = .backgroundGrey - self.friendListCollectionView.backgroundColor = .clear - } - - // MARK: - func - - private func setupDelegation() { - friendListCollectionView.delegate = self - friendListCollectionView.dataSource = self - } - - // MARK: - API - - private func requestWithFriends() { - Task { - do { - let data = try await self.detailRoomRepository.fetchWithFriend(roomId: "\(roomIndex)") - friendArray = data.members ?? [] - } catch NetworkError.serverError { - print("server Error") - } catch NetworkError.encodingError { - print("encoding Error") - } catch NetworkError.clientError(let message) { - print("client Error: \(String(describing: message))") - } - } - } -} - -extension FriendListViewController: UICollectionViewDataSource { - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return friendArray.count - } - - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: FriendCollectionViewCell.className, for: indexPath) as? FriendCollectionViewCell else { return UICollectionViewCell() } - cell.setupFont() - cell.setupViewLayer() - cell.makeBorderLayer(color: .white) - cell.setFriendName(name: friendArray[indexPath.item].nickname ?? "닉네임") - cell.setFriendImage(index: friendArray[indexPath.item].colorIndex ?? 0) - return cell - } -} - -extension FriendListViewController: UICollectionViewDelegateFlowLayout { - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - let size = (UIScreen.main.bounds.size.width - (28 * 2 + 14)) / 2 - return CGSize(width: size, height: size) - } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { - return UIEdgeInsets(top: 18, left: 28, bottom: 18, right: 28) - } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { - return 14 - } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { - return 14 - } -} diff --git a/Manito/Manito/Screens/Detail-Ing/MemoryViewController.swift b/Manito/Manito/Screens/Detail-Ing/MemoryViewController.swift deleted file mode 100644 index 7c7603131..000000000 --- a/Manito/Manito/Screens/Detail-Ing/MemoryViewController.swift +++ /dev/null @@ -1,315 +0,0 @@ -// -// MemoryViewController.swift -// Manito -// -// Created by 최성희 on 2022/06/14. -// - -import UIKit - -import SnapKit - -final class MemoryViewController: BaseViewController, BaseViewControllerType { - - private enum MemoryType: Int { - case manittee = 0 - case manitto = 1 - - var announcingText: String { - switch self { - case .manittee: - return TextLiteral.memoryViewControllerManitteeText - case .manitto: - return TextLiteral.memoryViewControllerManittoText - } - } - } - - private enum Size { - static let lineSpacing: CGFloat = 10.0 - static let margin: CGFloat = 16.0 - static let cellWidth: CGFloat = (UIScreen.main.bounds.size.width - (margin * 2 + lineSpacing)) / 2 - static let cellHeight: CGFloat = cellWidth * 0.9 - static let collectionViewHeight: CGFloat = cellHeight * 2 + lineSpacing - } - - // MARK: - properties - - private let shareButton: UIButton = { - let button = UIButton() - button.setImage(ImageLiterals.icInsta, for: .normal) - return button - }() - private lazy var segmentControl: UISegmentedControl = { - let control = UISegmentedControl(items: [TextLiteral.letterHeaderViewSegmentControlManitti, TextLiteral.letterHeaderViewSegmentControlManitto]) - let font = UIFont.font(.regular, ofSize: 14) - let normalTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white, .font: font] - let selectedTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.black, .font: font] - - control.setTitleTextAttributes(normalTextAttributes, for: .normal) - control.setTitleTextAttributes(selectedTextAttributes, for: .selected) - control.selectedSegmentTintColor = .white - control.backgroundColor = .darkGrey004 - control.selectedSegmentIndex = 0 - control.addTarget(self, action: #selector(changedIndexValue(_:)), for: .valueChanged) - - return control - }() - private let shareBoundView = UIView() - private let announcementLabel: UILabel = { - let label = UILabel() - label.font = .font(.regular, ofSize: 15) - return label - }() - private let nicknameLabel: UILabel = { - let label = UILabel() - label.font = .font(.regular, ofSize: 30) - return label - }() - private lazy var memoryCollectionView: UICollectionView = { - let flowLayout = UICollectionViewFlowLayout() - flowLayout.itemSize = CGSize(width: Size.cellWidth, height: Size.cellHeight) - flowLayout.minimumLineSpacing = Size.lineSpacing - flowLayout.minimumInteritemSpacing = Size.lineSpacing - flowLayout.sectionInset = UIEdgeInsets(top: 0, left: Size.margin, bottom: 0, right: Size.margin) - let collectionView = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) - collectionView.backgroundColor = .clear - collectionView.dataSource = self - collectionView.register(MemoryCollectionViewCell.self, forCellWithReuseIdentifier: MemoryCollectionViewCell.className) - return collectionView - }() - private let manittoTopImageView = UIImageView(image: ImageLiterals.imgCharacters) - private let manittoBottomImageView = UIImageView(image: ImageLiterals.imgCharacters) - private let characterImageView = UIImageView() - private let characterBackView: UIView = { - let view = UIView() - view.makeBorderLayer(color: .white) - view.layer.cornerRadius = 49.5 - return view - }() - - private var detailRoomRepository: DetailRoomRepository = DetailRoomRepositoryImpl() - private var memoryType: MemoryType = .manittee { - willSet { - setupData(with: newValue) - self.memoryCollectionView.reloadData() - } - } - private var memory: MemoryDTO? - private var roomId: String - - // MARK: - init - - init(roomId: String) { - self.roomId = roomId - super.init() - requestMemory(roomId: roomId) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - print("\(#file) is dead") - } - - // MARK: - life cycle - - override func viewDidLoad() { - super.viewDidLoad() - self.baseViewDidLoad() - } - - // MARK: - override - - override func setupNavigationBar() { - super.setupNavigationBar() - let shareButton = makeBarButtonItem(with: shareButton) - - navigationItem.rightBarButtonItem = shareButton - navigationController?.navigationBar.prefersLargeTitles = false - navigationItem.largeTitleDisplayMode = .automatic - title = TextLiteral.memoryViewControllerTitleLabel - } - - // MARK: - base func - - func setupLayout() { - view.addSubview(segmentControl) - segmentControl.snp.makeConstraints { - $0.top.equalTo(view.safeAreaLayoutGuide).inset(30) - $0.centerX.equalToSuperview() - $0.height.equalTo(36) - $0.width.equalTo(294) - } - - view.addSubview(shareBoundView) - shareBoundView.snp.makeConstraints { - $0.top.equalTo(segmentControl.snp.bottom).offset(32) - $0.leading.trailing.equalToSuperview() - $0.bottom.equalTo(view.safeAreaLayoutGuide).inset(31) - } - - shareBoundView.addSubview(manittoTopImageView) - manittoTopImageView.snp.makeConstraints { - $0.top.equalToSuperview() - $0.leading.trailing.equalToSuperview().inset(15) - $0.height.equalTo(40) - } - - shareBoundView.addSubview(announcementLabel) - announcementLabel.snp.makeConstraints { - $0.top.equalTo(manittoTopImageView.snp.bottom).offset(30) - $0.centerX.equalToSuperview() - } - - shareBoundView.addSubview(memoryCollectionView) - memoryCollectionView.snp.makeConstraints { - $0.top.equalTo(announcementLabel.snp.bottom).offset(30) - $0.leading.trailing.equalToSuperview() - $0.height.equalTo(Size.collectionViewHeight) - } - - shareBoundView.addSubview(manittoBottomImageView) - manittoBottomImageView.snp.makeConstraints { - $0.bottom.equalToSuperview() - $0.leading.trailing.equalToSuperview().inset(15) - $0.height.equalTo(40) - } - - shareBoundView.addSubview(nicknameLabel) - nicknameLabel.snp.makeConstraints { - $0.bottom.equalTo(manittoBottomImageView.snp.top).offset(-30) - $0.centerX.equalToSuperview() - } - - shareBoundView.addSubview(characterBackView) - characterBackView.snp.makeConstraints { - $0.centerX.equalToSuperview() - $0.centerY.equalTo(memoryCollectionView.snp.centerY) - $0.width.height.equalTo(99) - } - - characterBackView.addSubview(characterImageView) - characterImageView.snp.makeConstraints { - $0.center.equalToSuperview() - $0.width.height.equalTo(90) - } - } - - func configureUI() { - self.view.backgroundColor = .backgroundGrey - self.setupAction() - } - - // MARK: - func - - private func setupAction() { - let action = UIAction { [weak self] _ in - guard let self = self else { return } - - if let storyShareURL = URL(string: "instagram-stories://share") { - if UIApplication.shared.canOpenURL(storyShareURL) { - let renderer = UIGraphicsImageRenderer(size: self.shareBoundView.bounds.size) - let renderImage = renderer.image { _ in - self.shareBoundView.drawHierarchy(in: self.shareBoundView.bounds, afterScreenUpdates: true) - } - guard let imageData = renderImage.pngData() else {return} - let pasteboardItems: [String: Any] = [ - "com.instagram.sharedSticker.stickerImage": imageData - ] - let pasteboardOptions = [UIPasteboard.OptionsKey.expirationDate: Date().addingTimeInterval(300)] - - UIPasteboard.general.setItems([pasteboardItems], options: pasteboardOptions) - UIApplication.shared.open(storyShareURL, options: [:], completionHandler: nil) - } else { - self.makeAlert(title: TextLiteral.memoryViewControllerAlertTitle, - message: TextLiteral.memoryViewControllerAlertMessage) - } - } - } - shareButton.addAction(action, for: .touchUpInside) - } - - // MARK: - network - - private func setupData(with state: MemoryType) { - announcementLabel.text = state.announcingText - switch state { - case .manittee: - guard let manitteeColorIndex = memory?.memoriesWithManittee?.member?.colorIndex else { return } - nicknameLabel.text = memory?.memoriesWithManittee?.member?.nickname - characterBackView.backgroundColor = Character.allCases[manitteeColorIndex].color - characterImageView.image = Character.allCases[manitteeColorIndex].image - case .manitto: - guard let manittoColorIndex = memory?.memoriesWithManitto?.member?.colorIndex else { return } - nicknameLabel.text = memory?.memoriesWithManitto?.member?.nickname - characterBackView.backgroundColor = Character.allCases[manittoColorIndex].color - characterImageView.image = Character.allCases[manittoColorIndex].image - } - } - - private func requestMemory(roomId: String) { - Task { - do { - let data = try await self.detailRoomRepository.fetchMemory(roomId: roomId) - self.memory = data - self.setupData(with: .manittee) - self.memoryCollectionView.reloadData() - } catch NetworkError.serverError { - print("server Error") - } catch NetworkError.encodingError { - print("encoding Error") - } catch NetworkError.clientError(let message) { - print("client Error: \(String(describing: message))") - } - } - } - - // MARK: - selector - - @objc - private func changedIndexValue(_ sender: UISegmentedControl) { - segmentControl.selectedSegmentIndex = sender.selectedSegmentIndex - memoryType = MemoryType(rawValue: sender.selectedSegmentIndex) ?? .manittee - } -} - -extension MemoryViewController: UICollectionViewDataSource { - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - switch memoryType { - case .manittee: - guard let count = memory?.memoriesWithManittee?.messages?.count else { return 0 } - return count - case .manitto: - guard let count = memory?.memoriesWithManitto?.messages?.count else { return 0 } - return count - } - } - - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let cell: MemoryCollectionViewCell = collectionView.dequeueReusableCell(forIndexPath: indexPath) - var imageUrl: String - - switch memoryType { - case .manittee: - imageUrl = memory?.memoriesWithManittee?.messages?[indexPath.item].imageUrl ?? "" - cell.setData(imageUrl: memory?.memoriesWithManittee?.messages?[indexPath.item].imageUrl, - content: memory?.memoriesWithManittee?.messages?[indexPath.item].content) - case .manitto: - imageUrl = memory?.memoriesWithManitto?.messages?[indexPath.item].imageUrl ?? "" - cell.setData(imageUrl: memory?.memoriesWithManitto?.messages?[indexPath.item].imageUrl, - content: memory?.memoriesWithManitto?.messages?[indexPath.item].content) - } - - cell.didTappedImage = { [weak self] image in - let viewController = LetterImageViewController(imageUrl: imageUrl) - viewController.modalPresentationStyle = .fullScreen - viewController.modalTransitionStyle = .crossDissolve - self?.present(viewController, animated: true) - } - - return cell - } -} diff --git a/Manito/Manito/Screens/Detail-Ing/MissionEditViewController.swift b/Manito/Manito/Screens/Detail-Ing/MissionEditViewController.swift index 5963e595c..da54e499e 100644 --- a/Manito/Manito/Screens/Detail-Ing/MissionEditViewController.swift +++ b/Manito/Manito/Screens/Detail-Ing/MissionEditViewController.swift @@ -13,7 +13,7 @@ protocol MissionEditDelegate: AnyObject { func didChangeMission() } -final class MissionEditViewController: BaseViewController, BaseViewControllerType { +final class MissionEditViewController: UIViewController, BaseViewControllerType, Navigationable { // MARK: - property @@ -65,7 +65,7 @@ final class MissionEditViewController: BaseViewController, BaseViewControllerTyp init(mission: String, roomId: String) { self.mission = mission self.roomId = roomId - super.init() + super.init(nibName: nil, bundle: nil) } @available(*, unavailable) @@ -79,7 +79,7 @@ final class MissionEditViewController: BaseViewController, BaseViewControllerTyp super.viewDidLoad() self.baseViewDidLoad() self.setupGesture() - self.setupNotificationCenter() + self.setupNavigation() } // MARK: - base func @@ -88,7 +88,7 @@ final class MissionEditViewController: BaseViewController, BaseViewControllerTyp self.view.addSubview(self.backgroundView) self.backgroundView.snp.makeConstraints { $0.leading.trailing.equalToSuperview() - $0.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom).inset(-5) + $0.bottom.equalTo(self.view.keyboardLayoutGuide.snp.top).inset(-5) $0.height.equalTo(120) } @@ -121,19 +121,14 @@ final class MissionEditViewController: BaseViewController, BaseViewControllerTyp self.view.addGestureRecognizer(tapGesture) } - private func setupNotificationCenter() { - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) - } - private func didChangedTextField(_ text: String) { guard !text.isEmpty else { self.dismiss(animated: true) return } - self.makeRequestAlert(title: TextLiteral.missionEditViewControllerChangeMissionAlertTitle, - message: TextLiteral.missionEditViewControllerChangeMissionAlertMessage, - okTitle: TextLiteral.change, + self.makeRequestAlert(title: TextLiteral.DetailIng.missionEditAlertTitle.localized(), + message: TextLiteral.DetailIng.missionEditAlertMessage.localized(), + okTitle: TextLiteral.Detail.change.localized(), okStyle: .default, okAction: { [weak self] _ in guard let missionText = self?.missionTextField.text else { return } @@ -145,8 +140,8 @@ final class MissionEditViewController: BaseViewController, BaseViewControllerTyp self?.dismiss(animated: true) } case .failure: - self?.makeAlert(title: TextLiteral.missionEditViewControllerChangeMissionErrorAlertTitle, - message: TextLiteral.missionEditViewControllerChangeMissionErrorAlertMessage) + self?.makeAlert(title: TextLiteral.Common.Error.title.localized(), + message: TextLiteral.DetailIng.Error.missionEditMessage.localized()) } } }) @@ -159,20 +154,6 @@ final class MissionEditViewController: BaseViewController, BaseViewControllerTyp self.dismiss(animated: true) } - @objc private func keyboardWillShow(notification:NSNotification) { - if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { - UIView.animate(withDuration: 0.2, animations: { - self.backgroundView.transform = CGAffineTransform(translationX: 0, y: -keyboardSize.height + 30) - }) - } - } - - @objc private func keyboardWillHide(notification:NSNotification) { - UIView.animate(withDuration: 0.2, animations: { - self.backgroundView.transform = .identity - }) - } - // MARK: - network private func patchEditMission(mission: String, completionHandler: @escaping ((Result) -> Void)) { diff --git a/Manito/Manito/Screens/Detail-Ing/View/DetailingView.swift b/Manito/Manito/Screens/Detail-Ing/View/DetailingView.swift index 62551f0bb..8b3813b83 100644 --- a/Manito/Manito/Screens/Detail-Ing/View/DetailingView.swift +++ b/Manito/Manito/Screens/Detail-Ing/View/DetailingView.swift @@ -63,7 +63,7 @@ final class DetailingView: UIView, BaseViewType { }() private let missionTitleLabel: UILabel = { let label = UILabel() - label.text = TextLiteral.individualMissionViewTitleLabel + label.text = TextLiteral.DetailIng.individualMissionTitle.localized() label.textColor = .grey002 label.font = .font(.regular, ofSize: 14) return label @@ -75,7 +75,7 @@ final class DetailingView: UIView, BaseViewType { self?.delegate?.editMissionButtonDidTap(mission: mission) } button.addAction(action, for: .touchUpInside) - button.setImage(ImageLiterals.icPencil, for: .normal) + button.setImage(UIImage.Icon.pencil, for: .normal) return button }() private let missionContentsLabel: UILabel = { @@ -92,7 +92,7 @@ final class DetailingView: UIView, BaseViewType { }() private let informationTitleLabel: UILabel = { let label = UILabel() - label.text = TextLiteral.detailIngViewControllerDetailInformatioin + label.text = TextLiteral.Detail.informationTitle.localized() label.textColor = .white label.font = .font(.regular, ofSize: 16) return label @@ -113,10 +113,10 @@ final class DetailingView: UIView, BaseViewType { view.layer.cornerRadius = 49.5 return view }() - private let manitteeIconView = UIImageView(image: ImageLiterals.icManiTti) + private let manitteeIconView = UIImageView(image: UIImage.Icon.manitti) private let manitteeLabel: UILabel = { let label = UILabel() - label.text = "\(UserDefaultStorage.nickname)의 마니띠" + label.text = TextLiteral.Detail.manitteeTitle.localized(with: UserDefaultStorage.nickname) label.textColor = .white label.font = .font(.regular, ofSize: 15) return label @@ -137,10 +137,10 @@ final class DetailingView: UIView, BaseViewType { view.layer.cornerRadius = 49.5 return view }() - private let listIconView = UIImageView(image: ImageLiterals.icList) + private let listIconView = UIImageView(image: UIImage.Icon.list) private let listLabel: UILabel = { let label = UILabel() - label.text = TextLiteral.togetherFriend + label.text = TextLiteral.Detail.togetherFriendTitle.localized() label.textColor = .white label.font = .font(.regular, ofSize: 15) return label @@ -156,7 +156,7 @@ final class DetailingView: UIView, BaseViewType { missionId: self?.missionId ?? "") } button.addAction(action, for: .touchUpInside) - button.setTitle(TextLiteral.letterViewControllerTitle, for: .normal) + button.setTitle(TextLiteral.Letter.title.localized(), for: .normal) button.setTitleColor(.white, for: .normal) button.titleLabel?.font = .font(.regular, ofSize: 15) button.backgroundColor = .darkGrey002 @@ -169,7 +169,7 @@ final class DetailingView: UIView, BaseViewType { self?.delegate?.manittoMemoryButtonDidTap() } button.addAction(action, for: .touchUpInside) - button.setTitle(TextLiteral.memoryViewControllerTitleLabel, for: .normal) + button.setTitle(TextLiteral.Memory.title.localized(), for: .normal) button.setTitleColor(.white, for: .normal) button.titleLabel?.font = .font(.regular, ofSize: 15) button.backgroundColor = .darkGrey002 @@ -184,7 +184,7 @@ final class DetailingView: UIView, BaseViewType { return label }() private let manitiRealIconView: UIImageView = { - let imageView = UIImageView(image: ImageLiterals.imgMa) + let imageView = UIImageView(image: UIImage.Image.ma) imageView.alpha = 0 return imageView }() @@ -205,7 +205,7 @@ final class DetailingView: UIView, BaseViewType { self?.delegate?.manittoOpenButtonDidTap(nickname: manittoNickname) } button.addAction(action, for: .touchUpInside) - button.title = TextLiteral.detailIngViewControllerManitoOpenButton + button.title = TextLiteral.DetailIng.buttonOpen.localized() return button }() private let badgeLabel: LetterCountBadgeView = { @@ -216,7 +216,7 @@ final class DetailingView: UIView, BaseViewType { }() private let exitButton: UIButton = { let button = UIButton() - button.setImage(ImageLiterals.icMore, for: .normal) + button.setImage(UIImage.Icon.more, for: .normal) button.showsMenuAsPrimaryAction = true return button }() @@ -240,7 +240,7 @@ final class DetailingView: UIView, BaseViewType { self.addSubview(self.titleLabel) self.titleLabel.snp.makeConstraints { $0.top.equalToSuperview().offset(100) - $0.leading.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) } self.addSubview(self.periodLabel) @@ -260,7 +260,7 @@ final class DetailingView: UIView, BaseViewType { self.addSubview(self.missionBackgroundView) self.missionBackgroundView.snp.makeConstraints { $0.top.equalTo(self.periodLabel.snp.bottom).offset(31) - $0.leading.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.height.equalTo(100) } @@ -280,13 +280,13 @@ final class DetailingView: UIView, BaseViewType { self.addSubview(self.informationTitleLabel) self.informationTitleLabel.snp.makeConstraints { $0.top.equalTo(self.missionBackgroundView.snp.bottom).offset(44) - $0.leading.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) } self.addSubview(self.manitteeBackView) self.manitteeBackView.snp.makeConstraints { $0.top.equalTo(self.informationTitleLabel.snp.bottom).offset(31) - $0.leading.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.trailing.equalTo(self.snp.centerX).offset(-14) $0.height.equalTo((UIScreen.main.bounds.width - 28 - 40) / 2) } @@ -321,7 +321,7 @@ final class DetailingView: UIView, BaseViewType { self.listBackView.snp.makeConstraints { $0.top.equalTo(self.informationTitleLabel.snp.bottom).offset(31) $0.leading.equalTo(self.snp.centerX).offset(14) - $0.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.height.equalTo((UIScreen.main.bounds.width - 28 - 40) / 2) } @@ -348,20 +348,20 @@ final class DetailingView: UIView, BaseViewType { self.addSubview(self.letterBoxButton) self.letterBoxButton.snp.makeConstraints { $0.top.equalTo(self.manitteeBackView.snp.bottom).offset(25) - $0.leading.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.height.equalTo(80) } self.addSubview(self.manittoMemoryButton) self.manittoMemoryButton.snp.makeConstraints { $0.top.equalTo(self.letterBoxButton.snp.bottom).offset(18) - $0.leading.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.height.equalTo(80) } self.addSubview(self.manittoOpenButtonShadowView) self.manittoOpenButtonShadowView.snp.makeConstraints { - $0.leading.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.bottom.equalToSuperview().inset(50) $0.height.equalTo(60) } @@ -423,7 +423,9 @@ final class DetailingView: UIView, BaseViewType { self.missionId = room.mission?.id.description ?? "" DispatchQueue.main.async { self.titleLabel.text = room.roomInformation.title - self.periodLabel.text = "\(room.roomInformation.startDate.subStringToDate()) ~ \(room.roomInformation.endDate.subStringToDate())" + // FIXME: - subStringToDate() 메서드를 지우면서 임시적으로 처리해뒀습니다. + // !!!: - roomListItem에서는 Date로 가지고 있다가 화면으로 나올 때는 String 처리가 되어서 나온다던지 하는 가공 코드 필요해보입니다. + self.periodLabel.text = "\(room.roomInformation.startDate.toDefaultDate?.toDefaultString ?? "") ~ \(room.roomInformation.endDate.toDefaultDate?.toDefaultString ?? "")" self.manitteeAnimationLabel.text = room.manittee.nickname self.setupBadge(count: room.messages?.count ?? 0) self.updateMissionEditButton(room.admin, status: self.roomType) @@ -454,14 +456,14 @@ final class DetailingView: UIView, BaseViewType { private func setupExitButton(admin: Bool) { if admin { let menu = UIMenu(options: [], children: [ - UIAction(title: TextLiteral.detailWaitViewControllerDeleteRoom, handler: { [weak self] _ in + UIAction(title: TextLiteral.Detail.menuDelete.localized(), handler: { [weak self] _ in self?.delegate?.deleteButtonDidTap() }) ]) self.exitButton.menu = menu } else { let menu = UIMenu(options: [], children: [ - UIAction(title: TextLiteral.detailWaitViewControllerLeaveRoom, handler: { [weak self] _ in + UIAction(title: TextLiteral.Detail.menuLeave.localized(), handler: { [weak self] _ in self?.delegate?.leaveButtonDidTap() }) ]) @@ -480,7 +482,7 @@ final class DetailingView: UIView, BaseViewType { private func setupProcessingUI() { self.missionBackgroundView.makeBorderLayer(color: .subOrange) - self.statusLabel.text = TextLiteral.doing + self.statusLabel.text = TextLiteral.Common.processing.localized() self.statusLabel.backgroundColor = .mainRed self.manittoMemoryButton.isHidden = true self.exitButton.isHidden = true @@ -488,16 +490,16 @@ final class DetailingView: UIView, BaseViewType { private func setupPostUI() { self.missionBackgroundView.makeBorderLayer(color: .darkGrey001) - self.statusLabel.text = TextLiteral.done + self.statusLabel.text = TextLiteral.Common.done.localized() self.statusLabel.backgroundColor = .grey002 self.manittoMemoryButton.isHidden = false self.exitButton.isHidden = false - self.missionContentsLabel.attributedText = NSAttributedString(string: TextLiteral.detailIngViewControllerDoneMissionText) + self.missionContentsLabel.attributedText = NSAttributedString(string: TextLiteral.DetailDone.mission.localized()) } private func setupManittoOpenButton(date: String) { - guard let endDate = date.stringToDateYYYY() else { return } - self.manittoOpenButtonShadowView.isHidden = !(endDate.isOpenManitto) + guard let endDate = date.toFullDate else { return } + self.manittoOpenButtonShadowView.isHidden = !(endDate.canOpenManitto) } private func toggledManitteeAnimation(_ value: Bool) { @@ -539,3 +541,17 @@ final class DetailingView: UIView, BaseViewType { } } } + +private extension Date { + var isPastOpeningTime: Bool { + let now = Date() + let nineHoursTimeInterval: TimeInterval = 32400 + let dateAddNineHours = self + nineHoursTimeInterval + let distance = dateAddNineHours.distance(to: now) + return distance > 0 && distance < 54000 + } + + var canOpenManitto: Bool { + return self.isToday && self.isPastOpeningTime + } +} diff --git a/Manito/Manito/Screens/Detail-Wait/DetailEditViewController.swift b/Manito/Manito/Screens/Detail-Wait/DetailEditViewController.swift index 62a47a58a..06e773a1b 100644 --- a/Manito/Manito/Screens/Detail-Wait/DetailEditViewController.swift +++ b/Manito/Manito/Screens/Detail-Wait/DetailEditViewController.swift @@ -5,11 +5,12 @@ // Created by Mingwan Choi on 2022/06/13. // +import Combine import UIKit import SnapKit -final class DetailEditViewController: BaseViewController { +final class DetailEditViewController: UIViewController { // MARK: - ui component @@ -18,17 +19,19 @@ final class DetailEditViewController: BaseViewController { // MARK: - property private let editMode: DetailEditView.EditMode - private let detailRoomRepository: DetailRoomRepository = DetailRoomRepositoryImpl() - private let room: RoomInfo + private let viewModel: any BaseViewModelType weak var detailWaitDelegate: DetailWaitViewControllerDelegate? + private var cancellable: Set = Set() // MARK: - init - init(editMode: DetailEditView.EditMode, room: RoomInfo) { - self.detailEditView = DetailEditView(editMode: editMode) + init(editMode: DetailEditView.EditMode, + room: RoomInfo, + viewModel: any BaseViewModelType) { + self.detailEditView = DetailEditView(editMode: editMode, roomInfo: room) self.editMode = editMode - self.room = room - super.init() + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) } @available(*, unavailable) @@ -50,10 +53,8 @@ final class DetailEditViewController: BaseViewController { super.viewDidLoad() self.setupPresentationController() self.configureDelegation() - self.setupCalendarDateRange() - if self.editMode == .information { - self.setupMemberSliderValue() - } + self.bindViewModel() + self.bindCancelButton() } // MARK: - func @@ -64,18 +65,23 @@ final class DetailEditViewController: BaseViewController { } private func configureDelegation() { - self.detailEditView.configureDelegation(self) self.detailEditView.configureCalendarDelegate(self) } - private func setupCalendarDateRange() { - let startDate = self.room.roomInformation.startDate - let endDate = self.room.roomInformation.endDate + private func setupRoomInfoView(roomInfo: RoomInfo) { + let startDate = roomInfo.roomInformation.startDate + let endDate = roomInfo.roomInformation.endDate + let capacity = roomInfo.roomInformation.capacity + + self.setupCalendarDateRange(startDate: startDate, endDate: endDate) + self.setupMemberSliderValue(capacity: capacity) + } + + private func setupCalendarDateRange(startDate: String, endDate: String) { self.detailEditView.setupDateRange(from: startDate, to: endDate) } - private func setupMemberSliderValue() { - let capacity = self.room.roomInformation.capacity + private func setupMemberSliderValue(capacity: Int) { self.detailEditView.setupSliderValue(capacity) } @@ -88,7 +94,8 @@ final class DetailEditViewController: BaseViewController { } private func showDiscardActionSheet() { - let actionTitles = [TextLiteral.destructive, TextLiteral.cancel] + let actionTitles = [TextLiteral.Common.discardChanges.localized(), + TextLiteral.Common.cancel.localized()] let actionStyle: [UIAlertAction.Style] = [.destructive, .cancel] let actions: [((UIAlertAction) -> Void)?] = [{ [weak self] _ in self?.dismiss(animated: true) @@ -98,63 +105,93 @@ final class DetailEditViewController: BaseViewController { actions: actions) } - // MARK: - network - - private func putRoomInfo(roomDTO: CreatedRoomInfoRequestDTO, completionHandler: @escaping ((Result) -> Void)) { - let roomIndex = self.room.roomInformation.id - Task { - do { - let status = try await self.detailRoomRepository.putRoomInfo(roomId: roomIndex.description, - roomInfo: roomDTO) - switch status { - case 200..<300: - completionHandler(.success(())) - default: - completionHandler(.failure((.unknownError))) + private func bindViewModel() { + let output = self.transformedOutput() + self.bindOutputToViewModel(output) + } + + private func transformedOutput() -> DetailEditViewModel.Output? { + guard let viewModel = self.viewModel as? DetailEditViewModel else { return nil } + let input = DetailEditViewModel.Input( + viewDidLoad: self.viewDidLoadPublisher, + changeButtonDidTap: self.detailEditView.changeButtonSubject.eraseToAnyPublisher()) + + return viewModel.transform(from: input) + } + + private func bindOutputToViewModel(_ output: DetailEditViewModel.Output?) { + guard let output else { return } + + output.roomInfo + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] roomInfo in + self?.setupRoomInfoView(roomInfo: roomInfo) + }) + .store(in: &self.cancellable) + + output.isPassStartDate + .receive(on: DispatchQueue.main) + .filter { !$0 } + .sink(receiveValue: { [weak self] _ in + self?.showAlertPassedStartDate() + }) + .store(in: &self.cancellable) + + output.isOverMember + .receive(on: DispatchQueue.main) + .filter { !$0 } + .sink(receiveValue: { [weak self] _ in + self?.showAlertOverMember() + }) + .store(in: &self.cancellable) + + output.changeSuccess + .receive(on: DispatchQueue.main) + .sink(receiveValue: { [weak self] result in + switch result { + case .success(let statusCode): + if statusCode == 204 { + self?.detailWaitDelegate?.didTappedChangeButton() + self?.dismiss(animated: true) + } + case .failure(let error): + self?.showErrorAlert(error.localizedDescription) } - } catch NetworkError.serverError { - completionHandler(.failure(.serverError)) - } catch NetworkError.clientError(let message) { - completionHandler(.failure(.clientError(message: message))) - } - } + }) + .store(in: &self.cancellable) + } + + private func bindCancelButton() { + self.detailEditView.cancelButtonPublisher + .sink(receiveValue: { [weak self] _ in + self?.dismiss(animated: true) + }) + .store(in: &self.cancellable) } } -extension DetailEditViewController: UIAdaptivePresentationControllerDelegate { - func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) { - self.presentationControllerDidAttemptToDismissAlert() + +// MARK: - Helper +extension DetailEditViewController { + private func showAlertPassedStartDate() { + self.makeAlert(title: TextLiteral.Common.Error.title.localized(), + message: TextLiteral.DetailEdit.Error.date.localized()) + } + + private func showAlertOverMember() { + self.makeAlert(title: TextLiteral.DetailEdit.Error.memberTitle.localized(), + message: TextLiteral.DetailEdit.Error.memberMessage.localized()) + } + + private func showErrorAlert(_ text: String) { + self.makeAlert(title: TextLiteral.Common.Error.title.localized(), + message: text) } } -extension DetailEditViewController: DetailEditDelegate { - func cancelButtonDidTap() { - self.dismiss(animated: true) - } - - func changeButtonDidTap(capacity: Int, from startDate: String, to endDate: String) { - let roomTitle = self.room.roomInformation.title - let currentUserCount = self.room.participants.count - let dto = CreatedRoomInfoRequestDTO(title: roomTitle, - capacity: capacity, - startDate: "20\(startDate)", - endDate: "20\(endDate)") - if currentUserCount <= capacity { - self.putRoomInfo(roomDTO: dto) { [weak self] result in - switch result { - case .success: - self?.detailWaitDelegate?.didTappedChangeButton() - self?.cancelButtonDidTap() - case .failure: - self?.makeAlert(title: TextLiteral.detailEditViewControllerChangeErrorTitle, - message: TextLiteral.detailEditViewControllerChangeErrorMessage - ) - } - } - } else { - self.makeAlert(title: TextLiteral.detailEditViewControllerChangeRoomInfoAlertTitle, - message: TextLiteral.detailEditViewControllerChangeRoomInfoAlertMessage) - } +extension DetailEditViewController: UIAdaptivePresentationControllerDelegate { + func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) { + self.presentationControllerDidAttemptToDismissAlert() } } diff --git a/Manito/Manito/Screens/Detail-Wait/DetailWaitViewController.swift b/Manito/Manito/Screens/Detail-Wait/DetailWaitViewController.swift index bfcda3317..03500b659 100644 --- a/Manito/Manito/Screens/Detail-Wait/DetailWaitViewController.swift +++ b/Manito/Manito/Screens/Detail-Wait/DetailWaitViewController.swift @@ -14,7 +14,7 @@ protocol DetailWaitViewControllerDelegate: AnyObject { func didTappedChangeButton() } -final class DetailWaitViewController: BaseViewController { +final class DetailWaitViewController: UIViewController, Navigationable { // MARK: - ui component @@ -22,17 +22,18 @@ final class DetailWaitViewController: BaseViewController { // MARK: - property + private let createRoomSubject = PassthroughSubject() private let deleteMenuButtonSubject = PassthroughSubject() private let leaveMenuButtonSubject = PassthroughSubject() private let changeButtonSubject = PassthroughSubject() - private var cancellable = Set() + private var cancellable: Set = Set() private let detailWaitViewModel: DetailWaitViewModel // MARK: - init init(viewModel: DetailWaitViewModel) { self.detailWaitViewModel = viewModel - super.init() + super.init(nibName: nil, bundle: nil) } @available(*, unavailable) @@ -52,7 +53,7 @@ final class DetailWaitViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() - self.setupNotificationCenter() + self.setupNavigation() self.configureNavigationController() self.bindViewModel() self.setupBind() @@ -60,10 +61,6 @@ final class DetailWaitViewController: BaseViewController { // MARK: - func - private func setupNotificationCenter() { - NotificationCenter.default.addObserver(self, selector: #selector(self.didTapEnterButton), name: .createRoomInvitedCode, object: nil) - } - private func configureNavigationController() { guard let navigationController = self.navigationController else { return } self.detailWaitView.configureNavigationItem(navigationController) @@ -82,7 +79,9 @@ final class DetailWaitViewController: BaseViewController { editMenuButtonDidTap: self.detailWaitView.editMenuButtonSubject.eraseToAnyPublisher(), deleteMenuButtonDidTap: self.deleteMenuButtonSubject.eraseToAnyPublisher(), leaveMenuButtonDidTap: self.leaveMenuButtonSubject.eraseToAnyPublisher(), - changeButtonDidTap: self.changeButtonSubject.eraseToAnyPublisher()) + changeButtonDidTap: self.changeButtonSubject.eraseToAnyPublisher(), + roomDidCreate: self.createRoomSubject.eraseToAnyPublisher() + ) return self.detailWaitViewModel.transform(input) } @@ -92,11 +91,22 @@ final class DetailWaitViewController: BaseViewController { .sink(receiveCompletion: { [weak self] result in switch result { case .finished: return - case .failure(_): - self?.makeAlert(title: "에러 발생") + case .failure(let error): + self?.showAlertError(message: error.localizedDescription, + okAction: { [weak self] _ in + self?.navigationController?.popViewController(animated: true) + }) + } + }, receiveValue: { [weak self] result in + switch result { + case .success(let roomInfo): + self?.detailWaitView.updateDetailWaitView(room: roomInfo) + case .failure(let error): + self?.showAlertError(message: error.localizedDescription, + okAction: { [weak self] _ in + self?.navigationController?.popViewController(animated: true) + }) } - }, receiveValue: { [weak self] room in - self?.detailWaitView.updateDetailWaitView(room: room) }) .store(in: &self.cancellable) @@ -107,16 +117,18 @@ final class DetailWaitViewController: BaseViewController { }) .store(in: &self.cancellable) - output.manitteeNickname + output.selectManitteeInfo .receive(on: DispatchQueue.main) .sink(receiveCompletion: { [weak self] result in switch result { case .finished: return - case .failure(_): - self?.makeAlert(title: "에러 발생") + case .failure(let error): + self?.showAlertError(message: error.localizedDescription) } - }, receiveValue: { [weak self] nickname in - self?.presentSelectManittoViewController(nickname: nickname) + }, receiveValue: { [weak self] data in + guard let nickname = data.userInfo?.nickname, + let roomId = data.roomId else { return } + self?.presentSelectManittoViewController(nickname: nickname, roomId: roomId) }) .store(in: &self.cancellable) @@ -133,8 +145,8 @@ final class DetailWaitViewController: BaseViewController { .sink(receiveCompletion: { [weak self] result in switch result { case .finished: return - case .failure(_): - self?.makeAlert(title: "오류 발생") + case .failure(let error): + self?.showAlertError(message: error.localizedDescription) } }, receiveValue: { [weak self] _ in self?.navigationController?.popViewController(animated: true) @@ -146,8 +158,8 @@ final class DetailWaitViewController: BaseViewController { .sink(receiveCompletion: { [weak self] result in switch result { case .finished: return - case .failure(_): - self?.makeAlert(title: "오류 발생") + case .failure(let error): + self?.showAlertError(message: error.localizedDescription) } }, receiveValue: { [weak self] _ in self?.navigationController?.popViewController(animated: true) @@ -156,10 +168,42 @@ final class DetailWaitViewController: BaseViewController { output.passedStartDate .receive(on: DispatchQueue.main) - .sink(receiveValue: { [weak self] (isPassedStartDate, isAdmin) in + .sink(receiveCompletion: { [weak self] result in + switch result { + case .finished: return + case .failure(_): + self?.makeAlert(title: TextLiteral.Common.Error.title.localized()) + } + }, receiveValue: { [weak self] (isPassedStartDate, isAdmin) in self?.showStartDatePassedAlert(isPassedStartDate: isPassedStartDate, isAdmin: isAdmin) }) .store(in: &self.cancellable) + + output.invitedCodeView + .receive(on: DispatchQueue.main) + .sink(receiveCompletion: { [weak self] result in + switch result { + case .finished: return + case .failure(_): + self?.makeAlert(title: TextLiteral.Common.Error.title.localized()) + } + }, receiveValue: { [weak self] roomInfo in + self?.showInvitedCodeView(roomInfo: roomInfo) + }) + .store(in: &self.cancellable) + + output.changeOutput + .receive(on: DispatchQueue.main) + .sink(receiveCompletion: { [weak self] result in + switch result { + case .finished: return + case .failure(_): + self?.makeAlert(title: TextLiteral.Common.Error.title.localized()) + } + }, receiveValue: { [weak self] roomInfo in + self?.detailWaitView.updateDetailWaitView(room: roomInfo) + }) + .store(in: &self.cancellable) } private func setupBind() { @@ -177,14 +221,20 @@ final class DetailWaitViewController: BaseViewController { } private func showDetailEditViewController(roomInformation: RoomInfo, mode: DetailEditView.EditMode) { - let viewController = DetailEditViewController(editMode: mode, room: roomInformation) + let repository = DetailRoomRepositoryImpl() + let usecase = DetailEditUsecaseImpl(roomInformation: roomInformation, + repository: repository) + let viewModel = DetailEditViewModel(usecase: usecase) + let viewController = DetailEditViewController(editMode: mode, + room: roomInformation, + viewModel: viewModel) viewController.detailWaitDelegate = self self.present(viewController, animated: true) } - private func presentSelectManittoViewController(nickname: String) { - let roomIndex = self.detailWaitViewModel.roomIndex.description - let viewController = SelectManitteeViewController(roomId: roomIndex, manitteeNickname: nickname) + private func presentSelectManittoViewController(nickname: String, roomId: String) { + let viewModel = SelectManitteeViewModel(roomId: roomId, manitteeNickname: nickname) + let viewController = SelectManitteeViewController(viewModel: viewModel) viewController.modalTransitionStyle = .crossDissolve viewController.modalPresentationStyle = .fullScreen self.present(viewController, animated: true) @@ -192,23 +242,23 @@ final class DetailWaitViewController: BaseViewController { private func showToastView(code: String) { ToastView.showToast(code: code, - message: TextLiteral.detailWaitViewControllerCopyCode, + message: TextLiteral.DetailWait.toastCopyMessage.localized(), controller: self) } private func deleteRoom() { - self.makeRequestAlert(title: TextLiteral.datailWaitViewControllerDeleteTitle, - message: TextLiteral.datailWaitViewControllerDeleteMessage, - okTitle: TextLiteral.delete, + self.makeRequestAlert(title: TextLiteral.DetailWait.deleteAlertTitle.localized(), + message: TextLiteral.DetailWait.deleteAlertMessage.localized(), + okTitle: TextLiteral.Detail.delete.localized(), okAction: { [weak self] _ in self?.deleteMenuButtonSubject.send(()) }) } private func leaveRoom() { - self.makeRequestAlert(title: TextLiteral.datailWaitViewControllerExitTitle, - message: TextLiteral.datailWaitViewControllerExitMessage, - okTitle: TextLiteral.leave, + self.makeRequestAlert(title: TextLiteral.DetailWait.exitAlertTitle.localized(), + message: TextLiteral.DetailWait.exitAlertMessage.localized(), + okTitle: TextLiteral.Detail.leave.localized(), okAction: { [weak self] _ in self?.leaveMenuButtonSubject.send(()) }) @@ -217,12 +267,10 @@ final class DetailWaitViewController: BaseViewController { private func showStartDatePassedAlert(isPassedStartDate: Bool, isAdmin: Bool) { guard isPassedStartDate else { return } self.makeAlert( - title: isAdmin - ? TextLiteral.detailWaitViewControllerPastAlertTitle - : TextLiteral.detailWaitViewControllerPastAlertTitle, + title: TextLiteral.DetailWait.pastAlertTitle.localized(), message: isAdmin - ? TextLiteral.detailWaitViewControllerPastAdminAlertMessage - : TextLiteral.detailWaitViewControllerPastAlertMessage, + ? TextLiteral.DetailWait.pastAdminAlertMessage.localized() + : TextLiteral.DetailWait.pastAlertMessage.localized(), okAction: isAdmin ? { [weak self] _ in guard let roomInformaion = self?.detailWaitViewModel.makeRoomInformation() else { return } @@ -232,35 +280,32 @@ final class DetailWaitViewController: BaseViewController { ) } - // MARK: - selector - - @objc - private func didTapEnterButton() { - let roomInfo = self.detailWaitViewModel.makeRoomInformation() - let title = roomInfo.roomInformation.title - let capacity = roomInfo.roomInformation.capacity - let startDate = roomInfo.roomInformation.startDate - let endDate = roomInfo.roomInformation.endDate - let invitationCode = roomInfo.invitation.code - let roomDTO = RoomListItemDTO(id: nil, - title: title, - state: nil, - participatingCount: nil, - capacity: capacity, - startDate: startDate, - endDate: endDate) - let viewController = InvitedCodeViewController(roomInfo: roomDTO, - code: invitationCode) + private func showInvitedCodeView(roomInfo: RoomInfo) { + let viewModel = InvitedCodeViewModel(roomInfo: roomInfo) + let viewController = InvitedCodeViewController(viewModel: viewModel) viewController.modalPresentationStyle = .overCurrentContext viewController.modalTransitionStyle = .crossDissolve self.present(viewController, animated: true) } + + func sendCreateRoomEvent() { + self.createRoomSubject.send(()) + } } extension DetailWaitViewController: DetailWaitViewControllerDelegate { func didTappedChangeButton() { self.changeButtonSubject.send() - ToastView.showToast(message: "방 정보 수정 완료", + ToastView.showToast(message: TextLiteral.DetailWait.toastEditMessage.localized(), controller: self) } } + +extension DetailWaitViewController { + private func showAlertError(message: String, okAction: ((UIAlertAction) -> ())? = nil) { + self.makeAlert(title: TextLiteral.Common.Error.title.localized(), + message: message, + okAction: okAction + ) + } +} diff --git a/Manito/Manito/Screens/Detail-Wait/Services/DetailWaitService.swift b/Manito/Manito/Screens/Detail-Wait/Services/DetailWaitService.swift deleted file mode 100644 index 337c84ef7..000000000 --- a/Manito/Manito/Screens/Detail-Wait/Services/DetailWaitService.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// DetailWaitService.swift -// Manito -// -// Created by Mingwan Choi on 2023/07/03. -// - -import Foundation - -protocol DetailWaitServicable { - func fetchWaitingRoomInfo(roomId: String) async throws -> RoomInfoDTO - func patchStartManitto(roomId: String) async throws -> UserInfoDTO - func deleteRoom(roomId: String) async throws -> Int - func deleteLeaveRoom(roomId: String) async throws -> Int -} - -final class DetailWaitService: DetailWaitServicable { - - private let repository: DetailRoomRepository - - init(repository: DetailRoomRepository) { - self.repository = repository - } - - func fetchWaitingRoomInfo(roomId: String) async throws -> RoomInfoDTO { - do { - let roomData = try await self.repository.fetchRoomInfo(roomId: roomId) - return roomData - } catch NetworkError.serverError { - throw NetworkError.serverError - } catch NetworkError.clientError(let message) { - throw NetworkError.clientError(message: message) - } - } - - func patchStartManitto(roomId: String) async throws -> UserInfoDTO { - do { - let manitteeData = try await self.repository.patchStartManitto(roomId: roomId) - return manitteeData - } catch NetworkError.serverError { - throw NetworkError.serverError - } catch NetworkError.clientError(let message) { - throw NetworkError.clientError(message: message) - } - } - - func deleteRoom(roomId: String) async throws -> Int { - do { - let statusCode = try await self.repository.deleteRoom(roomId: roomId) - return statusCode - } catch NetworkError.serverError { - throw NetworkError.serverError - } catch NetworkError.clientError(let message) { - throw NetworkError.clientError(message: message) - } - } - - func deleteLeaveRoom(roomId: String) async throws -> Int { - do { - let statusCode = try await self.repository.deleteLeaveRoom(roomId: roomId) - return statusCode - } catch NetworkError.serverError { - throw NetworkError.serverError - } catch NetworkError.clientError(let message) { - throw NetworkError.clientError(message: message) - } - } -} diff --git a/Manito/Manito/Screens/Detail-Wait/UIComponent/CalendarView.swift b/Manito/Manito/Screens/Detail-Wait/UIComponent/CalendarView.swift index 872d522c2..1d58e0843 100644 --- a/Manito/Manito/Screens/Detail-Wait/UIComponent/CalendarView.swift +++ b/Manito/Manito/Screens/Detail-Wait/UIComponent/CalendarView.swift @@ -35,12 +35,12 @@ final class CalendarView: UIView { private let previousButton: UIButton = { let button = UIButton() - button.setImage(ImageLiterals.icBack, for: .normal) + button.setImage(UIImage.Button.back, for: .normal) return button }() private let nextButton: UIButton = { let button = UIButton() - button.setImage(ImageLiterals.icRight, for: .normal) + button.setImage(UIImage.Icon.right, for: .normal) return button }() private var calendar: FSCalendar = { @@ -154,8 +154,10 @@ final class CalendarView: UIView { } func setupDateRange() { - guard let startDate = self.startDateText.stringToDate else { return } - guard let endDate = self.endDateText.stringToDate else { return } + self.startDateTapPublisher.send("20\(self.startDateText)") + self.endDateTapPublisher.send("20\(self.endDateText)") + guard let startDate = self.startDateText.toDefaultDate else { return } + guard let endDate = self.endDateText.toDefaultDate else { return } self.setupCalendarRange(startDate: startDate, endDate: endDate) } @@ -187,8 +189,8 @@ final class CalendarView: UIView { self.calendar.select(addDate) startDate += self.oneDayInterval } - self.tempStartDateText = self.calendar.selectedDates[startIndex].dateToString - self.tempEndDateText = self.calendar.selectedDates[endIndex].dateToString + self.tempStartDateText = self.calendar.selectedDates[startIndex].toDefaultString + self.tempEndDateText = self.calendar.selectedDates[endIndex].toDefaultString } func countDateRange() -> Int { @@ -225,15 +227,15 @@ extension CalendarView: FSCalendarDelegate { DispatchQueue.main.async { calendar.deselect(date) } - self.viewController?.makeAlert(title: TextLiteral.calendarViewAlertMaxTitle, - message: TextLiteral.maxMessage) + self.viewController?.makeAlert(title: TextLiteral.Common.Calendar.maxAlertTitle.localized(), + message: TextLiteral.Common.Calendar.maxDateContent.localized()) } else { - self.tempEndDateText = date.dateToString + self.tempEndDateText = date.toDefaultString self.setDateRange() calendar.reloadData() } } else if isReclickedStartDate { - self.tempStartDateText = date.dateToString + self.tempStartDateText = date.toDefaultString self.tempEndDateText = "" (calendar.selectedDates).forEach { calendar.deselect($0) @@ -262,8 +264,8 @@ extension CalendarView: FSCalendarDelegate { func calendar(_ calendar: FSCalendar, shouldSelect date: Date, at monthPosition: FSCalendarMonthPosition) -> Bool { if date < Date() - self.oneDayInterval { - self.viewController?.makeAlert(title: TextLiteral.calendarViewAlertPastTitle, - message: TextLiteral.calendarViewAlertPastMessage) + self.viewController?.makeAlert(title: TextLiteral.Common.Calendar.pastAlertTitle.localized(), + message: TextLiteral.Common.Calendar.pastAlertMessage.localized()) return false } else { return true diff --git a/Manito/Manito/Screens/Detail-Wait/UIComponent/DetailWaitTitleView.swift b/Manito/Manito/Screens/Detail-Wait/UIComponent/DetailWaitTitleView.swift index 8e774b79d..487407bf2 100644 --- a/Manito/Manito/Screens/Detail-Wait/UIComponent/DetailWaitTitleView.swift +++ b/Manito/Manito/Screens/Detail-Wait/UIComponent/DetailWaitTitleView.swift @@ -41,7 +41,7 @@ final class DetailWaitTitleView: UIView { private let durationLabel: UILabel = { let durationText = UILabel() - durationText.text = TextLiteral.during + durationText.text = TextLiteral.DetailWait.duringTitle.localized() durationText.textColor = .grey001 durationText.font = .font(.regular, ofSize: 14) return durationText diff --git a/Manito/Manito/Screens/Detail-Wait/View/DetailEditView.swift b/Manito/Manito/Screens/Detail-Wait/View/DetailEditView.swift index 86282cdae..8e735a1db 100644 --- a/Manito/Manito/Screens/Detail-Wait/View/DetailEditView.swift +++ b/Manito/Manito/Screens/Detail-Wait/View/DetailEditView.swift @@ -5,15 +5,11 @@ // Created by Mingwan Choi on 2023/04/20. // +import Combine import UIKit import SnapKit -protocol DetailEditDelegate: AnyObject { - func cancelButtonDidTap() - func changeButtonDidTap(capacity: Int, from startDate: String, to endDate: String) -} - final class DetailEditView: UIView, BaseViewType { enum EditMode { @@ -25,7 +21,7 @@ final class DetailEditView: UIView, BaseViewType { private let cancelButton: UIButton = { let button = UIButton(type: .system) - button.setTitle(TextLiteral.cancel, for: .normal) + button.setTitle(TextLiteral.Common.cancel.localized(), for: .normal) button.setTitleColor(.white, for: .normal) button.titleLabel?.font = .font(.regular, ofSize: 16) return button @@ -38,20 +34,20 @@ final class DetailEditView: UIView, BaseViewType { }() private let changeButton: UIButton = { let button = UIButton(type: .system) - button.setTitle(TextLiteral.change, for: .normal) + button.setTitle(TextLiteral.Detail.change.localized(), for: .normal) button.setTitleColor(.subBlue, for: .normal) button.titleLabel?.font = .font(.regular, ofSize: 16) return button }() private let titleLabel: UILabel = { let label = UILabel() - label.text = TextLiteral.modifiedRoomInfo + label.text = TextLiteral.Detail.menuModifiedRoomInfo.localized() label.font = .font(.regular, ofSize: 16) return label }() private let manittoPeriodTitleLabel: UILabel = { let label = UILabel() - label.text = TextLiteral.detailEditViewControllerStartSetting + label.text = TextLiteral.DetailEdit.periodSettingTitle.localized() label.font = .font(.regular, ofSize: 16) label.textColor = .white return label @@ -59,28 +55,28 @@ final class DetailEditView: UIView, BaseViewType { let calendarView: CalendarView = CalendarView() private let helpLabel: UILabel = { let label = UILabel() - label.text = TextLiteral.maxMessage + label.text = TextLiteral.Common.Calendar.maxDateContent.localized() label.textColor = .grey004 label.font = .font(.regular, ofSize: 14) return label }() private lazy var numberOfParticipantsTitleLabel: UILabel = { let label = UILabel() - label.text = TextLiteral.detailEditViewControllerSetMember + label.text = TextLiteral.DetailEdit.memberSettingTitle.localized() label.font = .font(.regular, ofSize: 18) label.textColor = .white return label }() private lazy var minimumNumberOfParticipantsLabel: UILabel = { let label = UILabel() - label.text = "\(Int(self.participantsSlider.minimumValue))인" + label.text = TextLiteral.Common.people.localized(with: Int(self.participantsSlider.minimumValue)) label.font = .font(.regular, ofSize: 16) label.textColor = .white return label }() private lazy var maxNumberOfParticipantsLabel: UILabel = { let label = UILabel() - label.text = "\(Int(self.participantsSlider.maximumValue))인" + label.text = TextLiteral.Common.people.localized(with: Int(self.participantsSlider.maximumValue)) label.font = .font(.regular, ofSize: 16) label.textColor = .white return label @@ -92,7 +88,8 @@ final class DetailEditView: UIView, BaseViewType { slider.maximumTrackTintColor = .darkGrey003 slider.minimumTrackTintColor = .red001 slider.isContinuous = true - slider.setThumbImage(ImageLiterals.imageSliderThumb, for: .normal) + slider.setThumbImage(UIImage.Image.sliderThumb, for: .normal) + slider.addTarget(self, action: #selector(self.didSlideSlider(_:)), for: .valueChanged) return slider }() private lazy var numberOfParticipantsLabel: UILabel = { @@ -104,23 +101,40 @@ final class DetailEditView: UIView, BaseViewType { // MARK: - property - private weak var delegate: DetailEditDelegate? private weak var calendarDelegate: CalendarDelegate? private let editMode: EditMode private var maximumMemberCount: Int? { willSet(count) { if let count { - self.numberOfParticipantsLabel.text = count.description + TextLiteral.per + self.numberOfParticipantsLabel.text = TextLiteral.Common.people.localized(with: count) } } } + private let roomTitle: String + private let capacity: Int + private var cancellable = Set() + + var cancelButtonPublisher: AnyPublisher { + return self.cancelButton.tapPublisher + } + + var changeButtonSubject: PassthroughSubject = PassthroughSubject() + + var changeButtonPublisher: AnyPublisher { + return self.changeButton.tapPublisher + } + + lazy var sliderPublisher: CurrentValueSubject = CurrentValueSubject(self.capacity) // MARK: - init - init(editMode: EditMode) { + init(editMode: EditMode, roomInfo: RoomInfo) { self.editMode = editMode + self.roomTitle = roomInfo.roomInformation.title + self.capacity = roomInfo.roomInformation.capacity super.init(frame: .zero) self.baseInit() + self.bindChangeButton() } @available(*, unavailable) @@ -162,13 +176,13 @@ final class DetailEditView: UIView, BaseViewType { self.addSubview(self.manittoPeriodTitleLabel) self.manittoPeriodTitleLabel.snp.makeConstraints { $0.top.equalTo(self.cancelButton.snp.bottom).offset(51) - $0.leading.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) } self.addSubview(self.calendarView) self.calendarView.snp.makeConstraints { $0.top.equalTo(self.manittoPeriodTitleLabel.snp.bottom).offset(30) - $0.leading.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.height.equalTo(400) } @@ -186,8 +200,6 @@ final class DetailEditView: UIView, BaseViewType { func configureUI() { self.backgroundColor = .backgroundGrey - self.setupCancleButton() - self.setupChangeButton() } // MARK: - func @@ -196,7 +208,7 @@ final class DetailEditView: UIView, BaseViewType { self.addSubview(self.numberOfParticipantsTitleLabel) self.numberOfParticipantsTitleLabel.snp.makeConstraints { $0.top.equalTo(self.calendarView.snp.bottom).offset(60) - $0.leading.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) } self.addSubview(self.minimumNumberOfParticipantsLabel) @@ -226,25 +238,6 @@ final class DetailEditView: UIView, BaseViewType { } } - private func setupCancleButton() { - let action = UIAction { [weak self] _ in - self?.delegate?.cancelButtonDidTap() - } - self.cancelButton.addAction(action, for: .touchUpInside) - } - - private func setupChangeButton() { - let action = UIAction { [weak self] _ in - guard let capacity = self?.participantsSlider.value, - let startDateString = self?.calendarView.getTempStartDate(), - let endDateString = self?.calendarView.getTempEndDate() else { return } - self?.delegate?.changeButtonDidTap(capacity: Int(capacity), - from: startDateString, - to: endDateString) - } - self.changeButton.addAction(action, for: .touchUpInside) - } - func setupChangeButton(_ value: Bool) { self.changeButton.isEnabled = value self.changeButton.setTitleColor(.subBlue, for: .normal) @@ -269,11 +262,11 @@ final class DetailEditView: UIView, BaseViewType { } func setupDateRange(from startDateString: String, to endDateString: String) { - guard let startDate = startDateString.stringToDate else { return } + guard let startDate = startDateString.toDefaultDate else { return } if startDate.isPast { let fiveDaysInterval: TimeInterval = 86400 * 4 - self.calendarView.startDateText = Date().dateToString - self.calendarView.endDateText = (Date() + fiveDaysInterval).dateToString + self.calendarView.startDateText = Date().toDefaultString + self.calendarView.endDateText = (Date() + fiveDaysInterval).toDefaultString } else { self.calendarView.startDateText = startDateString self.calendarView.endDateText = endDateString @@ -281,11 +274,23 @@ final class DetailEditView: UIView, BaseViewType { self.calendarView.setupDateRange() } - func configureDelegation(_ delegate: DetailEditDelegate) { - self.delegate = delegate - } - func configureCalendarDelegate(_ delegate: CalendarDelegate) { self.calendarView.configureCalendarDelegate(delegate) } + + private func bindChangeButton() { + self.changeButton.tapPublisher.sink(receiveValue: { [weak self] _ in + self?.changeButtonSubject.send(CreatedRoomInfoRequestDTO(title: self?.roomTitle ?? "", + capacity: self?.sliderPublisher.value ?? 0, + startDate: "20\(self?.calendarView.getTempStartDate() ?? "")", + endDate: "20\(self?.calendarView.getTempEndDate() ?? "")")) + }) + .store(in: &self.cancellable) + } + + @objc + private func didSlideSlider(_ slider: UISlider) { + let value = Int(slider.value) + self.sliderPublisher.send(value) + } } diff --git a/Manito/Manito/Screens/Detail-Wait/View/DetailWaitView.swift b/Manito/Manito/Screens/Detail-Wait/View/DetailWaitView.swift index c16faee73..802470847 100644 --- a/Manito/Manito/Screens/Detail-Wait/View/DetailWaitView.swift +++ b/Manito/Manito/Screens/Detail-Wait/View/DetailWaitView.swift @@ -23,9 +23,9 @@ final class DetailWaitView: UIView, BaseViewType { var status: String { switch self { case .waiting: - return TextLiteral.datailWaitViewControllerButtonWaitingText + return TextLiteral.DetailWait.buttonWaiting.localized() case .start: - return TextLiteral.datailWaitViewControllerButtonStartText + return TextLiteral.DetailWait.buttonStart.localized() } } } @@ -46,14 +46,14 @@ final class DetailWaitView: UIView, BaseViewType { private let titleView: DetailWaitTitleView = DetailWaitTitleView() private let togetherFriendLabel: UILabel = { let label = UILabel() - label.text = TextLiteral.togetherFriend + label.text = TextLiteral.Detail.togetherFriendTitle.localized() label.textColor = .white label.font = .font(.regular, ofSize: 16) return label }() private let characterImageView: UIImageView = { let imageView = UIImageView() - imageView.image = ImageLiterals.imgNi + imageView.image = UIImage.Image.ni return imageView }() private let userCountLabel: UILabel = { @@ -64,7 +64,7 @@ final class DetailWaitView: UIView, BaseViewType { }() private let copyButton: UIButton = { let button = UIButton(type: .system) - button.setTitle(TextLiteral.copyCode, for: .normal) + button.setTitle(TextLiteral.DetailWait.copyCode.localized(), for: .normal) button.setTitleColor(.subBlue, for: .normal) button.titleLabel?.font = .font(.regular, ofSize: 16) return button @@ -108,14 +108,14 @@ final class DetailWaitView: UIView, BaseViewType { func setupLayout() { self.addSubview(self.titleView) self.titleView.snp.makeConstraints { - $0.leading.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.top.equalToSuperview().offset(100) $0.height.equalTo(86) } self.addSubview(self.togetherFriendLabel) self.togetherFriendLabel.snp.makeConstraints { - $0.leading.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.top.equalTo(titleView.snp.bottom).offset(44) } @@ -134,21 +134,21 @@ final class DetailWaitView: UIView, BaseViewType { self.addSubview(self.copyButton) self.copyButton.snp.makeConstraints { - $0.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.centerY.equalTo(self.togetherFriendLabel.snp.centerY) } self.addSubview(self.listTableView) self.listTableView.snp.makeConstraints { $0.top.equalTo(self.togetherFriendLabel.snp.bottom).offset(30) - $0.leading.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.centerX.equalToSuperview() $0.height.equalTo(44) } self.addSubview(self.startButton) self.startButton.snp.makeConstraints { - $0.leading.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.bottom.equalToSuperview().inset(65) $0.height.equalTo(60) } @@ -165,7 +165,7 @@ final class DetailWaitView: UIView, BaseViewType { self.titleView.setupLabelData(title: title, dateRange: dateRange) } - private func setupRelatedViews(of userStatus: Bool, _ isStart: Bool) { + private func setupRelatedViews(of userStatus: Bool) { self.showStartButtonForAdmin(userStatus) self.setExitButtonMenu(userStatus) self.setupTitleViewGesture(userStatus) @@ -201,14 +201,14 @@ final class DetailWaitView: UIView, BaseViewType { var children: [UIAction] switch type { case .admin: - children = [UIAction(title: TextLiteral.modifiedRoomInfo, handler: { [weak self] _ in + children = [UIAction(title: TextLiteral.Detail.menuModifiedRoomInfo.localized(), handler: { [weak self] _ in self?.editMenuButtonSubject.send(()) - }),UIAction(title: TextLiteral.detailWaitViewControllerDeleteRoom, handler: { [weak self] _ in + }),UIAction(title: TextLiteral.Detail.menuDelete.localized(), handler: { [weak self] _ in self?.deleteMenuButtonSubject.send(()) }) ] case .member: - children = [UIAction(title: TextLiteral.detailWaitViewControllerLeaveRoom, handler: { [weak self] _ in + children = [UIAction(title: TextLiteral.Detail.menuLeave.localized(), handler: { [weak self] _ in self?.leaveMenuButtonSubject.send(()) })] } @@ -236,7 +236,7 @@ final class DetailWaitView: UIView, BaseViewType { self.setupTitleViewData(title: room.roomInformation.title, status: room.roomInformation.state, dateRange: room.roomInformation.dateRangeText) - self.setupRelatedViews(of: room.admin, room.roomInformation.isStart) + self.setupRelatedViews(of: room.admin) self.configureStartButton(room.canStart) self.configureUserCountLabel(userCount: room.userCount) diff --git a/Manito/Manito/Screens/Detail-Wait/ViewModel/DetailEditViewModel.swift b/Manito/Manito/Screens/Detail-Wait/ViewModel/DetailEditViewModel.swift new file mode 100644 index 000000000..ff34203cd --- /dev/null +++ b/Manito/Manito/Screens/Detail-Wait/ViewModel/DetailEditViewModel.swift @@ -0,0 +1,75 @@ +// +// DetailEditViewModel.swift +// Manito +// +// Created by Mingwan Choi on 10/31/23. +// + +import Combine +import Foundation + +final class DetailEditViewModel: BaseViewModelType { + struct Input { + let viewDidLoad: AnyPublisher + let changeButtonDidTap: AnyPublisher + } + struct Output { + let roomInfo: AnyPublisher + let isPassStartDate: AnyPublisher + let isOverMember: AnyPublisher + let changeSuccess: AnyPublisher, Never> + } + + private let usecase: DetailEditUsecase + private var cancellable: Set = Set() + + init(usecase: DetailEditUsecase) { + self.usecase = usecase + } + + // MARK: - func + + func transform(from input: Input) -> Output { + let roomInfoOutput = input.viewDidLoad + .compactMap { [weak self] _ in + self?.usecase.roomInformation + } + .eraseToAnyPublisher() + + let isPastPublisher = input.changeButtonDidTap + .compactMap { [weak self] dto in + self?.usecase.vaildStartDateIsNotPast(startDate: dto.startDate) + } + .eraseToAnyPublisher() + + let isMemberOverPublisher = input.changeButtonDidTap + .compactMap { [weak self] dto in + self?.usecase.vaildMemberCountIsUnder(capacity: dto.capacity) + } + .eraseToAnyPublisher() + + let changeRoomOutput = input.changeButtonDidTap + .filter { [weak self] dto in + guard let self else { return false } + return self.usecase.vaildMemberCountIsUnder(capacity: dto.capacity) } + .filter { [weak self] dto in + guard let self else { return false } + return self.usecase.vaildStartDateIsNotPast(startDate: dto.startDate) } + .asyncMap { [weak self] dto -> Result in + do { + let statusCode = try await self?.usecase.changeRoomInformation(roomDto: dto) + return .success(statusCode ?? 0) + } catch(let error) { + return .failure(error) + } + } + .compactMap { $0 } + .eraseToAnyPublisher() + + return Output( + roomInfo: roomInfoOutput, + isPassStartDate: isPastPublisher, + isOverMember: isMemberOverPublisher, + changeSuccess: changeRoomOutput) + } +} diff --git a/Manito/Manito/Screens/Detail-Wait/ViewModel/DetailWaitViewModel.swift b/Manito/Manito/Screens/Detail-Wait/ViewModel/DetailWaitViewModel.swift index 5b1ffcb97..727d0733d 100644 --- a/Manito/Manito/Screens/Detail-Wait/ViewModel/DetailWaitViewModel.swift +++ b/Manito/Manito/Screens/Detail-Wait/ViewModel/DetailWaitViewModel.swift @@ -13,18 +13,6 @@ final class DetailWaitViewModel { typealias EditRoomInformation = (roomInformation: RoomInfo, mode: DetailEditView.EditMode) typealias PassedStartDateAndIsOwner = (passStartDate: Bool, isOwner: Bool) - // MARK: - property - - let roomIndex: Int - private let detailWaitService: DetailWaitServicable - private var cancellable = Set() - - private let roomInformationSubject = CurrentValueSubject(RoomInfo.emptyRoom) - private let manitteeNicknameSubject = PassthroughSubject() - private let deleteRoomSubject = PassthroughSubject() - private let leaveRoomSubject = PassthroughSubject() - private let changeButtonSubject = PassthroughSubject() - struct Input { let viewDidLoad: AnyPublisher let codeCopyButtonDidTap: AnyPublisher @@ -33,6 +21,7 @@ final class DetailWaitViewModel { let deleteMenuButtonDidTap: AnyPublisher let leaveMenuButtonDidTap: AnyPublisher let changeButtonDidTap: AnyPublisher + let roomDidCreate: AnyPublisher init(viewDidLoad: AnyPublisher = Empty().eraseToAnyPublisher(), codeCopyButtonDidTap: AnyPublisher = Empty().eraseToAnyPublisher(), @@ -40,7 +29,8 @@ final class DetailWaitViewModel { editMenuButtonDidTap: AnyPublisher = Empty().eraseToAnyPublisher(), deleteMenuButtonDidTap: AnyPublisher = Empty().eraseToAnyPublisher(), leaveMenuButtonDidTap: AnyPublisher = Empty().eraseToAnyPublisher(), - changeButtonDidTap: AnyPublisher = Empty().eraseToAnyPublisher()) { + changeButtonDidTap: AnyPublisher = Empty().eraseToAnyPublisher(), + roomDidCreate: AnyPublisher = Empty().eraseToAnyPublisher()) { self.viewDidLoad = viewDidLoad self.codeCopyButtonDidTap = codeCopyButtonDidTap self.startButtonDidTap = startButtonDidTap @@ -48,177 +38,166 @@ final class DetailWaitViewModel { self.deleteMenuButtonDidTap = deleteMenuButtonDidTap self.leaveMenuButtonDidTap = leaveMenuButtonDidTap self.changeButtonDidTap = changeButtonDidTap + self.roomDidCreate = roomDidCreate } } struct Output { - let roomInformation: CurrentValueSubject + let roomInformation: AnyPublisher, Never> let code: AnyPublisher - let manitteeNickname: PassthroughSubject + let selectManitteeInfo: AnyPublisher<(userInfo: UserInfo?, roomId: String?), Error> let editRoomInformation: AnyPublisher - let deleteRoom: PassthroughSubject - let leaveRoom: PassthroughSubject + let deleteRoom: AnyPublisher + let leaveRoom: AnyPublisher let passedStartDate: AnyPublisher + let invitedCodeView: AnyPublisher + let changeOutput: AnyPublisher } + // MARK: - property + + let roomId: String + private var cancellable: Set = Set() + private let usecase: DetailWaitUseCase + + // MARK: - init + + init(roomId: String, usecase: DetailWaitUseCase) { + self.roomId = roomId + self.usecase = usecase + } + + // MARK: - func + func transform(_ input: Input) -> Output { - input.viewDidLoad - .sink(receiveValue: { [weak self] _ in - guard let roomId = self?.roomIndex.description else { return } - self?.requestWaitRoomInfo(roomId: roomId) - }) - .store(in: &self.cancellable) + let viewDidLoad = input.viewDidLoad + .asyncMap { [weak self] _ -> Result in + do { + let roomInfo = try await self?.fetchRoomInformation(roomId: self?.roomId ?? "") + return .success(roomInfo ?? .emptyRoom) + } catch(let error) { + return .failure(error) + } + } + .eraseToAnyPublisher() let codeOutput = input.codeCopyButtonDidTap .map { [weak self] _ -> String in guard let self else { return "" } - return self.makeCode(roomInformation: self.roomInformationSubject.value) + return self.makeCode(roomInformation: self.usecase.roomInformation) } .eraseToAnyPublisher() - input.startButtonDidTap - .sink(receiveValue: { [weak self] _ in - guard let roomId = self?.roomIndex.description else { return } - self?.requestStartManitto(roomId: roomId) - }) - .store(in: &self.cancellable) + let manitteeOutput = input.startButtonDidTap + .asyncMap { [weak self] _ in + return try await self?.patchStartManitto(roomId: self?.roomId ?? "") + } + .compactMap { [weak self] in (userInfo: $0, roomId: self?.roomId) } + .eraseToAnyPublisher() let editRoomInformationOutput = input.editMenuButtonDidTap .map { [weak self] _ -> EditRoomInformation in guard let self else { return (RoomInfo.emptyRoom, .information) } - return self.makeEditRoomInformation(roomInformation: self.roomInformationSubject.value) + return self.makeEditRoomInformation(roomInformation: self.usecase.roomInformation) } .eraseToAnyPublisher() - input.deleteMenuButtonDidTap - .sink(receiveValue: { [weak self] _ in - guard let roomId = self?.roomIndex.description else { return } - self?.requestDeleteRoom(roomId: roomId) - }) - .store(in: &self.cancellable) + let deleteOutput = input.deleteMenuButtonDidTap + .asyncMap { [weak self] _ in + return try await self?.deleteRoom(roomId: self?.roomId ?? "") + } + .compactMap { $0 } + .eraseToAnyPublisher() + + let leaveRoomOutput = input.leaveMenuButtonDidTap + .asyncMap { [weak self] _ in + return try await self?.deleteLeaveRoom(roomId: self?.roomId ?? "") + } + .compactMap { $0 } + .eraseToAnyPublisher() + + let passedStartDateOutput = viewDidLoad + .map { [weak self] result -> PassedStartDateAndIsOwner in + switch result { + case .success(let roomInfo): + guard let self else { return (false, false) } + return self.makeIsAdmin(roomInformation: roomInfo) + case .failure: + return (false, false) + } + } + .eraseToAnyPublisher() - input.leaveMenuButtonDidTap - .sink(receiveValue: { [weak self] _ in - guard let roomId = self?.roomIndex.description else { return } - self?.requestDeleteLeaveRoom(roomId: roomId) - }) - .store(in: &self.cancellable) + let zipPublisher = Publishers.Zip(viewDidLoad, input.roomDidCreate) + .compactMap { $0.0 } + .map { result -> RoomInfo in + switch result { + case .success(let roomInfo): + return roomInfo + case .failure: + return RoomInfo.emptyRoom + } + } + .eraseToAnyPublisher() - let passedStartDateOutput = input.viewDidLoad - .delay(for: 0.5, scheduler: DispatchQueue.main) - .map { [weak self] _ -> PassedStartDateAndIsOwner in - guard let self else { return (false, false) } - return self.makeIsAdmin(roomInformation: self.roomInformationSubject.value) + let changeButtonOutput = input.changeButtonDidTap + .asyncMap { [weak self] _ in + return try await self?.fetchRoomInformation(roomId: self?.roomId ?? "") } + .compactMap { $0 } .eraseToAnyPublisher() - input.changeButtonDidTap - .sink(receiveValue: { [weak self] _ in - guard let roomId = self?.roomIndex.description else { return } - self?.requestWaitRoomInfo(roomId: roomId) - }) - .store(in: &self.cancellable) - return Output( - roomInformation: self.roomInformationSubject, + roomInformation: viewDidLoad, code: codeOutput, - manitteeNickname: self.manitteeNicknameSubject, + selectManitteeInfo: manitteeOutput, editRoomInformation: editRoomInformationOutput, - deleteRoom: self.deleteRoomSubject, - leaveRoom: self.leaveRoomSubject, - passedStartDate: passedStartDateOutput + deleteRoom: deleteOutput, + leaveRoom: leaveRoomOutput, + passedStartDate: passedStartDateOutput, + invitedCodeView: zipPublisher, + changeOutput: changeButtonOutput ) } - // MARK: - init - - init(roomIndex: Int, detailWaitService: DetailWaitServicable) { - self.roomIndex = roomIndex - self.detailWaitService = detailWaitService - } - - // MARK: - func - func makeRoomInformation() -> RoomInfo { - return self.roomInformationSubject.value + return self.usecase.roomInformation } func makeCode(roomInformation: RoomInfo) -> String { return roomInformation.invitation.code } + private func manitteNickname() -> String { + return self.usecase.roomInformation.manittee.nickname + } + func makeEditRoomInformation(roomInformation: RoomInfo) -> EditRoomInformation { let editMode: DetailEditView.EditMode = .information return (roomInformation, editMode) } func makeIsAdmin(roomInformation: RoomInfo) -> PassedStartDateAndIsOwner { - return (roomInformation.roomInformation.isStartDatePast, roomInformation.admin) } - func setRoomInformation(room: RoomInfo) { - self.roomInformationSubject.send(room) + private func fetchRoomInformation(roomId: String) async throws -> RoomInfo { + let roomInformation = try await self.usecase.fetchRoomInformaion(roomId: roomId) + return roomInformation.toRoomInfo() } - // MARK: - network - - private func requestWaitRoomInfo(roomId: String) { - Task { - do { - let room = try await self.detailWaitService.fetchWaitingRoomInfo(roomId: roomId) - self.roomInformationSubject.send(room.toRoomInfo()) - } catch(let error) { - guard let error = error as? NetworkError else { return } - self.roomInformationSubject.send(completion: .failure(error)) - } - } + private func patchStartManitto(roomId: String) async throws -> UserInfo { + let userInfo = try await self.usecase.patchStartManitto(roomId: roomId) + return userInfo.toUserInfo() } - private func requestStartManitto(roomId: String) { - Task { - do { - let manittee = try await self.detailWaitService.patchStartManitto(roomId: roomId) - guard let nickname = manittee.nickname else { return } - self.manitteeNicknameSubject.send(nickname) - } catch(let error) { - guard let error = error as? NetworkError else { return } - self.manitteeNicknameSubject.send(completion: .failure(error)) - } - } + private func deleteRoom(roomId: String) async throws -> Int { + let statusCode = try await self.usecase.deleteRoom(roomId: roomId) + return statusCode } - private func requestDeleteRoom(roomId: String) { - Task { - do { - let statusCode = try await self.detailWaitService.deleteRoom(roomId: roomId) - switch statusCode { - case 200..<300: - self.deleteRoomSubject.send(()) - default : - self.deleteRoomSubject.send(completion: .failure(.unknownError)) - } - } catch(let error) { - guard let error = error as? NetworkError else { return } - self.deleteRoomSubject.send(completion: .failure(error)) - } - } - } - - private func requestDeleteLeaveRoom(roomId: String) { - Task { - do { - let statusCode = try await self.detailWaitService.deleteLeaveRoom(roomId: roomId) - switch statusCode { - case 200..<300: - self.leaveRoomSubject.send(()) - default: - self.leaveRoomSubject.send(completion: .failure(.unknownError)) - } - } catch(let error) { - guard let error = error as? NetworkError else { return } - self.leaveRoomSubject.send(completion: .failure(error)) - } - } + private func deleteLeaveRoom(roomId: String) async throws -> Int { + let statusCode = try await self.usecase.deleteLeaveRoom(roomId: roomId) + return statusCode } } diff --git a/Manito/Manito/Screens/Interaction/OpenManittoViewController.swift b/Manito/Manito/Screens/Interaction/OpenManittoViewController.swift deleted file mode 100644 index 152048b2e..000000000 --- a/Manito/Manito/Screens/Interaction/OpenManittoViewController.swift +++ /dev/null @@ -1,125 +0,0 @@ -// -// OpenManittoViewController.swift -// Manito -// -// Created by SHIN YOON AH on 2022/06/16. -// - -import UIKit - -import SnapKit - -final class OpenManittoViewController: BaseViewController { - - // MARK: - ui component - - private let openManittoView: OpenManittoView = OpenManittoView() - - // MARK: - property - - private let detailRoomRepository: DetailRoomRepository = DetailRoomRepositoryImpl() - private var friendsList: FriendListDTO = FriendListDTO(count: 0, members: []) - private let roomId: String - private let manittoNickname: String - - // MARK: - init - - init(roomId: String, manittoNickname: String) { - self.roomId = roomId - self.manittoNickname = manittoNickname - super.init() - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - life cycle - - override func loadView() { - self.view = self.openManittoView - } - - override func viewDidLoad() { - super.viewDidLoad() - self.configureDelegation() - self.fetchManittoData() - } - - // MARK: - func - - private func configureDelegation() { - self.openManittoView.configureDelegation(self) - } - - private func fetchManittoData() { - self.fetchFriendList(roomId: self.roomId, manittoNickname: self.manittoNickname) { [weak self] response in - guard let self = self else { return } - switch response { - case .success((let list, let manittoIndex)): - self.friendsList = list - DispatchQueue.main.async { - self.openManittoView.setupManittoAnimation(friendList: list, - manittoIndex: manittoIndex, - manittoNickname: self.manittoNickname) - } - case .failure: - DispatchQueue.main.async { - self.makeAlert(title: TextLiteral.openManittoViewControllerErrorTitle, - message: TextLiteral.openManittoViewControllerErrorDescription) - self.dismiss(animated: true) - } - } - } - } - - // MARK: - network - - private func fetchFriendList(roomId: String, - manittoNickname: String, - completionHandler: @escaping (Result<(FriendListDTO, Int), NetworkError>) -> Void) { - Task { - do { - let data = try await self.detailRoomRepository.fetchWithFriend(roomId: roomId) - let manittoIndex = data.members?.firstIndex(where: { $0.nickname == manittoNickname }).map { Int($0) } ?? 0 - completionHandler(.success((data, manittoIndex))) - } catch NetworkError.serverError { - completionHandler(.failure(.serverError)) - } catch NetworkError.encodingError { - completionHandler(.failure(.encodingError)) - } catch NetworkError.clientError(let message) { - completionHandler(.failure(.clientError(message: message))) - } - } - } -} - -// MARK: - OpenManittoViewDelegate -extension OpenManittoViewController: OpenManittoViewDelegate { - func confirmButtonTapped() { - self.dismiss(animated: true) - } -} - -// MARK: - UICollectionViewDataSource -extension OpenManittoViewController: UICollectionViewDataSource { - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - guard let count = self.friendsList.count else { return 0 } - return count - } - - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let cell: OpenManittoCollectionViewCell = collectionView.dequeueReusableCell(forIndexPath: indexPath) - - if let colorIndex = self.friendsList.members?[indexPath.item].colorIndex { - cell.configureCell(colorIndex: colorIndex) - } - - if indexPath.item == self.openManittoView.randomIndex { - cell.highlightCell() - } - - return cell - } -} diff --git a/Manito/Manito/Screens/Interaction/SelectManitteeViewController.swift b/Manito/Manito/Screens/Interaction/SelectManitteeViewController.swift deleted file mode 100644 index cf0e5ddc9..000000000 --- a/Manito/Manito/Screens/Interaction/SelectManitteeViewController.swift +++ /dev/null @@ -1,81 +0,0 @@ -// -// SelectManitteeViewController.swift -// Manito -// -// Created by SHIN YOON AH on 2022/06/19. -// - -import UIKit - -final class SelectManitteeViewController: BaseViewController { - - private enum SelectionStep: Int { - case showJoystick = 0, showCapsule, openName, openButton - } - - // MARK: - ui component - - private let selectManitteeView: SelectManitteeView = SelectManitteeView() - - // MARK: - property - - private let roomId: String - private var stepType: SelectionStep? { - willSet(step) { - guard let stepIndex = step?.rawValue else { return } - self.selectManitteeView.manageStepView(step: stepIndex) - } - } - - // MARK: - init - - init(roomId: String, manitteeNickname: String) { - self.roomId = roomId - super.init() - self.selectManitteeView.configureUI(manitteeNickname: manitteeNickname) - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - life cycle - - override func loadView() { - self.view = self.selectManitteeView - } - - override func viewDidLoad() { - super.viewDidLoad() - self.setupStepType() - self.configureDelegation() - } - - // MARK: - func - - private func setupStepType() { - self.stepType = .showJoystick - } - - private func configureDelegation() { - self.selectManitteeView.configureDelegation(self) - } -} - -// MARK: - SelectManitteeViewDelegate -extension SelectManitteeViewController: SelectManitteeViewDelegate { - func confirmButtonDidTap() { - guard let presentingViewController = self.presentingViewController as? UINavigationController else { return } - let detailingViewController = DetailingViewController(roomId: self.roomId) - presentingViewController.popViewController(animated: true) - presentingViewController.pushViewController(detailingViewController, animated: false) - self.dismiss(animated: true) - } - - func moveToNextStep() { - guard let stepIndex = self.stepType?.rawValue, - let nextStep = SelectionStep(rawValue: stepIndex + 1) else { return } - self.stepType = nextStep - } -} diff --git a/Manito/Manito/Screens/InvitedCode/InvitedCodeViewController.swift b/Manito/Manito/Screens/InvitedCode/InvitedCodeViewController.swift index 205fdd438..ca3015273 100644 --- a/Manito/Manito/Screens/InvitedCode/InvitedCodeViewController.swift +++ b/Manito/Manito/Screens/InvitedCode/InvitedCodeViewController.swift @@ -5,162 +5,88 @@ // Created by SHIN YOON AH on 2022/06/09. // +import Combine import UIKit import SnapKit -class InvitedCodeViewController: BaseViewController, BaseViewControllerType { +final class InvitedCodeViewController: UIViewController { + + // MARK: - ui components + + private let invitedCodeView: InvitedCodeView = InvitedCodeView() + + // MARK: - property + + private let viewModel: any BaseViewModelType + private var cancellable: Set = Set() - var roomInfo: RoomListItemDTO - var code: String + // MARK: - init - init(roomInfo: RoomListItemDTO, code: String){ - self.roomInfo = roomInfo - self.code = code - super.init() + init(viewModel: any BaseViewModelType){ + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - // MARK: - property - private let invitedImageView: UIImageView = { - let imageView = UIImageView(image: ImageLiterals.imgCodeBackground) - imageView.isUserInteractionEnabled = true - return imageView - }() - private lazy var closeButton: UIButton = { - let button = UIButton() - let action = UIAction { [weak self] _ in - self?.dismiss(animated: true) - } - button.addAction(action, for: .touchUpInside) - button.setImage(ImageLiterals.btnXmark, for: .normal) - return button - }() - private lazy var roomTitleLabel: UILabel = { - let label = UILabel() - label.font = .font(.regular, ofSize: 34) - label.text = roomInfo.title - return label - }() - private lazy var roomDateLabel: UILabel = { - let label = UILabel() - label.font = .font(.regular, ofSize: 18) - label.text = "\(roomInfo.startDate ?? "") ~ \(roomInfo.endDate ?? "")" - return label - }() - private let roomImage = UIImageView(image: ImageLiterals.imgCharacterBrown) - private lazy var roomPersonLabel: UILabel = { - let label = UILabel() - label.font = .font(.regular, ofSize: 24) - label.text = "X \(roomInfo.capacity ?? 0)인" - return label - }() - private lazy var roomPersonView: UIView = { - let view = UIView() - view.addSubview(roomImage) - roomImage.snp.makeConstraints { - $0.top.equalToSuperview() - $0.height.width.equalTo(60) - } - view.addSubview(roomPersonLabel) - roomPersonLabel.snp.makeConstraints { - $0.top.equalToSuperview() - $0.leading.equalTo(roomImage.snp.trailing) - $0.centerY.equalTo(roomImage.snp.centerY) - } - return view - }() - private lazy var roomInviteCodeButton: UIButton = { - let button = UIButton(type: .system) - let buttonAction = UIAction { [weak self] _ in - if let code = self?.code { - ToastView.showToast(code: code, message: TextLiteral.detailWaitViewControllerCopyCode, controller: self ?? UIViewController()) - } - } - button.setTitle(code, for: .normal) - button.setTitleColor(.blue, for: .normal) - button.setTitleColor(.blue.withAlphaComponent(0.8), for: .highlighted) - button.titleLabel?.font = .font(.regular, ofSize: 50) - button.addAction(buttonAction, for: .touchUpInside) - return button - }() - private let roomInviteInfoLabel: UILabel = { - let label = UILabel() - label.font = .font(.regular, ofSize: 18) - label.text = TextLiteral.invitedCodeViewCOntroller - label.textColor = .backgroundGrey - return label - }() - - // MARK: - init - deinit { print("\(#file) is dead") } // MARK: - life cycle + override func loadView() { + self.view = self.invitedCodeView + } + override func viewDidLoad() { super.viewDidLoad() - self.baseViewDidLoad() + self.bindViewModel() + self.bindUI() } - // MARK: - base func + // MARK: - func - func setupLayout() { - view.addSubview(invitedImageView) - invitedImageView.snp.makeConstraints { - $0.top.equalTo(view.safeAreaLayoutGuide).inset(142) - $0.leading.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) - $0.height.equalTo(463) - } - - view.addSubview(closeButton) - closeButton.snp.makeConstraints { - $0.top.equalTo(view.safeAreaLayoutGuide).inset(16) - $0.trailing.equalTo(view.safeAreaLayoutGuide).inset(Size.leadingTrailingPadding) - $0.height.width.equalTo(44) - } - - invitedImageView.addSubview(roomTitleLabel) - roomTitleLabel.snp.makeConstraints { - $0.top.equalTo(invitedImageView.snp.top).inset(125) - $0.centerX.equalToSuperview() - } - - invitedImageView.addSubview(roomDateLabel) - roomDateLabel.snp.makeConstraints { - $0.top.equalTo(roomTitleLabel.snp.bottom).offset(7) - $0.centerX.equalToSuperview() - } + private func bindViewModel() { + let output = self.transformedOutput() + self.bindOutputToViewModel(output) + } + + private func transformedOutput() -> InvitedCodeViewModel.Output? { + guard let viewModel = self.viewModel as? InvitedCodeViewModel else { return nil } + let input = InvitedCodeViewModel.Input(viewDidLoad: self.viewDidLoadPublisher, + copyButtonDidTap: self.invitedCodeView.codeButtonDidTapPublisher) - invitedImageView.addSubview(roomPersonView) - roomPersonView.snp.makeConstraints { - $0.top.equalTo(roomDateLabel.snp.bottom).offset(7) - $0.centerX.equalToSuperview() - $0.width.equalTo(120) - $0.height.equalTo(60) - } + return viewModel.transform(from: input) + } + + private func bindOutputToViewModel(_ output: InvitedCodeViewModel.Output?) { + guard let output else { return } - invitedImageView.addSubview(roomInviteCodeButton) - roomInviteCodeButton.snp.makeConstraints { - $0.top.equalTo(roomPersonView.snp.bottom).offset(80) - $0.centerX.equalToSuperview() - $0.width.equalTo(242) - $0.height.equalTo(65) - } + output.roomInfo + .sink { [weak self] roomInfo in + self?.invitedCodeView.updateRoomInfo(roomInfo: roomInfo.roomInformation) + self?.invitedCodeView.updateCodeButtonTitle(code: roomInfo.invitation.code) + } + .store(in: &self.cancellable) - invitedImageView.addSubview(roomInviteInfoLabel) - roomInviteInfoLabel.snp.makeConstraints { - $0.top.equalTo(roomInviteCodeButton.snp.bottom).offset(10) - $0.centerX.equalToSuperview() - } + output.copyButtonDidTap + .sink { [weak self] code in + ToastView.showToast(code: code, + message: TextLiteral.DetailWait.toastCopyMessage.lowercased(), + controller: self ?? UIViewController()) + } + .store(in: &self.cancellable) } - func configureUI() { - self.view.backgroundColor = .black.withAlphaComponent(0.8) + private func bindUI() { + self.invitedCodeView.closeButtonDidTapPublisher + .sink { [weak self] _ in + self?.dismiss(animated: true) + } + .store(in: &self.cancellable) } } diff --git a/Manito/Manito/Screens/InvitedCode/View/InvitedCodeView.swift b/Manito/Manito/Screens/InvitedCode/View/InvitedCodeView.swift new file mode 100644 index 000000000..1ceb36e22 --- /dev/null +++ b/Manito/Manito/Screens/InvitedCode/View/InvitedCodeView.swift @@ -0,0 +1,158 @@ +// +// InvitedCodeView.swift +// Manito +// +// Created by 이성호 on 11/4/23. +// + +import Combine +import UIKit + +import SnapKit + +final class InvitedCodeView: UIView, BaseViewType { + + // MARK: - ui components + + private let invitedImageView: UIImageView = { + let imageView = UIImageView(image: UIImage.Image.codeBackground) + imageView.isUserInteractionEnabled = true + return imageView + }() + private let closeButton: UIButton = { + let button = UIButton() + button.setImage(UIImage.Button.xmark, for: .normal) + return button + }() + private let roomTitleLabel: UILabel = { + let label = UILabel() + label.font = .font(.regular, ofSize: 34) + return label + }() + private let roomDateLabel: UILabel = { + let label = UILabel() + label.font = .font(.regular, ofSize: 18) + return label + }() + private let roomImageView = UIImageView(image: UIImage.Image.characterBrown) + private let roomPersonLabel: UILabel = { + let label = UILabel() + label.font = .font(.regular, ofSize: 24) + return label + }() + private let roomPersonView: UIView = UIView() + private let roomInviteCodeButton: UIButton = { + let button = UIButton(type: .system) + button.setTitleColor(.blue, for: .normal) + button.setTitleColor(.blue.withAlphaComponent(0.8), for: .highlighted) + button.titleLabel?.font = .font(.regular, ofSize: 50) + return button + }() + private let roomInviteInfoLabel: UILabel = { + let label = UILabel() + label.font = .font(.regular, ofSize: 18) + label.text = TextLiteral.CreateRoom.invitedCodeTitle.localized() + label.textColor = .backgroundGrey + return label + }() + + // MARK: - property + + var closeButtonDidTapPublisher: AnyPublisher { + return self.closeButton.tapPublisher + } + + var codeButtonDidTapPublisher: AnyPublisher { + return self.roomInviteCodeButton.tapPublisher + } + + // MARK: - init + + override init(frame: CGRect) { + super.init(frame: frame) + self.baseInit() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - public func + + func setupLayout() { + self.addSubview(self.invitedImageView) + self.invitedImageView.snp.makeConstraints { + $0.top.equalTo(self.safeAreaLayoutGuide).inset(142) + $0.leading.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) + $0.height.equalTo(463) + } + + self.addSubview(self.closeButton) + self.closeButton.snp.makeConstraints { + $0.top.equalTo(self.safeAreaLayoutGuide).inset(16) + $0.trailing.equalTo(self.safeAreaLayoutGuide).inset(SizeLiteral.leadingTrailingPadding) + $0.height.width.equalTo(44) + } + + self.invitedImageView.addSubview(self.roomTitleLabel) + self.roomTitleLabel.snp.makeConstraints { + $0.top.equalTo(self.invitedImageView.snp.top).inset(125) + $0.centerX.equalToSuperview() + } + + self.invitedImageView.addSubview(self.roomDateLabel) + self.roomDateLabel.snp.makeConstraints { + $0.top.equalTo(self.roomTitleLabel.snp.bottom).offset(7) + $0.centerX.equalToSuperview() + } + + self.invitedImageView.addSubview(self.roomPersonView) + self.roomPersonView.snp.makeConstraints { + $0.top.equalTo(self.roomDateLabel.snp.bottom).offset(7) + $0.centerX.equalToSuperview() + $0.width.equalTo(120) + $0.height.equalTo(60) + } + + self.invitedImageView.addSubview(self.roomInviteCodeButton) + self.roomInviteCodeButton.snp.makeConstraints { + $0.top.equalTo(self.roomPersonView.snp.bottom).offset(80) + $0.centerX.equalToSuperview() + $0.width.equalTo(242) + $0.height.equalTo(65) + } + + self.invitedImageView.addSubview(self.roomInviteInfoLabel) + self.roomInviteInfoLabel.snp.makeConstraints { + $0.top.equalTo(self.roomInviteCodeButton.snp.bottom).offset(10) + $0.centerX.equalToSuperview() + } + + self.roomPersonView.addSubview(self.roomImageView) + self.roomImageView.snp.makeConstraints { + $0.top.equalToSuperview() + $0.height.width.equalTo(60) + } + + self.addSubview(self.roomPersonLabel) + self.roomPersonLabel.snp.makeConstraints { + $0.top.equalToSuperview() + $0.leading.equalTo(roomImageView.snp.trailing) + $0.centerY.equalTo(roomImageView.snp.centerY) + } + } + + func configureUI() { + self.backgroundColor = .black.withAlphaComponent(0.8) + } + + func updateRoomInfo(roomInfo: RoomListItem) { + self.roomTitleLabel.text = roomInfo.title + self.roomDateLabel.text = "\(roomInfo.startDate) ~ \(roomInfo.endDate)" + self.roomPersonLabel.text = TextLiteral.Common.xPeople.localized(with: roomInfo.capacity) + } + + func updateCodeButtonTitle(code: String) { + self.roomInviteCodeButton.setTitle(code, for: .normal) + } +} diff --git a/Manito/Manito/Screens/InvitedCode/ViewModel/InvitedCodeViewModel.swift b/Manito/Manito/Screens/InvitedCode/ViewModel/InvitedCodeViewModel.swift new file mode 100644 index 000000000..002f4419a --- /dev/null +++ b/Manito/Manito/Screens/InvitedCode/ViewModel/InvitedCodeViewModel.swift @@ -0,0 +1,48 @@ +// +// InvitedCodeViewModel.swift +// Manito +// +// Created by 이성호 on 11/4/23. +// + +import Foundation + +import Combine + +final class InvitedCodeViewModel: BaseViewModelType { + + struct Input { + let viewDidLoad: AnyPublisher + let copyButtonDidTap: AnyPublisher + } + + struct Output { + let roomInfo: AnyPublisher + let copyButtonDidTap: AnyPublisher + } + + // MARK: - property + + let roomInfo: RoomInfo + + // MARK: - init + + init(roomInfo: RoomInfo) { + self.roomInfo = roomInfo + } + + // MARK: - func + + func transform(from input: Input) -> Output { + let roomInfo = input.viewDidLoad + .compactMap { [weak self] _ in self?.roomInfo } + .eraseToAnyPublisher() + + let code = input.copyButtonDidTap + .compactMap { [weak self] in self?.roomInfo.invitation.code } + .eraseToAnyPublisher() + + return Output(roomInfo: roomInfo, + copyButtonDidTap: code) + } +} diff --git a/Manito/Manito/Screens/Letter/Views/UIComponents/CreateLetterPhotoView.swift b/Manito/Manito/Screens/Letter/Views/UIComponents/CreateLetterPhotoView.swift deleted file mode 100644 index 41e856b57..000000000 --- a/Manito/Manito/Screens/Letter/Views/UIComponents/CreateLetterPhotoView.swift +++ /dev/null @@ -1,284 +0,0 @@ -// -// CreateLetterPhotoView.swift -// Manito -// -// Created by SHIN YOON AH on 2022/06/13. -// - -import AVFoundation -import PhotosUI -import UIKit - -import SnapKit - -final class CreateLetterPhotoView: UIView { - - typealias alertAction = ((UIAlertAction) -> ()) - - private enum PHLibraryError: Error { - case loadError - - var errorDescription: String { - switch self { - case .loadError: - return TextLiteral.letterPhotoViewFail - } - } - } - - private enum SourceType { - case camera - case library - } - - - // MARK: - ui component - - private let importPhotosButton: UIButton = { - let button = UIButton() - button.makeBorderLayer(color: .white) - button.clipsToBounds = true - button.setImage(ImageLiterals.btnCamera, for: .normal) - button.imageView?.contentMode = .scaleAspectFill - button.setPreferredSymbolConfiguration(.init(pointSize: 25), forImageIn: .normal) - button.tintColor = .white - button.backgroundColor = .darkGrey004 - return button - }() - private let titleLabel: UILabel = { - let label = UILabel() - label.text = TextLiteral.letterPhotoViewTitleLabel - label.font = .font(.regular, ofSize: 16) - return label - }() - - // MARK: - property - - var sendHasImageValue: ((_ hasImage: Bool) -> ())? - - private var hasImage: Bool { - return self.importPhotosButton.imageView?.image != ImageLiterals.btnCamera - } - var image: UIImage? { - if self.importPhotosButton.imageView?.image == ImageLiterals.btnCamera { return nil } - return self.importPhotosButton.imageView?.image - } - - // MARK: - init - - override init(frame: CGRect) { - super.init(frame: frame) - self.setupLayout() - self.setupButtonAction() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - func - - private func setupLayout() { - self.addSubview(self.titleLabel) - self.titleLabel.snp.makeConstraints { - $0.top.leading.equalToSuperview() - } - - self.addSubview(self.importPhotosButton) - self.importPhotosButton.snp.makeConstraints { - $0.top.equalTo(self.titleLabel.snp.bottom).offset(17) - $0.leading.trailing.bottom.equalToSuperview() - $0.height.equalTo(209) - } - } - - private func setupButtonAction() { - let photoAction = UIAction { [weak self] _ in - self?.presentActionSheet(actionTitles: self?.actionTitles() ?? [], - actionStyle: self?.actionStyle() ?? [], - actions: self?.alertActions() ?? []) - } - self.importPhotosButton.addAction(photoAction, for: .touchUpInside) - } - - private func actionTitles() -> [String] { - return self.hasImage ? [TextLiteral.letterPhotoViewTakePhoto, - TextLiteral.letterPhotoViewChoosePhoto, - TextLiteral.letterPhotoViewDeletePhoto, - TextLiteral.cancel] - : [TextLiteral.letterPhotoViewTakePhoto, - TextLiteral.letterPhotoViewChoosePhoto, - TextLiteral.cancel] - } - - private func actionStyle() -> [UIAlertAction.Style] { - return self.hasImage ? [.default, .default, .default, .cancel] : [.default, .default, .cancel] - } - - private func alertActions() -> [alertAction?] { - let takePhotoAction: alertAction = { [weak self] _ in - self?.openPickerAccordingTo(.camera) - } - let photoLibraryAction: alertAction = { [weak self] _ in - self?.openPickerAccordingTo(.library) - } - let removePhotoAction: alertAction = { [weak self] _ in - self?.importPhotosButton.setImage(ImageLiterals.btnCamera, for: .normal) - self?.sendHasImageValue?(self?.hasImage ?? false) - } - - return self.hasImage ? [takePhotoAction, photoLibraryAction, removePhotoAction, nil] - : [takePhotoAction, photoLibraryAction, nil] - } - - private func presentActionSheet(message: String = TextLiteral.letterPhotoViewChoosePhotoToManitto, - actionTitles: [String], - actionStyle: [UIAlertAction.Style], - actions: [alertAction?]) { - self.viewController?.makeActionSheet(message: message, - actionTitles: actionTitles, - actionStyle: actionStyle, - actions: actions) - } - - private func openPickerAccordingTo(_ sourceType: SourceType) { - switch sourceType { - case .library: - let authorizationStatus = PHPhotoLibrary.authorizationStatus(for: .addOnly) - self.checkPHPickerControllerAuthorizationStatus(authorizationStatus) - case .camera: - self.checkImagePickerControllerAccessRight() - } - } - - private func checkPHPickerControllerAuthorizationStatus(_ authorizationStatus: PHAuthorizationStatus) { - switch authorizationStatus { - case .notDetermined: - PHPhotoLibrary.requestAuthorization(for: .addOnly) { [weak self] authorizationStatus in - if authorizationStatus == .authorized { - self?.phPickerControllerDidShow() - } - } - case .authorized: - self.phPickerControllerDidShow() - default: - self.openSettings() - } - } - - private func phPickerControllerDidShow() { - var configuration = PHPickerConfiguration() - configuration.filter = .any(of: [.images, .livePhotos]) - - DispatchQueue.main.async { - let phPickerController = PHPickerViewController(configuration: configuration) - phPickerController.delegate = self - self.viewController?.present(phPickerController, animated: true) - } - } - - private func openSettings() { - let settingAction: alertAction = { [weak self] _ in - guard let settingURL = URL(string: UIApplication.openSettingsURLString) else { - self?.viewController?.makeAlert(title: TextLiteral.letterPhotoViewErrorTitle, - message: TextLiteral.letterPhotoViewSettingFail) - return - } - UIApplication.shared.open(settingURL) - } - - self.viewController?.makeRequestAlert(title: TextLiteral.letterPhotoViewSetting, - message: TextLiteral.letterPhotoViewSettingAuthorization, - okTitle: "설정", - okStyle: .default, - okAction: settingAction, - completion: nil) - } - - private func checkImagePickerControllerAccessRight() { - guard UIImagePickerController.isSourceTypeAvailable(.camera) else { - self.viewController?.makeAlert(title: TextLiteral.letterPhotoViewErrorTitle, - message: TextLiteral.letterPhotoViewDeviceFail) - return - } - - AVCaptureDevice.requestAccess(for: .video) { [weak self] hasGranted in - DispatchQueue.main.async { - hasGranted ? self?.imagePickerControllerDidShow() : self?.openSettings() - } - } - } - - private func imagePickerControllerDidShow() { - let imagePickerController = UIImagePickerController() - imagePickerController.delegate = self - imagePickerController.sourceType = .camera - - DispatchQueue.main.async { - self.viewController?.present(imagePickerController, animated: true) - } - } -} - -// MARK: - UIImagePickerControllerDelegate & UINavigationControllerDelegate -extension CreateLetterPhotoView: UIImagePickerControllerDelegate & UINavigationControllerDelegate { - func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { - if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { - DispatchQueue.main.async { - self.importPhotosButton.setImage(image, for: .normal) - self.sendHasImageValue?(self.importPhotosButton.imageView?.image != ImageLiterals.btnCamera) - picker.dismiss(animated: true) - } - } - } -} - -// MARK: - PHPickerViewControllerDelegate -extension CreateLetterPhotoView: PHPickerViewControllerDelegate { - func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { - if let selectedAsset = results.first?.itemProvider { - self.pickerController(picker, didFinishPicking: selectedAsset) - } else { - self.pickerControllerDidCancel(picker) - } - } - - private func pickerControllerDidCancel(_ picker: PHPickerViewController) { - DispatchQueue.main.async { - picker.dismiss(animated: true) - } - } - - private func pickerController(_ picker: PHPickerViewController, didFinishPicking asset: NSItemProvider) { - self.loadUIImage(for: asset) { [weak self] result in - switch result { - case .success(let image): - DispatchQueue.main.async { - self?.importPhotosButton.setImage(image, for: .normal) - self?.sendHasImageValue?(self?.importPhotosButton.imageView?.image != ImageLiterals.btnCamera) - picker.dismiss(animated: true) - } - case .failure(let error): - DispatchQueue.main.async { - picker.makeAlert(title: "", message: error.errorDescription, okAction: { _ in - picker.dismiss(animated: true) - }) - } - } - } - } - - private func loadUIImage(for itemProvider: NSItemProvider, completionHandler: @escaping ((Result) -> ())) { - guard itemProvider.canLoadObject(ofClass: UIImage.self) else { completionHandler(.failure(.loadError)); return } - - itemProvider.loadObject(ofClass: UIImage.self) { image, error in - if error != nil { - completionHandler(.failure(.loadError)) - } - - if let image = image as? UIImage { - completionHandler(.success(image)) - } - } - } -} diff --git a/Manito/Manito/Screens/Letter/Views/ViewControllers/CreateLetterViewController.swift b/Manito/Manito/Screens/Letter/Views/ViewControllers/CreateLetterViewController.swift deleted file mode 100644 index 0737f9f19..000000000 --- a/Manito/Manito/Screens/Letter/Views/ViewControllers/CreateLetterViewController.swift +++ /dev/null @@ -1,141 +0,0 @@ -// -// CreateLetterViewController.swift -// Manito -// -// Created by SHIN YOON AH on 2022/06/13. -// - -import UIKit - -protocol CreateLetterViewControllerDelegate: AnyObject { - func refreshLetterData() -} - -final class CreateLetterViewController: BaseViewController { - - typealias AlertAction = ((UIAlertAction) -> ()) - - // MARK: - ui component - - private let createLetterView: CreateLetterView = CreateLetterView() - - // MARK: - property - - private let letterRepository: LetterRepository = LetterRepositoryImpl() - private let mission: String - private let manitteeId: String - private let roomId: String - private let missionId: String - - private weak var delegate: CreateLetterViewControllerDelegate? - - // MARK: - init - - init(manitteeId: String, roomId: String, mission: String, missionId: String) { - self.manitteeId = manitteeId - self.roomId = roomId - self.mission = mission - self.missionId = missionId - super.init() - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - life cycle - - override func loadView() { - self.view = self.createLetterView - } - - override func viewDidLoad() { - super.viewDidLoad() - self.configureUI() - self.configureDelegation() - self.configureNavigationController() - } - - // MARK: - func - - private func configureUI() { - self.createLetterView.configureMission(self.mission) - self.createLetterView.configureViewController(self) - } - - private func configureDelegation() { - self.createLetterView.configureDelegation(self) - } - - private func configureNavigationController() { - guard let navigationController = self.navigationController else { return } - self.createLetterView.configureNavigationController(navigationController) - self.createLetterView.configureNavigationBar(navigationController) - self.createLetterView.configureNavigationItem(navigationController) - } - - func configureDelegation(_ delegate: CreateLetterViewControllerDelegate) { - self.delegate = delegate - } - - // MARK: - network - - private func dispatchLetter(with letterDTO: LetterRequestDTO, - _ jpegData: Data? = nil, - completionHandler: @escaping ((Result) -> Void)) { - Task { - do { - let statusCode = try await self.letterRepository.dispatchLetter(roomId: self.roomId, - image: jpegData, - letter: letterDTO, - missionId: self.missionId) - switch statusCode { - case 200..<300: completionHandler(.success(())) - default: completionHandler(.failure(.unknownError)) - } - } catch NetworkError.serverError { - completionHandler(.failure(.serverError)) - } catch NetworkError.clientError(let message) { - completionHandler(.failure(.clientError(message: message))) - } - } - } -} - -// MARK: - CreateLetterViewDelegate -extension CreateLetterViewController: CreateLetterViewDelegate { - func presentationControllerDidDismiss() { - self.dismiss(animated: true) - } - - func showActionSheet() { - let dismissAction: AlertAction = { [weak self] _ in - self?.resignFirstResponder() - self?.dismiss(animated: true) - } - self.makeActionSheet(actionTitles: [TextLiteral.destructive, TextLiteral.cancel], - actionStyle: [.destructive, .cancel], - actions: [dismissAction, nil]) - } - - func sendLetterToManittee(with content: String?, _ image: UIImage?) { - let jpegData = image?.jpegData(compressionQuality: 0.3) - let letterDTO = LetterRequestDTO(manitteeId: self.manitteeId, messageContent: content) - - self.createLetterView.sending = true - self.dispatchLetter(with: letterDTO, jpegData) { [weak self] response in - DispatchQueue.main.async { - switch response { - case .success: - self?.delegate?.refreshLetterData() - self?.dismiss(animated: true) - case .failure: - self?.createLetterView.sending = false - self?.makeAlert(title: TextLiteral.createLetterViewControllerErrorTitle, - message: TextLiteral.createLetterViewControllerErrorMessage) - } - } - } - } -} diff --git a/Manito/Manito/Screens/Letter/Views/ViewControllers/LetterImageViewController.swift b/Manito/Manito/Screens/Letter/Views/ViewControllers/LetterImageViewController.swift deleted file mode 100644 index 249e0983d..000000000 --- a/Manito/Manito/Screens/Letter/Views/ViewControllers/LetterImageViewController.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// LetterImageViewController.swift -// Manito -// -// Created by Mingwan Choi on 2022/09/18. -// - -import Photos -import UIKit - -final class LetterImageViewController: BaseViewController { - - // MARK: - ui component - - private lazy var letterImageView: LetterImageView = LetterImageView() - - // MARK: - property - - private let imageUrl: String - - // MARK: - init - - init(imageUrl: String) { - self.imageUrl = imageUrl - super.init() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - life cycle - - override func loadView() { - self.view = self.letterImageView - } - - override func viewDidLoad() { - super.viewDidLoad() - self.configureDelegation() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - self.configureImageView() - } - - // MARK: - func - - private func configureImageView() { - self.letterImageView.configureImageFrame() - self.letterImageView.configureImage(self.imageUrl) - } - - private func configureDelegation() { - self.letterImageView.configureDelegate(self) - } -} - -// MARK: - LetterImageViewDelegate -extension LetterImageViewController: LetterImageViewDelegate { - func closeButtonTapped() { - self.dismiss(animated: true) - } - - func downloadImageAsset(_ imageAsset: UIImage?) { - self.uploadImage(for: imageAsset) { [weak self] result in - switch result { - case .success(let description): - self?.makeAlert(title: description.title, - message: description.message) - case .failure(let error): - self?.makeAlert(title: TextLiteral.letterImageViewControllerErrorTitle, - message: error.errorDescription) - } - } - } - - private func uploadImage(for image: UIImage?, completionHandler: @escaping ((Result<(title: String, message: String), LetterImageError>) -> ())) { - guard let image else { completionHandler(.failure(.invalidImage)); return } - - PHPhotoLibrary.shared().performChanges({ - PHAssetChangeRequest.creationRequestForAsset(from: image) - }) { (success, error) in - DispatchQueue.main.async { - if success { - completionHandler(.success((title: TextLiteral.letterImageViewControllerSuccessTitle, - message: TextLiteral.letterImageViewControllerSuccessMessage))) - } else { - completionHandler(.failure(.invalidPhotoLibrary)) - } - } - } - } -} diff --git a/Manito/Manito/Screens/Letter/Views/ViewControllers/LetterViewController+MailCompose.swift b/Manito/Manito/Screens/Letter/Views/ViewControllers/LetterViewController+MailCompose.swift deleted file mode 100644 index b3a9b7ee6..000000000 --- a/Manito/Manito/Screens/Letter/Views/ViewControllers/LetterViewController+MailCompose.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// LetterViewController+MailCompose.swift -// Manito -// -// Created by SHIN YOON AH on 2022/08/31. -// - -import MessageUI - -// MARK: - MFMailComposeViewControllerDelegate -extension LetterViewController: MFMailComposeViewControllerDelegate { - func sendReportMail(userNickname: String, content: String) { - if MFMailComposeViewController.canSendMail() { - let composeVC = MFMailComposeViewController() - let aenittoEmail = "aenitto@gmail.com" - let messageBody = """ - - ----------------------------- - - - 신고자 닉네임: \(userNickname) - - 신고 메시지 내용: - \(content) - - 신고 날짜: \(Date()) - - ------------------------------ - - 신고 내용을 작성해주세요. - - 신고 사유: - """ - - composeVC.mailComposeDelegate = self - composeVC.setToRecipients([aenittoEmail]) - composeVC.setSubject("[신고 관련 문의]") - composeVC.setMessageBody(messageBody, isHTML: false) - - self.present(composeVC, animated: true) - } - else { - self.showSendMailErrorAlert() - } - } - - private func showSendMailErrorAlert() { - let sendMailErrorAlert = UIAlertController(title: "메일 전송 실패", message: "아이폰 이메일 설정을 확인하고 다시 시도해주세요.", preferredStyle: .alert) - let confirmAction = UIAlertAction(title: "확인", style: .default) - sendMailErrorAlert.addAction(confirmAction) - self.present(sendMailErrorAlert, animated: true) - } - - func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { - controller.dismiss(animated: true) - } -} diff --git a/Manito/Manito/Screens/Letter/Views/Views/CreateLetterView.swift b/Manito/Manito/Screens/Letter/Views/Views/CreateLetterView.swift deleted file mode 100644 index 9ed930f0e..000000000 --- a/Manito/Manito/Screens/Letter/Views/Views/CreateLetterView.swift +++ /dev/null @@ -1,220 +0,0 @@ -// -// CreateLetterView.swift -// Manito -// -// Created by SHIN YOON AH on 2023/02/28. -// - -import UIKit - -import SnapKit - -protocol CreateLetterViewDelegate: AnyObject { - func presentationControllerDidDismiss() - func showActionSheet() - func sendLetterToManittee(with content: String?, _ image: UIImage?) -} - -final class CreateLetterView: UIView, BaseViewType { - - // MARK: - ui component - - private let indicatorView: UIView = { - let view = UIView() - view.backgroundColor = .white.withAlphaComponent(0.8) - view.layer.cornerRadius = 2 - return view - }() - private let cancelButton: UIButton = { - let button = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: 44, height: 44))) - button.titleLabel?.font = .font(.regular, ofSize: 16) - button.setTitle(TextLiteral.cancel, for: .normal) - button.setTitleColor(.white, for: .normal) - button.setTitleColor(.white.withAlphaComponent(0.5), for: .highlighted) - return button - }() - private let sendButton: UIButton = { - let button = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: 50, height: 44))) - button.titleLabel?.font = .font(.regular, ofSize: 16) - button.setTitle(TextLiteral.createLetterViewControllerSendButton, for: .normal) - button.setTitleColor(.subBlue, for: .normal) - button.setTitleColor(.subBlue.withAlphaComponent(0.5), for: .highlighted) - button.setTitleColor(.subBlue.withAlphaComponent(0.5), for: .disabled) - return button - }() - private let scrollView: UIScrollView = { - let scrollView = UIScrollView() - scrollView.showsVerticalScrollIndicator = false - scrollView.showsHorizontalScrollIndicator = false - return scrollView - }() - private let scrollContentView: UIView = UIView() - private let missionView: IndividualMissionView = IndividualMissionView() - private let letterTextView: CreateLetterTextView = CreateLetterTextView() - private let letterPhotoView: CreateLetterPhotoView = CreateLetterPhotoView() - - // MARK: - property - - private weak var delegate: CreateLetterViewDelegate? - - private var sendButtonObserver: (hasText: Bool, hasImage: Bool) = (false, false) { - willSet { - self.sendButton.isEnabled = newValue.hasText || newValue.hasImage - } - } - - var sending: Bool = false { - willSet(isDisabled) { - self.sendButton.isEnabled = !isDisabled - } - } - - // MARK: - init - - override init(frame: CGRect) { - super.init(frame: frame) - self.baseInit() - self.setupButtonAction() - self.observeSendButtonEnabledState() - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - base func - - func setupLayout() { - self.addSubview(self.indicatorView) - self.indicatorView.snp.makeConstraints { - $0.top.equalToSuperview().inset(9) - $0.centerX.equalToSuperview() - $0.height.equalTo(3) - $0.width.equalTo(40) - } - - self.addSubview(self.scrollView) - self.scrollView.snp.makeConstraints { - $0.top.equalTo(self.safeAreaLayoutGuide) - $0.leading.trailing.bottom.equalToSuperview() - } - - self.scrollView.addSubview(self.scrollContentView) - self.scrollContentView.snp.makeConstraints { - $0.edges.equalToSuperview() - $0.width.equalTo(self.scrollView.snp.width) - } - - self.scrollContentView.addSubview(self.missionView) - self.missionView.snp.makeConstraints { - $0.top.equalToSuperview().offset(25) - $0.leading.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) - $0.height.equalTo(100) - } - - self.scrollContentView.addSubview(self.letterTextView) - self.letterTextView.snp.makeConstraints { - $0.top.equalTo(self.missionView.snp.bottom).offset(32) - $0.leading.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) - } - - self.scrollContentView.addSubview(self.letterPhotoView) - self.letterPhotoView.snp.makeConstraints { - $0.top.equalTo(self.letterTextView.snp.bottom).offset(22) - $0.leading.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) - $0.bottom.equalToSuperview().inset(105) - } - } - - func configureUI() { - self.backgroundColor = .backgroundGrey - } - - // MARK: - func - - private func setupButtonAction() { - let cancelAction = UIAction { [weak self] _ in - self?.presentationControllerDidAttemptToDismiss() - } - self.cancelButton.addAction(cancelAction, for: .touchUpInside) - - let sendAction = UIAction { [weak self] _ in - let image = self?.letterPhotoView.image - let content = self?.letterTextView.text - self?.delegate?.sendLetterToManittee(with: content, image) - } - self.sendButton.addAction(sendAction, for: .touchUpInside) - } - - private func observeSendButtonEnabledState() { - self.letterTextView.sendHasTextValue = { [weak self] hasText in - self?.sendButtonObserver.hasText = hasText - } - - self.letterPhotoView.sendHasImageValue = { [weak self] hasImage in - self?.sendButtonObserver.hasImage = hasImage - } - } - - private func presentationControllerDidAttemptToDismiss() { - switch self.sendButtonObserver { - case let (hasText, hasImage) where hasText || hasImage: - self.delegate?.showActionSheet() - default: - self.delegate?.presentationControllerDidDismiss() - } - } - - func configureMission(_ mission: String) { - self.missionView.setupMission(with: mission) - } - - func configureViewController(_ viewController: UIViewController?) { - viewController?.isModalInPresentation = true - viewController?.title = TextLiteral.createLetterViewControllerTitle - } - - func configureDelegation(_ delegate: CreateLetterViewDelegate) { - self.delegate = delegate - } - - func configureNavigationController(_ navigationController: UINavigationController) { - navigationController.presentationController?.delegate = self - } - - func configureNavigationBar(_ navigationController: UINavigationController) { - let navigationBar = navigationController.navigationBar - let appearance = UINavigationBarAppearance() - let font = UIFont.font(.regular, ofSize: 16) - - appearance.titleTextAttributes = [.font: font] - appearance.shadowColor = .clear - appearance.configureWithTransparentBackground() - appearance.backgroundColor = .clear - appearance.backgroundImage = nil - appearance.shadowImage = nil - - navigationBar.standardAppearance = appearance - navigationBar.compactAppearance = appearance - navigationBar.scrollEdgeAppearance = appearance - } - - func configureNavigationItem(_ navigationController: UINavigationController) { - let navigationItem = navigationController.topViewController?.navigationItem - let cancelButton = UIBarButtonItem(customView: self.cancelButton) - let sendButton = UIBarButtonItem(customView: self.sendButton) - - sendButton.isEnabled = false - - navigationItem?.leftBarButtonItem = cancelButton - navigationItem?.rightBarButtonItem = sendButton - } -} - -// MARK: - UIAdaptivePresentationControllerDelegate -extension CreateLetterView: UIAdaptivePresentationControllerDelegate { - func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) { - self.presentationControllerDidAttemptToDismiss() - } -} diff --git a/Manito/Manito/Screens/Login/LoginViewController.swift b/Manito/Manito/Screens/Login/LoginViewController.swift index 0771e224f..dadbf99a7 100644 --- a/Manito/Manito/Screens/Login/LoginViewController.swift +++ b/Manito/Manito/Screens/Login/LoginViewController.swift @@ -10,12 +10,12 @@ import UIKit import SnapKit -final class LoginViewController: BaseViewController, BaseViewControllerType { +final class LoginViewController: UIViewController, BaseViewControllerType, Navigationable { // MARK: - ui component - private let logoImageView: UIImageView = UIImageView(image: ImageLiterals.imgAppIcon) - private let logoTextImageView: UIImageView = UIImageView(image: ImageLiterals.imgTextLogo) + private let logoImageView: UIImageView = UIImageView(image: UIImage.Image.appIcon) + private let logoTextImageView: UIImageView = UIImageView(image: UIImage.Image.textLogo) private let appleLoginButton: ASAuthorizationAppleIDButton = { let button = ASAuthorizationAppleIDButton(type: .signIn, style: .white) button.cornerRadius = 25 @@ -39,6 +39,7 @@ final class LoginViewController: BaseViewController, BaseViewControllerType { self.baseViewDidLoad() self.setupLoginButton() self.configureNavigationBar() + self.setupNavigation() } // MARK: - base func @@ -118,15 +119,19 @@ extension LoginViewController: ASAuthorizationControllerDelegate { if let isNewMember = data.isNewMember { if isNewMember { - self.navigationController?.pushViewController(CreateNicknameViewController(viewModel: NicknameViewModel(nicknameService: NicknameService(repository: SettingRepositoryImpl()))), animated: true) + let repository = SettingRepositoryImpl() + let nicknameUsecase = NicknameUsecaseImpl(repository: repository) + let textFieldUsecase = TextFieldUsecaseImpl() + let viewMdoel = NicknameViewModel(nicknameUsecase: nicknameUsecase, + textFieldUsecase: textFieldUsecase) + self.navigationController?.pushViewController(CreateNicknameViewController(viewModel: viewMdoel), animated: true) return } } UserDefaultHandler.setNickname(nickname: data.nickname ?? "") UserDefaultHandler.setIsSetFcmToken(isSetFcmToken: true) - let storyboard = UIStoryboard(name: "Main", bundle: nil) - let viewController = storyboard.instantiateViewController(withIdentifier: "MainNavigationController") + let viewController = UINavigationController(rootViewController: MainViewController()) viewController.modalPresentationStyle = .fullScreen viewController.modalTransitionStyle = .crossDissolve self.present(viewController, animated: true) diff --git a/Manito/Manito/Screens/Main/Cell/CreateRoomCollectionViewCell.swift b/Manito/Manito/Screens/Main/Cell/CreateRoomCollectionViewCell.swift index 9a0044d5d..9febdd071 100644 --- a/Manito/Manito/Screens/Main/Cell/CreateRoomCollectionViewCell.swift +++ b/Manito/Manito/Screens/Main/Cell/CreateRoomCollectionViewCell.swift @@ -13,10 +13,10 @@ final class CreateRoomCollectionViewCell: UICollectionViewCell, BaseViewType { // MARK: - ui component - private let imageView: UIImageView = UIImageView(image: ImageLiterals.icNewRoom) + private let imageView: UIImageView = UIImageView(image: UIImage.Icon.newRoom) private let circleView: UIView = { let circleView = UIView() - circleView.backgroundColor = .yellow + circleView.backgroundColor = .yellow001 circleView.layer.cornerRadius = 44 circleView.layer.borderWidth = 1 circleView.layer.borderColor = UIColor.grey003.cgColor @@ -24,7 +24,7 @@ final class CreateRoomCollectionViewCell: UICollectionViewCell, BaseViewType { }() private let menuLabel: UILabel = { let label = UILabel() - label.text = TextLiteral.createRoomCollectionViewCellMenuLabel + label.text = TextLiteral.Main.cellCreateRoomTitle.localized() label.textColor = .grey001 label.font = .font(.regular, ofSize: 14) return label diff --git a/Manito/Manito/Screens/Main/Cell/ManitoRoomCollectionCell.swift b/Manito/Manito/Screens/Main/Cell/ManitoRoomCollectionCell.swift index 87eb749f4..f0d0eda61 100644 --- a/Manito/Manito/Screens/Main/Cell/ManitoRoomCollectionCell.swift +++ b/Manito/Manito/Screens/Main/Cell/ManitoRoomCollectionCell.swift @@ -20,7 +20,7 @@ final class ManitoRoomCollectionViewCell: UICollectionViewCell, BaseViewType { // MARK: - ui component - private let imageView: UIImageView = UIImageView(image: ImageLiterals.imgNi) + private let imageView: UIImageView = UIImageView(image: UIImage.Image.ni) let memberLabel: UILabel = { let label = UILabel() label.textColor = .white @@ -29,7 +29,6 @@ final class ManitoRoomCollectionViewCell: UICollectionViewCell, BaseViewType { }() let roomLabel: UILabel = { let label = UILabel() - label.text = TextLiteral.manitoRoomCollectionViewCellRoomLabelTitle label.textColor = .white label.font = .font(.regular, ofSize: 20) label.textAlignment = .center diff --git a/Manito/Manito/Screens/Main/MainViewController.swift b/Manito/Manito/Screens/Main/MainViewController.swift index 0a9c5b244..8d8b2e3cc 100644 --- a/Manito/Manito/Screens/Main/MainViewController.swift +++ b/Manito/Manito/Screens/Main/MainViewController.swift @@ -11,7 +11,7 @@ import Gifu import SkeletonView import SnapKit -final class MainViewController: BaseViewController, BaseViewControllerType { +final class MainViewController: UIViewController, BaseViewControllerType { private enum InternalSize { static let collectionHorizontalSpacing: CGFloat = 20 @@ -29,25 +29,28 @@ final class MainViewController: BaseViewController, BaseViewControllerType { private let backgroundImageView: UIImageView = { let imageView = UIImageView() - imageView.image = ImageLiterals.imgBackground + imageView.image = UIImage.Image.background return imageView }() private let skeletonAnimation: SkeletonLayerAnimation = SkeletonAnimationBuilder().makeSlidingAnimation(withDirection: .leftRight) private let refreshControl: UIRefreshControl = UIRefreshControl() - private let appTitleView: UIImageView = UIImageView(image: ImageLiterals.imgLogo) + private let appTitleView: UIImageView = UIImageView(image: UIImage.Image.logo) private lazy var settingButton: SettingButton = { let button = SettingButton() let action = UIAction { [weak self] _ in - self?.navigationController?.pushViewController(SettingViewController(viewModel: SettingViewModel(settingService: SettingService(repository: SettingRepositoryImpl()))), animated: true) + let repository = SettingRepositoryImpl() + let usecase = SettingUsecaseImpl(repository: repository) + let viewModel = SettingViewModel(usecase: usecase) + self?.navigationController?.pushViewController(SettingViewController(viewModel: viewModel), animated: true) } button.addAction(action, for: .touchUpInside) return button }() - private let imgStar: UIImageView = UIImageView(image: ImageLiterals.imgStar) + private let imgStar: UIImageView = UIImageView(image: UIImage.Image.star) private let commonMissionView: CommonMissionView = CommonMissionView() private let menuTitle: UILabel = { let label = UILabel() - label.text = TextLiteral.mainViewControllerMenuTitle + label.text = TextLiteral.Main.listTitle.localized() label.textColor = .white label.font = .font(.regular, ofSize: 18) return label @@ -94,6 +97,7 @@ final class MainViewController: BaseViewController, BaseViewControllerType { override func viewDidLoad() { super.viewDidLoad() + self.setupNavigationBar() self.baseViewDidLoad() self.setupGifImage() self.setupRefreshControl() @@ -102,6 +106,7 @@ final class MainViewController: BaseViewController, BaseViewControllerType { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + self.delegateInteractivePopGesture() self.requestCommonMission() self.requestManittoRoomList() } @@ -145,7 +150,7 @@ final class MainViewController: BaseViewController, BaseViewControllerType { self.view.addSubview(self.commonMissionView) self.commonMissionView.snp.makeConstraints { $0.top.equalTo(self.imgStar.snp.bottom) - $0.leading.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.height.equalTo(InternalSize.commonMissionViewHeight) } @@ -174,9 +179,7 @@ final class MainViewController: BaseViewController, BaseViewControllerType { // MARK: - override - override func setupNavigationBar() { - super.setupNavigationBar() - + private func setupNavigationBar() { let appTitleView = self.makeBarButtonItem(with: self.appTitleView) let settingButtonView = self.makeBarButtonItem(with: self.settingButton) @@ -194,9 +197,9 @@ final class MainViewController: BaseViewController, BaseViewControllerType { private func setupGifImage() { DispatchQueue.main.async { - self.maCharacterImageView.animate(withGIFNamed: ImageLiterals.gifMa, animationBlock: nil) - self.niCharacterImageView.animate(withGIFNamed: ImageLiterals.gifNi, animationBlock: nil) - self.ttoCharacterImageView.animate(withGIFNamed: ImageLiterals.gifTto, animationBlock: nil) + self.maCharacterImageView.animate(withGIFNamed: GIFSet.ma, animationBlock: nil) + self.niCharacterImageView.animate(withGIFNamed: GIFSet.ni, animationBlock: nil) + self.ttoCharacterImageView.animate(withGIFNamed: GIFSet.tto, animationBlock: nil) } } @@ -225,31 +228,39 @@ final class MainViewController: BaseViewController, BaseViewControllerType { } private func createNewRoom() { - let alert = UIAlertController(title: TextLiteral.mainViewControllerNewRoomAlert, + let alert = UIAlertController(title: TextLiteral.Main.menuTitle.localized(), message: nil, preferredStyle: .actionSheet) - let createRoom = UIAlertAction(title: TextLiteral.createRoom, + let createRoom = UIAlertAction(title: TextLiteral.Common.createRoom.localized(), style: .default, handler: { [weak self] _ in - let createVC = CreateRoomViewController(viewModel: CreateRoomViewModel(createRoomService: CreateRoomService(repository: RoomParticipationRepositoryImpl()))) - let navigationController = UINavigationController(rootViewController: createVC) + let repository = RoomParticipationRepositoryImpl() + let usecase = CreateRoomUsecaseImpl(repository: repository) + let textFieldUsecase = TextFieldUsecaseImpl() + let viewController = CreateRoomViewController(viewModel: CreateRoomViewModel(usecase: usecase, + textFieldUsecase: textFieldUsecase)) + let navigationController = UINavigationController(rootViewController: viewController) navigationController.modalPresentationStyle = .overFullScreen DispatchQueue.main.async { self?.present(navigationController,animated: true) } }) - let enterRoom = UIAlertAction(title: TextLiteral.enterRoom, + let enterRoom = UIAlertAction(title: TextLiteral.Common.enterRoom.localized(), style: .default, handler: { [weak self] _ in - let viewController = ParticipateRoomViewController() + let usecase = ParticipateRoomUsecaseImpl(repository: RoomParticipationRepositoryImpl()) + let textFieldUsecase = TextFieldUsecaseImpl() + let viewModel = ParticipateRoomViewModel(usecase: usecase, + textFieldUsecase: textFieldUsecase) + let viewController = ParticipateRoomViewController(viewModel: viewModel) let navigationController = UINavigationController(rootViewController: viewController) navigationController.modalPresentationStyle = .overFullScreen DispatchQueue.main.async { self?.present(navigationController, animated: true) } }) - let cancel = UIAlertAction(title: TextLiteral.cancel, style: .cancel) + let cancel = UIAlertAction(title: TextLiteral.Common.cancel.localized(), style: .cancel) alert.addAction(createRoom) alert.addAction(enterRoom) @@ -257,24 +268,12 @@ final class MainViewController: BaseViewController, BaseViewControllerType { self.present(alert, animated: true) } - private func presentParticipateRoomViewController() { - let storyboard = UIStoryboard(name: "ParticipateRoom", bundle: nil) - let participateRoomViewController = storyboard.instantiateViewController(identifier: "ParticipateRoomViewController") - - participateRoomViewController.modalPresentationStyle = .fullScreen - participateRoomViewController.modalTransitionStyle = .crossDissolve - DispatchQueue.main.async { - self.present(participateRoomViewController, animated: true) - } - } - // FIXME: - roomIndex가 현재 item으로 설정되어 있고, index가 roomIndex로 설정되어있음. KTBQ2B private func pushDetailView(status: RoomStatus, roomIndex: Int, index: Int? = nil) { switch status { case .PRE: guard let index = index else { return } - let viewModel = DetailWaitViewModel(roomIndex: index, - detailWaitService: DetailWaitService(repository: DetailRoomRepositoryImpl())) + let viewModel = DetailWaitViewModel(roomId: index.description, usecase: DetailWaitUseCaseImpl(repository: DetailRoomRepositoryImpl())) let viewController = DetailWaitViewController(viewModel: viewModel) self.navigationController?.pushViewController(viewController, animated: true) @@ -295,8 +294,12 @@ final class MainViewController: BaseViewController, BaseViewControllerType { } func showRoomIdErrorAlert() { - self.makeAlert(title: TextLiteral.mainViewControllerShowIdErrorAlertTitle, - message: TextLiteral.mainViewControllerShowIdErrorAlertMessage) + self.makeAlert(title: TextLiteral.Main.Error.title, + message: TextLiteral.Main.Error.message) + } + + private func delegateInteractivePopGesture() { + self.navigationController?.interactivePopGestureRecognizer?.delegate = self } // MARK: - network @@ -411,3 +414,9 @@ extension MainViewController: UICollectionViewDelegateFlowLayout { } } } + +extension MainViewController: UIGestureRecognizerDelegate { + func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { + return false + } +} diff --git a/Manito/Manito/Screens/Main/UIComponent/CommonMissionView.swift b/Manito/Manito/Screens/Main/UIComponent/CommonMissionView.swift index ee3b47c80..611d6ba50 100644 --- a/Manito/Manito/Screens/Main/UIComponent/CommonMissionView.swift +++ b/Manito/Manito/Screens/Main/UIComponent/CommonMissionView.swift @@ -13,10 +13,10 @@ final class CommonMissionView: UIView { // MARK: - ui component - private let commonMissionImageView: UIImageView = UIImageView(image: ImageLiterals.imgCommonMisson) + private let commonMissionImageView: UIImageView = UIImageView(image: UIImage.Image.commonMisson) private let titleLabel: UILabel = { let label = UILabel() - label.text = TextLiteral.commonMissionViewTitle + label.text = TextLiteral.Main.commonMissionTitle.localized() label.textColor = .grey001 label.font = .font(.regular, ofSize: 15) return label diff --git a/Manito/Manito/Screens/NickName/Service/NicknameService.swift b/Manito/Manito/Screens/NickName/Usecase/NicknameUsecase.swift similarity index 62% rename from Manito/Manito/Screens/NickName/Service/NicknameService.swift rename to Manito/Manito/Screens/NickName/Usecase/NicknameUsecase.swift index c43274dde..885fc5f5f 100644 --- a/Manito/Manito/Screens/NickName/Service/NicknameService.swift +++ b/Manito/Manito/Screens/NickName/Usecase/NicknameUsecase.swift @@ -1,5 +1,5 @@ // -// NicknameService.swift +// NicknameUsecase.swift // Manito // // Created by 이성호 on 2023/09/02. @@ -7,11 +7,11 @@ import Foundation -protocol NicknameServicable { +protocol NicknameUsecase { func putUserInfo(nickname: NicknameDTO) async throws -> Int } -final class NicknameService: NicknameServicable { +final class NicknameUsecaseImpl: NicknameUsecase { private let repository: SettingRepository @@ -23,10 +23,8 @@ final class NicknameService: NicknameServicable { do { let statusCode = try await self.repository.putUserInfo(nickname: nickname) return statusCode - } catch NetworkError.serverError { - throw NetworkError.serverError - } catch NetworkError.clientError(let message) { - throw NetworkError.clientError(message: message) + } catch { + throw NicknameError.clientError } } } diff --git a/Manito/Manito/Screens/NickName/View/NicknameView.swift b/Manito/Manito/Screens/NickName/View/NicknameView.swift index 9733244b2..0cb1eeb66 100644 --- a/Manito/Manito/Screens/NickName/View/NicknameView.swift +++ b/Manito/Manito/Screens/NickName/View/NicknameView.swift @@ -14,10 +14,9 @@ final class NicknameView: UIView, BaseViewType { // MARK: - ui components - private lazy var titleLabel: UILabel = { + private let titleLabel: UILabel = { let label = UILabel() label.font = .font(.regular, ofSize: 34) - label.text = self.title return label }() private lazy var nicknameTextField: UITextField = { @@ -26,7 +25,7 @@ final class NicknameView: UIView, BaseViewType { NSAttributedString.Key.font : UIFont.font(.regular, ofSize: 18) ] textField.backgroundColor = .darkGrey002 - textField.attributedPlaceholder = NSAttributedString(string: TextLiteral.createNickNameViewControllerAskNickName, attributes:attributes) + textField.attributedPlaceholder = NSAttributedString(string: TextLiteral.Nickname.placeholder.localized(), attributes:attributes) textField.font = .font(.regular, ofSize: 18) textField.layer.cornerRadius = 10 textField.layer.masksToBounds = true @@ -48,7 +47,7 @@ final class NicknameView: UIView, BaseViewType { }() private let doneButton: MainButton = { let button = MainButton() - button.title = TextLiteral.done + button.title = TextLiteral.Common.done.localized() button.isDisabled = true return button }() @@ -56,8 +55,11 @@ final class NicknameView: UIView, BaseViewType { // MARK: - property private let title: String - lazy var doneButtonTapPublisher = self.doneButton.tapPublisher - let textFieldPublisher = PassthroughSubject() + + var doneButtonTapPublisher: AnyPublisher { + return self.doneButton.tapPublisher + } + let textFieldPublisher: PassthroughSubject = PassthroughSubject() // MARK: - init @@ -65,7 +67,7 @@ final class NicknameView: UIView, BaseViewType { self.title = title super.init(frame: .zero) self.baseInit() - self.setupNotificationCenter() + self.setupTitleLabel() } @available(*, unavailable) @@ -79,25 +81,25 @@ final class NicknameView: UIView, BaseViewType { self.addSubview(self.titleLabel) self.titleLabel.snp.makeConstraints { $0.top.equalTo(self.safeAreaLayoutGuide).inset(20) - $0.leading.equalTo(self.safeAreaLayoutGuide).inset(Size.leadingTrailingPadding) + $0.leading.equalTo(self.safeAreaLayoutGuide).inset(SizeLiteral.leadingTrailingPadding) } self.addSubview(self.nicknameTextField) self.nicknameTextField.snp.makeConstraints { $0.top.equalTo(self.titleLabel.snp.bottom).offset(66) - $0.leading.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.height.equalTo(60) } self.addSubview(self.textLimitLabel) self.textLimitLabel.snp.makeConstraints { $0.top.equalTo(self.nicknameTextField.snp.bottom).offset(10) - $0.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.trailing.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) } self.addSubview(self.doneButton) self.doneButton.snp.makeConstraints { - $0.bottom.equalTo(self.safeAreaLayoutGuide).inset(23) + $0.bottom.equalTo(self.keyboardLayoutGuide.snp.top).inset(-23) $0.centerX.equalToSuperview() } } @@ -106,12 +108,7 @@ final class NicknameView: UIView, BaseViewType { self.backgroundColor = .backgroundGrey } - // MARK: - func - - private func setupNotificationCenter() { - NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) - } + // MARK: - public func func configureNavigationItem(_ navigationController: UINavigationController) { navigationController.isNavigationBarHidden = false @@ -142,20 +139,10 @@ final class NicknameView: UIView, BaseViewType { self.nicknameTextField.text = nickname } - // MARK: - selector - - @objc private func keyboardWillShow(notification:NSNotification) { - if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { - UIView.animate(withDuration: 0.2, animations: { - self.doneButton.transform = CGAffineTransform(translationX: 0, y: -keyboardSize.height + 30) - }) - } - } + // MARK: - private func - @objc private func keyboardWillHide(notification:NSNotification) { - UIView.animate(withDuration: 0.2, animations: { - self.doneButton.transform = .identity - }) + private func setupTitleLabel() { + self.titleLabel.text = self.title } } diff --git a/Manito/Manito/Screens/NickName/ViewControllers/ChangeNicknameViewController.swift b/Manito/Manito/Screens/NickName/ViewControllers/ChangeNicknameViewController.swift index 9dd1ca4b0..30a7c1a43 100644 --- a/Manito/Manito/Screens/NickName/ViewControllers/ChangeNicknameViewController.swift +++ b/Manito/Manito/Screens/NickName/ViewControllers/ChangeNicknameViewController.swift @@ -10,20 +10,22 @@ import UIKit import SnapKit -final class ChangeNicknameViewController: BaseViewController { +final class ChangeNicknameViewController: UIViewController, Navigationable, Keyboardable { - // MARK: - property + // MARK: - ui component + + private let nicknameView: NicknameView = NicknameView(title: TextLiteral.Nickname.changeTitle.localized()) - private let viewModel: NicknameViewModel - private lazy var nicknameView: NicknameView = NicknameView(title: TextLiteral.changeNickNameViewControllerTitle) + // MARK: - property - private var cancellable = Set() + private let viewModel: any BaseViewModelType + private var cancellable: Set = Set() // MARK: - init - init(viewModel: NicknameViewModel) { + init(viewModel: any BaseViewModelType) { self.viewModel = viewModel - super.init() + super.init(nibName: nil, bundle: nil) } @available(*, unavailable) @@ -44,6 +46,8 @@ final class ChangeNicknameViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() self.bindViewModel() + self.setupNavigation() + self.setupKeyboardGesture() } // MARK: - override @@ -59,14 +63,17 @@ final class ChangeNicknameViewController: BaseViewController { self.bindOutputToViewModel(output) } - private func transformedOutput() -> NicknameViewModel.Output { + private func transformedOutput() -> NicknameViewModel.Output? { + guard let viewModel = self.viewModel as? NicknameViewModel else { return nil } let input = NicknameViewModel.Input(viewDidLoad: self.viewDidLoadPublisher, textFieldDidChanged: self.nicknameView.textFieldPublisher.eraseToAnyPublisher(), doneButtonDidTap: self.nicknameView.doneButtonTapPublisher.eraseToAnyPublisher()) return viewModel.transform(from: input) } - private func bindOutputToViewModel(_ output: NicknameViewModel.Output) { + private func bindOutputToViewModel(_ output: NicknameViewModel.Output?) { + guard let output else { return } + output.nickname .sink { [weak self] nickname in self?.nicknameView.updateNickname(nickname: nickname) @@ -95,13 +102,18 @@ final class ChangeNicknameViewController: BaseViewController { .receive(on: DispatchQueue.main) .sink { [weak self] result in switch result { - case .finished: return - case .failure(_): - self?.makeAlert(title: TextLiteral.fail, message: "실패") + case .success(): self?.popViewController() + case .failure(let error): self?.makeAlert(title: error.localizedDescription) } - } receiveValue: { [weak self] _ in - self?.navigationController?.popViewController(animated: true) } .store(in: &self.cancellable) } } + +// MARK: - Helper + +extension ChangeNicknameViewController { + private func popViewController() { + self.navigationController?.popViewController(animated: true) + } +} diff --git a/Manito/Manito/Screens/NickName/ViewControllers/CreateNicknameViewController.swift b/Manito/Manito/Screens/NickName/ViewControllers/CreateNicknameViewController.swift index c7cc0e931..eedd618d1 100644 --- a/Manito/Manito/Screens/NickName/ViewControllers/CreateNicknameViewController.swift +++ b/Manito/Manito/Screens/NickName/ViewControllers/CreateNicknameViewController.swift @@ -10,20 +10,22 @@ import UIKit import SnapKit -final class CreateNicknameViewController: BaseViewController { +final class CreateNicknameViewController: UIViewController, Keyboardable { - // MARK: - property + // MARK: - ui component + + private let nicknameView: NicknameView = NicknameView(title: TextLiteral.Nickname.createTitle.localized()) - private let viewModel: NicknameViewModel - private lazy var nicknameView: NicknameView = NicknameView(title: TextLiteral.createNickNameViewControllerTitle) + // MARK: - property - private var cancellable = Set() + private let viewModel: any BaseViewModelType + private var cancellable: Set = Set() // MARK: - init - init(viewModel: NicknameViewModel) { + init(viewModel: any BaseViewModelType) { self.viewModel = viewModel - super.init() + super.init(nibName: nil, bundle: nil) } @available(*, unavailable) @@ -46,27 +48,13 @@ final class CreateNicknameViewController: BaseViewController { self.configureNavigationController() self.setupBackButton() self.bindViewModel() - } - - private func presentMainViewController() { - let storyboard = UIStoryboard(name: "Main", bundle: nil) - let viewController = storyboard.instantiateViewController(withIdentifier: "MainNavigationController") - viewController.modalPresentationStyle = .fullScreen - viewController.modalTransitionStyle = .crossDissolve - present(viewController, animated: true) + self.setupKeyboardGesture() } override func endEditingView() { self.nicknameView.endEditingView() } - override func removeBarButtonItemOffset(with view: UIView, offsetX: CGFloat = 0, offsetY: CGFloat = 0) -> UIView { - let offsetView = UIView(frame: CGRect(x: 0, y: 0, width: 45, height: 45)) - offsetView.bounds = offsetView.bounds.offsetBy(dx: offsetX, dy: offsetY) - offsetView.addSubview(view) - return offsetView - } - // MARK: - func private func configureNavigationController() { @@ -75,25 +63,35 @@ final class CreateNicknameViewController: BaseViewController { } private func setupBackButton() { - let leftOffsetBackButton = removeBarButtonItemOffset(with: UIView(), offsetX: 10) + let leftOffsetBackButton = removeItemOffset(with: UIView(), offsetX: 10) let emptyView = makeBarButtonItem(with: leftOffsetBackButton) navigationItem.leftBarButtonItem = emptyView } + private func removeItemOffset(with view: UIView, offsetX: CGFloat = 0, offsetY: CGFloat = 0) -> UIView { + let offsetView = UIView(frame: CGRect(x: 0, y: 0, width: 45, height: 45)) + offsetView.bounds = offsetView.bounds.offsetBy(dx: offsetX, dy: offsetY) + offsetView.addSubview(view) + return offsetView + } + private func bindViewModel() { let output = self.transformedOutput() self.bindOutputToViewModel(output) } - private func transformedOutput() -> NicknameViewModel.Output { + private func transformedOutput() -> NicknameViewModel.Output? { + guard let viewModel = self.viewModel as? NicknameViewModel else { return nil } let input = NicknameViewModel.Input(viewDidLoad: self.viewDidLoadPublisher, textFieldDidChanged: self.nicknameView.textFieldPublisher.eraseToAnyPublisher(), - doneButtonDidTap: self.nicknameView.doneButtonTapPublisher.eraseToAnyPublisher()) + doneButtonDidTap: self.nicknameView.doneButtonTapPublisher) return viewModel.transform(from: input) } - private func bindOutputToViewModel(_ output: NicknameViewModel.Output) { + private func bindOutputToViewModel(_ output: NicknameViewModel.Output?) { + guard let output else { return } + output.counts .sink { [weak self] (textCount, maxCount) in self?.nicknameView.updateTextCount(count: textCount, maxLength: maxCount) @@ -116,13 +114,21 @@ final class CreateNicknameViewController: BaseViewController { .receive(on: DispatchQueue.main) .sink { [weak self] result in switch result { - case .finished: return - case .failure(_): - self?.makeAlert(title: TextLiteral.fail, message: "실패") + case .success(): self?.presentMainViewController() + case .failure(let error): self?.makeAlert(title: error.localizedDescription) } - } receiveValue: { [weak self] _ in - self?.presentMainViewController() } .store(in: &self.cancellable) } } + +// MARK: - Helper + +extension CreateNicknameViewController { + private func presentMainViewController() { + let viewController = UINavigationController(rootViewController: MainViewController()) + viewController.modalPresentationStyle = .fullScreen + viewController.modalTransitionStyle = .crossDissolve + present(viewController, animated: true) + } +} diff --git a/Manito/Manito/Screens/NickName/ViewModel/NicknameViewModel.swift b/Manito/Manito/Screens/NickName/ViewModel/NicknameViewModel.swift index 8494bc92f..f79ee9cf2 100644 --- a/Manito/Manito/Screens/NickName/ViewModel/NicknameViewModel.swift +++ b/Manito/Manito/Screens/NickName/ViewModel/NicknameViewModel.swift @@ -8,19 +8,19 @@ import Combine import Foundation -final class NicknameViewModel { +final class NicknameViewModel: BaseViewModelType { typealias Counts = (textCount: Int, maxCount: Int) // MARK: - property - let maxCount: Int = 5 + private let maxCount: Int = 5 - private let nicknameService: NicknameService - private var cancellable = Set() + private let nicknameUsecase: NicknameUsecase + private let textFieldUsecase: TextFieldUsecase + private var cancellable: Set = Set() - private let nicknameSubject = CurrentValueSubject("") - private let doneButtonSubject = PassthroughSubject() + private let nicknameSubject: CurrentValueSubject = CurrentValueSubject("") struct Input { let viewDidLoad: AnyPublisher @@ -33,9 +33,19 @@ final class NicknameViewModel { let counts: AnyPublisher let fixedTitleByMaxCount: AnyPublisher let isEnabled: AnyPublisher - let doneButton: PassthroughSubject + let doneButton: AnyPublisher, Never> } + // MARK: - init + + init(nicknameUsecase: NicknameUsecase, + textFieldUsecase: TextFieldUsecase) { + self.nicknameUsecase = nicknameUsecase + self.textFieldUsecase = textFieldUsecase + } + + // MARK: - func + func transform(from input: Input) -> Output { let nickname = input.viewDidLoad .map { UserDefaultStorage.nickname } @@ -56,74 +66,46 @@ final class NicknameViewModel { let fixedTitle = input.textFieldDidChanged .map { [weak self] text -> String in - let isOverMaxCount = self?.isOverMaxCount(titleCount: text.count, maxCount: self?.maxCount ?? 0) ?? false - - if isOverMaxCount { - let endIndex = text.index(text.startIndex, offsetBy: self?.maxCount ?? 0) - let fixedText = text[text.startIndex.. Bool in - return !text.isEmpty - } + .map { !$0.isEmpty } .eraseToAnyPublisher() - input.doneButtonDidTap - .sink(receiveValue: { [weak self] _ in - self?.didTapDoneButton() - }) - .store(in: &self.cancellable) + let doneButtonDidTap = input.doneButtonDidTap + .asyncMap { [weak self] _ -> Result in + do { + let nickname = self?.nicknameSubject.value + self?.saveNicknameToUserDefault(nickname: nickname ?? "") + let _ = try await self?.putUserInfo(nickname: NicknameDTO(nickname: nickname ?? "")) + return .success(()) + } catch (let error) { + return .failure(error) + } + } + .eraseToAnyPublisher() return Output(nickname: nickname, counts: mergeCount, fixedTitleByMaxCount: fixedTitle, isEnabled: isEnabled, - doneButton: self.doneButtonSubject) + doneButton: doneButtonDidTap) } - // MARK: - init - - init(nicknameService: NicknameService) { - self.nicknameService = nicknameService - } - - // MARK: - func - - private func isOverMaxCount(titleCount: Int, maxCount: Int) -> Bool { - if titleCount > maxCount { return true } - else { return false } - } - - private func didTapDoneButton() { - let nickname = self.nicknameSubject.value + private func saveNicknameToUserDefault(nickname: String) { UserData.setValue(nickname, forKey: .nickname) UserDefaultHandler.setIsSetFcmToken(isSetFcmToken: true) - self.requestCreateNickname(nickname: NicknameDTO(nickname: nickname)) } - - // MARK: - network - - private func requestCreateNickname(nickname: NicknameDTO) { - Task { - do { - let statusCode = try await self.nicknameService.putUserInfo(nickname: nickname) - switch statusCode { - case 200..<300: - UserDefaultHandler.setNickname(nickname: nickname.nickname) - self.doneButtonSubject.send() - default: - self.doneButtonSubject.send(completion: .failure(.unknownError)) - } - } catch(let error) { - guard let error = error as? NetworkError else { return } - self.doneButtonSubject.send(completion: .failure(error)) - } - } +} + +// MARK: - Helper + +extension NicknameViewModel { + private func putUserInfo(nickname: NicknameDTO) async throws -> Int { + return try await self.nicknameUsecase.putUserInfo(nickname: nickname) } } diff --git a/Manito/Manito/Screens/ParticipateRoom/ParticipateRoomView.swift b/Manito/Manito/Screens/ParticipateRoom/ParticipateRoomView.swift deleted file mode 100644 index cff222b5c..000000000 --- a/Manito/Manito/Screens/ParticipateRoom/ParticipateRoomView.swift +++ /dev/null @@ -1,159 +0,0 @@ -// -// ParticipateRoomView.swift -// Manito -// -// Created by 이성호 on 2023/05/11. -// - -import UIKit - -import SnapKit - -protocol ParticipateRoomViewDelegate: AnyObject { - func closeButtonDidTap() - func nextButtonDidTap(code: String) - func observeNextNotification(roomId: Int) -} - -final class ParticipateRoomView: UIView, BaseViewType { - - // MARK: - ui component - - private let titleLabel: UILabel = { - let label = UILabel() - label.text = TextLiteral.enterRoom - label.font = .font(.regular, ofSize: 34) - return label - }() - private let closeButton: UIButton = { - let button = UIButton(frame: CGRect(origin: .zero, size: CGSize(width: 44, height: 44))) - button.setImage(ImageLiterals.btnXmark, for: .normal) - return button - }() - private let nextButton: MainButton = { - let button = MainButton() - button.title = TextLiteral.searchRoom - button.isDisabled = true - return button - }() - private let inputInvitedCodeView: InputInvitedCodeView = InputInvitedCodeView() - - // MARK: - property - - private weak var delegate: ParticipateRoomViewDelegate? - - // MARK: - init - - override init(frame: CGRect) { - super.init(frame: frame) - self.baseInit() - self.setupButtonAction() - self.setupNotificationCenter() - self.detectNextButtonStatus() - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - base func - - func setupLayout() { - self.addSubview(self.titleLabel) - self.titleLabel.snp.makeConstraints { - $0.top.equalTo(self.safeAreaLayoutGuide).inset(20) - $0.leading.equalToSuperview().inset(Size.leadingTrailingPadding) - } - - self.addSubview(self.nextButton) - self.nextButton.snp.makeConstraints { - $0.leading.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) - $0.bottom.equalTo(self.safeAreaLayoutGuide).inset(23) - $0.height.equalTo(60) - } - - self.addSubview(self.inputInvitedCodeView) - self.inputInvitedCodeView.snp.makeConstraints { - $0.top.equalTo(self.titleLabel.snp.bottom).offset(66) - $0.leading.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) - $0.bottom.equalTo(self.nextButton.snp.top) - } - - self.bringSubviewToFront(self.nextButton) - } - - func configureUI() { - self.backgroundColor = .backgroundGrey - } - - // MARK: - func - - private func setupButtonAction() { - let didTapCloseButton = UIAction { [weak self] _ in - self?.delegate?.closeButtonDidTap() - } - - let didTapNextButton = UIAction { [weak self] _ in - guard let code = self?.inputInvitedCodeView.roomCodeTextField.text else { return } - self?.delegate?.nextButtonDidTap(code: code) - } - - self.closeButton.addAction(didTapCloseButton, for: .touchUpInside) - self.nextButton.addAction(didTapNextButton, for: .touchUpInside) - } - - private func setupNotificationCenter() { - NotificationCenter.default.addObserver(self, selector: #selector(self.didReceiveNextNotification(_:)), name: .nextNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil) - NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: UIResponder.keyboardWillHideNotification, object: nil) - } - - private func detectNextButtonStatus() { - self.inputInvitedCodeView.changeNextButtonEnableStatus = { [weak self] isEnable in - self?.nextButton.isDisabled = !isEnable - } - } - - func configureDelegate(_ delegate: ParticipateRoomViewDelegate) { - self.delegate = delegate - } - - func configureNavigationBarItem(_ navigationController: UINavigationController) { - let navigationItem = navigationController.topViewController?.navigationItem - let closeButton = UIBarButtonItem(customView: self.closeButton) - - navigationItem?.rightBarButtonItem = closeButton - navigationItem?.leftBarButtonItem = nil - } - - func endEditing() { - if !self.nextButton.isTouchInside { - self.endEditing(true) - } - } - - // MARK: - selector - - @objc - private func keyboardWillShow(notification: NSNotification) { - if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue { - UIView.animate(withDuration: 0.2, animations: { - self.nextButton.transform = CGAffineTransform(translationX: 0, y: -keyboardSize.height + 30) - }) - } - } - - @objc - private func keyboardWillHide(notification: NSNotification) { - UIView.animate(withDuration: 0.2, animations: { - self.nextButton.transform = .identity - }) - } - - @objc - private func didReceiveNextNotification(_ notification: Notification) { - guard let id = notification.userInfo?["roomId"] as? Int else { return } - self.delegate?.observeNextNotification(roomId: id) - } -} diff --git a/Manito/Manito/Screens/ParticipateRoom/ParticipateRoomViewController.swift b/Manito/Manito/Screens/ParticipateRoom/ParticipateRoomViewController.swift deleted file mode 100644 index 093d8fab4..000000000 --- a/Manito/Manito/Screens/ParticipateRoom/ParticipateRoomViewController.swift +++ /dev/null @@ -1,95 +0,0 @@ -// -// ParticipateRoomViewController.swift -// Manito -// -// Created by COBY_PRO on 2022/06/15. -// - -import UIKit - -import SnapKit - -final class ParticipateRoomViewController: BaseViewController { - - // MARK: - ui component - - private let participateRoomView: ParticipateRoomView = ParticipateRoomView() - - // MARK: - property - - private let roomParticipationRepository: RoomParticipationRepository = RoomParticipationRepositoryImpl() - - // MARK: - init - - deinit { - print("\(#file) is dead") - } - - // MARK: - life cycle - - override func loadView() { - self.view = self.participateRoomView - } - - override func viewDidLoad() { - super.viewDidLoad() - self.configureDelegation() - self.configureNavigation() - } - - // MARK: - override - - override func endEditingView() { - self.participateRoomView.endEditing() - } - - // MARK: - func - - private func configureDelegation() { - self.participateRoomView.configureDelegate(self) - } - - private func configureNavigation() { - guard let navigationController = self.navigationController else { return } - self.participateRoomView.configureNavigationBarItem(navigationController) - } - - // MARK: - network - - private func dispatchInviteCode(_ code : String) { - Task { - do { - let data = try await self.roomParticipationRepository.dispatchVerifyCode(code: code) - guard let id = data.id else { return } - let viewController = CheckRoomViewController() - viewController.modalPresentationStyle = .overFullScreen - viewController.modalTransitionStyle = .crossDissolve - viewController.roomInfo = data - viewController.roomId = id - - self.present(viewController, animated: true) - } catch NetworkError.serverError { - print("server Error") - } catch NetworkError.encodingError { - print("encoding Error") - } catch NetworkError.clientError(let message) { - self.makeAlert(title: TextLiteral.checkRoomViewControllerErrorAlertTitle, message: TextLiteral.checkRoomViewControllerErrorAlertMessage) - print("client Error: \(String(describing: message))") - } - } - } -} - -extension ParticipateRoomViewController: ParticipateRoomViewDelegate { - func closeButtonDidTap() { - self.dismiss(animated: true) - } - - func nextButtonDidTap(code: String) { - self.dispatchInviteCode(code) - } - - func observeNextNotification(roomId: Int) { - self.navigationController?.pushViewController(ChooseCharacterViewController(roomId: roomId), animated: true) - } -} diff --git a/Manito/Manito/Screens/ParticipateRoom/UIComponent/InputInvitedCodeView.swift b/Manito/Manito/Screens/ParticipateRoom/UIComponent/InputInvitedCodeView.swift deleted file mode 100644 index d1f27faf8..000000000 --- a/Manito/Manito/Screens/ParticipateRoom/UIComponent/InputInvitedCodeView.swift +++ /dev/null @@ -1,110 +0,0 @@ -// -// InputInvitedCodeView.swift -// Manito -// -// Created by COBY_PRO on 2022/06/15. -// - -import UIKit - -import SnapKit - -final class InputInvitedCodeView: UIView { - - private let maxLength = 6 - - var changeNextButtonEnableStatus: ((Bool) -> ())? - - // MARK: - property - - lazy var roomCodeTextField: UITextField = { - let textField = UITextField() - let attributes = [ - NSAttributedString.Key.font : UIFont.font(.regular, ofSize: 18) - ] - textField.backgroundColor = .darkGrey002 - textField.attributedPlaceholder = NSAttributedString(string: TextLiteral.inputInvitedCodeViewRoomCodeText, attributes: attributes) - textField.textAlignment = .center - textField.makeBorderLayer(color: .white) - textField.font = .font(.regular, ofSize: 18) - textField.returnKeyType = .done - textField.delegate = self - textField.autocorrectionType = .no - textField.autocapitalizationType = .allCharacters - textField.becomeFirstResponder() - return textField - }() - private lazy var roomsTextLimit : UILabel = { - let label = UILabel() - label.text = "\(String(describing: roomCodeTextField.text?.count ?? 0))/\(maxLength)" - label.font = .font(.regular, ofSize: 20) - label.textColor = .grey002 - return label - }() - - // MARK: - life cycle - - override init(frame: CGRect) { - super.init(frame: frame) - render() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func render() { - self.addSubview(roomCodeTextField) - roomCodeTextField.snp.makeConstraints { - $0.top.leading.trailing.equalToSuperview() - $0.height.equalTo(60) - } - - self.addSubview(roomsTextLimit) - roomsTextLimit.snp.makeConstraints { - $0.top.equalTo(roomCodeTextField.snp.bottom).offset(10) - $0.trailing.equalToSuperview().inset(Size.leadingTrailingPadding) - } - } - - // MARK: - func - - private func setCounter(count: Int) { - if count <= maxLength { - roomsTextLimit.text = "\(count)/\(maxLength)" - } else { - roomsTextLimit.text = "\(maxLength)/\(maxLength)" - } - } - - private func checkMaxLength(textField: UITextField, maxLength: Int) { - if let text = textField.text { - if text.count > maxLength { - let endIndex = text.index(text.startIndex, offsetBy: maxLength) - let fixedText = text[text.startIndex.. Bool { - roomCodeTextField.resignFirstResponder() - } - - func textFieldDidChangeSelection(_ textField: UITextField) { - setCounter(count: textField.text?.count ?? 0) - checkMaxLength(textField: roomCodeTextField, maxLength: maxLength) - - guard let textCount = roomCodeTextField.text?.count else { return } - let hasText = textCount >= maxLength - changeNextButtonEnableStatus?(hasText) - - textField.text = textField.text?.uppercased() - } -} diff --git a/Manito/Manito/Screens/Setting/ViewControllers/SettingViewController+MailComposeViewControllerDelegate.swift b/Manito/Manito/Screens/Setting/ViewControllers/SettingViewController+MailComposeViewControllerDelegate.swift deleted file mode 100644 index f6ebf3169..000000000 --- a/Manito/Manito/Screens/Setting/ViewControllers/SettingViewController+MailComposeViewControllerDelegate.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// SettingViewController+MailComposeViewControllerDelegate.swift -// Manito -// -// Created by 이성호 on 2023/05/12. -// - -import MessageUI - -// MARK: - MFMailComposeViewControllerDelegate -extension SettingViewController: MFMailComposeViewControllerDelegate { - func sendReportMail() { - if MFMailComposeViewController.canSendMail() { - let composeViewController = MFMailComposeViewController() - let aenittoEmail = "aenitto@gmail.com" - let messageBody = """ - - ----------------------------- - - - 문의하는 닉네임: \(String(describing: UserDefaultStorage.nickname)) - - 문의 메시지 제목 한줄 요약: - - 문의 날짜: \(Date()) - - ------------------------------ - - 문의 내용을 작성해주세요. - - """ - - composeViewController.mailComposeDelegate = self - composeViewController.setToRecipients([aenittoEmail]) - composeViewController.setSubject("[문의 사항]") - composeViewController.setMessageBody(messageBody, isHTML: false) - - self.present(composeViewController, animated: true, completion: nil) - } - else { - self.showSendMailErrorAlert() - } - } - - private func showSendMailErrorAlert() { - let sendMailErrorAlert = UIAlertController(title: "메일 전송 실패", message: "아이폰 이메일 설정을 확인하고 다시 시도해주세요.", preferredStyle: .alert) - let confirmAction = UIAlertAction(title: "확인", style: .default) - sendMailErrorAlert.addAction(confirmAction) - self.present(sendMailErrorAlert, animated: true, completion: nil) - } - - func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { - controller.dismiss(animated: true, completion: nil) - } -} diff --git a/Manito/Manito/Screens/Setting/ViewControllers/SettingViewController.swift b/Manito/Manito/Screens/Setting/ViewControllers/SettingViewController.swift deleted file mode 100644 index 79cdd81ae..000000000 --- a/Manito/Manito/Screens/Setting/ViewControllers/SettingViewController.swift +++ /dev/null @@ -1,160 +0,0 @@ -// -// SettingViewController.swift -// Manito -// -// Created by 이성호 on 2022/07/02. -// - -import Combine -import UIKit - -import SnapKit - -final class SettingViewController: BaseViewController { - - // MARK: - ui component - - private let settingView: SettingView = SettingView() - - // MARK: - property - - private var cancellable = Set() - private let viewModel: SettingViewModel - - // MARK: - init - - init(viewModel: SettingViewModel) { - self.viewModel = viewModel - super.init() - } - - @available(*, unavailable) - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - print("\(#file) is dead") - } - - // MARK: - life cycle - - override func loadView() { - self.view = self.settingView - } - - override func viewDidLoad() { - super.viewDidLoad() - self.configureDelegation() - self.bindViewModel() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - self.configureNavigationBar() - } - - // MARK: - func - - private func configureDelegation() { - self.settingView.configureDelegate(self) - } - - private func configureNavigationBar() { - self.navigationController?.navigationBar.prefersLargeTitles = false - } - - private func bindViewModel() { - let output = self.transformedOutput() - self.bindOutputToViewModel(output) - } - - private func transformedOutput() -> SettingViewModel.Output { - let input = SettingViewModel.Input(withdrawalButtonDidTap: self.settingView.withdrawalButtonPublisher.eraseToAnyPublisher(), - logoutButtonDidTap: self.settingView.logoutButtonPublisher.eraseToAnyPublisher()) - return viewModel.transform(from: input) - } - - private func bindOutputToViewModel(_ output: SettingViewModel.Output) { - output.deleteUser - .receive(on: DispatchQueue.main) - .sink { [weak self] result in - switch result { - case .finished: return - case .failure(_): - self?.makeAlert(title: TextLiteral.fail, message: TextLiteral.settingViewControllerFailMessage) - } - } receiveValue: { [weak self] _ in - self?.deleteUser() - } - .store(in: &self.cancellable) - - output.logout - .receive(on: DispatchQueue.main) - .sink { [weak self] _ in - self?.logout() - } - .store(in: &self.cancellable) - } - - private func deleteUser() { - UserDefaultHandler.clearAllDataExcludingFcmToken() - - guard let sceneDelgate = UIApplication.shared.connectedScenes.first?.delegate - as? SceneDelegate else { return } - sceneDelgate.moveToLoginViewController() - } - - private func logout() { - guard let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate - as? SceneDelegate else { return } - sceneDelegate.moveToLoginViewController() - } -} - -// MARK: - Extensions - -extension SettingViewController: SettingViewDelegate { - func changNicknameButtonDidTap() { - self.navigationController?.pushViewController(ChangeNicknameViewController(viewModel: NicknameViewModel(nicknameService: NicknameService(repository: SettingRepositoryImpl()))), animated: true) - } - - func personalInfomationButtonDidTap() { - if let url = URL(string: URLLiteral.personalInfomationUrl) { - UIApplication.shared.open(url, options: [:]) - } - } - - func termsOfServiceButtonDidTap() { - if let url = URL(string: URLLiteral.termsOfServiceUrl) { - UIApplication.shared.open(url, options: [:]) - } - } - - func developerInfoButtonDidTap() { - self.navigationController?.pushViewController(SettingDeveloperInfoViewController(), animated: true) - } - - func helpButtonDidTap() { - self.sendReportMail() - } - - func logoutButtonDidTap() { - self.makeRequestAlert(title: TextLiteral.settingViewControllerLogoutAlertTitle, - message: "", - okTitle: TextLiteral.confirm, - cancelTitle: TextLiteral.cancel, - okAction: { [weak self] _ in - self?.settingView.logoutButtonPublisher.send() - }) - } - - func withdrawalButtonDidTap() { - self.makeRequestAlert(title: TextLiteral.alert, - message: TextLiteral.settingViewControllerWithdrawalMessage, - okTitle: TextLiteral.settingViewControllerWithdrawal, - okAction: { [weak self] _ in - self?.settingView.withdrawalButtonPublisher.send() - }) - } -} diff --git a/Manito/Manito/Screens/Setting/ViewModel/SettingViewModel.swift b/Manito/Manito/Screens/Setting/ViewModel/SettingViewModel.swift deleted file mode 100644 index acfb86611..000000000 --- a/Manito/Manito/Screens/Setting/ViewModel/SettingViewModel.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// SettingViewModel.swift -// Manito -// -// Created by 이성호 on 2023/09/01. -// - -import Combine -import Foundation - -final class SettingViewModel { - - // MARK: - property - - private let settingService: SettingService - private var cancellable = Set() - - private let deleteUserSubject = PassthroughSubject() - private let logoutSubject = PassthroughSubject() - - struct Input { - let withdrawalButtonDidTap: AnyPublisher - let logoutButtonDidTap: AnyPublisher - } - - struct Output { - let deleteUser: PassthroughSubject - let logout: PassthroughSubject - } - - func transform(from input: Input) -> Output { - input.withdrawalButtonDidTap - .sink(receiveValue: { [weak self] _ in - self?.requestDeleteUser() - }) - .store(in: &self.cancellable) - - input.logoutButtonDidTap - .sink { [weak self] _ in - UserDefaultHandler.clearAllDataExcludingFcmToken() - self?.logoutSubject.send() - } - .store(in: &self.cancellable) - - return Output(deleteUser: self.deleteUserSubject, - logout: self.logoutSubject) - } - - // MARK: - init - - init(settingService: SettingService) { - self.settingService = settingService - } - - // MARK: - func - - private func requestDeleteUser() { - Task { - do { - let statusCode = try await self.settingService.deleteUser() - switch statusCode { - case 200..<300: - self.deleteUserSubject.send() - default: - self.deleteUserSubject.send(completion: .failure(.unknownError)) - } - } catch(let error) { - guard let error = error as? NetworkError else { return } - self.deleteUserSubject.send(completion: .failure(error)) - } - } - } -} diff --git a/Manito/Manito/Screens/SettingDeveloperInfo/Cell/DeveloperInfoViewCell.swift b/Manito/Manito/Screens/SettingDeveloperInfo/Cell/DeveloperInfoViewCell.swift index ff6db443e..0d2aedae4 100644 --- a/Manito/Manito/Screens/SettingDeveloperInfo/Cell/DeveloperInfoViewCell.swift +++ b/Manito/Manito/Screens/SettingDeveloperInfo/Cell/DeveloperInfoViewCell.swift @@ -15,7 +15,7 @@ class DeveloperInfoViewCell: UICollectionViewCell, BaseViewType { let developerImageView: UIImageView = { let imageView = UIImageView() - imageView.image = ImageLiterals.imgNi + imageView.image = UIImage.Image.ni return imageView }() @@ -57,7 +57,7 @@ class DeveloperInfoViewCell: UICollectionViewCell, BaseViewType { addSubview(developerImageView) developerImageView.snp.makeConstraints { $0.top.bottom.equalToSuperview().inset(10) - $0.leading.equalToSuperview().inset(Size.leadingTrailingPadding) + $0.leading.equalToSuperview().inset(SizeLiteral.leadingTrailingPadding) $0.width.height.equalTo(80) } diff --git a/Manito/Manito/Screens/SettingDeveloperInfo/SettingDeveloperInfoViewController.swift b/Manito/Manito/Screens/SettingDeveloperInfo/SettingDeveloperInfoViewController.swift index 48edd106c..27062e6bc 100644 --- a/Manito/Manito/Screens/SettingDeveloperInfo/SettingDeveloperInfoViewController.swift +++ b/Manito/Manito/Screens/SettingDeveloperInfo/SettingDeveloperInfoViewController.swift @@ -9,47 +9,47 @@ import UIKit import SnapKit -final class SettingDeveloperInfoViewController: BaseViewController, BaseViewControllerType { +final class SettingDeveloperInfoViewController: UIViewController, BaseViewControllerType, Navigationable { - // 개발자 정보 데이터 + // FIXME: - 개발자 Data를 따로 빼고 그 안에서 데이터 관리 private let developerData: [[String: Any]] = [ [ - "image": ImageLiterals.imgMaCoby, + "image": UIImage.Image.coby, "name": "김도영 Coby", "info": "디너를 좋아하는 코비" ], [ - "image": ImageLiterals.imgMaLeo, + "image": UIImage.Image.leo, "name": "방석진 Leo", "info": "서버를 위해 온 천사 리오" ], [ - "image": ImageLiterals.imgMaDuna, + "image": UIImage.Image.duna, "name": "신윤아 Duna", "info": "그저 신! 갓듀나^__^" ], [ - "image": ImageLiterals.imgMaHoya, + "image": UIImage.Image.hoya, "name": "이성호 Hoya", "info": "아낌없이 주고 (마시는) 호야" ], [ - "image": ImageLiterals.imgMaDinner, + "image": UIImage.Image.dinner, "name": "이정환 Dinner", "info": "하면 다 잘 하는 디너" ], [ - "image": ImageLiterals.imgMaChemi, + "image": UIImage.Image.chemi, "name": "최민관 Chemi", "info": "우직하고 호기심 가득한 케미" ], [ - "image": ImageLiterals.imgMaLivvy, + "image": UIImage.Image.livvy, "name": "최성희 Livvy", "info": "여려 보이지만 강한 리비" ], [ - "image": ImageLiterals.imgMaDaon, + "image": UIImage.Image.daon, "name": "홍지혜 Daon", "info": "서버를 위해 온 천사 다온" ] @@ -106,20 +106,14 @@ final class SettingDeveloperInfoViewController: BaseViewController, BaseViewCont override func viewDidLoad() { super.viewDidLoad() self.baseViewDidLoad() + self.setupNavigation() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) setupLargeTitle() } - - // MARK: - override - - override func setupNavigationBar() { - super.setupNavigationBar() - title = TextLiteral.settingDeveloperInfoTitle - } - + // MARK: - base func func setupLayout() { @@ -132,6 +126,7 @@ final class SettingDeveloperInfoViewController: BaseViewController, BaseViewCont func configureUI() { self.view.backgroundColor = .backgroundGrey + self.title = TextLiteral.Setting.developerInfo.localized() } // MARK: - func diff --git a/Manito/Manito/Screens/SettingDeveloperInfo/UIComponent/SettingDeveloperInfoHeaderView.swift b/Manito/Manito/Screens/SettingDeveloperInfo/UIComponent/SettingDeveloperInfoHeaderView.swift index 5e2f1878e..33d5ef9d6 100644 --- a/Manito/Manito/Screens/SettingDeveloperInfo/UIComponent/SettingDeveloperInfoHeaderView.swift +++ b/Manito/Manito/Screens/SettingDeveloperInfo/UIComponent/SettingDeveloperInfoHeaderView.swift @@ -13,7 +13,7 @@ final class SettingDeveloperInfoHeaderView: UICollectionReusableView { // MARK: - property - private let developerRoomView = UIImageView(image: ImageLiterals.imgDevBackground) + private let developerRoomView = UIImageView(image: UIImage.Image.devBackground) // MARK: - init diff --git a/Manito/Manito/Screens/Splash/SplashViewController.swift b/Manito/Manito/Screens/Splash/SplashViewController.swift deleted file mode 100644 index 78bbb7f9c..000000000 --- a/Manito/Manito/Screens/Splash/SplashViewController.swift +++ /dev/null @@ -1,93 +0,0 @@ -// -// SplashViewController.swift -// Manito -// -// Created by SHIN YOON AH on 2022/06/16. -// - -import UIKit - -import Gifu - -final class SplashViewController: UIViewController, BaseViewControllerType { - - // MARK: - ui component - - @IBOutlet weak var gifImageView: GIFImageView! - - // MARK: - property - - private let isLogin: Bool = UserDefaultStorage.isLogin - private let nickname: String? = UserDefaultStorage.nickname - private let isSetFcmToken: Bool = UserDefaultStorage.isSetFcmToken - - // MARK: - init - - deinit { - print("\(#file) is dead") - } - - // MARK: - life cycle - - override func viewDidLoad() { - super.viewDidLoad() - self.baseViewDidLoad() - self.setupGifImage() - self.presentViewControllerAfterDelay() - } - - // MARK: - base func - - func setupLayout() { - // FIXME: - 스토리보드를 코드 베이스로 바꿔야 하는 화면입니다. - } - - func configureUI() { - self.view.backgroundColor = .backgroundGrey - } - - // MARK: - func - - private func presentLoginViewConroller() { - let viewController = LoginViewController() - let navigtionViewController = UINavigationController(rootViewController: viewController) - navigtionViewController.setNavigationBarHidden(true, animated: true) - navigtionViewController.modalPresentationStyle = .fullScreen - navigtionViewController.modalTransitionStyle = .crossDissolve - self.present(navigtionViewController, animated: true) - } - - private func presentNicknameSettingViewController() { - let viewController = CreateNicknameViewController(viewModel: NicknameViewModel(nicknameService: NicknameService(repository: SettingRepositoryImpl()))) - viewController.modalPresentationStyle = .fullScreen - viewController.modalTransitionStyle = .crossDissolve - self.present(viewController, animated: true) - } - - private func presentMainViewController() { - let navigationController = UINavigationController(rootViewController: MainViewController()) - navigationController.modalPresentationStyle = .fullScreen - navigationController.modalTransitionStyle = .crossDissolve - self.present(navigationController, animated: true) - } - - private func setupGifImage() { - DispatchQueue.main.async { - self.gifImageView.animate(withGIFNamed: ImageLiterals.gifLogo) - } - } - - private func presentViewControllerAfterDelay() { - DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { - if !self.isSetFcmToken { - self.presentLoginViewConroller() - } else if self.isLogin { - self.presentMainViewController() - } else if self.isLogin && self.nickname == "" { - self.presentNicknameSettingViewController() - } else { - self.presentLoginViewConroller() - } - } - } -} diff --git a/Manito/Manito/Service/MailComposeManager/MailComposeManager.swift b/Manito/Manito/Service/MailComposeManager/MailComposeManager.swift new file mode 100644 index 000000000..01e4672af --- /dev/null +++ b/Manito/Manito/Service/MailComposeManager/MailComposeManager.swift @@ -0,0 +1,53 @@ +// +// MailComposeManager.swift +// Manito +// +// Created by SHIN YOON AH on 10/13/23. +// + +import MessageUI + +final class MailComposeManager: NSObject { + + // MARK: - property + + weak var viewController: UIViewController? + + // MARK: - func + + func sendMail(title: String, content: String) { + if MFMailComposeViewController.canSendMail() { + self.showMail(title: title, content: content) + } else { + self.showErrorAlert() + } + } + + // MARK: - Private - func + + private func showMail(title: String, content: String) { + let controller = MFMailComposeViewController() + let aenittoEmail = TextLiteral.Mail.aenittoEmail.localized() + controller.mailComposeDelegate = self + controller.setToRecipients([aenittoEmail]) + controller.setSubject(title) + controller.setMessageBody(content, isHTML: false) + self.viewController?.present(controller, animated: true) + } + + private func showErrorAlert() { + let alertController = UIAlertController(title: TextLiteral.Mail.Error.title.localized(), + message: TextLiteral.Mail.Error.message.localized(), + preferredStyle: .alert) + let confirmAction = UIAlertAction(title: TextLiteral.Common.confirm.localized(), style: .default) + alertController.addAction(confirmAction) + self.viewController?.present(alertController, animated: true) + } +} + +// MARK: - MFMailComposeViewControllerDelegate +extension MailComposeManager: MFMailComposeViewControllerDelegate { + func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { + controller.dismiss(animated: true) + } +} diff --git a/Manito/Manito/Service/PhotoPickerManager/Error/PHPickerError.swift b/Manito/Manito/Service/PhotoPickerManager/Error/PHPickerError.swift new file mode 100644 index 000000000..418be3bca --- /dev/null +++ b/Manito/Manito/Service/PhotoPickerManager/Error/PHPickerError.swift @@ -0,0 +1,26 @@ +// +// PHPickerError.swift +// Manito +// +// Created by SHIN YOON AH on 2023/09/27. +// + +import Foundation + +enum PHPickerError: LocalizedError { + case deniedAuthorization + case cantloadPhoto + case cantloadDevice + case cantOpenSetting +} + +extension PHPickerError { + var errorDescription: String? { + switch self { + case .deniedAuthorization: return TextLiteral.SendLetter.Error.photosAuthorizationMessage.localized() + case .cantloadPhoto: return TextLiteral.SendLetter.Error.photoLoadMessage.localized() + case .cantloadDevice: return TextLiteral.SendLetter.Error.deviceMessage.localized() + case .cantOpenSetting: return TextLiteral.SendLetter.Error.settingMessage.localized() + } + } +} diff --git a/Manito/Manito/Service/PhotoPickerManager/PhotoPickerManager.swift b/Manito/Manito/Service/PhotoPickerManager/PhotoPickerManager.swift new file mode 100644 index 000000000..a79ac8acc --- /dev/null +++ b/Manito/Manito/Service/PhotoPickerManager/PhotoPickerManager.swift @@ -0,0 +1,181 @@ +// +// PhotoPickerManager.swift +// Manito +// +// Created by SHIN YOON AH on 2023/09/27. +// + +import AVFoundation +import Combine +import PhotosUI +import UIKit + +final class PhotoPickerManager: NSObject { + + // MARK: - property + + var loadImage: ((Result) -> Void)? + + weak var viewController: UIViewController? + + // MARK: - func + + func openPhotos() { + switch PHPhotoLibrary.authorizationStatus(for: .addOnly) { + case .notDetermined: + self.requestAuthorizationToPHPhotoLibrary() + case .authorized: + self.phPickerControllerDidShow() + default: + self.openSettings() + } + } + + func openCamera() { + guard UIImagePickerController.isSourceTypeAvailable(.camera) else { + self.loadImage?(.failure(.cantloadDevice)) + return + } + + AVCaptureDevice.requestAccess(for: .video) { [weak self] hasGranted in + DispatchQueue.main.async { + hasGranted ? self?.imagePickerControllerDidShow() : self?.openSettings() + } + } + } + + func savePhoto(image: UIImage) { + PHPhotoLibrary.shared().performChanges({ + PHAssetChangeRequest.creationRequestForAsset(from: image) + }) { [weak self] (success, error) in + DispatchQueue.main.async { + if success { + self?.viewController?.makeAlert(title: TextLiteral.Letter.saveAlertTitle.localized(), + message: TextLiteral.Letter.saveAlertMessage.localized()) + } else { + self?.viewController?.makeAlert(title: TextLiteral.Common.Error.title.localized(), + message: TextLiteral.Letter.Error.imageSaveMessage.localized()) + } + } + } + } +} + +extension PhotoPickerManager { + + // MARK: - Private - func + + private func requestAuthorizationToPHPhotoLibrary() { + PHPhotoLibrary.requestAuthorization(for: .addOnly) { [weak self] authorizationStatus in + if authorizationStatus == .authorized { + self?.phPickerControllerDidShow() + } else { + self?.loadImage?(.failure(.deniedAuthorization)) + } + } + } + + private func phPickerControllerDidShow() { + var configuration = PHPickerConfiguration() + configuration.filter = .any(of: [.images, .livePhotos]) + + DispatchQueue.main.async { + let phPickerController = PHPickerViewController(configuration: configuration) + phPickerController.delegate = self + self.viewController?.present(phPickerController, animated: true) + } + } + + private func openSettings() { + let settingAction: ((UIAlertAction) -> Void)? = { [weak self] _ in + guard let settingURL = URL(string: UIApplication.openSettingsURLString) else { + self?.loadImage?(.failure(.cantOpenSetting)) + return + } + UIApplication.shared.open(settingURL) + } + + DispatchQueue.main.async { + self.viewController?.makeRequestAlert(title: TextLiteral.Common.Error.title.localized(), + message: TextLiteral.SendLetter.Error.authorizationMessage.localized(with: Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ?? "애니또"), + okTitle: TextLiteral.SendLetter.Error.buttonSetting.localized(), + okStyle: .default, + okAction: settingAction) + } + } + + private func imagePickerControllerDidShow() { + let imagePickerController = UIImagePickerController() + imagePickerController.delegate = self + imagePickerController.sourceType = .camera + + DispatchQueue.main.async { + self.viewController?.present(imagePickerController, animated: true) + } + } +} + +// MARK: - UIImagePickerControllerDelegate & UINavigationControllerDelegate +extension PhotoPickerManager: UIImagePickerControllerDelegate & UINavigationControllerDelegate { + func imagePickerController(_ picker: UIImagePickerController, + didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { + if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { + self.loadImage?(.success(image)) + } else { + self.loadImage?(.failure(.cantloadPhoto)) + } + } +} + +// MARK: - PHPickerViewControllerDelegate +extension PhotoPickerManager: PHPickerViewControllerDelegate { + func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) { + if let selectedAsset = results.first?.itemProvider { + self.pickerController(picker, didFinishPicking: selectedAsset) + } else { + self.pickerControllerDidCancel(picker) + } + } + + private func pickerControllerDidCancel(_ picker: PHPickerViewController) { + DispatchQueue.main.async { + picker.dismiss(animated: true) + } + } + + private func pickerController(_ picker: PHPickerViewController, didFinishPicking asset: NSItemProvider) { + self.loadObject(for: asset) { [weak self] result in + switch result { + case .success(let image): + DispatchQueue.main.async { + picker.dismiss(animated: true, completion: { + self?.loadImage?(.success(image)) + }) + } + case .failure(let error): + DispatchQueue.main.async { + picker.dismiss(animated: true, completion: { + self?.loadImage?(.failure(error)) + }) + } + } + } + } + + private func loadObject(for itemProvider: NSItemProvider, + completionHandler: @escaping ((Result) -> ())) { + guard itemProvider.canLoadObject(ofClass: UIImage.self) else { completionHandler(.failure(.cantloadPhoto)) + return + } + + itemProvider.loadObject(ofClass: UIImage.self) { image, error in + if error != nil { + completionHandler(.failure(.cantloadPhoto)) + } + + if let image = image as? UIImage { + completionHandler(.success(image)) + } + } + } +} diff --git a/Manito/Manito/Util/Extension/Combine/UIControl+Combine.swift b/Manito/Manito/Util/Extension/Combine/UIControl+Combine.swift index a5f2ac25e..5f8223b11 100644 --- a/Manito/Manito/Util/Extension/Combine/UIControl+Combine.swift +++ b/Manito/Manito/Util/Extension/Combine/UIControl+Combine.swift @@ -74,3 +74,20 @@ extension UISegmentedControl { .eraseToAnyPublisher() } } + +extension UIControl { + var buttonTapPublisher: AnyPublisher { + self.controlPublisher(for: .touchUpInside) + .map { _ in Void() } + .eraseToAnyPublisher() + } +} + +extension UISlider { + var valuePublisher: AnyPublisher { + controlPublisher(for: .valueChanged) + .map { $0 as! UISlider } + .map { Int($0.value) } + .eraseToAnyPublisher() + } + } diff --git a/Manito/Manito/Util/Extension/Type/Date+Extension.swift b/Manito/Manito/Util/Extension/Type/Date+Extension.swift index cd6f768ce..17f7358d5 100644 --- a/Manito/Manito/Util/Extension/Type/Date+Extension.swift +++ b/Manito/Manito/Util/Extension/Type/Date+Extension.swift @@ -8,38 +8,31 @@ import Foundation extension Date { - var dateToString: String { - let formatter = DateFormatter() - formatter.dateFormat = "yy.MM.dd" - return formatter.string(from: self) - } - - var letterDateToString: String { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy.MM.dd" - return formatter.string(from: self) - } - + /// 해당 날짜가 오늘인지 var isToday: Bool { let now = Date() let distance = self.distance(to: now) return distance > 0 && distance < 86400 } - var isOverOpenTime: Bool { - let now = Date() - let nineHoursTimeInterval: TimeInterval = 32400 - let dateAddNineHours = self + nineHoursTimeInterval - let distance = dateAddNineHours.distance(to: now) - return distance > 0 && distance < 54000 + /// 해당 날짜가 지난날인지 + var isPast: Bool { + let distance = self.distance(to: Date()) + return distance > 86400 } - var isOpenManitto: Bool { - return self.isToday && self.isOverOpenTime + /// Date 값을 yy.MM.dd 형식의 String 값으로 변환 + var toDefaultString: String { + let formatter = DateFormatter() + formatter.dateFormat = "yy.MM.dd" + formatter.locale = Locale(identifier: "ko_KR") + return formatter.string(from: self) } - var isPast: Bool { - let distance = self.distance(to: Date()) - return distance > 86400 + /// Date 값을 yyyy.MM.dd 형식의 String 값으로 변환 + var toFullString: String { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy.MM.dd" + return formatter.string(from: self) } } diff --git a/Manito/Manito/Util/Extension/Type/Notification+Extension.swift b/Manito/Manito/Util/Extension/Type/Notification+Extension.swift deleted file mode 100644 index f01d6d829..000000000 --- a/Manito/Manito/Util/Extension/Type/Notification+Extension.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// UINotification+Extension.swift -// Manito -// -// Created by 이성호 on 2022/06/18. -// - -import Foundation - -extension Notification.Name { - static let nextNotification = Notification.Name("NextNotification") - static let createRoomInvitedCode = Notification.Name("CreateRoomInvitedCode") -} diff --git a/Manito/Manito/Util/Extension/Type/String+Date.swift b/Manito/Manito/Util/Extension/Type/String+DateFormatter.swift similarity index 53% rename from Manito/Manito/Util/Extension/Type/String+Date.swift rename to Manito/Manito/Util/Extension/Type/String+DateFormatter.swift index d42d579ae..fa79eea75 100644 --- a/Manito/Manito/Util/Extension/Type/String+Date.swift +++ b/Manito/Manito/Util/Extension/Type/String+DateFormatter.swift @@ -8,23 +8,19 @@ import Foundation extension String { - var stringToDate: Date? { + /// String 값을 yy.MM.dd 형식의 Date 값으로 변환 + var toDefaultDate: Date? { let formatter = DateFormatter() formatter.dateFormat = "yy.MM.dd" + formatter.locale = Locale(identifier: "ko_KR") return formatter.date(from: self) } - func subStringToDate() -> String { - let startIdx: String.Index = self.index(self.startIndex, offsetBy: 2) - return String(self[startIdx...]) - } - - func stringToDateYYYY() -> Date? { + /// String 값을 yyyy.MM.dd 형식의 Date 값으로 변환 + var toFullDate: Date? { let formatter = DateFormatter() formatter.dateFormat = "yyyy.MM.dd" - formatter.timeZone = TimeZone(abbreviation: "KST") formatter.locale = Locale(identifier: "ko_KR") - let date = formatter.date(from: self) - return date + return formatter.date(from: self) } } diff --git a/Manito/Manito/Util/Extension/UI/UIColor+Extension.swift b/Manito/Manito/Util/Extension/UI/UIColor+Extension.swift deleted file mode 100644 index 11f18a136..000000000 --- a/Manito/Manito/Util/Extension/UI/UIColor+Extension.swift +++ /dev/null @@ -1,168 +0,0 @@ -// -// UIColor+Extension.swift -// Manito -// -// Created by SHIN YOON AH on 2022/06/09. -// - -import UIKit - -extension UIColor { - - // MARK: - red - - static var mainRed: UIColor { - return UIColor(hex: "#CA2214") - } - - static var shadowRed: UIColor { - return UIColor(hex: "#A4291F") - } - - static var red001: UIColor { - return UIColor(hex: "#C84842") - } - - static var red002: UIColor { - return UIColor(hex: "#823029") - } - - // MARK: - background - - static var backgroundGrey: UIColor { - return UIColor(hex: "#242424") - } - - // MARK: - grey - - static var grey001: UIColor { - return UIColor(hex: "#D9D9D9") - } - - static var grey002: UIColor { - return UIColor(hex: "#A5A5A5") - } - - static var grey003: UIColor { - return UIColor(hex: "#828282") - } - - static var grey004: UIColor { - return UIColor(hex: "#717174") - } - - static var grey005: UIColor { - return UIColor(hex: "#C1C1C1") - } - - // MARK: - darkGrey - - static var darkGrey001: UIColor { - return UIColor(hex: "#616161") - } - - static var darkGrey002: UIColor { - return UIColor(hex: "#5A5A5A") - } - - static var darkGrey003: UIColor { - return UIColor(hex: "#3D3D3D") - } - - static var darkGrey004: UIColor { - return UIColor(hex: "#343434") - } - - // MARK: - orange - - static var subOrange: UIColor { - return UIColor(hex: "#EAB33D") - } - - // MARK: - blue - - static var subBlue: UIColor { - return UIColor(hex: "#3472EB") - } - - static var backgroundBlue: UIColor { - return UIColor(hex: "#8BC4E7") - } - - static var codeBlue: UIColor { - return UIColor(hex: "#001AFF") - } - // MARK: - yellow - - static var yellow: UIColor { - return UIColor(hex: "#EDC845") - } - - static var shadowYellow: UIColor { - return UIColor(hex: "#C7A83C") - } - - // MARK: - pink - - static var subPink: UIColor { - return UIColor(hex: "#F18DB1") - } - - // MARK: - badge - - static var badgeBeige: UIColor { - return UIColor(hex: "#FFDBBA") - } - - // MARK: - character - - static var characterYellow: UIColor { - return UIColor(hex: "#EFDC4A") - } - - static var characterRed: UIColor { - return UIColor(hex: "#D03D40") - } - - static var characterOrange: UIColor { - return UIColor(hex: "#D78041") - } - - static var characterBlue: UIColor { - return UIColor(hex: "#0811CD") - } - - static var characterLightGreen: UIColor { - return UIColor(hex: "#8AB542") - } - - static var characterPurple: UIColor { - return UIColor(hex: "#8B3183") - } - - static var characterGreen: UIColor { - return UIColor(hex: "#43844E") - } - - static var characterPink: UIColor { - return UIColor(hex: "#E46593") - } -} - -extension UIColor { - convenience init(hex: String, alpha: CGFloat = 1.0) { - var hexFormatted: String = hex.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).uppercased() - - if hexFormatted.hasPrefix("#") { - hexFormatted = String(hexFormatted.dropFirst()) - } - - assert(hexFormatted.count == 6, "Invalid hex code used.") - var rgbValue: UInt64 = 0 - Scanner(string: hexFormatted).scanHexInt64(&rgbValue) - - self.init(red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, - green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, - blue: CGFloat(rgbValue & 0x0000FF) / 255.0, alpha: alpha) - } -} diff --git a/Manito/Manito/Util/Extension/UI/UIFont+Extension.swift b/Manito/Manito/Util/Extension/UI/UIFont+Extension.swift deleted file mode 100644 index 0fb822487..000000000 --- a/Manito/Manito/Util/Extension/UI/UIFont+Extension.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// UIFont+Extension.swift -// Manito -// -// Created by SHIN YOON AH on 2022/06/09. -// - -import UIKit - -enum AppFontName: String { - case regular = "DungGeunMo" -} - -extension UIFont { - static func font(_ style: AppFontName, ofSize size: CGFloat) -> UIFont { - return UIFont(name: style.rawValue, size: size)! - } -} diff --git a/Manito/Manito/Util/Extension/UI/UIView/UIImageView+Cache.swift b/Manito/Manito/Util/Extension/UI/UIView/UIImageView+Cache.swift index 20807ab95..d444aa9cf 100644 --- a/Manito/Manito/Util/Extension/UI/UIView/UIImageView+Cache.swift +++ b/Manito/Manito/Util/Extension/UI/UIView/UIImageView+Cache.swift @@ -23,8 +23,10 @@ extension UIImageView { guard let url = URL(string: url) else { return } URLSession.shared.dataTask(with: url) { data, response, error in + // FIXME: - 데이터베이스에서 이미지가 날라가면서 URL은 존재하는데 IMAGE가 없는 경우가 발생했습니다. + // Error가 발생하는 경우를 후에 추가하겠습니다. guard let data = data else { return } - let image = UIImage(data: data)! + guard let image = UIImage(data: data) else { return } DispatchQueue.main.async { ImageCacheManager.shared.setObject(image, forKey: cachedKey) self.image = image diff --git a/Manito/Manito/Util/Extension/UI/UIView/UILabel+Extension.swift b/Manito/Manito/Util/Extension/UI/UIView/UILabel+Config.swift similarity index 65% rename from Manito/Manito/Util/Extension/UI/UIView/UILabel+Extension.swift rename to Manito/Manito/Util/Extension/UI/UIView/UILabel+Config.swift index 93dc70734..b85bdd7ae 100644 --- a/Manito/Manito/Util/Extension/UI/UIView/UILabel+Extension.swift +++ b/Manito/Manito/Util/Extension/UI/UIView/UILabel+Config.swift @@ -1,5 +1,5 @@ // -// UILabel+Extension.swift +// UILabel+Config.swift // Manito // // Created by SHIN YOON AH on 2022/06/12. @@ -19,22 +19,6 @@ extension UILabel { } } - func setTyping(text: String, characterDelay: TimeInterval = 5.0) { - self.text = "" - - let writingTask = DispatchWorkItem { [weak self] in - text.forEach { char in - DispatchQueue.main.async { - self?.text?.append(char) - } - Thread.sleep(forTimeInterval: characterDelay/100) - } - } - - let queue: DispatchQueue = .init(label: "typespeed", qos: .userInteractive) - queue.asyncAfter(deadline: .now() + 0.7, execute: writingTask) - } - func applyColor(to targetString: String, with color: UIColor) { if let labelText = self.text, labelText.count > 0 { let attributedString = NSMutableAttributedString(string: labelText) diff --git a/Manito/Manito/Util/Extension/UI/UIView/UIView+Animation.swift b/Manito/Manito/Util/Extension/UI/UIView/UIView+Animation.swift new file mode 100644 index 000000000..5bb406ecf --- /dev/null +++ b/Manito/Manito/Util/Extension/UI/UIView/UIView+Animation.swift @@ -0,0 +1,27 @@ +// +// UIView+Animation.swift +// Manito +// +// Created by SHIN YOON AH on 2023/09/09. +// + +import UIKit + +extension UIView { + func fadeIn(duration: TimeInterval = 0.4, + delay: TimeInterval = 0.0, + completion: @escaping ((Bool) -> ()) = { (_: Bool) -> () in }) { + UIView.animate(withDuration: duration, + delay: delay, + options: .curveEaseIn, + animations: { + self.alpha = 1.0 + }, completion: completion) + } + + func fadeOut(duration: TimeInterval = 0.3) { + UIView.animate(withDuration: duration) { + self.alpha = 0.0 + } + } +} diff --git a/Manito/Manito/Util/Extension/UI/UIView/UIView+Extension.swift b/Manito/Manito/Util/Extension/UI/UIView/UIView+Extension.swift index 67abf58b2..c8898b143 100644 --- a/Manito/Manito/Util/Extension/UI/UIView/UIView+Extension.swift +++ b/Manito/Manito/Util/Extension/UI/UIView/UIView+Extension.swift @@ -8,7 +8,7 @@ import UIKit extension UIView { - + // FIXME: - CalendarView, CreateLetterPhotoView 내부에서 삭제한 후 제거 예정 var viewController: UIViewController? { if let nextResponder = self.next as? UIViewController { return nextResponder @@ -38,21 +38,4 @@ extension UIView { layer.borderColor = color.cgColor return self } - - func fadeIn(duration: TimeInterval = 0.4, - delay: TimeInterval = 0.0, - completion: @escaping ((Bool) -> ()) = { (_: Bool) -> () in }) { - UIView.animate(withDuration: duration, - delay: delay, - options: .curveEaseIn, - animations: { - self.alpha = 1.0 - }, completion: completion) - } - - func fadeOut(duration: TimeInterval = 0.3) { - UIView.animate(withDuration: duration) { - self.alpha = 0.0 - } - } } diff --git a/Manito/Manito/Util/Extension/UI/UIViewController/UIViewController+Extension.swift b/Manito/Manito/Util/Extension/UI/UIViewController/UIViewController+Extension.swift new file mode 100644 index 000000000..19d3a8a26 --- /dev/null +++ b/Manito/Manito/Util/Extension/UI/UIViewController/UIViewController+Extension.swift @@ -0,0 +1,21 @@ +// +// UIViewController+Extension.swift +// Manito +// +// Created by Mingwan Choi on 2023/09/11. +// + +import UIKit + +extension UIViewController { + func makeBarButtonItem(with view: T) -> UIBarButtonItem { + return UIBarButtonItem(customView: view) + } + + func removeBarButtonItemOffset(with button: UIButton, offsetX: CGFloat = 0, offsetY: CGFloat = 0) -> UIView { + let offsetView = UIView(frame: CGRect(x: 0, y: 0, width: 45, height: 45)) + offsetView.bounds = offsetView.bounds.offsetBy(dx: offsetX, dy: offsetY) + offsetView.addSubview(button) + return offsetView + } +} diff --git a/Manito/Manito/Util/Literal/GIFSet.swift b/Manito/Manito/Util/Literal/GIFSet.swift new file mode 100644 index 000000000..df617025c --- /dev/null +++ b/Manito/Manito/Util/Literal/GIFSet.swift @@ -0,0 +1,17 @@ +// +// GIFSet.swift +// Manito +// +// Created by SHIN YOON AH on 2023/09/10. +// + +import Foundation + +enum GIFSet { + static let logo = "logo.gif" + static let joystick = "joystick" + static let capsule = "capsule" + static let ma = "gifMa" + static let ni = "gifNi" + static let tto = "gifTto" +} diff --git a/Manito/Manito/Util/Literal/ImageLiteral.swift b/Manito/Manito/Util/Literal/ImageLiteral.swift deleted file mode 100644 index 3cc3e1a37..000000000 --- a/Manito/Manito/Util/Literal/ImageLiteral.swift +++ /dev/null @@ -1,104 +0,0 @@ -// -// ImageLiteral.swift -// Manito -// -// Created by SHIN YOON AH on 2022/06/09. -// - -import UIKit - -enum ImageLiterals { - - // MARK: - icon - - static var icList: UIImage { .load(name: "btnList") } - static var icManiTti: UIImage { .load(name: "btnManiTti") } - static var icNewRoom: UIImage { .load(name: "btnNewRoom") } - static var icBack: UIImage { .load(name: "ic_back")} - static var icInsta: UIImage { .load(name: "ic_insta")} - static var icReport: UIImage { .load(name: "ic_report")} - static var icMore: UIImage { .load(name: "ic_more")} - static var icLetterMissionInfo: UIImage { .load(name: "ic_letterInfo")} - static var icMissionInfo: UIImage { .load(name: "ic_missionInfo")} - static var icRight: UIImage { .load(name: "ic_right")} - static var icSave: UIImage { .load(name: "ic_save")} - static var icPencil: UIImage { .load(name: "ic_pencil") } - - // MARK: - button - - static var btnBack: UIImage { .load(name: "ic_back") } - static var btnXmark: UIImage { .load(name: "ic_exit") } - static var btnSetting: UIImage { .load(name: "ic_setting") } - static var btnCamera: UIImage { .load(name: "ic_camera") } - - // MARK: - image - - static var imgAppIcon: UIImage { .load(name: "imgAppIcon")} - static var imgLogo: UIImage { .load(name: "imgLogo") } - static var imgTextLogo: UIImage { .load(name: "imgTextLogo")} - static var imgBackground: UIImage { .load(name: "imgBackground") } - static var imgDevBackground: UIImage { .load(name: "imgDevBackground") } - static var imgStar: UIImage { .load(name: "imgStar") } - static var imageSliderThumb: UIImage { .load(name: "btnSliderThumb") } - static var imgCharacters: UIImage { .load(name: "img_characters") } - static var imgCodeBackground: UIImage { .load(name: "imgCodeBackground") } - static var imgCommonMisson: UIImage { .load(name: "imgCommonMisson") } - static var imgEnterRoom: UIImage { .load(name: "imgEnterRoom") } - static var imgGuideBox: UIImage { .load(name: "img_guideBox") } - static var imgMa: UIImage { .load(name: "imgMa") } - static var imgNi: UIImage { .load(name: "imgNi") } - static var imgTto: UIImage { .load(name: "imgTto") } - static var imgMaChemi: UIImage { .load(name: "imgMaChemi") } - static var imgMaCoby: UIImage { .load(name: "imgMaCoby") } - static var imgMaDuna: UIImage { .load(name: "imgMaDuna") } - static var imgMaDinner: UIImage { .load(name: "imgMaDinner") } - static var imgMaHoya: UIImage { .load(name: "imgMaHoya") } - static var imgMaLivvy: UIImage { .load(name: "imgMaLivvy") } - static var imgMaDaon: UIImage { .load(name: "imgMaDaon") } - static var imgMaLeo: UIImage { .load(name: "imgMaLeo") } - - // MARK: - gif - - static var gifLogo = "logo" - static var gifJoystick = "joystick" - static var gifCapsule = "capsule" - static var gifMa = "gifMa" - static var gifNi = "gifNi" - static var gifTto = "gifTto" - - // MARK: - character - static var imgCharacterPink: UIImage { .load(name: "imgCharacterPink") } - static var imgCharacterBrown: UIImage { .load(name: "imgCharacterBrown") } - static var imgCharacterBlue: UIImage { .load(name: "imgCharacterBlue") } - static var imgCharacterRed: UIImage { .load(name: "imgCharacterRed") } - static var imgCharacterOrange: UIImage { .load(name: "imgCharacterOrange") } - static var imgCharacterYellow: UIImage { .load(name: "imgCharacterYellow") } - static var imgCharacterLightGreen: UIImage { .load(name: "imgCharacterLightGreen") } - static var imgCharacterHeavyPink: UIImage { .load(name: "imgCharacterHeavyPink") } - static var imgCharacterPurple: UIImage { .load(name: "imgCharacterPurple") } -} - -extension UIImage { - static func load(name: String) -> UIImage { - guard let image = UIImage(named: name, in: nil, compatibleWith: nil) else { - return UIImage() - } - image.accessibilityIdentifier = name - return image - } - - static func load(systemName: String) -> UIImage { - guard let image = UIImage(systemName: systemName, compatibleWith: nil) else { - return UIImage() - } - image.accessibilityIdentifier = systemName - return image - } - - func resize(to size: CGSize) -> UIImage { - let image = UIGraphicsImageRenderer(size: size).image { _ in - draw(in: CGRect(origin: .zero, size: size)) - } - return image - } -} diff --git a/Manito/Manito/Util/Literal/SizeLiteral.swift b/Manito/Manito/Util/Literal/SizeLiteral.swift index fa7543607..d0b122e6d 100644 --- a/Manito/Manito/Util/Literal/SizeLiteral.swift +++ b/Manito/Manito/Util/Literal/SizeLiteral.swift @@ -7,6 +7,6 @@ import UIKit -enum Size { +enum SizeLiteral { static let leadingTrailingPadding: CGFloat = 20 } diff --git a/Manito/Manito/Util/Literal/TextLiteral.swift b/Manito/Manito/Util/Literal/TextLiteral.swift index 9558054a2..919eb5061 100644 --- a/Manito/Manito/Util/Literal/TextLiteral.swift +++ b/Manito/Manito/Util/Literal/TextLiteral.swift @@ -9,233 +9,273 @@ import Foundation enum TextLiteral { - // MARK: - App Name - static let appName = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ?? "애니또" - - // MARK: - AnyWhere - static let done: String = "완료" - static let cancel: String = "취소" - static let doing: String = "진행중" - static let waiting: String = "대기중" - static let confirm: String = "확인" - static let choose: String = "선택" - static let enterRoom: String = "방 참가하기" - static let searchRoom: String = "방 조회하기" - static let during: String = "진행 기간" - static let delete: String = "삭제" - static let leave: String = "나가기" - static let togetherFriend: String = "함께하는 친구들" - static let copyCode: String = "방 코드 복사" - static let change: String = "변경" - static let modifiedRoomInfo: String = "방 정보 수정" - static let maxMessage: String = "최대 7일까지 선택가능해요" - static let destructive: String = "변경 사항 폐기" - static let per: String = "인" - static let x: String = "X" - static let createRoom: String = "방 생성하기" - static let next: String = "다음" - static let previous: String = "이전" - static let errorAlertTitle: String = "오류 발생" - static let alert: String = "경고" - static let fail: String = "실패" - - // MARK: - SettingDeveloperInfo - static let settingDeveloperInfoTitle: String = "개발자 정보" - - // MARK: - SettingViewController - static let settingViewControllerChangeNickNameTitle: String = "닉네임 변경하기" - static let settingViewControllerPersonalInfomationTitle: String = "개인정보 처리방침" - static let settingViewControllerTermsOfServiceTitle: String = "이용 약관" - static let settingViewControllerDeveloperInfoTitle: String = "개발자 정보" - static let settingViewControllerHelpTitle: String = "문의하기" - static let settingViewControllerLogoutTitle: String = "로그아웃" - static let settingViewControllerWithdrawalTitle: String = "서비스 탈퇴" - static let settingViewControllerLogoutAlertTitle: String = "로그아웃 하시겠습니까?" - static let settingViewControllerWithdrawalMessage: String = "서비스 탈퇴 시 지금까지 내용이 전부 삭제됩니다. \n 탈퇴 하시겠습니까?" - static let settingViewControllerWithdrawal: String = "탈퇴" - static let settingViewControllerFailMessage: String = "서비스 탈퇴에 실패했습니다. 다시 시도해주세요." - - // MARK: - CreateNickNameViewController - static let createNickNameViewControllerTitle: String = "닉네임 설정" - static let createNickNameViewControllerAskNickName: String = "닉네임을 적어주세요" - - // MARK: - CommonMissonView - static let commonMissionViewTitle: String = "오늘의 공통미션" - - // MARK: - ManitoRoomCollectionViewCell - static let manitoRoomCollectionViewCellRoomLabelTitle: String = "마니또" - - // MARK: - CreateRoomCollectionViewCell - static let createRoomCollectionViewCellMenuLabel: String = "새로운 마니또 시작" - - // MARK: - MainViewController - static let mainViewControllerMenuTitle: String = "참여중인 애니또" - static let mainViewControllerGuideText: String = "공통 미션이란?\n마니또에 참여한 모두에게 \n수행하는 미션이에요. " - static let mainViewControllerNewRoomAlert: String = "새로운 마니또 시작" - static let mainViewControllerShowIdErrorAlertTitle: String = "해당 마니또 방의 정보를 불러오지 못했습니다." - static let mainViewControllerShowIdErrorAlertMessage: String = "해당 마니또 방으로 이동할 수 없습니다." - - // MARK: - SelectManitteeViewController - static let selectManitteeViewControllerInformationText: String = - """ - 레버를 스와이프해서 - 내 마니띠를 확인하세요. - """ - - // MARK: - OpenManittoViewController - static let openManittoViewControllerTitle: String = "당신의 마니또는?" - static let openManittoViewControllerErrorTitle: String = "오류" - static let openManittoViewControllerErrorDescription = "마니또를 확인할 수 없습니다." - static let openManittoViewControllerPopupDescription: String = - """ - 내일 함께 했던 추억이 열립니다. - 마니또 방에서 확인해 보세요! - """ - - // MARK: - ChooseCharacterViewController - static let chooseCharacterViewControllerTitleLabel: String = "캐릭터 선택" - static let chooseCharacterViewControllerSubTitleLabel: String = "당신만의 캐릭터를 정해주세요" - - // MARK: - CheckRoomViewController - static let checkRoomViewControllerQuestionLabel: String = "이 방으로 입장할까요?" - static let checkRoomViewControllerNoButtonLabel: String = "NO" - static let checkRoomViewControllerYesBUttonLabel: String = "YES" - static let checkRoomViewControllerErrorAlertTitle: String = "해당하는 애니또 방이 없어요" - static let checkRoomViewControllerErrorAlertMessage: String = "초대 코드를 다시 확인해 주세요" - - // MARK: - InputInvitedCodeView - static let inputInvitedCodeViewRoomCodeText: String = "초대코드 입력" - - // MARK: - LetterHeaderView - static let letterHeaderViewSegmentControlManitti: String = "마니띠에게" - static let letterHeaderViewSegmentControlManitto: String = "마니또로부터" - - // MARK: - BottomSendLetterView - static let sendLetterViewSendLetterButton: String = "쪽지 쓰기" - - // MARK: - IndividualMissionView - static let individualMissionViewTitleLabel: String = "오늘의 개별 미션" - - // MARK: - CreateLetterTextView - static let letterTextViewTitleLabel: String = "쪽지 작성" - - // MARK: - CreateLetterPhotoView - static let letterPhotoViewTitleLabel: String = "사진 추가" - static let letterPhotoViewTakePhoto: String = "사진 촬영" - static let letterPhotoViewChoosePhoto: String = "사진 보관함에서 선택" - static let letterPhotoViewDeletePhoto: String = "사진 지우기" - static let letterPhotoViewChoosePhotoToManitto: String = "마니또에게 보낼 사진 선택" - static let letterPhotoViewSetting: String = "설정" - static let letterPhotoViewFail: String = "사진을 불러올 수 없습니다." - static let letterPhotoViewErrorTitle: String = "오류" - static let letterPhotoViewSettingFail = "설정 화면을 연결할 수 없습니다." - static let letterPhotoViewDeviceFail = "해당 기기에서 카메라를 사용할 수 없습니다." - static let letterPhotoViewSettingAuthorization = "\(appName)가 카메라에 접근이 허용되어 있지 않습니다. 설정화면으로 가시겠습니까?" - - // MARK: - LetterViewController - static let letterViewControllerTitle = "쪽지함" - static let letterViewControllerGuideText = "쪽지 쓰기는?\n마니띠에게만 쓰기가 가능해요.\n마니또로부터는 확인만 할 수 있어요." - static let letterViewControllerEmptyViewFrom = """ - 쪽지함이 비었어요. - 곧 마니또가 쪽지를 보낼거에요! - """ - static let letterViewControllerEmptyViewTo = """ - 쪽지함이 비었어요. - 마니띠에게 쪽지를 보내볼까요? - """ - static let letterViewControllerErrorTitle: String = "오류" - static let letterViewControllerErrorDescription: String = "쪽지를 가져올 수 없습니다." - - // MARK: - LetterImageViewController - static let letterImageViewControllerErrorTitle = "오류 발생" - static let letterImageViewControllerErrorMessage = "사진을 저장할 수 없습니다." - static let letterImageViewControllerSuccessTitle = "저장 성공" - static let letterImageViewControllerSuccessMessage = "사진을 앨범에 저장했어요." - - // MARK: - CreateLetterViewController - static let createLetterViewControllerSendButton: String = "보내기" - static let createLetterViewControllerTitle: String = "쪽지 작성하기" - static let createLetterViewControllerErrorTitle = "오류 발생" - static let createLetterViewControllerErrorMessage = "쪽지 전송에 실패했습니다. 다시 시도해주세요." + /// 프로젝트 내부에서 공통으로 사용하는 텍스트 + enum Common { + static let confirm = "common_confirm" + static let cancel = "common_cancel" + static let done = "common_done" + static let processing = "common_processing" + static let waiting = "common_waiting" + static let enterRoom = "common_enter_room" + static let searchRoom = "common_search_room" + static let createRoom = "common_create_room" + static let people = "common_people" + static let xPeople = "common_x_people" + static let discardChanges = "common_discard_changes" + static let warningTitle = "common_warning_title" + enum Calendar { + static let maxDateContent = "common_calendar_max_date_content" + static let maxAlertTitle = "common_calendar_max_alert_title" + static let pastAlertTitle = "common_calendar_past_alert_title" + static let pastAlertMessage = "common_calendar_past_alert_message" + } + + enum Error { + static let title = "common_error_title" + static let networkServer = "common_network_server_error" + static let networkAuthorized = "common_network_authorized_error" + } + } + + /// Main 화면에서 사용하는 텍스트 + enum Main { + static let listTitle = "main_list_title" + static let menuTitle = "main_menu_title" + static let commonMissionTitle = "main_common_mission_title" + static let cellCreateRoomTitle = "main_cell_create_room_title" + static let guide = "main_guide" + + enum Error { + static let title = "main_error_title" + static let message = "main_error_message" + } + } + + /// Detail 화면에서 공통으로 사용하는 텍스트 + enum Detail { + static let menuModifiedRoomInfo = "detail_menu_modified_room_info" + static let togetherFriendTitle = "detail_together_friend_title" + static let informationTitle = "detail_information_title" + static let manitteeTitle = "detail_manittee_title" + static let change = "detail_change" + static let menuDelete = "detail_menu_delete" + static let menuLeave = "detail_menu_leave" + static let delete = "detail_delete" + static let leave = "detail_leave" + } + + /// Detail Wait 화면에서 사용하는 텍스트 + enum DetailWait { + static let copyCode = "detail_wait_copy_code" + static let buttonWaiting = "datail_wait_button_waiting" + static let buttonStart = "datail_wait_button_start" + static let toastCopyMessage = "detail_wait_toast_copy_message" + static let toastEditMessage = "detail_wait_toast_edit_message" + static let duringTitle = "detail_wait_during_title" + static let pastAlertTitle = "detail_wait_past_alert_title" + static let pastAlertMessage = "detail_wait_past_alert_message" + static let pastAdminAlertMessage = "detail_wait_past_admin_alert_message" + static let deleteAlertTitle = "datail_wait_delete_alert_title" + static let deleteAlertMessage = "datail_wait_delete_alert_message" + static let exitAlertTitle = "datail_wait_exit_alert_title" + static let exitAlertMessage = "datail_wait_exit_alert_message" + + enum Error { + static let fetchRoom = "detail_wait_fetch_error_message" + static let startManitto = "detail_wait_start_error_message" + static let deleteRoom = "detail_wait_delete_error_message" + static let leaveRoom = "detail_wait_leave_error_message" + } + } + + /// Detail Ing 화면에서 사용하는 텍스트 + enum DetailIng { + static let individualMissionTitle = "detail_ing_individual_mission_title" + static let selectManitteeTitle = "detail_ing_select_manittee_title" + static let openManittoTitle = "detail_ing_open_manitto_title" + static let openManittoPopupContent = "detail_ing_open_manitto_popup_content" + static let openManittoPopupHelperContent = "detail_ing_open_manitto_popup_helper_content" + static let guide = "detail_ing_guide" + static let buttonOpen = "detail_ing_button_open" + static let missionMenuSetting = "detail_ing_mission_menu_setting" + static let missionMenuReset = "detail_ing_mission_menu_reset" + static let missionMenuTitle = "detail_ing_mission_menu_title" + static let resetAlertTitle = "detail_ing_reset_alert_title" + static let resetAlertMessage = "detail_ing_reset_alert_message" + static let resetAlertOk = "detail_ing_reset_alert_ok" + static let missionEditAlertTitle = "detail_ing_mission_edit_alert_title" + static let missionEditAlertMessage = "detail_ing_mission_edit_alert_message" + + enum Error { + static let resetMessage = "detail_ing_reset_error_message" + static let missionEditMessage = "detail_ing_mission_edit_error_message" + static let fetchFriendMessage = "detail_ing_fetch_friend_error_message" + static let openManittoMessage = "detail_ing_open_manitto_error_message" + } + } + + /// Detail Done 화면에서 사용하는 텍스트 + enum DetailDone { + static let mission = "detail_done_mission" + static let exitAlertTitle = "detail_done_exit_alert_title" + static let exitAlertMessage = "detail_done_exit_alert_message" + static let exitAlertAdminTitle = "detail_done_exit_alert_admin_title" + static let exitAlertAdminMessage = "detail_done_exit_alert_admin_message" + } + + /// Detail Edit 화면에서 사용하는 텍스트 + enum DetailEdit { + static let periodSettingTitle = "detail_edit_period_setting_title" + static let memberSettingTitle = "detail_edit_member_setting_title" + + enum Error { + static let message = "detail_edit_error_message" + static let memberTitle = "detail_edit_member_error_title" + static let memberMessage = "detail_edit_member_error_message" + static let date = "detail_edit_date_error_message" + } + } + + /// Letter 화면에서 사용하는 텍스트 + enum Letter { + static let title = "letter_title" + static let manittoTitle = "letter_manitto_title" + static let manitteTitle = "letter_manitte_title" + static let guide = "letter_guide" + static let buttonSend = "letter_button_send" + static let emptyFromContent = "letter_empty_from_content" + static let emptyToContent = "letter_empty_to_content" + static let saveAlertTitle = "letter_save_alert_title" + static let saveAlertMessage = "letter_image_save_alert_message" + static let emailEmptyContent = "letter_email_empty_content" + static let mission = "letter_mission" + static let missionToday = "letter_mission_today" + + enum Error { + static let fetchMessage = "letter_fetch_error_message" + static let imageSaveMessage = "letter_image_save_error_message" + } + } + + /// Send Letter 화면에서 사용하는 텍스트 + enum SendLetter { + static let title = "letter_send_title" + static let photoTitle = "letter_send_photo_title" + static let textTitle = "letter_send_text_title" + static let buttonSend = "letter_send_button_send" + static let photoMenuTitle = "letter_send_photo_menu_title" + static let photoMenuTakePhoto = "letter_send_photo_menu_take_photo" + static let photoMenuChoosePhoto = "letter_send_photo_menu_choose_photo" + static let photoMenuDeletePhoto = "letter_send_photo_menu_delete_photo" + + enum Error { + static let sendMessage = "letter_send_error_mesage" + static let photoLoadMessage = "letter_send_photo_load_error_message" + static let settingMessage = "letter_send_photo_setting_error_message" + static let photosAuthorizationMessage = "letter_send_photo_photos_authorization_error_message" + static let deviceMessage = "letter_send_photo_device_error_message" + static let authorizationMessage = "letter_photo_authorization_error_message" + static let buttonSetting = "letter_send_photo_error_button_setting" + } + } + + /// Memory 화면에서 사용하는 텍스트 + enum Memory { + static let title = "memory_title" + static let manittoContent = "memory_manitto_content" + static let manitteContent = "memory_manitte_content" + + enum Error { + static let instaTitle = "memory_insta_error_title" + static let instaMessage = "memory_insta_error_message" + } + } + + /// CreateRoom 화면에서 사용하는 텍스트 + enum CreateRoom { + static let next = "create_room_next" + static let previous = "create_room_previous" + static let inputDateTitle = "create_room_input_date_title" + static let inputPersonTitle = "create_room_input_person_title" + static let inputNameTitle = "create_room_input_name_title" + static let invitedCodeTitle = "create_room_invited_code_title" + } + + /// ParticipateRoom 화면에서 사용하는 텍스트 + enum ParticipateRoom { + static let title = "participate_room_title" + static let chooseCharacterTitle = "participate_room_choose_character_title" + static let chooseCharacterSubTitle = "participate_room_choose_character_subTitle" + static let buttonYes = "participate_room_button_yes" + static let buttonNo = "participate_room_button_no" + static let inputCodePlaceholder = "participate_room_input_code_placeholder" + + enum Error { + static let title = "participate_room_error_title" + static let message = "participate_room_error_message" + static let alreadyJoinTitle = "participate_room_already_join_error_title" + static let alreadyJoinMessage = "participate_room_already_join_error_message" + } + } + + /// Setting 화면에서 사용하는 텍스트 + enum Setting { + static let changeNickname = "setting_change_nickname" + static let personalInformation = "setting_personal_information" + static let termsOfService = "setting_terms_of_service" + static let developerInfo = "setting_developer_info" + static let inquiry = "setting_inquiry" + static let logout = "setting_logout" + static let withdrawal = "setting_withdrawal" + static let logoutAlertTitle = "setting_logout_alert_title" + static let withdrawalAlertMessage = "setting_withdrawal_alert_message" + static let withdrawalAlertOk = "setting_withdrawal_alert_ok" + + enum Error { + static let withDrawalMessage = "setting_withdrawal_error_message" + } + } + + /// Nickname 화면에서 사용하는 텍스트 + enum Nickname { + static let changeTitle = "nickname_change_title" + static let createTitle = "nickname_create_title" + static let placeholder = "nickname_placeholder" + } + + /// 메일 작성 부분에서 사용하는 텍스트 + enum Mail { + static let inquiryMessage = "mail_inquiry_message" + static let inquiryTitle = "mail_inquiry_title" + static let reportMessage = "mail_report_message" + static let reportTitle = "mail_report_title" + static let aenittoEmail = "mail_aenitto_email" + + enum Error { + static let title = "mail_error_title" + static let message = "mail_error_message" + } + } - // MARK: - DetailIngViewController - static let detailIngViewControllerManitoOpenButton: String = "마니또 공개" - static let detailIngViewControllerGuideText: String = "개별 미션이란?\n나의 마니띠에게\n수행하는 미션이에요." - static let detailIngViewControllerDoneMissionText: String = "종료된 마니또예요" - static let detailIngViewControllerDoneExitAlertTitle: String = "정말 방을 나가시겠습니까?" - static let detailIngViewControllerDoneExitAlertAdminTitle: String = "정말 방을 삭제하시겠습니까?" - static let detailIngViewControllerDoneExitAlertMessage: String = "나간 방은 다시 들어올 수 없어요" - static let detailIngViewControllerDoneExitAlertAdmin: String = "방장이 방을 삭제하면\n 참여자들의 방도 삭제됩니다" - static let detailIngViewControllerDetailInformatioin: String = "진행 정보" - static let detailIngViewControllerMissionEditTitle: String = "개별 미션 설정" - static let detailIngViewControllerSelfEditMissionTitle: String = "개별 미션 직접 설정" - static let detailIngViewControllerResetMissionTitle: String = "기본 미션으로 변경" - static let detailIngViewControllerResetMissionAlertTitle: String = "개별 미션을 되돌리시겠습니까?" - static let detailIngViewControllerResetMissionAlertMessage: String = "기존에 주어지는 기본 미션으로 재설정됩니다." - static let detailIngViewControllerResetMissionAlertOkTitle: String = "되돌리기" - static let detailIngViewControllerResetMissionErrorAlertOkTitle: String = "오류 발생" - static let detailIngViewControllerResetMissionErrorAlertOkMessage: String = "미션을 되돌릴 수 없습니다.\n다시 시도해주세요." - - // MARK: - CalendarView - static let calendarViewAlertMaxTitle: String = "최대 선택 기간을 넘었어요" - - static let calendarViewAlertPastTitle: String = "지난 날을 선택하셨어요" - static let calendarViewAlertPastMessage: String = "오늘보다 이전 날짜는 \n 선택하실 수 없어요" - - // MARK: - DatailWaitViewController - static let datailWaitViewControllerDeleteTitle: String = "방을 삭제하실 건가요?" - static let datailWaitViewControllerExitTitle: String = "정말 나가실거예요?" - static let datailWaitViewControllerDeleteMessage: String = "방을 삭제하시면 다시 되돌릴 수 없습니다." - static let datailWaitViewControllerExitMessage: String = "초대코드를 입력하면 \n 다시 들어올 수 있어요." - static let datailWaitViewControllerButtonWaitingText: String = "시작을 기다리는 중..." - static let datailWaitViewControllerButtonStartText: String = "마니또 시작" - - static let detailWaitViewControllerDeleteRoom: String = "방 삭제" - static let detailWaitViewControllerLeaveRoom: String = "방 나가기" - static let detailWaitViewControllerCode: String = "초대코드" - static let detailWaitViewControllerCopyCode: String = "코드 복사 완료!" - static let detailWaitViewControllerPastAlertTitle: String = "마니또 시작일이 지났어요" - static let detailWaitViewControllerPastAlertMessage: String = "방장이 진행기간을 재설정 \n 할 때까지 기다려주세요." - static let detailWaitViewControllerPastAdminAlertMessage: String = "진행기간을 재설정 해주세요" - static let detailWaitViewControllerDeleteErrorMessage: String = "방 삭제에 실패했습니다. 다시 시도해주세요" - static let detailWaitViewControllerStartErrorMessage: String = "마니또 시작에 실패했습니다. 다시 시도해주세요" - static let detailWaitViewControllerLeaveErrorMessage: String = "방 나가기에 실패했습니다. 다시 시도해주세요" - static let detailWaitViewControllerLoadDataMessage: String = "방 정보를 불러올 수 없습니다. 다시 시도해주세요" - - // MARK: - MemoryViewController - static let memoryViewControllerTitleLabel: String = "함께 했던 기록" - static let memoryViewControllerManittoText: String = "나를 챙겨줘서 고마워, 마니또" - static let memoryViewControllerManitteeText: String = "내가 챙겨줘서 좋았지? 마니띠" - static let memoryViewControllerAlertTitle: String = "스토리 공유에 실패했어요" - static let memoryViewControllerAlertMessage: String = "스토리 공유를 하려면 인스타그램이 필요합니다" - - // MARK: - DetailEditViewController - static let detailEditViewControllerStartSetting: String = "진행기간 설정" - static let detailEditViewControllerSetMember: String = "인원 설정" - static let detailEditViewControllerChangeRoomInfoAlertTitle: String = "인원을 다시 설정해 주세요" - static let detailEditViewControllerChangeRoomInfoAlertMessage: String = "현재 인원보다 최대 인원을 \n더 적게 설정할 수 없어요." - static let detailEditViewControllerChangeErrorTitle: String = "오류 발생" - static let detailEditViewControllerChangeErrorMessage: String = "방 정보 수정에 실패했습니다. 다시 시도해주세요" - - // MARK: - InputNameView - static let inputNameViewRoomNameText: String = "방 이름을 적어주세요" - - // MARK: - InputPersonView - static let inputPersonViewTitle: String = "최대 참여 인원을 설정해 주세요" - - // MARK: - InputDateView - static let inputDateViewTitle: String = "진행 기간을 설정해 주세요" - - // MARK: - ChangeNickNameViewController - static let changeNickNameViewControllerTitle: String = "닉네임 변경" - - // MARK: - InvitedCodeViewController - static let invitedCodeViewCOntroller: String = "글자를 탭하여 코드를 복사하세요" - - // MARK: - MissionEditViewController - static let missionEditViewControllerChangeMissionAlertTitle: String = "개별 미션을 수정하시겠습니까?" - static let missionEditViewControllerChangeMissionAlertMessage: String = "기본 미션으로 변경을 통해\n되돌릴 수 있습니다." - static let missionEditViewControllerChangeMissionErrorAlertTitle: String = "오류 발생" - static let missionEditViewControllerChangeMissionErrorAlertMessage: String = "개별 미션을 수정 할 수 없습니다.\n다시 시도해주세요." + /// Sign 화면에서 사용하는 텍스트 + enum Sign { + enum Error { + static let credential = "sign_error_credential" + static let token = "sign_error_token" + static let tokenToString = "sign_error_tokenToString" + } + } +} + +extension String { + /// Localizable의 Key로부터 Value를 가져오는 메서드 + func localized(comment: String = "") -> String { + return NSLocalizedString(self, comment: comment) + } + + /// Localizable의 Key로부터 Value를 가져오는 메서드 - Argument가 있는 경우 + func localized(with argument: CVarArg..., comment: String = "") -> String { + return String(format: self.localized(comment: comment), argument) + } } diff --git a/Manito/Manito/Util/Literal/URLLiteral.swift b/Manito/Manito/Util/Literal/URLLiteral.swift index 9fdcae7a6..2d42ab1ba 100644 --- a/Manito/Manito/Util/Literal/URLLiteral.swift +++ b/Manito/Manito/Util/Literal/URLLiteral.swift @@ -8,9 +8,13 @@ import Foundation enum URLLiteral { - - // MARK: - notion url + enum Setting { + static let personalInformation: String = "https://torpid-spy-8e4.notion.site/767e80eea1734539aead3b814016b361" + static let termsOfService: String = "https://torpid-spy-8e4.notion.site/445bd6a8c8dc459d915158935dcc3298" + } - static let personalInfomationUrl: String = "https://torpid-spy-8e4.notion.site/767e80eea1734539aead3b814016b361" - static let termsOfServiceUrl: String = "https://torpid-spy-8e4.notion.site/445bd6a8c8dc459d915158935dcc3298" + enum Memory { + static let instagram: String = "instagram-stories://share?source_application=\(Bundle.main.instagramAppID)" + static let instagramBundle: String = "com.instagram.sharedSticker.stickerImage" + } } diff --git a/Manito/Manito/Util/Literal/en.lproj/Localizable.strings b/Manito/Manito/Util/Literal/en.lproj/Localizable.strings new file mode 100644 index 000000000..ead37c03e --- /dev/null +++ b/Manito/Manito/Util/Literal/en.lproj/Localizable.strings @@ -0,0 +1,156 @@ +"common_confirm" = "확인"; +"common_cancel" = "취소"; +"common_done" = "완료"; +"common_processing" = "진행중"; +"common_waiting" = "대기중"; +"common_enter_room" = "방 참가하기"; +"common_search_room" = "방 조회하기"; +"common_create_room" = "방 생성하기"; +"common_people" = "%d인"; +"common_x_people" = "X %d인"; +"common_discard_changes" = "변경 사항 폐기"; +"common_warning_title" = "경고"; +"common_calendar_max_date_content" = "최대 7일까지 선택가능해요"; +"common_calendar_max_alert_title" = "최대 선택 기간을 넘었어요"; +"common_calendar_past_alert_title" = "지난 날을 선택하셨어요"; +"common_calendar_past_alert_message" = "오늘보다 이전 날짜는 \n 선택하실 수 없어요"; +"common_error_title" = "오류 발생"; +"common_network_server_error" = "애니또에 문제가 발생했습니다."; +"common_network_authorized_error" = "자동 로그인이 만료되었습니다. 다시 로그인해주세요."; +"main_list_title" = "참여중인 애니또"; +"main_menu_title" = "새로운 마니또 시작"; +"main_common_mission_title" = "오늘의 공통미션"; +"main_cell_create_room_title" = "새로운 마니또 시작"; +"main_guide" = "공통 미션이란?\n마니또에 참여한 모두에게 \n수행하는 미션이에요."; +"main_error_title" = "해당 마니또 방의 정보를 불러오지 못했습니다."; +"main_error_message" = "해당 마니또 방으로 이동할 수 없습니다."; +"detail_menu_modified_room_info" = "방 정보 수정"; +"detail_together_friend_title" = "함께하는 친구들"; +"detail_information_title" = "진행 정보"; +"detail_manittee_title" = "%@의 마니띠"; +"detail_change" = "변경"; +"detail_menu_delete" = "방 삭제"; +"detail_menu_leave" = "방 나가기"; +"detail_delete" = "삭제"; +"detail_leave" = "나가기"; +"detail_wait_copy_code" = "방 코드 복사"; +"datail_wait_button_waiting" = "시작을 기다리는 중..."; +"datail_wait_button_start" = "마니또 시작"; +"detail_wait_toast_copy_message" = "코드 복사 완료!"; +"detail_wait_toast_edit_message" = "방 정보 수정 완료"; +"detail_wait_during_title" = "진행 기간"; +"detail_wait_past_alert_title" = "마니또 시작일이 지났어요"; +"detail_wait_past_alert_message" = "방장이 진행기간을 재설정 \n 할 때까지 기다려주세요."; +"detail_wait_past_admin_alert_message" = "진행기간을 재설정 해주세요"; +"datail_wait_delete_alert_title" = "방을 삭제하실 건가요?"; +"datail_wait_delete_alert_message" = "방을 삭제하시면 다시 되돌릴 수 없습니다."; +"datail_wait_exit_alert_title" = "정말 나가실거예요?"; +"datail_wait_exit_alert_message" = "초대코드를 입력하면 \n 다시 들어올 수 있어요."; +"detail_wait_fetch_error_message" = "방 정보를 불러오는데 실패했습니다."; +"detail_wait_start_error_message" = "마니또 시작에 실패했습니다. \n다시 시도해주세요."; +"detail_wait_delete_error_message" = "방 삭제에 실패했습니다. \n다시 시도해주세요."; +"detail_wait_leave_error_message" = "방 나가기에 실패했습니다. \n다시 시도해 주세요."; +"detail_ing_individual_mission_title" = "오늘의 개별 미션"; +"detail_ing_select_manittee_title" = "레버를 스와이프해서\n내 마니띠를 확인하세요."; +"detail_ing_open_manitto_title" = "당신의 마니또는?"; +"detail_ing_open_manitto_popup_content" = "%@의 마니또는\n%@입니다."; +"detail_ing_open_manitto_popup_helper_content" = "내일 함께 했던 추억이 열립니다.\n마니또 방에서 확인해 보세요!"; +"detail_ing_guide" = "개별 미션이란?\n나의 마니띠에게\n수행하는 미션이에요."; +"detail_ing_button_open" = "마니또 공개"; +"detail_ing_mission_menu_setting" = "개별 미션 직접 설정"; +"detail_ing_mission_menu_reset" = "기본 미션으로 변경"; +"detail_ing_mission_menu_title" = "개별 미션 설정"; +"detail_ing_reset_alert_title" = "개별 미션을 되돌리시겠습니까?"; +"detail_ing_reset_alert_message" = "기존에 주어지는 기본 미션으로 재설정됩니다."; +"detail_ing_reset_alert_ok" = "되돌리기"; +"detail_ing_mission_edit_alert_title" = "개별 미션을 수정하시겠습니까?"; +"detail_ing_mission_edit_alert_message" = "기본 미션으로 변경을 통해\n되돌릴 수 있습니다."; +"detail_ing_reset_error_message" = "미션을 되돌릴 수 없습니다.\n다시 시도해주세요."; +"detail_ing_mission_edit_error_message" = "개별 미션을 수정 할 수 없습니다.\n다시 시도해주세요."; +"detail_ing_fetch_friend_error_message" = "방에 함께하는 친구들 정보를 불러올 수 없습니다."; +"detail_ing_open_manitto_error_message" = "마니또를 확인할 수 없습니다."; +"detail_done_mission" = "종료된 마니또예요"; +"detail_done_exit_alert_title" = "정말 방을 나가시겠습니까?"; +"detail_done_exit_alert_message" = "나간 방은 다시 들어올 수 없어요"; +"detail_done_exit_alert_admin_title" = "정말 방을 삭제하시겠습니까?"; +"detail_done_exit_alert_admin_message" = "방장이 방을 삭제하면\n 참여자들의 방도 삭제됩니다"; +"detail_edit_period_setting_title" = "진행기간 설정"; +"detail_edit_member_setting_title" = "인원 설정"; +"detail_edit_error_message" = "방 정보 수정에 실패했습니다. 다시 시도해주세요"; +"detail_edit_member_error_title" = "인원을 다시 설정해 주세요"; +"detail_edit_member_error_message" = "현재 인원보다 최대 인원을 \n더 적게 설정할 수 없어요"; +"detail_edit_date_error_message" = "시작 날짜가 오늘보다 과거일 순 없어요"; +"letter_title" = "쪽지함"; +"letter_manitto_title" = "마니또로부터"; +"letter_manitte_title" = "마니띠에게"; +"letter_guide" = "쪽지 쓰기는?\n마니띠에게만 쓰기가 가능해요.\n마니또로부터는 확인만 할 수 있어요."; +"letter_button_send" = "쪽지 쓰기"; +"letter_empty_from_content" = "쪽지함이 비었어요.\n곧 마니또가 쪽지를 보낼거에요!"; +"letter_empty_to_content" = "쪽지함이 비었어요.\n마니띠에게 쪽지를 보내볼까요?"; +"letter_save_alert_title" = "저장 성공"; +"letter_image_save_alert_message" = "사진을 앨범에 저장했어요."; +"letter_email_empty_content" = "쪽지 내용 없음"; +"letter_mission" = "%@의 개별미션\n[%@]"; +"letter_mission_today" = "오늘"; +"letter_fetch_error_message" = "쪽지를 가져올 수 없습니다."; +"letter_image_save_error_message" = "사진을 저장할 수 없습니다."; +"letter_send_title" = "쪽지 작성하기"; +"letter_send_photo_title" = "사진 추가"; +"letter_send_text_title" = "쪽지 작성"; +"letter_send_button_send" = "보내기"; +"letter_send_photo_menu_title" = "마니또에게 보낼 사진 선택"; +"letter_send_photo_menu_take_photo" = "사진 촬영"; +"letter_send_photo_menu_choose_photo" = "사진 보관함에서 선택"; +"letter_send_photo_menu_delete_photo" = "사진 지우기"; +"letter_send_error_mesage" = "쪽지 전송에 실패했습니다. 다시 시도해주세요."; +"letter_send_photo_load_error_message" = "사진을 불러올 수 없습니다."; +"letter_send_photo_setting_error_message" = "설정 화면을 연결할 수 없습니다."; +"letter_send_photo_photos_authorization_error_message" = "%@가 사진첩에 접근할 권한이 없습니다."; +"letter_send_photo_device_error_message" = "해당 기기에서 카메라를 사용할 수 없습니다."; +"letter_photo_authorization_error_message" = "%@가 카메라에 접근이 허용되어 있지 않습니다. 설정화면으로 가시겠습니까?"; +"letter_send_photo_error_button_setting" = "설정"; +"memory_title" = "함께 했던 기록"; +"memory_manitto_content" = "나를 챙겨줘서 고마워, 마니또"; +"memory_manitte_content" = "내가 챙겨줘서 좋았지? 마니띠"; +"memory_insta_error_title" = "스토리 공유에 실패했어요"; +"memory_insta_error_message" = "스토리 공유를 하려면 인스타그램이 필요합니다"; +"create_room_next" = "다음"; +"create_room_previous" = "이전"; +"create_room_input_date_title" = "진행 기간을 설정해 주세요"; +"create_room_input_person_title" = "최대 참여 인원을 설정해 주세요"; +"create_room_input_name_title" = "방 이름을 적어주세요"; +"create_room_invited_code_title" = "글자를 탭하여 코드를 복사하세요"; +"participate_room_title" = "이 방으로 입장할까요?"; +"participate_room_choose_character_title" = "캐릭터 선택"; +"participate_room_choose_character_subTitle" = "당신만의 캐릭터를 정해주세요"; +"participate_room_button_yes" = "YES"; +"participate_room_button_no" = "NO"; +"participate_room_input_code_placeholder" = "초대코드 입력"; +"participate_room_error_title" = "해당하는 애니또 방이 없어요"; +"participate_room_error_message" = "초대 코드를 다시 확인해 주세요"; +"participate_room_already_join_error_title" = "이미 참여중인 방입니다"; +"participate_room_already_join_error_message" = "참여중인 애니또 리스트를 확인해 보세요"; +"setting_change_nickname" = "닉네임 변경하기"; +"setting_personal_information" = "개인정보 처리방침"; +"setting_terms_of_service" = "이용 약관"; +"setting_developer_info" = "개발자 정보"; +"setting_inquiry" = "문의하기"; +"setting_logout" = "로그아웃"; +"setting_withdrawal" = "서비스 탈퇴"; +"setting_logout_alert_title" = "로그아웃 하시겠습니까?"; +"setting_withdrawal_alert_message" = "서비스 탈퇴 시 지금까지 내용이 전부 삭제됩니다. \n 탈퇴 하시겠습니까?"; +"setting_withdrawal_alert_ok" = "탈퇴"; +"setting_withdrawal_error_message" = "서비스 탈퇴에 실패했습니다. 다시 시도해주세요."; +"nickname_change_title" = "닉네임 변경"; +"nickname_create_title" = "닉네임 설정"; +"nickname_placeholder" = "닉네임을 적어주세요"; +"mail_inquiry_message" = "\n\n-----------------------------\n\n- 문의하는 닉네임: %@\n- 문의 날짜: %@\n\n------------------------------\n\n문의 내용을 작성해주세요.\n"; +"mail_inquiry_title" = "[문의 사항]"; +"mail_report_message" = "\n\n-----------------------------\n\n- 신고자 닉네임: %@\n- 신고 메시지 내용: %@\n- 신고 날짜: %@\n\n------------------------------\n\n신고 내용을 작성해주세요.\n"; +"mail_report_title" = "[신고 관련 문의]"; +"mail_aenitto_email" = "aenitto@gmail.com"; +"mail_error_title" = "메일 전송 실패"; +"mail_error_message" = "아이폰 이메일 설정을 확인하고 다시 시도해주세요."; +"sign_error_credential" = "애플 로그인 실패"; +"sign_error_token" = "애플 로그인 토큰 획득 실패"; +"sign_error_tokenToString" = "애플 로그인 토큰 String 변환 실패"; diff --git a/Manito/Manito/Util/Script/LocalizationScript.py b/Manito/Manito/Util/Script/LocalizationScript.py new file mode 100644 index 000000000..4dd3296a9 --- /dev/null +++ b/Manito/Manito/Util/Script/LocalizationScript.py @@ -0,0 +1,89 @@ + +# https://github.com/3dollar-in-my-pocket/3dollars-in-my-pocket-ios/blob/master/3dollar-in-my-pocket/script/string_resource.py + +import os +import sys +import urllib +import csv +from imp import reload +from urllib.request import urlretrieve + +reload(sys) + +gdoc_id = "1eccIkg8KM8tj-HR1FW_6b-F50asLr-DNj9hP_hvAYc8/edit#gid=0" + +def get_gdoc_information(): + download_path = sys.argv[1] + try: + csv_file = export_csv_from_sheet(gdoc_id) + string_list = get_strings_from_csv(csv_file) + write_strings(string_list, download_path) + except Exception as e: + print(":::::::::::::ERROR:::::::::::::") + print(e) + + +def export_csv_from_sheet(gdoc_id, download_path=None, ): + print("Downloading the CVS file with id %s" % gdoc_id) + + resource = gdoc_id.split('/')[0] + tab = gdoc_id.split('#')[1].split('=')[1] + resource_id = 'spreadsheet:' + resource + + if download_path is None: + download_path = os.path.abspath(os.path.dirname(__file__)) + + file_name = os.path.join(download_path, '%s.csv' % (resource)) + + print('download_path : %s' % download_path) + print('Downloading spreadsheet to %s' % file_name) + + url = 'https://docs.google.com/spreadsheet/ccc?key=%s&output=csv' % (resource) + urlretrieve(url, file_name) + + print("Download Completed!") + + return file_name + + +def get_strings_from_csv(file_name): + print("read CSV file : %s" % file_name) + + source_csv = open(file_name, "r") + csv_reader = csv.reader(source_csv) + header = csv_reader.__next__() + index_key = header.index("key") + index_kr = header.index("kr") + + string_list = [] + + # Loop through the lines in the file and get each coordinate + for row in csv_reader: + key = row[index_key] + kr = row[index_kr] + + dict_string = { + "key": key, + "kr": kr + } + string_list.append(dict_string) + + source_csv.close() + os.remove(file_name) + + return string_list + + +def write_strings(string_list, save_path): + if not os.path.exists(save_path + "/Util/Literal/en.lproj/"): + os.makedirs(save_path + "/Util/Literal/en.lproj/") + + en_string_file = open(save_path + "/Util/Literal/en.lproj/Localizable.strings", "w") + + for item in string_list: + en_string_file.write("\"" + item["key"] + "\" = \"" + item["kr"] + "\";\n") + + en_string_file.close() + +if __name__ == '__main__': + get_gdoc_information() diff --git a/Manito/ManitoTests/Domain/Entity/RoomInfoTest.swift b/Manito/ManitoTests/Domain/Entity/RoomInfoTest.swift new file mode 100644 index 000000000..514864dce --- /dev/null +++ b/Manito/ManitoTests/Domain/Entity/RoomInfoTest.swift @@ -0,0 +1,233 @@ +// +// RoomInfoTest.swift +// ManitoTests +// +// Created by Mingwan Choi on 2023/09/19. +// + +import XCTest +@testable import Manito + +final class RoomInfoTest: XCTestCase { + func test_canStart_변수가_존재하는가() { + let sut = RoomInfo.testRoom + + let _ = sut.canStart + } + + func test_canStart_방장이아니고_시작인원이4명이하고_시작날짜가오늘이아닐때() { + // given + let sut = RoomInfo( + roomInformation: RoomListItem(id: 0, + title: "test", + state: "PRE", + capacity: 10, + startDate: "2030.01.01", + endDate: "2030.01.05"), + participants: ParticipantList(count: 3, members: [UserInfo.testUserManittee, UserInfo.testUserManittee, UserInfo.testUserManittee]), + manittee: UserInfo.testUserManittee, + manitto: UserInfo.testUserManitto, + invitation: InvitationCode.testInvitationCode, + didViewRoulette: false, + mission: IndividualMission.testIndividualMission, + admin: false, + messages: MessageCountInfo.testMessageInfo) + + // when + let canStart = sut.canStart + + // then + XCTAssertFalse(canStart) + } + + func test_canStart_방장이아니고_시작인원이4명이하고_시작날짜가오늘일때() { + // given + let oneTimeInterval: TimeInterval = 86400 + let todayString = Date().toFullString + let endDate = todayString.toDefaultDate! + oneTimeInterval + let endDateString = endDate.toFullString + let sut = RoomInfo( + roomInformation: RoomListItem(id: 0, + title: "test", + state: "PRE", + capacity: 10, + startDate: todayString, + endDate: endDateString), + participants: ParticipantList(count: 3, members: [UserInfo.testUserManittee, UserInfo.testUserManittee, UserInfo.testUserManittee]), + manittee: UserInfo.testUserManittee, + manitto: UserInfo.testUserManitto, + invitation: InvitationCode.testInvitationCode, + didViewRoulette: false, + mission: IndividualMission.testIndividualMission, + admin: false, + messages: MessageCountInfo.testMessageInfo) + + // when + let canStart = sut.canStart + + // then + XCTAssertFalse(canStart) + } + + func test_canStart_방장이아니고_시작인원이4명이상이고_시작날짜가오늘이아닐때() { + // given + let sut = RoomInfo( + roomInformation: RoomListItem(id: 0, + title: "test", + state: "PRE", + capacity: 10, + startDate: "2030.01.01", + endDate: "2030.01.05"), + participants: ParticipantList(count: 4, members: [UserInfo.testUserManittee, UserInfo.testUserManittee, UserInfo.testUserManittee, UserInfo.testUserManittee]), + manittee: UserInfo.testUserManittee, + manitto: UserInfo.testUserManitto, + invitation: InvitationCode.testInvitationCode, + didViewRoulette: false, + mission: IndividualMission.testIndividualMission, + admin: false, + messages: MessageCountInfo.testMessageInfo) + + // when + let canStart = sut.canStart + + // then + XCTAssertFalse(canStart) + } + + func test_canStart_방장이아니고_시작인원이4명이상이고_시작날짜가오늘일때() { + // given + let oneTimeInterval: TimeInterval = 86400 + let todayString = Date().toFullString + let endDate = todayString.toDefaultDate! + oneTimeInterval + let endDateString = endDate.toFullString + let sut = RoomInfo( + roomInformation: RoomListItem(id: 0, + title: "test", + state: "PRE", + capacity: 10, + startDate: todayString, + endDate: endDateString), + participants: ParticipantList(count: 4, members: [UserInfo.testUserManittee, UserInfo.testUserManittee, UserInfo.testUserManittee, UserInfo.testUserManittee]), + manittee: UserInfo.testUserManittee, + manitto: UserInfo.testUserManitto, + invitation: InvitationCode.testInvitationCode, + didViewRoulette: false, + mission: IndividualMission.testIndividualMission, + admin: false, + messages: MessageCountInfo.testMessageInfo) + + // when + let canStart = sut.canStart + + // then + XCTAssertFalse(canStart) + } + + func test_canStart_방장이고_시작인원이4명이하고_시작날짜가오늘이아닐때() { + // given + let sut = RoomInfo( + roomInformation: RoomListItem(id: 0, + title: "test", + state: "PRE", + capacity: 10, + startDate: "2030.01.01", + endDate: "2030.01.05"), + participants: ParticipantList(count: 3, members: [UserInfo.testUserManittee, UserInfo.testUserManittee, UserInfo.testUserManittee]), + manittee: UserInfo.testUserManittee, + manitto: UserInfo.testUserManitto, + invitation: InvitationCode.testInvitationCode, + didViewRoulette: false, + mission: IndividualMission.testIndividualMission, + admin: true, + messages: MessageCountInfo.testMessageInfo) + + // when + let canStart = sut.canStart + + // then + XCTAssertFalse(canStart) + } + + func test_canStart_방장이고_시작인원이4명이하고_시작날짜가오늘일때() { + // given + let oneTimeInterval: TimeInterval = 86400 + let todayString = Date().toFullString + let endDate = todayString.toDefaultDate! + oneTimeInterval + let endDateString = endDate.toFullString + let sut = RoomInfo( + roomInformation: RoomListItem(id: 0, + title: "test", + state: "PRE", + capacity: 10, + startDate: todayString, + endDate: endDateString), + participants: ParticipantList(count: 3, members: [UserInfo.testUserManittee, UserInfo.testUserManittee, UserInfo.testUserManittee]), + manittee: UserInfo.testUserManittee, + manitto: UserInfo.testUserManitto, + invitation: InvitationCode.testInvitationCode, + didViewRoulette: false, + mission: IndividualMission.testIndividualMission, + admin: true, + messages: MessageCountInfo.testMessageInfo) + + // when + let canStart = sut.canStart + + // then + XCTAssertFalse(canStart) + } + + func test_canStart_방장이고_시작인원이4명이상이고_시작날짜가오늘이아닐때() { + // given + let sut = RoomInfo( + roomInformation: RoomListItem(id: 0, + title: "test", + state: "PRE", + capacity: 10, + startDate: "2030.01.01", + endDate: "2030.01.05"), + participants: ParticipantList(count: 4, members: [UserInfo.testUserManittee, UserInfo.testUserManittee, UserInfo.testUserManittee, UserInfo.testUserManittee]), + manittee: UserInfo.testUserManittee, + manitto: UserInfo.testUserManitto, + invitation: InvitationCode.testInvitationCode, + didViewRoulette: false, + mission: IndividualMission.testIndividualMission, + admin: true, + messages: MessageCountInfo.testMessageInfo) + + // when + let canStart = sut.canStart + + // then + XCTAssertFalse(canStart) + } + + func test_canStart_방장이고_시작인원이4명이상이고_시작날짜가오늘일때() { + // given + let oneTimeInterval: TimeInterval = 86400 + let todayString = Date().toFullString + let endDate = todayString.toDefaultDate! + oneTimeInterval + let endDateString = endDate.toFullString + let sut = RoomInfo( + roomInformation: RoomListItem(id: 0, + title: "test", + state: "PRE", + capacity: 10, + startDate: todayString, + endDate: endDateString), + participants: ParticipantList(count: 4, members: [UserInfo.testUserManittee, UserInfo.testUserManittee, UserInfo.testUserManittee, UserInfo.testUserManittee]), + manittee: UserInfo.testUserManittee, + manitto: UserInfo.testUserManitto, + invitation: InvitationCode.testInvitationCode, + didViewRoulette: false, + mission: IndividualMission.testIndividualMission, + admin: true, + messages: MessageCountInfo.testMessageInfo) + + // when + let canStart = sut.canStart + + // then + XCTAssertTrue(canStart) + } +} diff --git a/Manito/ManitoTests/Domain/Entity/RoomListItemTest.swift b/Manito/ManitoTests/Domain/Entity/RoomListItemTest.swift new file mode 100644 index 000000000..aaa1147a6 --- /dev/null +++ b/Manito/ManitoTests/Domain/Entity/RoomListItemTest.swift @@ -0,0 +1,84 @@ +// +// RoomListItemTest.swift +// ManitoTests +// +// Created by Mingwan Choi on 2023/09/18. +// + +import Foundation +import XCTest +@testable import Manito + +final class RoomListItemTest: XCTestCase { + func test_dateRangeText_변수가_있는가() { + let sut = RoomListItem.testRoomListItem + + let _ = sut.dateRangeText + } + + func test_dateRangeText_변수가_올바른값을_리턴가는가() { + // given + let sut = RoomListItem.testRoomListItem + + // when + let dateRangeText = sut.dateRangeText + + // then + XCTAssertEqual(dateRangeText, "2023.01.01 ~ 2023.01.05") + } + + func test_dateRangeText_변수에_틀린값이_들어왔을때_실패하는가() { + // given + let sut = RoomListItem.testRoomListItem + + // when + let dateRangeText = sut.dateRangeText + + // then + XCTAssertNotEqual(dateRangeText, "2023.01.01 ~ 2023.01.05") + } + + func test_isStartDatePast_변수가_있는가() { + let sut = RoomListItem.testRoomListItem + + let _ = sut.isStartDatePast + } + + func test_isStartDatePast_과거날짜에대해_올바르게_반환하는가() { + // given + let sut = RoomListItem.testRoomListItem + + // when + let isPast = sut.isStartDatePast + + // then + XCTAssertEqual(isPast, true) + } + + func test_isStartDatePast_미래날짜에대해_올바르게_반환하는가() { + // given + let sut = RoomListItem(id: 0, title: "", state: "", capacity: 0, startDate: "2030.01.01", endDate: "2030.01.05") + + // when + let isPast = sut.isStartDatePast + + // then + XCTAssertEqual(isPast, false) + } + + func test_isStartDatePast_오늘날짜에대해_올바르게_반환하는가() { + // given + let today = Date() + let ondDay: TimeInterval = 86400 + let todayAfterFiveDays = Date() + ondDay + let todayString = today.toFullString + let endDateString = todayAfterFiveDays.toFullString + let sut = RoomListItem(id: 0, title: "", state: "", capacity: 0, startDate: todayString, endDate: endDateString) + + // when + let isPast = sut.isStartDatePast + + // then + XCTAssertEqual(isPast, false) + } +} diff --git a/Manito/ManitoTests/Domain/Usecase/DetailEditUsecaseTest.swift b/Manito/ManitoTests/Domain/Usecase/DetailEditUsecaseTest.swift new file mode 100644 index 000000000..49d2fd7d6 --- /dev/null +++ b/Manito/ManitoTests/Domain/Usecase/DetailEditUsecaseTest.swift @@ -0,0 +1,138 @@ +// +// DetailEditUsecaseTest.swift +// ManitoTests +// +// Created by Mingwan Choi on 11/8/23. +// + +import XCTest +@testable import Manito + +final class DetailEditUsecaseTest: XCTestCase { + private var mockUsecase: DetailEditUsecase! + + override func setUp() { + self.mockUsecase = MockDetailEditUsecase(roomInformation: .testRoom) + } + + override func tearDown() { + self.mockUsecase = nil + } + + func test_vaildStartDateIsNotPast함수에_올바른_날짜형식의_텍스트에_반응하는가() { + // given + let date = "aaaa.aa.aa" + // when + let sut = self.mockUsecase.vaildStartDateIsNotPast(startDate: date) + // then + XCTAssertFalse(sut) + } + + func test_vaildStartDateIsNotPast함수에_과거날짜가_들어갔을때() { + // given + let date = "2000.01.01" + // when + let sut = self.mockUsecase.vaildStartDateIsNotPast(startDate: date) + // then + XCTAssertFalse(sut) + } + + func test_vaildStartDateIsNotPast함수에_오늘날짜가_들어갔을때() { + // given + let today = Date().toFullString + // when + let sut = self.mockUsecase.vaildStartDateIsNotPast(startDate: today) + // then + XCTAssertTrue(sut) + } + + func test_vaildStartDateIsNotPast함수에_내일날짜가_들어갔을때() { + // given + let oneDay: TimeInterval = 86400 + let tomorrow = (Date() + oneDay).toFullString + // when + let sut = self.mockUsecase.vaildStartDateIsNotPast(startDate: tomorrow) + // then + XCTAssertTrue(sut) + } + + func test_vaildStartDateIsNotPast함수에_먼미래날짜가_들어갔을때() { + // given + let date = "2050.01.01" + // when + let sut = self.mockUsecase.vaildStartDateIsNotPast(startDate: date) + // then + XCTAssertTrue(sut) + } + + func test_vaildMemberCountIsUnder함수에_5명이참여중인데_4명으로_수정했을때() { + // given + let capacity = 4 + // when + let sut = self.mockUsecase.vaildMemberCountIsUnder(capacity: capacity) + // then + XCTAssertFalse(sut) + } + + func test_vaildMemberCountIsUnder함수에_5명이참여중인데_5명으로_수정했을때() { + // given + let capacity = 5 + // when + let sut = self.mockUsecase.vaildMemberCountIsUnder(capacity: capacity) + // then + XCTAssertTrue(sut) + } + + func test_vaildMemberCountIsUnder함수에_5명이참여중인데_10명으로_수정했을때() { + // given + let capacity = 10 + // when + let sut = self.mockUsecase.vaildMemberCountIsUnder(capacity: capacity) + // then + XCTAssertTrue(sut) + } + + func test_changeRoomInformation() async throws { + let dto: CreatedRoomInfoRequestDTO = .init(title: "테스트", + capacity: 5, + startDate: "2025.01.01", + endDate: "2025.01.05") + + do { + let statusCode = try await self.mockUsecase.changeRoomInformation(roomDto: dto) + if statusCode == 204 { + XCTAssertTrue(true) + } else { + XCTFail() + } + } catch { + XCTFail() + } + } +} + +final class MockDetailEditUsecase: DetailEditUsecase { + private let usecaseImpl: DetailEditUsecase = DetailEditUsecaseImpl(roomInformation: .testRoom, + repository: DetailRoomRepositoryImpl()) + var roomInformation: Manito.RoomInfo + + init(roomInformation: Manito.RoomInfo) { + self.roomInformation = roomInformation + } + + func vaildStartDateIsNotPast(startDate: String) -> Bool { + let value = self.usecaseImpl.vaildStartDateIsNotPast(startDate: startDate) + return value + } + + func vaildMemberCountIsUnder(capacity: Int) -> Bool { + let value = self.usecaseImpl.vaildMemberCountIsUnder(capacity: capacity) + return value + } + + func changeRoomInformation(roomDto: Manito.CreatedRoomInfoRequestDTO) async throws -> Int { + try await Task.sleep(nanoseconds: 3_000) + + return 204 + } +} diff --git a/Manito/ManitoTests/Domain/Usecase/DetailWaitUsecaseTest.swift b/Manito/ManitoTests/Domain/Usecase/DetailWaitUsecaseTest.swift new file mode 100644 index 000000000..2c2ac6108 --- /dev/null +++ b/Manito/ManitoTests/Domain/Usecase/DetailWaitUsecaseTest.swift @@ -0,0 +1,132 @@ +// +// DetailWaitUsecaseTest.swift +// ManitoTests +// +// Created by Mingwan Choi on 10/12/23. +// + +import XCTest +@testable import Manito + +final class DetailWaitUsecaseTest: XCTestCase { + private var mockUsecase: DetailWaitUseCase! + + override func setUp() { + self.mockUsecase = MockDetailWaitUsecase(statusCode: 204) + } + + override func tearDown() { + self.mockUsecase = nil + } + + func test_fetchRoomInformaion함수가_올바른값을_리턴하는가() async throws { + let sut = RoomInfoDTO.testDummyRoomDTO + + do { + let roomInfoDTO = try await self.mockUsecase.fetchRoomInformaion(roomId: "") + XCTAssertEqual(sut, roomInfoDTO) + } catch { + XCTFail() + } + } + + func test_patchStartManitto함수가_올바른값을_리턴하는가() async throws { + let sut = UserInfoDTO.testDummyUserManittee + + do { + let manittee = try await self.mockUsecase.patchStartManitto(roomId: "") + XCTAssertEqual(sut, manittee) + } catch { + XCTFail() + } + } + + func test_deleteRoom함수가_올바른값을_리턴하는가() async throws { + do { + let statusCode = try await self.mockUsecase.deleteRoom(roomId: "") + if statusCode == 204 { + XCTAssertTrue(true) + } else { + XCTFail() + } + } catch { + XCTFail() + } + } + + func test_deleteLeaveRoom함수가_올바른값을_리턴하는가() async throws { + do { + let statusCode = try await self.mockUsecase.deleteLeaveRoom(roomId: "") + if statusCode == 204 { + XCTAssertTrue(true) + } else { + XCTFail() + } + } catch { + XCTFail() + } + } + + func test_delete함수가_400이들어왔을때_에러를_리턴하는가() async throws { + let mock = MockDetailWaitUsecase(statusCode: 400) + + do { + let statusCode = try await mock.deleteRoom(roomId: "") + if statusCode == 204 { + XCTFail() + } else { + XCTAssertTrue(true) + } + } catch { + XCTAssertTrue(true) + } + } + + func test_deleteLeave함수가_400이들어왔을때_에러를_리턴하는가() async throws { + let mock = MockDetailWaitUsecase(statusCode: 400) + + do { + let statusCode = try await mock.deleteLeaveRoom(roomId: "") + if statusCode == 204 { + XCTFail() + } else { + XCTAssertTrue(true) + } + } catch { + XCTAssertTrue(true) + } + } +} + +final class MockDetailWaitUsecase: DetailWaitUseCase { + let statusCode: Int + var roomInformation: Manito.RoomInfo = .testRoom + + init(statusCode: Int) { + self.statusCode = statusCode + } + + func fetchRoomInformaion(roomId: String) async throws -> RoomInfoDTO { + try await Task.sleep(nanoseconds: 3_000) + + return .testDummyRoomDTO + } + + func patchStartManitto(roomId: String) async throws -> UserInfoDTO { + try await Task.sleep(nanoseconds: 3_000) + + return .testDummyUserManittee + } + + func deleteRoom(roomId: String) async throws -> Int { + try await Task.sleep(nanoseconds: 3_000) + + return self.statusCode + } + + func deleteLeaveRoom(roomId: String) async throws -> Int { + try await Task.sleep(nanoseconds: 3_000) + + return self.statusCode + } +} diff --git a/Manito/ManitoTests/Presentation/DetailWait/Service/MockDetailWaitService.swift b/Manito/ManitoTests/Presentation/DetailWait/Service/MockDetailWaitService.swift deleted file mode 100644 index a7a80c61d..000000000 --- a/Manito/ManitoTests/Presentation/DetailWait/Service/MockDetailWaitService.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// MockDetailWaitService.swift -// ManitoTests -// -// Created by Mingwan Choi on 2023/09/02. -// - -import Foundation -@testable import Manito - -final class MockDetailWaitService: DetailWaitServicable { - // FIXME: - network mocking 만들어야함. - func fetchWaitingRoomInfo(roomId: String) async throws -> Manito.RoomInfoDTO { - let room = RoomInfoDTO( - roomInformation: RoomListItemDTO(id: 1, title: "테스트타이틀", state: "PRE", participatingCount: 5, capacity: 5, startDate: "2023.01.01", endDate: "2023.01.05"), - participants: ParticipantListDTO(count: 5, members: [ - UserInfoDTO(id: "100", nickname: "유저1"), - UserInfoDTO(id: "200", nickname: "유저2"), - UserInfoDTO(id: "300", nickname: "유저3"), - UserInfoDTO(id: "400", nickname: "유저4"), - UserInfoDTO(id: "500", nickname: "유저5") - ]), - manittee: UserInfoDTO(id: "1", nickname: "테스트마니띠"), - manitto: UserInfoDTO(id: "2", nickname: "테스트마니또"), - invitation: InvitationCodeDTO(code: "ABCDEF"), - didViewRoulette: false, - mission: IndividualMissionDTO(id: 1, content: "테스트미션"), - admin: false, - messages: MessageCountInfoDTO(count: 3)) - return room - } - - func patchStartManitto(roomId: String) async throws -> Manito.UserInfoDTO { - return UserInfoDTO(id: "1", nickname: "테스트마니띠") - } - - func deleteRoom(roomId: String) async throws -> Int { - return 200 - } - - func deleteLeaveRoom(roomId: String) async throws -> Int { - return 200 - } -} diff --git a/Manito/ManitoTests/Presentation/DetailWait/ViewModel/DetailWaitViewModelTest.swift b/Manito/ManitoTests/Presentation/DetailWait/ViewModel/DetailWaitViewModelTest.swift index fba6c0e59..a53d2ebce 100644 --- a/Manito/ManitoTests/Presentation/DetailWait/ViewModel/DetailWaitViewModelTest.swift +++ b/Manito/ManitoTests/Presentation/DetailWait/ViewModel/DetailWaitViewModelTest.swift @@ -12,16 +12,13 @@ import XCTest final class DetailWaitViewModelTest: XCTestCase { private var viewModel: DetailWaitViewModel! - private var service: MockDetailWaitService! private var cancellable: Set! private var output: DetailWaitViewModel.Output! override func setUp() { super.setUp() - self.service = MockDetailWaitService() - self.viewModel = DetailWaitViewModel(roomIndex: 0, detailWaitService: self.service) + self.viewModel = DetailWaitViewModel(roomId: "0", usecase: MockDetailWaitUsecase(statusCode: 200)) self.cancellable = Set() - self.viewModel.setRoomInformation(room: RoomInfo.testRoom) } override func tearDown() { @@ -70,7 +67,6 @@ final class DetailWaitViewModelTest: XCTestCase { func testMakeRoomInformation() { // given let checkRoom = RoomInfo.testRoom - self.viewModel.setRoomInformation(room: checkRoom) // then let testRoom = self.viewModel.makeRoomInformation() @@ -117,8 +113,13 @@ final class DetailWaitViewModelTest: XCTestCase { case .finished: break } - }, receiveValue: { room in - testRoom = room + }, receiveValue: { result in + switch result { + case .success(let roomInfo): + testRoom = roomInfo + case .failure: + XCTFail() + } expectation.fulfill() }) .store(in: &self.cancellable) @@ -139,7 +140,7 @@ final class DetailWaitViewModelTest: XCTestCase { let output = self.viewModel.transform(input) // when - output.manitteeNickname + output.selectManitteeInfo .sink(receiveCompletion: { result in switch result { case .finished: @@ -147,7 +148,8 @@ final class DetailWaitViewModelTest: XCTestCase { case .failure: XCTFail("fail") } - }, receiveValue: { nickname in + }, receiveValue: { data in + guard let nickname = data.userInfo?.nickname else { return } testNickname = nickname expectation.fulfill() }) diff --git a/Manito/ManitoTests/Util/Extension/Type/Date+ExtensionTest.swift b/Manito/ManitoTests/Util/Extension/Type/Date+ExtensionTest.swift new file mode 100644 index 000000000..73799e9b6 --- /dev/null +++ b/Manito/ManitoTests/Util/Extension/Type/Date+ExtensionTest.swift @@ -0,0 +1,103 @@ +// +// Date+ExtensionTest.swift +// ManitoTests +// +// Created by Mingwan Choi on 2023/09/19. +// + +import XCTest +@testable import Manito + +final class Date_ExtensionTest: XCTestCase { + func test_isToday_변수가_존재하는가() { + let sut = Date() + + let _ = sut.isToday + } + + func test_isToday_오늘날짜에_올바르게_반환하는가() { + // given + let dateString = Date().toFullString + let today = dateString.toFullDate! + + // when + let isToday = today.isToday + + // then + XCTAssertTrue(isToday) + } + + func test_isToday_과거날짜에_올바르게_반환하는가() { + // given + let oneTimeInterval: TimeInterval = 86400 + let dateString = Date().toFullString + let today = dateString.toFullDate! + let sut = today - oneTimeInterval + + // when + let isToday = sut.isToday + + // then + XCTAssertFalse(isToday) + } + + func test_isToday_미래날짜에_올바르게_반환하는가() { + // given + let oneTimeInterval: TimeInterval = 86400 + let dateString = Date().toFullString + let today = dateString.toFullDate! + let sut = today + oneTimeInterval + + // when + let isToday = sut.isToday + + // then + XCTAssertFalse(isToday) + } + + func test_isPast_변수가_존재하는가() { + let date = Date() + + let _ = date.isPast + } + + func test_isPast_과거날짜에_올바르게_반환하는가() { + // given + let oneTimeInterval: TimeInterval = 86400 + let dateString = Date().toFullString + let today = dateString.toFullDate! + let sut = today - oneTimeInterval + + // when + let isPast = sut.isPast + + // then + XCTAssertTrue(isPast) + } + + func test_isPast_미래날짜에_올바르게_반환하는가() { + // given + let oneTimeInterval: TimeInterval = 86400 + let dateString = Date().toFullString + let today = dateString.toFullDate! + let sut = today + oneTimeInterval + + // when + let isPast = sut.isPast + + // then + XCTAssertFalse(isPast) + } + + func test_isPast_오늘날짜에_올바르게_반환하는가() { + // given + let dateString = Date().toFullString + let today = dateString.toFullDate! + + // when + let isPast = today.isPast + + // then + XCTAssertFalse(isPast) + } +} diff --git a/Manito/Modules/MTResource/MTResource.xcodeproj/project.pbxproj b/Manito/Modules/MTResource/MTResource.xcodeproj/project.pbxproj new file mode 100644 index 000000000..c58c919fe --- /dev/null +++ b/Manito/Modules/MTResource/MTResource.xcodeproj/project.pbxproj @@ -0,0 +1,402 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + B5546A1F2AADD618004D9FE6 /* MTResource.h in Headers */ = {isa = PBXBuildFile; fileRef = B5546A1E2AADD618004D9FE6 /* MTResource.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B5546A312AADD7CA004D9FE6 /* Image.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5546A302AADD7CA004D9FE6 /* Image.xcassets */; }; + B5546A432AADF609004D9FE6 /* DungGeunMo.otf in Resources */ = {isa = PBXBuildFile; fileRef = B5546A422AADF609004D9FE6 /* DungGeunMo.otf */; }; + B5546A462AADF6B3004D9FE6 /* ColorSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5546A452AADF6B3004D9FE6 /* ColorSet.swift */; }; + B5546A482AADF7ED004D9FE6 /* FontSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5546A472AADF7ED004D9FE6 /* FontSet.swift */; }; + B5546A4A2AADF8BF004D9FE6 /* ImageSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5546A492AADF8BF004D9FE6 /* ImageSet.swift */; }; + B5ABB3972AAEDEE100F31C78 /* BundleToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5ABB3962AAEDEE100F31C78 /* BundleToken.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + B5546A1B2AADD618004D9FE6 /* MTResource.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MTResource.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B5546A1E2AADD618004D9FE6 /* MTResource.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MTResource.h; sourceTree = ""; }; + B5546A302AADD7CA004D9FE6 /* Image.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Image.xcassets; sourceTree = ""; }; + B5546A422AADF609004D9FE6 /* DungGeunMo.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = DungGeunMo.otf; sourceTree = ""; }; + B5546A452AADF6B3004D9FE6 /* ColorSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorSet.swift; sourceTree = ""; }; + B5546A472AADF7ED004D9FE6 /* FontSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontSet.swift; sourceTree = ""; }; + B5546A492AADF8BF004D9FE6 /* ImageSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageSet.swift; sourceTree = ""; }; + B5ABB3962AAEDEE100F31C78 /* BundleToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleToken.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + B5546A182AADD618004D9FE6 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + B5546A112AADD618004D9FE6 = { + isa = PBXGroup; + children = ( + B5546A1D2AADD618004D9FE6 /* MTResource */, + B5546A1C2AADD618004D9FE6 /* Products */, + ); + sourceTree = ""; + }; + B5546A1C2AADD618004D9FE6 /* Products */ = { + isa = PBXGroup; + children = ( + B5546A1B2AADD618004D9FE6 /* MTResource.framework */, + ); + name = Products; + sourceTree = ""; + }; + B5546A1D2AADD618004D9FE6 /* MTResource */ = { + isa = PBXGroup; + children = ( + B5546A1E2AADD618004D9FE6 /* MTResource.h */, + B5546A452AADF6B3004D9FE6 /* ColorSet.swift */, + B5546A472AADF7ED004D9FE6 /* FontSet.swift */, + B5546A492AADF8BF004D9FE6 /* ImageSet.swift */, + B5546A2F2AADD72F004D9FE6 /* Resource */, + B5ABB3952AAEDED000F31C78 /* Foundation */, + ); + path = MTResource; + sourceTree = ""; + }; + B5546A2F2AADD72F004D9FE6 /* Resource */ = { + isa = PBXGroup; + children = ( + B5546A302AADD7CA004D9FE6 /* Image.xcassets */, + B5546A412AADF5F7004D9FE6 /* Fonts */, + ); + path = Resource; + sourceTree = ""; + }; + B5546A412AADF5F7004D9FE6 /* Fonts */ = { + isa = PBXGroup; + children = ( + B5546A422AADF609004D9FE6 /* DungGeunMo.otf */, + ); + path = Fonts; + sourceTree = ""; + }; + B5ABB3952AAEDED000F31C78 /* Foundation */ = { + isa = PBXGroup; + children = ( + B5ABB3962AAEDEE100F31C78 /* BundleToken.swift */, + ); + path = Foundation; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + B5546A162AADD618004D9FE6 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + B5546A1F2AADD618004D9FE6 /* MTResource.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + B5546A1A2AADD618004D9FE6 /* MTResource */ = { + isa = PBXNativeTarget; + buildConfigurationList = B5546A222AADD618004D9FE6 /* Build configuration list for PBXNativeTarget "MTResource" */; + buildPhases = ( + B5546A162AADD618004D9FE6 /* Headers */, + B5546A172AADD618004D9FE6 /* Sources */, + B5546A182AADD618004D9FE6 /* Frameworks */, + B5546A192AADD618004D9FE6 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = MTResource; + productName = MTResource; + productReference = B5546A1B2AADD618004D9FE6 /* MTResource.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + B5546A122AADD618004D9FE6 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastUpgradeCheck = 1430; + TargetAttributes = { + B5546A1A2AADD618004D9FE6 = { + CreatedOnToolsVersion = 14.3.1; + LastSwiftMigration = 1430; + }; + }; + }; + buildConfigurationList = B5546A152AADD618004D9FE6 /* Build configuration list for PBXProject "MTResource" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = B5546A112AADD618004D9FE6; + productRefGroup = B5546A1C2AADD618004D9FE6 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + B5546A1A2AADD618004D9FE6 /* MTResource */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + B5546A192AADD618004D9FE6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B5546A312AADD7CA004D9FE6 /* Image.xcassets in Resources */, + B5546A432AADF609004D9FE6 /* DungGeunMo.otf in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + B5546A172AADD618004D9FE6 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B5546A4A2AADF8BF004D9FE6 /* ImageSet.swift in Sources */, + B5546A462AADF6B3004D9FE6 /* ColorSet.swift in Sources */, + B5ABB3972AAEDEE100F31C78 /* BundleToken.swift in Sources */, + B5546A482AADF7ED004D9FE6 /* FontSet.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + B5546A202AADD618004D9FE6 /* Dev */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Dev; + }; + B5546A212AADD618004D9FE6 /* Prod */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Prod; + }; + B5546A232AADD618004D9FE6 /* Dev */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = com.TeamFirefighter.MTResource; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Dev; + }; + B5546A242AADD618004D9FE6 /* Prod */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = com.TeamFirefighter.MTResource; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Prod; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + B5546A152AADD618004D9FE6 /* Build configuration list for PBXProject "MTResource" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B5546A202AADD618004D9FE6 /* Dev */, + B5546A212AADD618004D9FE6 /* Prod */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Prod; + }; + B5546A222AADD618004D9FE6 /* Build configuration list for PBXNativeTarget "MTResource" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B5546A232AADD618004D9FE6 /* Dev */, + B5546A242AADD618004D9FE6 /* Prod */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Prod; + }; +/* End XCConfigurationList section */ + }; + rootObject = B5546A122AADD618004D9FE6 /* Project object */; +} diff --git a/Manito/Modules/MTResource/MTResource.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Manito/Modules/MTResource/MTResource.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/Manito/Modules/MTResource/MTResource.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Manito/Modules/MTResource/MTResource.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Manito/Modules/MTResource/MTResource.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/Manito/Modules/MTResource/MTResource.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Manito/Modules/MTResource/MTResource/ColorSet.swift b/Manito/Modules/MTResource/MTResource/ColorSet.swift new file mode 100644 index 000000000..c424d7341 --- /dev/null +++ b/Manito/Modules/MTResource/MTResource/ColorSet.swift @@ -0,0 +1,89 @@ +// +// ColorSet.swift +// MTResource +// +// Created by SHIN YOON AH on 2023/09/10. +// + +import UIKit + +public extension UIColor { + + // MARK: - red + + static var mainRed: UIColor { return UIColor(hex: "#CA2214") } + static var shadowRed: UIColor { return UIColor(hex: "#A4291F") } + static var red001: UIColor { return UIColor(hex: "#C84842") } + static var red002: UIColor { return UIColor(hex: "#823029") } + + // MARK: - background + + static var backgroundGrey: UIColor { return UIColor(hex: "#242424") } + + // MARK: - grey + + static var grey001: UIColor { return UIColor(hex: "#D9D9D9") } + static var grey002: UIColor { return UIColor(hex: "#A5A5A5") } + static var grey003: UIColor { return UIColor(hex: "#828282") } + static var grey004: UIColor { return UIColor(hex: "#717174") } + static var grey005: UIColor { return UIColor(hex: "#C1C1C1") } + + // MARK: - darkGrey + + static var darkGrey001: UIColor { return UIColor(hex: "#616161") } + static var darkGrey002: UIColor { return UIColor(hex: "#5A5A5A") } + static var darkGrey003: UIColor { return UIColor(hex: "#3D3D3D") } + static var darkGrey004: UIColor { return UIColor(hex: "#343434") } + + // MARK: - orange + + static var subOrange: UIColor { return UIColor(hex: "#EAB33D") } + + // MARK: - blue + + static var subBlue: UIColor { return UIColor(hex: "#3472EB") } + static var backgroundBlue: UIColor { return UIColor(hex: "#8BC4E7") } + static var codeBlue: UIColor { return UIColor(hex: "#001AFF") } + + // MARK: - yellow + + static var yellow001: UIColor { return UIColor(hex: "#EDC845") } + static var shadowYellow: UIColor { return UIColor(hex: "#C7A83C") } + + // MARK: - pink + + static var subPink: UIColor { return UIColor(hex: "#F18DB1") } + + // MARK: - badge + + static var badgeBeige: UIColor { return UIColor(hex: "#FFDBBA") } + + // MARK: - character + + static var characterYellow: UIColor { return UIColor(hex: "#EFDC4A") } + static var characterRed: UIColor { return UIColor(hex: "#D03D40") } + static var characterOrange: UIColor { return UIColor(hex: "#D78041") } + static var characterBlue: UIColor { return UIColor(hex: "#0811CD") } + static var characterLightGreen: UIColor { return UIColor(hex: "#8AB542") } + static var characterPurple: UIColor { return UIColor(hex: "#8B3183") } + static var characterGreen: UIColor { return UIColor(hex: "#43844E") } + static var characterPink: UIColor { return UIColor(hex: "#E46593") } +} + +public extension UIColor { + convenience init(hex: String, alpha: CGFloat = 1.0) { + var hexFormatted: String = hex.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).uppercased() + + if hexFormatted.hasPrefix("#") { + hexFormatted = String(hexFormatted.dropFirst()) + } + + assert(hexFormatted.count == 6, "Invalid hex code used.") + var rgbValue: UInt64 = 0 + Scanner(string: hexFormatted).scanHexInt64(&rgbValue) + + self.init(red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, + green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, + blue: CGFloat(rgbValue & 0x0000FF) / 255.0, alpha: alpha) + } +} diff --git a/Manito/Modules/MTResource/MTResource/FontSet.swift b/Manito/Modules/MTResource/MTResource/FontSet.swift new file mode 100644 index 000000000..1f8448784 --- /dev/null +++ b/Manito/Modules/MTResource/MTResource/FontSet.swift @@ -0,0 +1,36 @@ +// +// FontSet.swift +// MTResource +// +// Created by SHIN YOON AH on 2023/09/10. +// + +import UIKit + +public enum AppFontName: String { + case regular = "DungGeunMo" + + var path: String { + switch self { + case .regular: return "DungGeunMo.otf" + } + } + + public func register() { + guard let url = BundleToken.bundle.url(forResource: self.path, withExtension: nil) else { return } + CTFontManagerRegisterFontsForURL(url as CFURL, .process, nil) + } + + fileprivate func registerIfNeeded() { + if !UIFont.fontNames(forFamilyName: self.rawValue).contains(self.rawValue) { + register() + } + } +} + +public extension UIFont { + static func font(_ style: AppFontName, ofSize size: CGFloat) -> UIFont { + style.registerIfNeeded() + return UIFont(name: style.rawValue, size: size)! + } +} diff --git a/Manito/Modules/MTResource/MTResource/Foundation/BundleToken.swift b/Manito/Modules/MTResource/MTResource/Foundation/BundleToken.swift new file mode 100644 index 000000000..d02d41b1c --- /dev/null +++ b/Manito/Modules/MTResource/MTResource/Foundation/BundleToken.swift @@ -0,0 +1,14 @@ +// +// BundleToken.swift +// MTResource +// +// Created by SHIN YOON AH on 2023/09/11. +// + +import Foundation + +final class BundleToken { + static var bundle: Bundle { + return Bundle(for: BundleToken.self) + } +} diff --git a/Manito/Modules/MTResource/MTResource/ImageSet.swift b/Manito/Modules/MTResource/MTResource/ImageSet.swift new file mode 100644 index 000000000..8544085d1 --- /dev/null +++ b/Manito/Modules/MTResource/MTResource/ImageSet.swift @@ -0,0 +1,87 @@ +// +// ImageSet.swift +// MTResource +// +// Created by SHIN YOON AH on 2023/09/10. +// + +import UIKit + +public extension UIImage { + + enum Icon { + public static var list: UIImage { .load(name: "btnList") } + public static var manitti: UIImage { .load(name: "btnManiTti") } + public static var newRoom: UIImage { .load(name: "btnNewRoom") } + public static var insta: UIImage { .load(name: "ic_insta")} + public static var report: UIImage { .load(name: "ic_report")} + public static var more: UIImage { .load(name: "ic_more")} + public static var letterMissionInfo: UIImage { .load(name: "ic_letterInfo")} + public static var missionInfo: UIImage { .load(name: "ic_missionInfo")} + public static var right: UIImage { .load(name: "ic_right")} + public static var save: UIImage { .load(name: "ic_save")} + public static var pencil: UIImage { .load(name: "ic_pencil") } + } + + enum Button { + public static var back: UIImage { .load(name: "ic_back") } + public static var xmark: UIImage { .load(name: "ic_exit") } + public static var setting: UIImage { .load(name: "ic_setting") } + public static var camera: UIImage { .load(name: "ic_camera") } + } + + enum Image { + public static var appIcon: UIImage { .load(name: "imgAppIcon")} + public static var logo: UIImage { .load(name: "imgLogo") } + public static var textLogo: UIImage { .load(name: "imgTextLogo")} + public static var background: UIImage { .load(name: "imgBackground") } + public static var devBackground: UIImage { .load(name: "imgDevBackground") } + public static var star: UIImage { .load(name: "imgStar") } + public static var sliderThumb: UIImage { .load(name: "btnSliderThumb") } + public static var characters: UIImage { .load(name: "img_characters") } + public static var codeBackground: UIImage { .load(name: "imgCodeBackground") } + public static var commonMisson: UIImage { .load(name: "imgCommonMisson") } + public static var enterRoom: UIImage { .load(name: "imgEnterRoom") } + public static var guideBox: UIImage { .load(name: "img_guideBox") } + public static var ma: UIImage { .load(name: "imgMa") } + public static var ni: UIImage { .load(name: "imgNi") } + public static var tto: UIImage { .load(name: "imgTto") } + public static var chemi: UIImage { .load(name: "imgMaChemi") } + public static var coby: UIImage { .load(name: "imgMaCoby") } + public static var duna: UIImage { .load(name: "imgMaDuna") } + public static var dinner: UIImage { .load(name: "imgMaDinner") } + public static var hoya: UIImage { .load(name: "imgMaHoya") } + public static var livvy: UIImage { .load(name: "imgMaLivvy") } + public static var daon: UIImage { .load(name: "imgMaDaon") } + public static var leo: UIImage { .load(name: "imgMaLeo") } + public static var characterPink: UIImage { .load(name: "imgCharacterPink") } + public static var characterBrown: UIImage { .load(name: "imgCharacterBrown") } + public static var characterBlue: UIImage { .load(name: "imgCharacterBlue") } + public static var characterRed: UIImage { .load(name: "imgCharacterRed") } + public static var characterOrange: UIImage { .load(name: "imgCharacterOrange") } + public static var characterYellow: UIImage { .load(name: "imgCharacterYellow") } + public static var characterLightGreen: UIImage { .load(name: "imgCharacterLightGreen") } + public static var characterHeavyPink: UIImage { .load(name: "imgCharacterHeavyPink") } + public static var characterPurple: UIImage { .load(name: "imgCharacterPurple") } + } + +} + +public extension UIImage { + static func load(name: String) -> UIImage { + let bundle = BundleToken.bundle + guard let image = UIImage(named: name, in: bundle, compatibleWith: nil) else { + return UIImage() + } + image.accessibilityIdentifier = name + return image + } + + static func load(systemName: String) -> UIImage { + guard let image = UIImage(systemName: systemName, compatibleWith: nil) else { + return UIImage() + } + image.accessibilityIdentifier = systemName + return image + } +} diff --git a/Manito/Modules/MTResource/MTResource/MTResource.h b/Manito/Modules/MTResource/MTResource/MTResource.h new file mode 100644 index 000000000..d35fe2ba8 --- /dev/null +++ b/Manito/Modules/MTResource/MTResource/MTResource.h @@ -0,0 +1,18 @@ +// +// MTResource.h +// MTResource +// +// Created by SHIN YOON AH on 2023/09/10. +// + +#import + +//! Project version number for MTResource. +FOUNDATION_EXPORT double MTResourceVersionNumber; + +//! Project version string for MTResource. +FOUNDATION_EXPORT const unsigned char MTResourceVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/Manito/Manito/Resource/Fonts/DungGeunMo.otf b/Manito/Modules/MTResource/MTResource/Resource/Fonts/DungGeunMo.otf similarity index 100% rename from Manito/Manito/Resource/Fonts/DungGeunMo.otf rename to Manito/Modules/MTResource/MTResource/Resource/Fonts/DungGeunMo.otf diff --git a/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/Contents.json new file mode 100644 index 000000000..73c00596a --- /dev/null +++ b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Manito/Manito/Resource/Assets.xcassets/btnList.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnList.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/btnList.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnList.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/btnList.imageset/btnList.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnList.imageset/btnList.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/btnList.imageset/btnList.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnList.imageset/btnList.png diff --git a/Manito/Manito/Resource/Assets.xcassets/btnList.imageset/btnList@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnList.imageset/btnList@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/btnList.imageset/btnList@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnList.imageset/btnList@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/btnList.imageset/btnList@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnList.imageset/btnList@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/btnList.imageset/btnList@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnList.imageset/btnList@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/btnManiTti.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnManiTti.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/btnManiTti.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnManiTti.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/btnManiTti.imageset/btnManiTti.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnManiTti.imageset/btnManiTti.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/btnManiTti.imageset/btnManiTti.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnManiTti.imageset/btnManiTti.png diff --git a/Manito/Manito/Resource/Assets.xcassets/btnManiTti.imageset/btnManiTti@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnManiTti.imageset/btnManiTti@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/btnManiTti.imageset/btnManiTti@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnManiTti.imageset/btnManiTti@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/btnManiTti.imageset/btnManiTti@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnManiTti.imageset/btnManiTti@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/btnManiTti.imageset/btnManiTti@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnManiTti.imageset/btnManiTti@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/btnNewRoom.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnNewRoom.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/btnNewRoom.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnNewRoom.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/btnNewRoom.imageset/btnNewRoom.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnNewRoom.imageset/btnNewRoom.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/btnNewRoom.imageset/btnNewRoom.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnNewRoom.imageset/btnNewRoom.png diff --git a/Manito/Manito/Resource/Assets.xcassets/btnNewRoom.imageset/btnNewRoom@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnNewRoom.imageset/btnNewRoom@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/btnNewRoom.imageset/btnNewRoom@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnNewRoom.imageset/btnNewRoom@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/btnNewRoom.imageset/btnNewRoom@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnNewRoom.imageset/btnNewRoom@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/btnNewRoom.imageset/btnNewRoom@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnNewRoom.imageset/btnNewRoom@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/btnSliderThumb.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnSliderThumb.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/btnSliderThumb.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnSliderThumb.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/btnSliderThumb.imageset/btnSllderThumb.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnSliderThumb.imageset/btnSllderThumb.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/btnSliderThumb.imageset/btnSllderThumb.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnSliderThumb.imageset/btnSllderThumb.png diff --git a/Manito/Manito/Resource/Assets.xcassets/btnSliderThumb.imageset/btnSllderThumb@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnSliderThumb.imageset/btnSllderThumb@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/btnSliderThumb.imageset/btnSllderThumb@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnSliderThumb.imageset/btnSllderThumb@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/btnSliderThumb.imageset/btnSllderThumb@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnSliderThumb.imageset/btnSllderThumb@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/btnSliderThumb.imageset/btnSllderThumb@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/btnSliderThumb.imageset/btnSllderThumb@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_back.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_back.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_back.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_back.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_back.imageset/ic_back.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_back.imageset/ic_back.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_back.imageset/ic_back.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_back.imageset/ic_back.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_back.imageset/ic_back@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_back.imageset/ic_back@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_back.imageset/ic_back@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_back.imageset/ic_back@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_back.imageset/ic_back@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_back.imageset/ic_back@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_back.imageset/ic_back@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_back.imageset/ic_back@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_camera.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_camera.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_camera.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_camera.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_camera.imageset/ic_camera.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_camera.imageset/ic_camera.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_camera.imageset/ic_camera.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_camera.imageset/ic_camera.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_camera.imageset/ic_camera@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_camera.imageset/ic_camera@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_camera.imageset/ic_camera@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_camera.imageset/ic_camera@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_camera.imageset/ic_camera@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_camera.imageset/ic_camera@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_camera.imageset/ic_camera@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_camera.imageset/ic_camera@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_exit.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_exit.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_exit.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_exit.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_exit.imageset/ic_exit.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_exit.imageset/ic_exit.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_exit.imageset/ic_exit.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_exit.imageset/ic_exit.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_exit.imageset/ic_exit@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_exit.imageset/ic_exit@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_exit.imageset/ic_exit@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_exit.imageset/ic_exit@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_exit.imageset/ic_exit@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_exit.imageset/ic_exit@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_exit.imageset/ic_exit@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_exit.imageset/ic_exit@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_insta.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_insta.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_insta.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_insta.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_insta.imageset/ic_insta.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_insta.imageset/ic_insta.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_insta.imageset/ic_insta.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_insta.imageset/ic_insta.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_insta.imageset/ic_insta@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_insta.imageset/ic_insta@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_insta.imageset/ic_insta@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_insta.imageset/ic_insta@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_insta.imageset/ic_insta@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_insta.imageset/ic_insta@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_insta.imageset/ic_insta@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_insta.imageset/ic_insta@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_letterInfo.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_letterInfo.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_letterInfo.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_letterInfo.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_letterInfo.imageset/ic_letterInfo.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_letterInfo.imageset/ic_letterInfo.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_letterInfo.imageset/ic_letterInfo.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_letterInfo.imageset/ic_letterInfo.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_letterInfo.imageset/ic_letterInfo@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_letterInfo.imageset/ic_letterInfo@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_letterInfo.imageset/ic_letterInfo@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_letterInfo.imageset/ic_letterInfo@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_letterInfo.imageset/ic_letterInfo@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_letterInfo.imageset/ic_letterInfo@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_letterInfo.imageset/ic_letterInfo@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_letterInfo.imageset/ic_letterInfo@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_missionInfo.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_missionInfo.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_missionInfo.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_missionInfo.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_missionInfo.imageset/ic_missionInfo.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_missionInfo.imageset/ic_missionInfo.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_missionInfo.imageset/ic_missionInfo.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_missionInfo.imageset/ic_missionInfo.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_missionInfo.imageset/ic_missionInfo@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_missionInfo.imageset/ic_missionInfo@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_missionInfo.imageset/ic_missionInfo@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_missionInfo.imageset/ic_missionInfo@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_missionInfo.imageset/ic_missionInfo@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_missionInfo.imageset/ic_missionInfo@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_missionInfo.imageset/ic_missionInfo@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_missionInfo.imageset/ic_missionInfo@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_more.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_more.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_more.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_more.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_more.imageset/ic_more.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_more.imageset/ic_more.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_more.imageset/ic_more.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_more.imageset/ic_more.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_more.imageset/ic_more@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_more.imageset/ic_more@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_more.imageset/ic_more@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_more.imageset/ic_more@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_more.imageset/ic_more@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_more.imageset/ic_more@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_more.imageset/ic_more@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_more.imageset/ic_more@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_pencil.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_pencil.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_pencil.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_pencil.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_pencil.imageset/ic_pencil.pdf b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_pencil.imageset/ic_pencil.pdf similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_pencil.imageset/ic_pencil.pdf rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_pencil.imageset/ic_pencil.pdf diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_report.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_report.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_report.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_report.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_report.imageset/ic_report.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_report.imageset/ic_report.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_report.imageset/ic_report.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_report.imageset/ic_report.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_report.imageset/ic_report@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_report.imageset/ic_report@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_report.imageset/ic_report@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_report.imageset/ic_report@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_report.imageset/ic_report@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_report.imageset/ic_report@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_report.imageset/ic_report@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_report.imageset/ic_report@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_right.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_right.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_right.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_right.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_right.imageset/ic_right.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_right.imageset/ic_right.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_right.imageset/ic_right.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_right.imageset/ic_right.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_right.imageset/ic_right@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_right.imageset/ic_right@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_right.imageset/ic_right@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_right.imageset/ic_right@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_right.imageset/ic_right@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_right.imageset/ic_right@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_right.imageset/ic_right@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_right.imageset/ic_right@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_save.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_save.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_save.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_save.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_save.imageset/ic_save.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_save.imageset/ic_save.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_save.imageset/ic_save.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_save.imageset/ic_save.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_save.imageset/ic_save@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_save.imageset/ic_save@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_save.imageset/ic_save@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_save.imageset/ic_save@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_save.imageset/ic_save@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_save.imageset/ic_save@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_save.imageset/ic_save@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_save.imageset/ic_save@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_setting.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_setting.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_setting.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_setting.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_setting.imageset/ic_setting.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_setting.imageset/ic_setting.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_setting.imageset/ic_setting.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_setting.imageset/ic_setting.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_setting.imageset/ic_setting@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_setting.imageset/ic_setting@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_setting.imageset/ic_setting@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_setting.imageset/ic_setting@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/ic_setting.imageset/ic_setting@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_setting.imageset/ic_setting@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/ic_setting.imageset/ic_setting@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/ic_setting.imageset/ic_setting@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgAppIcon.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgAppIcon.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgAppIcon.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgAppIcon.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgAppIcon.imageset/imgAppIcon.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgAppIcon.imageset/imgAppIcon.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgAppIcon.imageset/imgAppIcon.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgAppIcon.imageset/imgAppIcon.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgAppIcon.imageset/imgAppIcon@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgAppIcon.imageset/imgAppIcon@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgAppIcon.imageset/imgAppIcon@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgAppIcon.imageset/imgAppIcon@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgAppIcon.imageset/imgAppIcon@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgAppIcon.imageset/imgAppIcon@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgAppIcon.imageset/imgAppIcon@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgAppIcon.imageset/imgAppIcon@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgBackground.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgBackground.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgBackground.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgBackground.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgBackground.imageset/imgBackground.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgBackground.imageset/imgBackground.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgBackground.imageset/imgBackground.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgBackground.imageset/imgBackground.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgBackground.imageset/imgBackground@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgBackground.imageset/imgBackground@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgBackground.imageset/imgBackground@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgBackground.imageset/imgBackground@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgBackground.imageset/imgBackground@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgBackground.imageset/imgBackground@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgBackground.imageset/imgBackground@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgBackground.imageset/imgBackground@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCharacterBlue.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterBlue.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCharacterBlue.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterBlue.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCharacterBlue.imageset/imgCharacterBlue.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterBlue.imageset/imgCharacterBlue.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCharacterBlue.imageset/imgCharacterBlue.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterBlue.imageset/imgCharacterBlue.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCharacterBrown.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterBrown.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCharacterBrown.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterBrown.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCharacterBrown.imageset/imgCharacterBrown.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterBrown.imageset/imgCharacterBrown.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCharacterBrown.imageset/imgCharacterBrown.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterBrown.imageset/imgCharacterBrown.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCharacterHeavyPink.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterHeavyPink.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCharacterHeavyPink.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterHeavyPink.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCharacterHeavyPink.imageset/imgCharacterHeavyPink.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterHeavyPink.imageset/imgCharacterHeavyPink.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCharacterHeavyPink.imageset/imgCharacterHeavyPink.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterHeavyPink.imageset/imgCharacterHeavyPink.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCharacterLightGreen.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterLightGreen.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCharacterLightGreen.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterLightGreen.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCharacterLightGreen.imageset/imgCharacterLightGreen.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterLightGreen.imageset/imgCharacterLightGreen.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCharacterLightGreen.imageset/imgCharacterLightGreen.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterLightGreen.imageset/imgCharacterLightGreen.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCharacterOrange.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterOrange.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCharacterOrange.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterOrange.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCharacterOrange.imageset/imgCharacterOrange.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterOrange.imageset/imgCharacterOrange.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCharacterOrange.imageset/imgCharacterOrange.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterOrange.imageset/imgCharacterOrange.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCharacterPink.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterPink.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCharacterPink.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterPink.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCharacterPink.imageset/imgCharacterPink.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterPink.imageset/imgCharacterPink.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCharacterPink.imageset/imgCharacterPink.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterPink.imageset/imgCharacterPink.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCharacterPurple.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterPurple.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCharacterPurple.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterPurple.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCharacterPurple.imageset/imgCharacterPurple.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterPurple.imageset/imgCharacterPurple.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCharacterPurple.imageset/imgCharacterPurple.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterPurple.imageset/imgCharacterPurple.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCharacterRed.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterRed.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCharacterRed.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterRed.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCharacterRed.imageset/imgCharacterRed.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterRed.imageset/imgCharacterRed.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCharacterRed.imageset/imgCharacterRed.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterRed.imageset/imgCharacterRed.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCharacterYellow.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterYellow.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCharacterYellow.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterYellow.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCharacterYellow.imageset/imgCharacterYellow.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterYellow.imageset/imgCharacterYellow.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCharacterYellow.imageset/imgCharacterYellow.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCharacterYellow.imageset/imgCharacterYellow.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCodeBackground.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCodeBackground.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCodeBackground.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCodeBackground.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCodeBackground.imageset/imgCodeBackground.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCodeBackground.imageset/imgCodeBackground.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCodeBackground.imageset/imgCodeBackground.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCodeBackground.imageset/imgCodeBackground.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCodeBackground.imageset/imgCodeBackground@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCodeBackground.imageset/imgCodeBackground@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCodeBackground.imageset/imgCodeBackground@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCodeBackground.imageset/imgCodeBackground@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCodeBackground.imageset/imgCodeBackground@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCodeBackground.imageset/imgCodeBackground@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCodeBackground.imageset/imgCodeBackground@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCodeBackground.imageset/imgCodeBackground@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCommonMisson.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCommonMisson.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCommonMisson.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCommonMisson.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCommonMisson.imageset/imgCommonMisson.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCommonMisson.imageset/imgCommonMisson.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCommonMisson.imageset/imgCommonMisson.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCommonMisson.imageset/imgCommonMisson.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCommonMisson.imageset/imgCommonMisson@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCommonMisson.imageset/imgCommonMisson@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCommonMisson.imageset/imgCommonMisson@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCommonMisson.imageset/imgCommonMisson@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgCommonMisson.imageset/imgCommonMisson@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCommonMisson.imageset/imgCommonMisson@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgCommonMisson.imageset/imgCommonMisson@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgCommonMisson.imageset/imgCommonMisson@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgDevBackground.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgDevBackground.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgDevBackground.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgDevBackground.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgDevBackground.imageset/imgBackground.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgDevBackground.imageset/imgBackground.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgDevBackground.imageset/imgBackground.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgDevBackground.imageset/imgBackground.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgEnterRoom.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgEnterRoom.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgEnterRoom.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgEnterRoom.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgEnterRoom.imageset/imgEnterRoom.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgEnterRoom.imageset/imgEnterRoom.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgEnterRoom.imageset/imgEnterRoom.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgEnterRoom.imageset/imgEnterRoom.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgEnterRoom.imageset/imgEnterRoom@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgEnterRoom.imageset/imgEnterRoom@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgEnterRoom.imageset/imgEnterRoom@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgEnterRoom.imageset/imgEnterRoom@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgEnterRoom.imageset/imgEnterRoom@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgEnterRoom.imageset/imgEnterRoom@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgEnterRoom.imageset/imgEnterRoom@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgEnterRoom.imageset/imgEnterRoom@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgLogo.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgLogo.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgLogo.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgLogo.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgLogo.imageset/imgLogo.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgLogo.imageset/imgLogo.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgLogo.imageset/imgLogo.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgLogo.imageset/imgLogo.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgLogo.imageset/imgLogo@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgLogo.imageset/imgLogo@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgLogo.imageset/imgLogo@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgLogo.imageset/imgLogo@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgLogo.imageset/imgLogo@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgLogo.imageset/imgLogo@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgLogo.imageset/imgLogo@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgLogo.imageset/imgLogo@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMa.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMa.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMa.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMa.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMa.imageset/imgMa.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMa.imageset/imgMa.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMa.imageset/imgMa.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMa.imageset/imgMa.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMa.imageset/imgMa@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMa.imageset/imgMa@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMa.imageset/imgMa@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMa.imageset/imgMa@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMa.imageset/imgMa@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMa.imageset/imgMa@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMa.imageset/imgMa@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMa.imageset/imgMa@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaChemi.imageset/Chemi01 1.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaChemi.imageset/Chemi01 1.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaChemi.imageset/Chemi01 1.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaChemi.imageset/Chemi01 1.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaChemi.imageset/Chemi01 1@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaChemi.imageset/Chemi01 1@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaChemi.imageset/Chemi01 1@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaChemi.imageset/Chemi01 1@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaChemi.imageset/Chemi01 1@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaChemi.imageset/Chemi01 1@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaChemi.imageset/Chemi01 1@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaChemi.imageset/Chemi01 1@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaChemi.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaChemi.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaChemi.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaChemi.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaCoby.imageset/Coby01 1.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaCoby.imageset/Coby01 1.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaCoby.imageset/Coby01 1.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaCoby.imageset/Coby01 1.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaCoby.imageset/Coby01 1@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaCoby.imageset/Coby01 1@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaCoby.imageset/Coby01 1@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaCoby.imageset/Coby01 1@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaCoby.imageset/Coby01 1@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaCoby.imageset/Coby01 1@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaCoby.imageset/Coby01 1@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaCoby.imageset/Coby01 1@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaCoby.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaCoby.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaCoby.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaCoby.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaDaon.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaDaon.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaDaon.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaDaon.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaDaon.imageset/imgMaDaon.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaDaon.imageset/imgMaDaon.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaDaon.imageset/imgMaDaon.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaDaon.imageset/imgMaDaon.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaDinner.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaDinner.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaDinner.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaDinner.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaDinner.imageset/Dinner01 1.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaDinner.imageset/Dinner01 1.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaDinner.imageset/Dinner01 1.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaDinner.imageset/Dinner01 1.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaDinner.imageset/Dinner01 1@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaDinner.imageset/Dinner01 1@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaDinner.imageset/Dinner01 1@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaDinner.imageset/Dinner01 1@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaDinner.imageset/Dinner01 1@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaDinner.imageset/Dinner01 1@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaDinner.imageset/Dinner01 1@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaDinner.imageset/Dinner01 1@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaDuna.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaDuna.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaDuna.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaDuna.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaDuna.imageset/Duna01 1.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaDuna.imageset/Duna01 1.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaDuna.imageset/Duna01 1.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaDuna.imageset/Duna01 1.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaDuna.imageset/Duna01 1@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaDuna.imageset/Duna01 1@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaDuna.imageset/Duna01 1@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaDuna.imageset/Duna01 1@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaDuna.imageset/Duna01 1@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaDuna.imageset/Duna01 1@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaDuna.imageset/Duna01 1@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaDuna.imageset/Duna01 1@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaHoya.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaHoya.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaHoya.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaHoya.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaHoya.imageset/Hoya 1.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaHoya.imageset/Hoya 1.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaHoya.imageset/Hoya 1.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaHoya.imageset/Hoya 1.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaHoya.imageset/Hoya 1@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaHoya.imageset/Hoya 1@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaHoya.imageset/Hoya 1@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaHoya.imageset/Hoya 1@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaHoya.imageset/Hoya 1@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaHoya.imageset/Hoya 1@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaHoya.imageset/Hoya 1@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaHoya.imageset/Hoya 1@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaLeo.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaLeo.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaLeo.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaLeo.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaLeo.imageset/imgMaLeo.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaLeo.imageset/imgMaLeo.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaLeo.imageset/imgMaLeo.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaLeo.imageset/imgMaLeo.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaLivvy.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaLivvy.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaLivvy.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaLivvy.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaLivvy.imageset/Livvy01 1.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaLivvy.imageset/Livvy01 1.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaLivvy.imageset/Livvy01 1.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaLivvy.imageset/Livvy01 1.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaLivvy.imageset/Livvy01 1@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaLivvy.imageset/Livvy01 1@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaLivvy.imageset/Livvy01 1@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaLivvy.imageset/Livvy01 1@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgMaLivvy.imageset/Livvy01 1@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaLivvy.imageset/Livvy01 1@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgMaLivvy.imageset/Livvy01 1@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgMaLivvy.imageset/Livvy01 1@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgNi.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgNi.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgNi.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgNi.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgNi.imageset/imgNi.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgNi.imageset/imgNi.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgNi.imageset/imgNi.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgNi.imageset/imgNi.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgNi.imageset/imgNi@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgNi.imageset/imgNi@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgNi.imageset/imgNi@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgNi.imageset/imgNi@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgNi.imageset/imgNi@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgNi.imageset/imgNi@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgNi.imageset/imgNi@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgNi.imageset/imgNi@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgStar.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgStar.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgStar.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgStar.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgStar.imageset/imgStar.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgStar.imageset/imgStar.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgStar.imageset/imgStar.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgStar.imageset/imgStar.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgStar.imageset/imgStar@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgStar.imageset/imgStar@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgStar.imageset/imgStar@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgStar.imageset/imgStar@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgStar.imageset/imgStar@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgStar.imageset/imgStar@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgStar.imageset/imgStar@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgStar.imageset/imgStar@3x.png diff --git a/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgTextLogo.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgTextLogo.imageset/Contents.json new file mode 100644 index 000000000..9493d391a --- /dev/null +++ b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgTextLogo.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "imgTextLogo.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "imgTextLogo@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "imgTextLogo@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgTextLogo.imageset/imgTextLogo.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgTextLogo.imageset/imgTextLogo.png new file mode 100644 index 000000000..473e4eace Binary files /dev/null and b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgTextLogo.imageset/imgTextLogo.png differ diff --git a/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgTextLogo.imageset/imgTextLogo@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgTextLogo.imageset/imgTextLogo@2x.png new file mode 100644 index 000000000..8e095d5ba Binary files /dev/null and b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgTextLogo.imageset/imgTextLogo@2x.png differ diff --git a/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgTextLogo.imageset/imgTextLogo@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgTextLogo.imageset/imgTextLogo@3x.png new file mode 100644 index 000000000..55523b4cd Binary files /dev/null and b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgTextLogo.imageset/imgTextLogo@3x.png differ diff --git a/Manito/Manito/Resource/Assets.xcassets/imgTto.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgTto.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgTto.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgTto.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/imgTto.imageset/imgTti.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgTto.imageset/imgTti.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgTto.imageset/imgTti.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgTto.imageset/imgTti.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgTto.imageset/imgTti@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgTto.imageset/imgTti@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgTto.imageset/imgTti@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgTto.imageset/imgTti@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/imgTto.imageset/imgTti@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgTto.imageset/imgTti@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/imgTto.imageset/imgTti@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/imgTto.imageset/imgTti@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/img_characters.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/img_characters.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/img_characters.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/img_characters.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/img_characters.imageset/img_characters.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/img_characters.imageset/img_characters.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/img_characters.imageset/img_characters.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/img_characters.imageset/img_characters.png diff --git a/Manito/Manito/Resource/Assets.xcassets/img_characters.imageset/img_characters@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/img_characters.imageset/img_characters@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/img_characters.imageset/img_characters@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/img_characters.imageset/img_characters@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/img_characters.imageset/img_characters@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/img_characters.imageset/img_characters@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/img_characters.imageset/img_characters@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/img_characters.imageset/img_characters@3x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/img_guideBox.imageset/Contents.json b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/img_guideBox.imageset/Contents.json similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/img_guideBox.imageset/Contents.json rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/img_guideBox.imageset/Contents.json diff --git a/Manito/Manito/Resource/Assets.xcassets/img_guideBox.imageset/img_guideBox.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/img_guideBox.imageset/img_guideBox.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/img_guideBox.imageset/img_guideBox.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/img_guideBox.imageset/img_guideBox.png diff --git a/Manito/Manito/Resource/Assets.xcassets/img_guideBox.imageset/img_guideBox@2x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/img_guideBox.imageset/img_guideBox@2x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/img_guideBox.imageset/img_guideBox@2x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/img_guideBox.imageset/img_guideBox@2x.png diff --git a/Manito/Manito/Resource/Assets.xcassets/img_guideBox.imageset/img_guideBox@3x.png b/Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/img_guideBox.imageset/img_guideBox@3x.png similarity index 100% rename from Manito/Manito/Resource/Assets.xcassets/img_guideBox.imageset/img_guideBox@3x.png rename to Manito/Modules/MTResource/MTResource/Resource/Image.xcassets/img_guideBox.imageset/img_guideBox@3x.png diff --git a/README.md b/README.md index 4d6ebaa57..c974584cf 100644 --- a/README.md +++ b/README.md @@ -39,23 +39,24 @@ ### 🛠 Development Environment -스크린샷 2021-11-19 오후 3 52 02 스크린샷 2021-11-19 오후 3 52 02 +스크린샷 2021-11-19 오후 3 52 02 스크린샷 2021-11-19 오후 3 52 02 ### :sparkles: Skills & Tech Stack * UIKit -* Storyboard + Code base -* URLSession -* Github +* AutoLayout + Code base +* MVVM + Clean Architecture +* Combine +* URLSession(Custom Network Library - MTNetwork) ### 🎁 Library -| Name | Version | | -| ----------------- | :-----: | ----- | -| SnapKit | `5.6.0` | `SPM` | -| FSCalendar | `2.8.4` | `SPM` | -| Gifu | `3.3.1` | `SPM` | -| Firebase | `9.6.0` | `SPM` | -| SkeletonView | `main` | `SPM` | +| Name | | +| ----------------- | ----- | +| SnapKit | `SPM` | +| FSCalendar | `SPM` | +| Gifu | `SPM` | +| Firebase | `SPM` | +| SkeletonView | `SPM` | ### 🔀 Git branch & [Git Flow](https://techblog.woowahan.com/2553/) @@ -72,35 +73,48 @@ hotfix/11-main-activty-bug ### 🗂 Folder Structure ``` -Aenitto-iOS +Manito | - └── Aenitto - |── 🗂 Network - │ │── 📁 Storage - │ │── 📁 Protocol - │ │── 📁 API - │ │── 📁 EndPoint - │ │── 📁 Foundation - │ └── 📁 Models - │ - |── 🗂 Global + |── Packages + │ └── 🗂 MTNetwork + |── Modules + │ └── ⚒️ MTResourse + |── Configuration + │ │── 🗂 Dev + │ └── 🗂 Prod + │ └── 🔨 Prod.xcconfig + └── Manito + │── 🗂 App + │ │── 📄 SceneDelegate + │ │── 📄 AppDelegate + │ └── 🗒 Info.plist + |── 🗂 Presentation + │ │── 📁 Common + │ └── 📁 Scene + │ └── 📁 Scene + │ │── 📁 View + │ │── 📁 ViewController + │ └── 📁 ViewModel + |── 🗂 Domain │ │── 📁 Error - │ │── 📁 Utils - │ │── 📁 Literal - │ │── 📁 Base - │ │── 📁 Supports - │ │ │── 📄 SceneDelegate - │ │ │── 📄 AppDelegate - │ │ │── 🗒 GoogleService-Info.plist - │ │ └── 🗒 Info.plist + │ │── 📁 Usecase + │ └── 📁 Entity + |── 🗂 Data + │ │── 📁 DTO + │ │── 📁 Repository + │ └── 📁 Network + │ │── 📁 Foundation + │ └── 📁 EndPoint + |── 🗂 Util + │ │── 📁 Logger │ │── 📁 Extension - │ │── 📁 UIComponent - │ └── 📁 Resource - │ │── 🖼 Assets.xcassets - │ │── 📁 Font - │ │── 📁 GIF - │ └── 📁 Storyboard - └── 🗂 Screens + │ │── 📁 Script + │ │ └── 📄 LocalizationScript.py + │ └── 📁 Literal + │ │── 📄 Localizable + │ └── 📄 TextLiteral + |── 🗂 Service + └── 🗂 Screens(legacy) |── 📁 Splash |── 📁 Main |── 📁 Letter