diff --git a/.gitignore b/.gitignore index 6e4907c3..06cb809b 100644 --- a/.gitignore +++ b/.gitignore @@ -140,9 +140,6 @@ iOSInjectionProject/ !*.xcworkspace/contents.xcworkspacedata **/xcshareddata/WorkspaceSettings.xcsettings -# Firebase Property List -GoogleService-Info.plist - # SwiftPackageManager Package.resolved diff --git a/Doolda/Doolda.xcodeproj/project.pbxproj b/Doolda/Doolda.xcodeproj/project.pbxproj index 4597a643..b857370a 100644 --- a/Doolda/Doolda.xcodeproj/project.pbxproj +++ b/Doolda/Doolda.xcodeproj/project.pbxproj @@ -9,16 +9,25 @@ /* Begin PBXBuildFile section */ 1D04F41A273954CD00CFCC3A /* CGColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D04F419273954CD00CFCC3A /* CGColor+Extensions.swift */; }; 1D04F41C273957E600CFCC3A /* BackgroundType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D04F41B273957E600CFCC3A /* BackgroundType.swift */; }; + 1D08135E2758C6C300DCF432 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 1D0813602758C6C300DCF432 /* InfoPlist.strings */; }; 1D097E97273CFA5900A4810B /* RawPageRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D097E96273CFA5900A4810B /* RawPageRepository.swift */; }; 1D0C8BC42732AF780091ACC0 /* GetUserUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0C8BC32732AF780091ACC0 /* GetUserUseCase.swift */; }; - 1D0C8BCC2732B5F90091ACC0 /* GetPairIdUseCaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0C8BCB2732B5F90091ACC0 /* GetPairIdUseCaseTest.swift */; }; - 1D0C8BD02732B7780091ACC0 /* UserRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9F105A2730F1B20060FEB7 /* UserRepositoryProtocol.swift */; }; - 1D0C8BD12732B7BA0091ACC0 /* GetUserUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0C8BC32732AF780091ACC0 /* GetUserUseCase.swift */; }; + 1D1C8EC42755ECBE0044AF95 /* GetUserUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0C8BC32732AF780091ACC0 /* GetUserUseCase.swift */; }; + 1D1C8EC72755ED690044AF95 /* UserRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9F105A2730F1B20060FEB7 /* UserRepositoryProtocol.swift */; }; + 1D1C8EC82755EDF20044AF95 /* DummyUserRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1C8EC52755ECF10044AF95 /* DummyUserRepository.swift */; }; + 1D1C8EC92755EE420044AF95 /* DDID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936A273564DB0036CED2 /* DDID.swift */; }; + 1D1C8ECA2755EE530044AF95 /* DDIDDataTransferObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936C2735655C0036CED2 /* DDIDDataTransferObject.swift */; }; + 1D1C8ECB2755EE8C0044AF95 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936E27356E160036CED2 /* User.swift */; }; + 1D1C8ECC2755EF080044AF95 /* DummyError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC44599E2755E0E1004888B9 /* DummyError.swift */; }; + 1D1C8ECD2755F6BB0044AF95 /* GetUserUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F1760C2754C2CE006ACB41 /* GetUserUseCaseProtocol.swift */; }; + 1D1C8ECE2755F71F0044AF95 /* DDIDDataTransferObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936C2735655C0036CED2 /* DDIDDataTransferObject.swift */; }; + 1D1C8ECF2755F7270044AF95 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936E27356E160036CED2 /* User.swift */; }; + 1D1C8ED02755F7480044AF95 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDADCB3274BCE63000330C9 /* Secrets.swift */; }; + 1D1C8ED12755F7500044AF95 /* FCMTokenDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D999467274CAC4700C72273 /* FCMTokenDocument.swift */; }; 1D3629FE2733C79300CFC069 /* RegisterUserUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D3629FD2733C79300CFC069 /* RegisterUserUseCase.swift */; }; 1D3925B5273B9172003A0B70 /* DateFormatter+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D3925B4273B9172003A0B70 /* DateFormatter+Extensions.swift */; }; 1D3925BD273BB4BE003A0B70 /* EditPageUseCaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D3925BC273BB4BE003A0B70 /* EditPageUseCaseTest.swift */; }; 1D3925C1273BB513003A0B70 /* EditPageUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE5E53D273929CE004F794F /* EditPageUseCase.swift */; }; - 1D3925C2273BD500003A0B70 /* ImageUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310125C327398DF600E43160 /* ImageUseCase.swift */; }; 1D3925C3273BD610003A0B70 /* PageRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE5E53F273932B0004F794F /* PageRepositoryProtocol.swift */; }; 1D3925C4273BD613003A0B70 /* RawPageRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE5E5412739352B004F794F /* RawPageRepositoryProtocol.swift */; }; 1D3925C5273BD62B003A0B70 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936E27356E160036CED2 /* User.swift */; }; @@ -33,7 +42,9 @@ 1D3925CE273BDB37003A0B70 /* DateFormatter+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D3925B4273B9172003A0B70 /* DateFormatter+Extensions.swift */; }; 1D3925D0273C1263003A0B70 /* CGFloat+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D3925CF273C1263003A0B70 /* CGFloat+Extensions.swift */; }; 1D3925D1273C12A8003A0B70 /* CGFloat+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D3925CF273C1263003A0B70 /* CGFloat+Extensions.swift */; }; - 1D58277D2732883E00FD0EA5 /* PairingViewCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D58277C2732883E00FD0EA5 /* PairingViewCoordinatorProtocol.swift */; }; + 1D3BAC08274E2F3B00CD23EE /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 1D3BAC07274E2F3B00CD23EE /* FirebaseMessaging */; }; + 1D3BAC0B274E2F6500CD23EE /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 1D3BAC0A274E2F6500CD23EE /* SnapKit */; }; + 1D3BAC0F274E307A00CD23EE /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 1D3BAC0E274E307A00CD23EE /* Kingfisher */; }; 1D6232E72730F0DF00A9AD6B /* PairingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6232E62730F0DF00A9AD6B /* PairingViewController.swift */; }; 1D6232E92730FA3900A9AD6B /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6232E82730FA3900A9AD6B /* UIColor+Extensions.swift */; }; 1D6232ED27310EE300A9AD6B /* UIImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6232EC27310EE300A9AD6B /* UIImage+Extensions.swift */; }; @@ -42,20 +53,75 @@ 1D6232F527313D8E00A9AD6B /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6232F427313D8E00A9AD6B /* UIView+Extensions.swift */; }; 1D6232F7273140CF00A9AD6B /* CopyableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6232F6273140CF00A9AD6B /* CopyableLabel.swift */; }; 1D6232F9273173E300A9AD6B /* UIResponder+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D6232F8273173E300A9AD6B /* UIResponder+Extensions.swift */; }; + 1D8646A3274D2DB600C4511D /* PushMessageEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8646A2274D2DB600C4511D /* PushMessageEntity.swift */; }; 1D8BE0792740FE5E00201E3E /* PageDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8BE0782740FE5E00201E3E /* PageDocument.swift */; }; - 1D8BE07B2742A3DB00201E3E /* DiaryPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8BE07A2742A3DB00201E3E /* DiaryPageView.swift */; }; + 1D8BE07B2742A3DB00201E3E /* DiaryCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8BE07A2742A3DB00201E3E /* DiaryCollectionViewCell.swift */; }; 1D8BE07D2743437600201E3E /* DiaryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8BE07C2743437600201E3E /* DiaryViewModel.swift */; }; 1D8BE07F2743846A00201E3E /* DiaryCollectionViewHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8BE07E2743846A00201E3E /* DiaryCollectionViewHeader.swift */; }; 1D992B42273D0EAB00B63954 /* PageRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D992B41273D0EAB00B63954 /* PageRepository.swift */; }; + 1D992E332755E8EC002C4457 /* GetUserUseCaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D992E322755E8EC002C4457 /* GetUserUseCaseTest.swift */; }; + 1D999462274CA94600C72273 /* FCMTokenUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D999461274CA94600C72273 /* FCMTokenUseCase.swift */; }; + 1D999464274CAAAC00C72273 /* FCMTokenRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D999463274CAAAC00C72273 /* FCMTokenRepositoryProtocol.swift */; }; + 1D999466274CAB8900C72273 /* FCMTokenRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D999465274CAB8900C72273 /* FCMTokenRepository.swift */; }; + 1D999468274CAC4700C72273 /* FCMTokenDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D999467274CAC4700C72273 /* FCMTokenDocument.swift */; }; + 1D99946A274CE5F700C72273 /* FirebaseMessageRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D999469274CE5F700C72273 /* FirebaseMessageRepositoryProtocol.swift */; }; + 1D99946C274CEF2900C72273 /* FirebaseMessageRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D99946B274CEF2900C72273 /* FirebaseMessageRepository.swift */; }; + 1D99946E274CEFC500C72273 /* FirebaseMessageUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D99946D274CEFC500C72273 /* FirebaseMessageUseCase.swift */; }; + 1D9DC09D2749F2BE0060587B /* FilterOptionBottomSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9DC09C2749F2BE0060587B /* FilterOptionBottomSheetViewController.swift */; }; + 1D9DC09F274A0E290060587B /* FilterOptionBottomSheetViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9DC09E274A0E290060587B /* FilterOptionBottomSheetViewModel.swift */; }; + 1D9DC0A1274A47D50060587B /* DooldaButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9DC0A0274A47D50060587B /* DooldaButton.swift */; }; + 1D9DC0A3274B31950060587B /* DiaryBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9DC0A2274B31940060587B /* DiaryBackgroundView.swift */; }; + 1D9DC0A5274B8A890060587B /* Collection+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D9DC0A4274B8A890060587B /* Collection+Extensions.swift */; }; + 1DAEB005275601760040E8C1 /* DummyUserRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1C8EC52755ECF10044AF95 /* DummyUserRepository.swift */; }; + 1DAEB012275603C80040E8C1 /* RegisterUserUseCaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DAEB011275603C80040E8C1 /* RegisterUserUseCaseTest.swift */; }; + 1DAEB018275606960040E8C1 /* RegisterUserUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D3629FD2733C79300CFC069 /* RegisterUserUseCase.swift */; }; + 1DAEB0192756069E0040E8C1 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936E27356E160036CED2 /* User.swift */; }; + 1DAEB01A275606A30040E8C1 /* DDID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936A273564DB0036CED2 /* DDID.swift */; }; + 1DAEB01B275606A70040E8C1 /* DDIDDataTransferObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936C2735655C0036CED2 /* DDIDDataTransferObject.swift */; }; + 1DAEB01C275606BD0040E8C1 /* DummyUserRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1C8EC52755ECF10044AF95 /* DummyUserRepository.swift */; }; + 1DAEB01D275609250040E8C1 /* RegisterUserUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176102754C370006ACB41 /* RegisterUserUseCaseProtocol.swift */; }; + 1DAEB01E2756095C0040E8C1 /* DummyError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC44599E2755E0E1004888B9 /* DummyError.swift */; }; + 1DAEB01F275609610040E8C1 /* UserRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9F105A2730F1B20060FEB7 /* UserRepositoryProtocol.swift */; }; + 1DAEB02727560E8F0040E8C1 /* FCMTokenUseCaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DAEB02627560E8F0040E8C1 /* FCMTokenUseCaseTest.swift */; }; + 1DAEB02D27560EA20040E8C1 /* DummyError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC44599E2755E0E1004888B9 /* DummyError.swift */; }; + 1DAEB03027560F7A0040E8C1 /* FCMTokenRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D999463274CAAAC00C72273 /* FCMTokenRepositoryProtocol.swift */; }; + 1DAEB03127560F960040E8C1 /* DummyFCMTokenRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DAEB02E27560F5F0040E8C1 /* DummyFCMTokenRepository.swift */; }; + 1DAEB03227560FB80040E8C1 /* DDID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936A273564DB0036CED2 /* DDID.swift */; }; + 1DAEB03327560FBB0040E8C1 /* DDIDDataTransferObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936C2735655C0036CED2 /* DDIDDataTransferObject.swift */; }; + 1DAEB034275612D30040E8C1 /* FCMTokenUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D999461274CA94600C72273 /* FCMTokenUseCase.swift */; }; + 1DAEB035275613EE0040E8C1 /* FCMTokenUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176262754C77C006ACB41 /* FCMTokenUseCaseProtocol.swift */; }; + 1DAEB03D275617010040E8C1 /* FirebaseMessageUseCaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DAEB03C275617010040E8C1 /* FirebaseMessageUseCaseTest.swift */; }; + 1DAEB043275617940040E8C1 /* FirebaseMessageRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D999469274CE5F700C72273 /* FirebaseMessageRepositoryProtocol.swift */; }; + 1DAEB046275617CB0040E8C1 /* DummyFirebaseMessageRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DAEB044275617A40040E8C1 /* DummyFirebaseMessageRepository.swift */; }; + 1DAEB047275619FC0040E8C1 /* DummyError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC44599E2755E0E1004888B9 /* DummyError.swift */; }; + 1DAEB04827561A260040E8C1 /* FirebaseMessageUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D99946D274CEFC500C72273 /* FirebaseMessageUseCase.swift */; }; + 1DAEB04927561D850040E8C1 /* DummyFCMTokenRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DAEB02E27560F5F0040E8C1 /* DummyFCMTokenRepository.swift */; }; + 1DAEB04A27561DC00040E8C1 /* PushMessageEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8646A2274D2DB600C4511D /* PushMessageEntity.swift */; }; + 1DAEB04B27561E220040E8C1 /* DDID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936A273564DB0036CED2 /* DDID.swift */; }; + 1DAEB04C27561E790040E8C1 /* FirebaseMessageUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176282754C7DA006ACB41 /* FirebaseMessageUseCaseProtocol.swift */; }; + 1DAEB04D2756228F0040E8C1 /* FCMTokenRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D999463274CAAAC00C72273 /* FCMTokenRepositoryProtocol.swift */; }; 1DBF5715273A2C5100D28F5E /* FontColorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DBF5714273A2C5100D28F5E /* FontColorType.swift */; }; 1DBF5717273A328D00D28F5E /* PhotoFrameType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DBF5716273A328D00D28F5E /* PhotoFrameType.swift */; }; + 1DD0F34D2747AABA00FC5E65 /* FontColorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DBF5714273A2C5100D28F5E /* FontColorType.swift */; }; + 1DD0F34E2747AAC700FC5E65 /* PairRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE9D3DB2738C9A00001F155 /* PairRepositoryProtocol.swift */; }; + 1DD0F34F2747AAD800FC5E65 /* TextComponentEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31012597273842DA00E43160 /* TextComponentEntity.swift */; }; + 1DD0F3502747AAE100FC5E65 /* ImageRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319C21CD273BA6D8009CE65C /* ImageRepositoryProtocol.swift */; }; + 1DD0F3512747AAEC00FC5E65 /* StickerComponentEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31012595273842B300E43160 /* StickerComponentEntity.swift */; }; + 1DD0F3522747AB4400FC5E65 /* CIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319C222C273CEFBB009CE65C /* CIImage+Extension.swift */; }; + 1DD0F3532747ABD400FC5E65 /* DateFormatter+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D3925B4273B9172003A0B70 /* DateFormatter+Extensions.swift */; }; + 1DD0F3542747ABDA00FC5E65 /* UserDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB78F27325C4D008AC3D2 /* UserDocument.swift */; }; + 1DD0F3552747ABE000FC5E65 /* PairDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB7942732627C008AC3D2 /* PairDocument.swift */; }; + 1DD0F3562747ABEC00FC5E65 /* PageDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D8BE0782740FE5E00201E3E /* PageDocument.swift */; }; + 1DD0F3572747ABFD00FC5E65 /* DDID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936A273564DB0036CED2 /* DDID.swift */; }; + 1DD0F3582747AC0300FC5E65 /* PageEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F5D4792733E88F000FB471 /* PageEntity.swift */; }; + 1DD58BD62754ACA600B93184 /* DiaryPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DD58BD52754ACA600B93184 /* DiaryPageView.swift */; }; 1DD6AD4027321F7800592AC8 /* dovemayo.otf in Resources */ = {isa = PBXBuildFile; fileRef = 1DD6AD3F27321E5800592AC8 /* dovemayo.otf */; }; - 1DE5E5362738BBAA004F794F /* CoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE5E5352738BBAA004F794F /* CoordinatorProtocol.swift */; }; - 1DE5E5382738BCE0004F794F /* DiaryViewCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE5E5372738BCE0004F794F /* DiaryViewCoordinatorProtocol.swift */; }; + 1DDADCB4274BCE63000330C9 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDADCB3274BCE63000330C9 /* Secrets.swift */; }; + 1DE5E5362738BBAA004F794F /* BaseCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE5E5352738BBAA004F794F /* BaseCoordinator.swift */; }; 1DE5E53E273929CE004F794F /* EditPageUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE5E53D273929CE004F794F /* EditPageUseCase.swift */; }; 1DE5E540273932B0004F794F /* PageRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE5E53F273932B0004F794F /* PageRepositoryProtocol.swift */; }; 1DE5E5422739352B004F794F /* RawPageRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE5E5412739352B004F794F /* RawPageRepositoryProtocol.swift */; }; - 3101258F2737987B00E43160 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3101258E2737987B00E43160 /* GoogleService-Info.plist */; }; + 1DF7094927562B5A0005FD92 /* DDIDDataTransferObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936C2735655C0036CED2 /* DDIDDataTransferObject.swift */; }; 310125922738325800E43160 /* UIAlertController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310125912738325800E43160 /* UIAlertController+Extensions.swift */; }; 3101259427383DED00E43160 /* PhotoComponentEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101259327383DED00E43160 /* PhotoComponentEntity.swift */; }; 31012596273842B300E43160 /* StickerComponentEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31012595273842B300E43160 /* StickerComponentEntity.swift */; }; @@ -63,49 +129,32 @@ 3101259C2738430200E43160 /* StickerPackEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101259B2738430200E43160 /* StickerPackEntity.swift */; }; 310125A027393D3B00E43160 /* ImageComposeUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101259F27393D3B00E43160 /* ImageComposeUseCase.swift */; }; 310125C427398DF600E43160 /* ImageUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310125C327398DF600E43160 /* ImageUseCase.swift */; }; - 310125CC2739A56600E43160 /* ImageComposeUseCaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310125CB2739A56600E43160 /* ImageComposeUseCaseTest.swift */; }; - 310125D22739A5D200E43160 /* ImageComposeUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101259F27393D3B00E43160 /* ImageComposeUseCase.swift */; }; 310125D6273A6F3400E43160 /* FileManagerPersistenceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310125D5273A6F3400E43160 /* FileManagerPersistenceService.swift */; }; 310125D8273A6F6B00E43160 /* FileManagerPersistenceServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310125D7273A6F6B00E43160 /* FileManagerPersistenceServiceProtocol.swift */; }; - 310D80D32731402F00F15F4F /* ParingViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310D80D22731402F00F15F4F /* ParingViewCoordinator.swift */; }; + 310D80D32731402F00F15F4F /* PairingViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310D80D22731402F00F15F4F /* PairingViewCoordinator.swift */; }; 310D80D52731405B00F15F4F /* DiaryViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310D80D42731405B00F15F4F /* DiaryViewCoordinator.swift */; }; - 310D81262733C7FC00F15F4F /* SplashViewModelTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310D81252733C7FC00F15F4F /* SplashViewModelTest.swift */; }; - 310D812C2733C83B00F15F4F /* SplashViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664893FB27302947009F03E2 /* SplashViewModel.swift */; }; - 310D812D2733C84B00F15F4F /* SplashViewCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664893FD273029AC009F03E2 /* SplashViewCoordinatorProtocol.swift */; }; - 310D812E2733C86000F15F4F /* GetMyIdUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9F10572730F0060060FEB7 /* GetMyIdUseCase.swift */; }; - 310D812F2733C86000F15F4F /* GetUserUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D0C8BC32732AF780091ACC0 /* GetUserUseCase.swift */; }; - 310D81322733CC1900F15F4F /* RegisterUserUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D3629FD2733C79300CFC069 /* RegisterUserUseCase.swift */; }; - 310D81332733CD5400F15F4F /* UserRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9F105A2730F1B20060FEB7 /* UserRepositoryProtocol.swift */; }; 310D81482733F56700F15F4F /* DiaryViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310D81472733F56700F15F4F /* DiaryViewController.swift */; }; - 3161EE942742A44100B922DD /* StickerPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3161EE932742A44100B922DD /* StickerPickerViewController.swift */; }; + 3161EE942742A44100B922DD /* StickerPickerBottomSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3161EE932742A44100B922DD /* StickerPickerBottomSheetViewController.swift */; }; 3161EE9827436EC600B922DD /* StickerPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3161EE9727436EC600B922DD /* StickerPickerView.swift */; }; - 3161EE9A274377E300B922DD /* PackedStickerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3161EE99274377E300B922DD /* PackedStickerCell.swift */; }; - 3161EE9C274377F400B922DD /* UnpackedStickerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3161EE9B274377F400B922DD /* UnpackedStickerCell.swift */; }; - 3161EEDD2744C51000B922DD /* buddySticker_0.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EEDC2744C51000B922DD /* buddySticker_0.png */; }; - 3161EEE52744F57900B922DD /* buddySticker_cover.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EEE42744F57800B922DD /* buddySticker_cover.png */; }; - 3161EEF1274663BD00B922DD /* boolbadaSticker_cover.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EEEB274663BB00B922DD /* boolbadaSticker_cover.png */; }; - 3161EEF2274663BD00B922DD /* boolbadaSticker_0.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EEEC274663BC00B922DD /* boolbadaSticker_0.png */; }; - 3161EEF3274663BD00B922DD /* boolbadaSticker_2.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EEED274663BC00B922DD /* boolbadaSticker_2.png */; }; - 3161EEF4274663BD00B922DD /* boolbadaSticker_4.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EEEE274663BC00B922DD /* boolbadaSticker_4.png */; }; - 3161EEF5274663BD00B922DD /* boolbadaSticker_3.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EEEF274663BC00B922DD /* boolbadaSticker_3.png */; }; - 3161EEF6274663BD00B922DD /* boolbadaSticker_1.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EEF0274663BC00B922DD /* boolbadaSticker_1.png */; }; - 3161EF032746644600B922DD /* htmlCoder_2.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EEF82746644300B922DD /* htmlCoder_2.png */; }; - 3161EF042746644600B922DD /* htmlCoder_3.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EEF92746644300B922DD /* htmlCoder_3.png */; }; - 3161EF052746644600B922DD /* htmlCoder_10.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EEFA2746644400B922DD /* htmlCoder_10.png */; }; - 3161EF062746644600B922DD /* htmlCoder_1.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EEFB2746644400B922DD /* htmlCoder_1.png */; }; - 3161EF072746644600B922DD /* htmlCoder_5.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EEFC2746644400B922DD /* htmlCoder_5.png */; }; - 3161EF082746644600B922DD /* htmlCoder_6.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EEFD2746644400B922DD /* htmlCoder_6.png */; }; - 3161EF092746644600B922DD /* htmlCoder_8.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EEFE2746644500B922DD /* htmlCoder_8.png */; }; - 3161EF0A2746644600B922DD /* htmlCoder_9.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EEFF2746644500B922DD /* htmlCoder_9.png */; }; - 3161EF0B2746644600B922DD /* htmlCoder_0.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EF002746644500B922DD /* htmlCoder_0.png */; }; - 3161EF0C2746644600B922DD /* htmlCoder_7.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EF012746644500B922DD /* htmlCoder_7.png */; }; - 3161EF0D2746644600B922DD /* htmlCoder_4.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EF022746644600B922DD /* htmlCoder_4.png */; }; - 3161EF0F2746645E00B922DD /* htmlCoder_cover.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EF0E2746645E00B922DD /* htmlCoder_cover.png */; }; - 3161EF142746651100B922DD /* buddySticker_1.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EF102746651000B922DD /* buddySticker_1.png */; }; - 3161EF152746651100B922DD /* buddySticker_3.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EF112746651000B922DD /* buddySticker_3.png */; }; - 3161EF162746651100B922DD /* buddySticker_4.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EF122746651000B922DD /* buddySticker_4.png */; }; - 3161EF172746651100B922DD /* buddySticker_2.png in Resources */ = {isa = PBXBuildFile; fileRef = 3161EF132746651100B922DD /* buddySticker_2.png */; }; - 319C21CA273B9703009CE65C /* FileDocuments.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319C21C9273B9703009CE65C /* FileDocuments.swift */; }; + 3161EE9A274377E300B922DD /* PackedStickerCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3161EE99274377E300B922DD /* PackedStickerCollectionViewCell.swift */; }; + 3161EE9C274377F400B922DD /* UnpackedStickerCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3161EE9B274377F400B922DD /* UnpackedStickerCollectionViewCell.swift */; }; + 317FB60B274A3851005DF3A3 /* StickerPickerCollectionViewFooter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317FB60A274A3850005DF3A3 /* StickerPickerCollectionViewFooter.swift */; }; + 317FB60D274A8F59005DF3A3 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317FB60C274A8F59005DF3A3 /* SettingsViewController.swift */; }; + 317FB611274A9175005DF3A3 /* SettingsViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317FB610274A9175005DF3A3 /* SettingsViewCoordinator.swift */; }; + 317FB613274AA879005DF3A3 /* SettingsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317FB612274AA879005DF3A3 /* SettingsTableViewCell.swift */; }; + 317FB615274B9A92005DF3A3 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317FB614274B9A92005DF3A3 /* SettingsViewModel.swift */; }; + 317FB617274BAAEC005DF3A3 /* DooldaInfoType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317FB616274BAAEB005DF3A3 /* DooldaInfoType.swift */; }; + 317FB619274BBC9D005DF3A3 /* SettingsTableViewHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317FB618274BBC9D005DF3A3 /* SettingsTableViewHeader.swift */; }; + 317FB61B274BCE68005DF3A3 /* InformationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 317FB61A274BCE68005DF3A3 /* InformationViewController.swift */; }; + 318D9F5B274CC77800AA6F0E /* FontPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318D9F5A274CC77800AA6F0E /* FontPickerViewController.swift */; }; + 318D9F62274CFF9E00AA6F0E /* uhBeeMysen.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 318D9F5D274CFBA100AA6F0E /* uhBeeMysen.ttf */; }; + 318D9F63274CFFA000AA6F0E /* dungGeunMo.otf in Resources */ = {isa = PBXBuildFile; fileRef = 318D9F5E274CFBEA00AA6F0E /* dungGeunMo.otf */; }; + 318D9F64274CFFA200AA6F0E /* kotraHope.otf in Resources */ = {isa = PBXBuildFile; fileRef = 318D9F5F274CFC0800AA6F0E /* kotraHope.otf */; }; + 318D9F67274D4D5000AA6F0E /* darae.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 318D9F66274D4D5000AA6F0E /* darae.ttf */; }; + 318D9F69274D4D9900AA6F0E /* kyoboHandwriting.otf in Resources */ = {isa = PBXBuildFile; fileRef = 318D9F68274D4D9900AA6F0E /* kyoboHandwriting.otf */; }; + 318D9F70274E12D900AA6F0E /* PageDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318D9F6F274E12D900AA6F0E /* PageDetailViewModel.swift */; }; + 318D9F72274E17AB00AA6F0E /* PageDetailViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318D9F71274E17AB00AA6F0E /* PageDetailViewCoordinator.swift */; }; + 318D9F76274E258D00AA6F0E /* PageDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 318D9F75274E258D00AA6F0E /* PageDetailViewController.swift */; }; 319C21CE273BA6D8009CE65C /* ImageRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319C21CD273BA6D8009CE65C /* ImageRepositoryProtocol.swift */; }; 319C21D0273BA7D4009CE65C /* ImageRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319C21CF273BA7D4009CE65C /* ImageRepository.swift */; }; 319C21D8273BD764009CE65C /* URLSessionNetworkServiceTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319C21D7273BD764009CE65C /* URLSessionNetworkServiceTest.swift */; }; @@ -114,19 +163,29 @@ 319C21E0273BD791009CE65C /* URLRequsetBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC822FAB27360F8600C918A0 /* URLRequsetBuilder.swift */; }; 319C21E1273BD805009CE65C /* URLSessionNetworkServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC822FB12736BF8400C918A0 /* URLSessionNetworkServiceProtocol.swift */; }; 319C222D273CEFBB009CE65C /* CIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319C222C273CEFBB009CE65C /* CIImage+Extension.swift */; }; + 31BEC80B2754DD74009AF22A /* license.txt in Resources */ = {isa = PBXBuildFile; fileRef = 31BEC80A2754DD74009AF22A /* license.txt */; }; + 31BEC81F2755B154009AF22A /* Config.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 31BEC81E2755B154009AF22A /* Config.xcconfig */; }; + 31BEC8212755B160009AF22A /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 31BEC8202755B160009AF22A /* GoogleService-Info.plist */; }; + 31BEC8342755EA89009AF22A /* ImageUseCaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31BEC8332755EA89009AF22A /* ImageUseCaseTest.swift */; }; + 31BEC8382755EB2C009AF22A /* ImageUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176162754C4F6006ACB41 /* ImageUseCaseProtocol.swift */; }; + 31BEC83B2755EBCD009AF22A /* DummyImageRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31BEC8392755EBB7009AF22A /* DummyImageRepository.swift */; }; + 31BEC83C2755EBFE009AF22A /* ImageRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319C21CD273BA6D8009CE65C /* ImageRepositoryProtocol.swift */; }; + 31BEC83D2755ED1C009AF22A /* ImageUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 310125C327398DF600E43160 /* ImageUseCase.swift */; }; + 31BEC83E2755ED57009AF22A /* CIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 319C222C273CEFBB009CE65C /* CIImage+Extension.swift */; }; + 31BEC83F2755ED60009AF22A /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936E27356E160036CED2 /* User.swift */; }; + 31BEC8402755ED64009AF22A /* DDID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936A273564DB0036CED2 /* DDID.swift */; }; + 31BEC8412755EF02009AF22A /* DummyError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC44599E2755E0E1004888B9 /* DummyError.swift */; }; + 31BEC8422755F4F2009AF22A /* DDIDDataTransferObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936C2735655C0036CED2 /* DDIDDataTransferObject.swift */; }; 661A840427434FE10077E0E3 /* GetPageUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 661A840327434FE10077E0E3 /* GetPageUseCase.swift */; }; 661A8406274351230077E0E3 /* CheckMyTurnUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 661A8405274351230077E0E3 /* CheckMyTurnUseCase.swift */; }; 664893C1273007ED009F03E2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664893C0273007ED009F03E2 /* AppDelegate.swift */; }; 664893C3273007ED009F03E2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664893C2273007ED009F03E2 /* SceneDelegate.swift */; }; 664893CA273007F1009F03E2 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 664893C9273007F1009F03E2 /* Assets.xcassets */; }; 664893CD273007F1009F03E2 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 664893CB273007F1009F03E2 /* LaunchScreen.storyboard */; }; - 664893ED27300D7D009F03E2 /* SnapKit in Frameworks */ = {isa = PBXBuildFile; productRef = 664893EC27300D7D009F03E2 /* SnapKit */; }; - 664893F027300DB9009F03E2 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 664893EF27300DB9009F03E2 /* Kingfisher */; }; 664893F4273023D3009F03E2 /* SplashViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664893F3273023D3009F03E2 /* SplashViewController.swift */; }; 664893F82730272B009F03E2 /* AppCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664893F72730272B009F03E2 /* AppCoordinator.swift */; }; 664893FA27302905009F03E2 /* SplashViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664893F927302905009F03E2 /* SplashViewCoordinator.swift */; }; 664893FC27302947009F03E2 /* SplashViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664893FB27302947009F03E2 /* SplashViewModel.swift */; }; - 664893FE273029AC009F03E2 /* SplashViewCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664893FD273029AC009F03E2 /* SplashViewCoordinatorProtocol.swift */; }; 664894182730F1B1009F03E2 /* PairingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664894172730F1B0009F03E2 /* PairingViewModel.swift */; }; 6653039B2743B3FF00E3075C /* CoreDataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 665303992743B3FF00E3075C /* CoreDataModel.xcdatamodeld */; }; 6653039D2743B49E00E3075C /* CoreDataPersistenceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6653039C2743B49E00E3075C /* CoreDataPersistenceService.swift */; }; @@ -135,13 +194,70 @@ 665303A62743E09A00E3075C /* CoreDataPersistenceServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 665303A52743E09A00E3075C /* CoreDataPersistenceServiceProtocol.swift */; }; 665303A82744C6F500E3075C /* CoreDataPageEntityPersistenceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 665303A72744C6F500E3075C /* CoreDataPageEntityPersistenceService.swift */; }; 665303AA2744CE3400E3075C /* CoreDataPageEntityPersistenceServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 665303A92744CE3400E3075C /* CoreDataPageEntityPersistenceServiceProtocol.swift */; }; - 669ADE3E2732E55F007B529B /* UserRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9F105A2730F1B20060FEB7 /* UserRepositoryProtocol.swift */; }; - 669ADE3F2732E55F007B529B /* RefreshPairIdUseCaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 669ADE342732E4A8007B529B /* RefreshPairIdUseCaseTest.swift */; }; - 66B4ADFF2742BEFC0021EA27 /* Normal.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 66B4ADFC2742BEFC0021EA27 /* Normal.jpg */; }; - 66B4AE002742BEFC0021EA27 /* Polaroid.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 66B4ADFD2742BEFC0021EA27 /* Polaroid.jpg */; }; - 66B4AE012742BEFC0021EA27 /* LifeFourCuts.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 66B4ADFE2742BEFC0021EA27 /* LifeFourCuts.jpg */; }; + 66B4ADFF2742BEFC0021EA27 /* normal.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 66B4ADFC2742BEFC0021EA27 /* normal.jpg */; }; + 66B4AE002742BEFC0021EA27 /* polaroid.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 66B4ADFD2742BEFC0021EA27 /* polaroid.jpg */; }; + 66B4AE012742BEFC0021EA27 /* lifeFourCuts.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 66B4ADFE2742BEFC0021EA27 /* lifeFourCuts.jpg */; }; + 66C2CA882755E9F200AFE5EE /* PairUserUseCaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C2CA872755E9F200AFE5EE /* PairUserUseCaseTest.swift */; }; + 66C2CA8E2755EA6D00AFE5EE /* PairUserUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE9370273671910036CED2 /* PairUserUseCase.swift */; }; + 66C2CA8F2755EA7A00AFE5EE /* PairUserUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F1760A2754C27C006ACB41 /* PairUserUseCaseProtocol.swift */; }; + 66C2CA902755EB4800AFE5EE /* DummyPairRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4459A52755E470004888B9 /* DummyPairRepository.swift */; }; + 66C2CA952755ECBC00AFE5EE /* DummyError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC44599E2755E0E1004888B9 /* DummyError.swift */; }; + 66C2CA972755ED7300AFE5EE /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936E27356E160036CED2 /* User.swift */; }; + 66C2CA982755ED7A00AFE5EE /* DDID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936A273564DB0036CED2 /* DDID.swift */; }; + 66C2CA992755ED7A00AFE5EE /* DDIDDataTransferObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936C2735655C0036CED2 /* DDIDDataTransferObject.swift */; }; + 66C2CA9A2755EDB300AFE5EE /* UserRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9F105A2730F1B20060FEB7 /* UserRepositoryProtocol.swift */; }; + 66C2CA9B2755EDB300AFE5EE /* PairRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE9D3DB2738C9A00001F155 /* PairRepositoryProtocol.swift */; }; + 66C2CAA62756040000AFE5EE /* DummyUserRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1C8EC52755ECF10044AF95 /* DummyUserRepository.swift */; }; + 66C2CAAE27561E1200AFE5EE /* RefreshUserUseCaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C2CAAD27561E1200AFE5EE /* RefreshUserUseCaseTest.swift */; }; + 66C2CAB427561E8500AFE5EE /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936E27356E160036CED2 /* User.swift */; }; + 66C2CAB527561E8E00AFE5EE /* DDID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936A273564DB0036CED2 /* DDID.swift */; }; + 66C2CAB627561E8E00AFE5EE /* DDIDDataTransferObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936C2735655C0036CED2 /* DDIDDataTransferObject.swift */; }; + 66C2CAB727561E9900AFE5EE /* RefreshUserUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE9372273688E10036CED2 /* RefreshUserUseCase.swift */; }; + 66C2CAB827561E9900AFE5EE /* RefreshUserUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F1760E2754C334006ACB41 /* RefreshUserUseCaseProtocol.swift */; }; + 66C2CAB927561EB400AFE5EE /* DummyUserRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D1C8EC52755ECF10044AF95 /* DummyUserRepository.swift */; }; + 66C2CABA27561EF900AFE5EE /* DummyError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC44599E2755E0E1004888B9 /* DummyError.swift */; }; + 66C2CABB27561F1100AFE5EE /* UserRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9F105A2730F1B20060FEB7 /* UserRepositoryProtocol.swift */; }; + 66C2CAC3275623F700AFE5EE /* CheckMyTurnUseCaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C2CAC2275623F700AFE5EE /* CheckMyTurnUseCaseTest.swift */; }; + 66C2CAC92756247200AFE5EE /* CheckMyTurnUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 661A8405274351230077E0E3 /* CheckMyTurnUseCase.swift */; }; + 66C2CACA2756247200AFE5EE /* CheckMyTurnUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176182754C547006ACB41 /* CheckMyTurnUseCaseProtocol.swift */; }; + 66C2CACB2756248600AFE5EE /* DummyPairRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4459A52755E470004888B9 /* DummyPairRepository.swift */; }; + 66C2CACC275624B500AFE5EE /* PairRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE9D3DB2738C9A00001F155 /* PairRepositoryProtocol.swift */; }; + 66C2CACD275624BF00AFE5EE /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936E27356E160036CED2 /* User.swift */; }; + 66C2CACE275624C500AFE5EE /* DDID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936A273564DB0036CED2 /* DDID.swift */; }; + 66C2CACF275624C500AFE5EE /* DDIDDataTransferObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936C2735655C0036CED2 /* DDIDDataTransferObject.swift */; }; + 66C2CAD0275624D800AFE5EE /* DummyError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC44599E2755E0E1004888B9 /* DummyError.swift */; }; + 66C2CAD927562C1600AFE5EE /* GetPageUseCaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C2CAD827562C1600AFE5EE /* GetPageUseCaseTest.swift */; }; + 66C2CADF27562C6100AFE5EE /* GetPageUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 661A840327434FE10077E0E3 /* GetPageUseCase.swift */; }; + 66C2CAE027562C6100AFE5EE /* GetPageUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F1761A2754C589006ACB41 /* GetPageUseCaseProtocol.swift */; }; + 66C2CAE127562C9D00AFE5EE /* PageRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE5E53F273932B0004F794F /* PageRepositoryProtocol.swift */; }; + 66C2CAE227562CAF00AFE5EE /* PageEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F5D4792733E88F000FB471 /* PageEntity.swift */; }; + 66C2CAE327562CB900AFE5EE /* DDID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936A273564DB0036CED2 /* DDID.swift */; }; + 66C2CAE427562CB900AFE5EE /* DDIDDataTransferObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936C2735655C0036CED2 /* DDIDDataTransferObject.swift */; }; + 66C2CAE527562CE000AFE5EE /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936E27356E160036CED2 /* User.swift */; }; + 66C2CAE627562D8900AFE5EE /* DummyPageRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC44599B2755DF86004888B9 /* DummyPageRepository.swift */; }; + 66C2CAE727562DB500AFE5EE /* DummyError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC44599E2755E0E1004888B9 /* DummyError.swift */; }; + 66C2CAEF275640CA00AFE5EE /* GetRawPageUseCaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C2CAEE275640CA00AFE5EE /* GetRawPageUseCaseTest.swift */; }; + 66C2CAF52756412E00AFE5EE /* GetRawPageUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C482DA2746135500823151 /* GetRawPageUseCase.swift */; }; + 66C2CAF62756412E00AFE5EE /* GetRawPageUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176242754C74C006ACB41 /* GetRawPageUseCaseProtocol.swift */; }; + 66C2CAF72756414A00AFE5EE /* RawPageEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F5D47D2733EE8F000FB471 /* RawPageEntity.swift */; }; + 66C2CAF82756415000AFE5EE /* PageEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F5D4792733E88F000FB471 /* PageEntity.swift */; }; + 66C2CAF92756419300AFE5EE /* ComponentEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F5D47B2733EA32000FB471 /* ComponentEntity.swift */; }; + 66C2CAFA275641A700AFE5EE /* PhotoComponentEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3101259327383DED00E43160 /* PhotoComponentEntity.swift */; }; + 66C2CAFB275641A700AFE5EE /* StickerComponentEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31012595273842B300E43160 /* StickerComponentEntity.swift */; }; + 66C2CAFC275641A700AFE5EE /* TextComponentEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31012597273842DA00E43160 /* TextComponentEntity.swift */; }; + 66C2CAFD275641CB00AFE5EE /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936E27356E160036CED2 /* User.swift */; }; + 66C2CAFE275641D000AFE5EE /* DDID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936A273564DB0036CED2 /* DDID.swift */; }; + 66C2CAFF275641D000AFE5EE /* DDIDDataTransferObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936C2735655C0036CED2 /* DDIDDataTransferObject.swift */; }; + 66C2CB00275641EE00AFE5EE /* RawPageRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DE5E5412739352B004F794F /* RawPageRepositoryProtocol.swift */; }; + 66C2CB012756421800AFE5EE /* BackgroundType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1D04F41B273957E600CFCC3A /* BackgroundType.swift */; }; + 66C2CB022756424D00AFE5EE /* FontColorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DBF5714273A2C5100D28F5E /* FontColorType.swift */; }; + 66C2CB03275642C500AFE5EE /* DummyError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC44599E2755E0E1004888B9 /* DummyError.swift */; }; + 66C2CB04275642D000AFE5EE /* DummyRawPageRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4459A32755E434004888B9 /* DummyRawPageRepository.swift */; }; 66C482DB2746135500823151 /* GetRawPageUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66C482DA2746135500823151 /* GetRawPageUseCase.swift */; }; 66CAFF242740F63A00A48DCF /* BackgroundTypePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66CAFF232740F63A00A48DCF /* BackgroundTypePickerViewController.swift */; }; + 66CCB6E6275666AD00E3FD64 /* ComponentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC445A0C27562590004888B9 /* ComponentType.swift */; }; + 66CCB6E7275667EA00E3FD64 /* ComponentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC445A0C27562590004888B9 /* ComponentType.swift */; }; + 66CCB6F8275924AB00E3FD64 /* polaroid_long.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 66CCB6F7275924AB00E3FD64 /* polaroid_long.jpg */; }; 66CD644E273A490800E55C03 /* BottomSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66CD644D273A490800E55C03 /* BottomSheetViewController.swift */; }; 66CD6450273A5C4700E55C03 /* PhotoPickerBottomSheetViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66CD644F273A5C4700E55C03 /* PhotoPickerBottomSheetViewController.swift */; }; 66CD6456273BB88A00E55C03 /* PhotoFrameCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66CD6455273BB88A00E55C03 /* PhotoFrameCollectionViewCell.swift */; }; @@ -149,6 +265,24 @@ 66CD6464273D125F00E55C03 /* CGImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66CD6463273D125E00E55C03 /* CGImage+Extensions.swift */; }; 66ECCD3A273E5342003990EE /* CarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66ECCD39273E5342003990EE /* CarouselView.swift */; }; 66ECCD3C273F7C9F003990EE /* CustomActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66ECCD3B273F7C9E003990EE /* CustomActivityIndicator.swift */; }; + 66F1760527548260006ACB41 /* UnpairUserUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F1760427548260006ACB41 /* UnpairUserUseCase.swift */; }; + 66F176092754C21B006ACB41 /* GetMyIdUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176082754C21B006ACB41 /* GetMyIdUseCaseProtocol.swift */; }; + 66F1760B2754C27C006ACB41 /* PairUserUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F1760A2754C27C006ACB41 /* PairUserUseCaseProtocol.swift */; }; + 66F1760D2754C2CF006ACB41 /* GetUserUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F1760C2754C2CE006ACB41 /* GetUserUseCaseProtocol.swift */; }; + 66F1760F2754C334006ACB41 /* RefreshUserUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F1760E2754C334006ACB41 /* RefreshUserUseCaseProtocol.swift */; }; + 66F176112754C370006ACB41 /* RegisterUserUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176102754C370006ACB41 /* RegisterUserUseCaseProtocol.swift */; }; + 66F176132754C3C3006ACB41 /* EditPageUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176122754C3C3006ACB41 /* EditPageUseCaseProtocol.swift */; }; + 66F176152754C413006ACB41 /* ImageComposeUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176142754C413006ACB41 /* ImageComposeUseCaseProtocol.swift */; }; + 66F176172754C4F6006ACB41 /* ImageUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176162754C4F6006ACB41 /* ImageUseCaseProtocol.swift */; }; + 66F176192754C547006ACB41 /* CheckMyTurnUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176182754C547006ACB41 /* CheckMyTurnUseCaseProtocol.swift */; }; + 66F1761B2754C589006ACB41 /* GetPageUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F1761A2754C589006ACB41 /* GetPageUseCaseProtocol.swift */; }; + 66F1761D2754C5FF006ACB41 /* StickerUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F1761C2754C5FF006ACB41 /* StickerUseCaseProtocol.swift */; }; + 66F1761F2754C656006ACB41 /* TextUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F1761E2754C656006ACB41 /* TextUseCaseProtocol.swift */; }; + 66F176212754C6A3006ACB41 /* PushNotificationStateUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176202754C6A3006ACB41 /* PushNotificationStateUseCaseProtocol.swift */; }; + 66F176232754C6EE006ACB41 /* GlobalFontUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176222754C6EE006ACB41 /* GlobalFontUseCaseProtocol.swift */; }; + 66F176252754C74C006ACB41 /* GetRawPageUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176242754C74C006ACB41 /* GetRawPageUseCaseProtocol.swift */; }; + 66F176272754C77C006ACB41 /* FCMTokenUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176262754C77C006ACB41 /* FCMTokenUseCaseProtocol.swift */; }; + 66F176292754C7DA006ACB41 /* FirebaseMessageUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176282754C7DA006ACB41 /* FirebaseMessageUseCaseProtocol.swift */; }; 66F5D47A2733E88F000FB471 /* PageEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F5D4792733E88F000FB471 /* PageEntity.swift */; }; 66F5D47C2733EA32000FB471 /* ComponentEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F5D47B2733EA32000FB471 /* ComponentEntity.swift */; }; 66F5D47E2733EE8F000FB471 /* RawPageEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F5D47D2733EE8F000FB471 /* RawPageEntity.swift */; }; @@ -158,28 +292,54 @@ 66FE9371273671910036CED2 /* PairUserUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE9370273671910036CED2 /* PairUserUseCase.swift */; }; 66FE9373273688E10036CED2 /* RefreshUserUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE9372273688E10036CED2 /* RefreshUserUseCase.swift */; }; 66FE93CC27396B960036CED2 /* PhotoPickerBottomSheetViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE93CB27396B950036CED2 /* PhotoPickerBottomSheetViewModel.swift */; }; - FC19598A274284E200AB6059 /* StickerPickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC195989274284E200AB6059 /* StickerPickerViewModel.swift */; }; + FC19598A274284E200AB6059 /* StickerPickerBottomSheetViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC195989274284E200AB6059 /* StickerPickerBottomSheetViewModel.swift */; }; + FC23C228274E17C200EBAE4C /* PageDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC23C227274E17C200EBAE4C /* PageDetailViewController.swift */; }; FC28CB32274609F300BA47BB /* TextUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC28CB31274609F300BA47BB /* TextUseCase.swift */; }; + FC44599C2755DF86004888B9 /* DummyPageRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC44599B2755DF86004888B9 /* DummyPageRepository.swift */; }; + FC44599D2755E02F004888B9 /* ImageUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176162754C4F6006ACB41 /* ImageUseCaseProtocol.swift */; }; + FC44599F2755E0E1004888B9 /* DummyError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC44599E2755E0E1004888B9 /* DummyError.swift */; }; + FC4459A02755E12F004888B9 /* EditPageUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176122754C3C3006ACB41 /* EditPageUseCaseProtocol.swift */; }; + FC4459A22755E41C004888B9 /* DummyImageUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4459A12755E41C004888B9 /* DummyImageUseCase.swift */; }; + FC4459A42755E434004888B9 /* DummyRawPageRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4459A32755E434004888B9 /* DummyRawPageRepository.swift */; }; + FC4459A62755E470004888B9 /* DummyPairRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4459A52755E470004888B9 /* DummyPairRepository.swift */; }; + FC4459AE2755E8C4004888B9 /* GetMyIdUseCaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4459AD2755E8C4004888B9 /* GetMyIdUseCaseTest.swift */; }; + FC4459B42755EB47004888B9 /* GetMyIdUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9F10572730F0060060FEB7 /* GetMyIdUseCase.swift */; }; + FC4459B52755EB47004888B9 /* GetMyIdUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176082754C21B006ACB41 /* GetMyIdUseCaseProtocol.swift */; }; + FC4459B62755EB96004888B9 /* UserRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9F105A2730F1B20060FEB7 /* UserRepositoryProtocol.swift */; }; + FC4459B92755EC0F004888B9 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936E27356E160036CED2 /* User.swift */; }; + FC4459BA2755EC71004888B9 /* DummyError.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC44599E2755E0E1004888B9 /* DummyError.swift */; }; + FC4459BB2755ED05004888B9 /* DDID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936A273564DB0036CED2 /* DDID.swift */; }; + FC4459BC2755ED0A004888B9 /* DDIDDataTransferObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FE936C2735655C0036CED2 /* DDIDDataTransferObject.swift */; }; + FC4459DB27560731004888B9 /* GlobalFontUseCaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4459DA27560731004888B9 /* GlobalFontUseCaseTest.swift */; }; + FC4459E127560782004888B9 /* GlobalFontUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD24D7727439717000203F7 /* GlobalFontUseCase.swift */; }; + FC4459E227560782004888B9 /* GlobalFontUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176222754C6EE006ACB41 /* GlobalFontUseCaseProtocol.swift */; }; + FC4459E32756079E004888B9 /* FontType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC803F52744C22F00FFE4DC /* FontType.swift */; }; + FC4459E4275607A3004888B9 /* GlobalFontRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD24D74274392AB000203F7 /* GlobalFontRepositoryProtocol.swift */; }; + FC4459E5275607AB004888B9 /* UIFont+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD24D7027437F31000203F7 /* UIFont+Extensions.swift */; }; + FC4459E727560909004888B9 /* DummyGlobalFontRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4459E627560909004888B9 /* DummyGlobalFontRepository.swift */; }; + FC4459EF27561A48004888B9 /* PushNotificationStateUseCaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4459EE27561A48004888B9 /* PushNotificationStateUseCaseTest.swift */; }; + FC4459F527561ACB004888B9 /* PushNotificationStateUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD24D7D2743ADC1000203F7 /* PushNotificationStateUseCase.swift */; }; + FC4459F627561ACB004888B9 /* PushNotificationStateUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F176202754C6A3006ACB41 /* PushNotificationStateUseCaseProtocol.swift */; }; + FC4459F727561ACB004888B9 /* PushNotificationStateRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD24D7B2743AAE2000203F7 /* PushNotificationStateRepositoryProtocol.swift */; }; + FC4459F927561BB3004888B9 /* DummyPushNotificationStateRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC4459F827561BB3004888B9 /* DummyPushNotificationStateRepository.swift */; }; + FC445A01275622CB004888B9 /* TextUseCaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC445A00275622CB004888B9 /* TextUseCaseTest.swift */; }; + FC445A07275622E2004888B9 /* TextUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC28CB31274609F300BA47BB /* TextUseCase.swift */; }; + FC445A08275622E2004888B9 /* TextUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F1761E2754C656006ACB41 /* TextUseCaseProtocol.swift */; }; + FC445A09275624C5004888B9 /* FontColorType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DBF5714273A2C5100D28F5E /* FontColorType.swift */; }; + FC445A0A275624CA004888B9 /* TextComponentEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31012597273842DA00E43160 /* TextComponentEntity.swift */; }; + FC445A0B275624F2004888B9 /* ComponentEntity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66F5D47B2733EA32000FB471 /* ComponentEntity.swift */; }; + FC445A0D27562590004888B9 /* ComponentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC445A0C27562590004888B9 /* ComponentType.swift */; }; + FC445A0E27562590004888B9 /* ComponentType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC445A0C27562590004888B9 /* ComponentType.swift */; }; + FC51FA67274BB9D60069D39C /* FontColorPickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC51FA66274BB9D60069D39C /* FontColorPickerView.swift */; }; + FC51FA69274BBB3C0069D39C /* FontColorCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC51FA68274BBB3C0069D39C /* FontColorCollectionViewCell.swift */; }; + FC51FA6D274C89330069D39C /* FontSizeControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC51FA6C274C89330069D39C /* FontSizeControlView.swift */; }; FC5FB79027325C4D008AC3D2 /* UserDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB78F27325C4D008AC3D2 /* UserDocument.swift */; }; - FC5FB79127325D2C008AC3D2 /* UserDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB78F27325C4D008AC3D2 /* UserDocument.swift */; }; FC5FB793273260C3008AC3D2 /* UserRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB792273260C3008AC3D2 /* UserRepository.swift */; }; FC5FB7952732627C008AC3D2 /* PairDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB7942732627C008AC3D2 /* PairDocument.swift */; }; - FC5FB79627326306008AC3D2 /* PairDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB7942732627C008AC3D2 /* PairDocument.swift */; }; FC5FB79A273284E2008AC3D2 /* UserDefaultsPersistenceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB799273284E2008AC3D2 /* UserDefaultsPersistenceService.swift */; }; FC5FB79C27328579008AC3D2 /* UserDefaultsPersistenceServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB79B27328579008AC3D2 /* UserDefaultsPersistenceServiceProtocol.swift */; }; - FC5FB7CC27339213008AC3D2 /* UserRepositoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB7CB27339213008AC3D2 /* UserRepositoryTests.swift */; }; - FC5FB7D22733927B008AC3D2 /* UserRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9F105A2730F1B20060FEB7 /* UserRepositoryProtocol.swift */; }; - FC5FB7D32733927B008AC3D2 /* UserRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB792273260C3008AC3D2 /* UserRepository.swift */; }; - FC5FB7D42733927B008AC3D2 /* UserDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB78F27325C4D008AC3D2 /* UserDocument.swift */; }; - FC5FB7D52733927B008AC3D2 /* PairDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB7942732627C008AC3D2 /* PairDocument.swift */; }; - FC5FB7D82733927B008AC3D2 /* UserDefaultsPersistenceServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB79B27328579008AC3D2 /* UserDefaultsPersistenceServiceProtocol.swift */; }; - FC5FB7D92733927B008AC3D2 /* UserDefaultsPersistenceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB799273284E2008AC3D2 /* UserDefaultsPersistenceService.swift */; }; FC5FB7DC2733A4AA008AC3D2 /* UserDefaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB7DB2733A4A9008AC3D2 /* UserDefaults+Extensions.swift */; }; - FC5FB7DD2733A4AA008AC3D2 /* UserDefaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB7DB2733A4A9008AC3D2 /* UserDefaults+Extensions.swift */; }; - FC5FB7DE2733A4AA008AC3D2 /* UserDefaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB7DB2733A4A9008AC3D2 /* UserDefaults+Extensions.swift */; }; FC5FB7E02733A6D5008AC3D2 /* FirebaseCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB7DF2733A6D5008AC3D2 /* FirebaseCollection.swift */; }; - FC5FB7E12733A6D5008AC3D2 /* FirebaseCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB7DF2733A6D5008AC3D2 /* FirebaseCollection.swift */; }; - FC5FB7E22733A6D5008AC3D2 /* FirebaseCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC5FB7DF2733A6D5008AC3D2 /* FirebaseCollection.swift */; }; FC7AA339274345280059D0CD /* StickerUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC7AA338274345280059D0CD /* StickerUseCase.swift */; }; FC822FAA2736088100C918A0 /* URLSessionNetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC822FA92736088100C918A0 /* URLSessionNetworkService.swift */; }; FC822FAC27360F8600C918A0 /* URLRequsetBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC822FAB27360F8600C918A0 /* URLRequsetBuilder.swift */; }; @@ -187,17 +347,12 @@ FC822FB22736BF8400C918A0 /* URLSessionNetworkServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC822FB12736BF8400C918A0 /* URLSessionNetworkServiceProtocol.swift */; }; FC9F10582730F0060060FEB7 /* GetMyIdUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9F10572730F0060060FEB7 /* GetMyIdUseCase.swift */; }; FC9F105B2730F1B20060FEB7 /* UserRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9F105A2730F1B20060FEB7 /* UserRepositoryProtocol.swift */; }; - FC9F1065273102E30060FEB7 /* GetMyIdUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9F1064273102E30060FEB7 /* GetMyIdUseCaseTests.swift */; }; - FC9F106B2731035E0060FEB7 /* GetMyIdUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9F10572730F0060060FEB7 /* GetMyIdUseCase.swift */; }; - FC9F106C2731035E0060FEB7 /* UserRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9F105A2730F1B20060FEB7 /* UserRepositoryProtocol.swift */; }; - FC9F108227315EB80060FEB7 /* FirebaseNetworkTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9F108127315EB80060FEB7 /* FirebaseNetworkTest.swift */; }; - FCC803F62744C22F00FFE4DC /* DoolDaFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC803F52744C22F00FFE4DC /* DoolDaFont.swift */; }; - FCC803F82744FEF800FFE4DC /* SettingViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC803F72744FEF800FFE4DC /* SettingViewModel.swift */; }; - FCC803FA27451A8000FFE4DC /* TextInputViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC803F927451A8000FFE4DC /* TextInputViewController.swift */; }; - FCC803FC27451C9600FFE4DC /* TextInputViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC803FB27451C9600FFE4DC /* TextInputViewModel.swift */; }; + FCC803F62744C22F00FFE4DC /* FontType.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC803F52744C22F00FFE4DC /* FontType.swift */; }; + FCC803FA27451A8000FFE4DC /* TextEditViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC803F927451A8000FFE4DC /* TextEditViewController.swift */; }; + FCC803FC27451C9600FFE4DC /* TextEditViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCC803FB27451C9600FFE4DC /* TextEditViewModel.swift */; }; FCCF022F273A12B400EAB00B /* EditPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCCF022E273A12B400EAB00B /* EditPageViewController.swift */; }; FCCF0231273A145400EAB00B /* EditPageViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCCF0230273A145400EAB00B /* EditPageViewCoordinator.swift */; }; - FCCF0233273A719600EAB00B /* PageControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCCF0232273A719600EAB00B /* PageControlView.swift */; }; + FCCF0233273A719600EAB00B /* PageComponentControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCCF0232273A719600EAB00B /* PageComponentControlView.swift */; }; FCD24D7127437F31000203F7 /* UIFont+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD24D7027437F31000203F7 /* UIFont+Extensions.swift */; }; FCD24D7327439268000203F7 /* GlobalFontRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD24D7227439268000203F7 /* GlobalFontRepository.swift */; }; FCD24D75274392AB000203F7 /* GlobalFontRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCD24D74274392AB000203F7 /* GlobalFontRepositoryProtocol.swift */; }; @@ -208,60 +363,31 @@ FCE9D3DC2738C9A00001F155 /* PairRepositoryProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE9D3DB2738C9A00001F155 /* PairRepositoryProtocol.swift */; }; FCE9D3DE2738CFB50001F155 /* PairRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE9D3DD2738CFB50001F155 /* PairRepository.swift */; }; FCE9D3F427391F260001F155 /* EditPageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE9D3F327391F260001F155 /* EditPageViewModel.swift */; }; - FCE9D3F62739550C0001F155 /* EditPageViewCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCE9D3F52739550C0001F155 /* EditPageViewCoordinatorProtocol.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 1D097E94273CFA1200A4810B /* PBXContainerItemProxy */ = { + 1DAEB013275603C80040E8C1 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 664893B5273007ED009F03E2 /* Project object */; proxyType = 1; remoteGlobalIDString = 664893BC273007ED009F03E2; remoteInfo = Doolda; }; - 310125CD2739A56600E43160 /* PBXContainerItemProxy */ = { + FC4459DC27560731004888B9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 664893B5273007ED009F03E2 /* Project object */; proxyType = 1; remoteGlobalIDString = 664893BC273007ED009F03E2; remoteInfo = Doolda; }; - 6648946027327F1D009F03E2 /* PBXContainerItemProxy */ = { + FC4459F027561A48004888B9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 664893B5273007ED009F03E2 /* Project object */; proxyType = 1; remoteGlobalIDString = 664893BC273007ED009F03E2; remoteInfo = Doolda; }; - 669ADE362732E4A8007B529B /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 664893B5273007ED009F03E2 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 664893BC273007ED009F03E2; - remoteInfo = Doolda; - }; - FC5FB7A527328A19008AC3D2 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 664893B5273007ED009F03E2 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 664893BC273007ED009F03E2; - remoteInfo = Doolda; - }; - FC5FB7CD27339213008AC3D2 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 664893B5273007ED009F03E2 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 664893BC273007ED009F03E2; - remoteInfo = Doolda; - }; - FC9F1066273102E30060FEB7 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 664893B5273007ED009F03E2 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 664893BC273007ED009F03E2; - remoteInfo = Doolda; - }; - FC9F108327315EB80060FEB7 /* PBXContainerItemProxy */ = { + FC445A02275622CB004888B9 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 664893B5273007ED009F03E2 /* Project object */; proxyType = 1; @@ -270,19 +396,34 @@ }; /* End PBXContainerItemProxy section */ +/* Begin PBXCopyFilesBuildPhase section */ + 1D0C7E9C274F7F3D00812C82 /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 1D04F419273954CD00CFCC3A /* CGColor+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGColor+Extensions.swift"; sourceTree = ""; }; 1D04F41B273957E600CFCC3A /* BackgroundType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundType.swift; sourceTree = ""; }; + 1D08135B2758C62900DCF432 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/LaunchScreen.strings; sourceTree = ""; }; + 1D08135F2758C6C300DCF432 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/InfoPlist.strings; sourceTree = ""; }; 1D097E96273CFA5900A4810B /* RawPageRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawPageRepository.swift; sourceTree = ""; }; + 1D0C7E8F274F7F3A00812C82 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; }; + 1D0C7E91274F7F3A00812C82 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; }; 1D0C8BC32732AF780091ACC0 /* GetUserUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetUserUseCase.swift; sourceTree = ""; }; - 1D0C8BC92732B5F90091ACC0 /* GetPairIdUseCaseTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GetPairIdUseCaseTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 1D0C8BCB2732B5F90091ACC0 /* GetPairIdUseCaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetPairIdUseCaseTest.swift; sourceTree = ""; }; + 1D1C8EC52755ECF10044AF95 /* DummyUserRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyUserRepository.swift; sourceTree = ""; }; 1D3629FD2733C79300CFC069 /* RegisterUserUseCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegisterUserUseCase.swift; sourceTree = ""; }; 1D3925B4273B9172003A0B70 /* DateFormatter+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DateFormatter+Extensions.swift"; sourceTree = ""; }; 1D3925BA273BB4BE003A0B70 /* EditPageUseCaseTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = EditPageUseCaseTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 1D3925BC273BB4BE003A0B70 /* EditPageUseCaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditPageUseCaseTest.swift; sourceTree = ""; }; 1D3925CF273C1263003A0B70 /* CGFloat+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGFloat+Extensions.swift"; sourceTree = ""; }; - 1D58277C2732883E00FD0EA5 /* PairingViewCoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairingViewCoordinatorProtocol.swift; sourceTree = ""; }; 1D6232E62730F0DF00A9AD6B /* PairingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairingViewController.swift; sourceTree = ""; }; 1D6232E82730FA3900A9AD6B /* UIColor+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = ""; }; 1D6232EC27310EE300A9AD6B /* UIImage+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extensions.swift"; sourceTree = ""; }; @@ -291,21 +432,46 @@ 1D6232F427313D8E00A9AD6B /* UIView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = ""; }; 1D6232F6273140CF00A9AD6B /* CopyableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CopyableLabel.swift; sourceTree = ""; }; 1D6232F8273173E300A9AD6B /* UIResponder+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIResponder+Extensions.swift"; sourceTree = ""; }; + 1D8646A2274D2DB600C4511D /* PushMessageEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushMessageEntity.swift; sourceTree = ""; }; + 1D8AFD0727506BE100822B89 /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; + 1D8AFD0827506BE100822B89 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 1D8BE0782740FE5E00201E3E /* PageDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageDocument.swift; sourceTree = ""; }; - 1D8BE07A2742A3DB00201E3E /* DiaryPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryPageView.swift; sourceTree = ""; }; + 1D8BE07A2742A3DB00201E3E /* DiaryCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryCollectionViewCell.swift; sourceTree = ""; }; 1D8BE07C2743437600201E3E /* DiaryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryViewModel.swift; sourceTree = ""; }; 1D8BE07E2743846A00201E3E /* DiaryCollectionViewHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryCollectionViewHeader.swift; sourceTree = ""; }; 1D992B41273D0EAB00B63954 /* PageRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageRepository.swift; sourceTree = ""; }; + 1D992E302755E8EC002C4457 /* GetUserUseCaseTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GetUserUseCaseTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 1D992E322755E8EC002C4457 /* GetUserUseCaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetUserUseCaseTest.swift; sourceTree = ""; }; + 1D99945E274C8BF600C72273 /* Doolda.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Doolda.entitlements; sourceTree = ""; }; + 1D999461274CA94600C72273 /* FCMTokenUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCMTokenUseCase.swift; sourceTree = ""; }; + 1D999463274CAAAC00C72273 /* FCMTokenRepositoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCMTokenRepositoryProtocol.swift; sourceTree = ""; }; + 1D999465274CAB8900C72273 /* FCMTokenRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCMTokenRepository.swift; sourceTree = ""; }; + 1D999467274CAC4700C72273 /* FCMTokenDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCMTokenDocument.swift; sourceTree = ""; }; + 1D999469274CE5F700C72273 /* FirebaseMessageRepositoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseMessageRepositoryProtocol.swift; sourceTree = ""; }; + 1D99946B274CEF2900C72273 /* FirebaseMessageRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseMessageRepository.swift; sourceTree = ""; }; + 1D99946D274CEFC500C72273 /* FirebaseMessageUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseMessageUseCase.swift; sourceTree = ""; }; + 1D9DC09C2749F2BE0060587B /* FilterOptionBottomSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterOptionBottomSheetViewController.swift; sourceTree = ""; }; + 1D9DC09E274A0E290060587B /* FilterOptionBottomSheetViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilterOptionBottomSheetViewModel.swift; sourceTree = ""; }; + 1D9DC0A0274A47D50060587B /* DooldaButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DooldaButton.swift; sourceTree = ""; }; + 1D9DC0A2274B31940060587B /* DiaryBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryBackgroundView.swift; sourceTree = ""; }; + 1D9DC0A4274B8A890060587B /* Collection+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection+Extensions.swift"; sourceTree = ""; }; + 1DAEB00F275603C80040E8C1 /* RegisterUserUseCaseTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RegisterUserUseCaseTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 1DAEB011275603C80040E8C1 /* RegisterUserUseCaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterUserUseCaseTest.swift; sourceTree = ""; }; + 1DAEB02427560E8F0040E8C1 /* FCMTokenUseCaseTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FCMTokenUseCaseTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 1DAEB02627560E8F0040E8C1 /* FCMTokenUseCaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCMTokenUseCaseTest.swift; sourceTree = ""; }; + 1DAEB02E27560F5F0040E8C1 /* DummyFCMTokenRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyFCMTokenRepository.swift; sourceTree = ""; }; + 1DAEB03A275617010040E8C1 /* FirebaseMessageUseCaseTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FirebaseMessageUseCaseTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 1DAEB03C275617010040E8C1 /* FirebaseMessageUseCaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseMessageUseCaseTest.swift; sourceTree = ""; }; + 1DAEB044275617A40040E8C1 /* DummyFirebaseMessageRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyFirebaseMessageRepository.swift; sourceTree = ""; }; 1DBF5714273A2C5100D28F5E /* FontColorType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontColorType.swift; sourceTree = ""; }; 1DBF5716273A328D00D28F5E /* PhotoFrameType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoFrameType.swift; sourceTree = ""; }; + 1DD58BD52754ACA600B93184 /* DiaryPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryPageView.swift; sourceTree = ""; }; 1DD6AD3F27321E5800592AC8 /* dovemayo.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = dovemayo.otf; sourceTree = ""; }; - 1DDAF0512730D267006CD027 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; - 1DE5E5352738BBAA004F794F /* CoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoordinatorProtocol.swift; sourceTree = ""; }; - 1DE5E5372738BCE0004F794F /* DiaryViewCoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryViewCoordinatorProtocol.swift; sourceTree = ""; }; + 1DDADCB3274BCE63000330C9 /* Secrets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Secrets.swift; sourceTree = ""; }; + 1DE5E5352738BBAA004F794F /* BaseCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseCoordinator.swift; sourceTree = ""; }; 1DE5E53D273929CE004F794F /* EditPageUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditPageUseCase.swift; sourceTree = ""; }; 1DE5E53F273932B0004F794F /* PageRepositoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageRepositoryProtocol.swift; sourceTree = ""; }; 1DE5E5412739352B004F794F /* RawPageRepositoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawPageRepositoryProtocol.swift; sourceTree = ""; }; - 3101258E2737987B00E43160 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 310125912738325800E43160 /* UIAlertController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+Extensions.swift"; sourceTree = ""; }; 3101259327383DED00E43160 /* PhotoComponentEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoComponentEntity.swift; sourceTree = ""; }; 31012595273842B300E43160 /* StickerComponentEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerComponentEntity.swift; sourceTree = ""; }; @@ -313,52 +479,43 @@ 3101259B2738430200E43160 /* StickerPackEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPackEntity.swift; sourceTree = ""; }; 3101259F27393D3B00E43160 /* ImageComposeUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageComposeUseCase.swift; sourceTree = ""; }; 310125C327398DF600E43160 /* ImageUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUseCase.swift; sourceTree = ""; }; - 310125C92739A56600E43160 /* ImageComposeUseCaseTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ImageComposeUseCaseTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 310125CB2739A56600E43160 /* ImageComposeUseCaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageComposeUseCaseTest.swift; sourceTree = ""; }; 310125D5273A6F3400E43160 /* FileManagerPersistenceService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManagerPersistenceService.swift; sourceTree = ""; }; 310125D7273A6F6B00E43160 /* FileManagerPersistenceServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManagerPersistenceServiceProtocol.swift; sourceTree = ""; }; - 310D80CE273106CA00F15F4F /* UIColor+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extensions.swift"; sourceTree = ""; }; - 310D80D22731402F00F15F4F /* ParingViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParingViewCoordinator.swift; sourceTree = ""; }; + 310D80D22731402F00F15F4F /* PairingViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairingViewCoordinator.swift; sourceTree = ""; }; 310D80D42731405B00F15F4F /* DiaryViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryViewCoordinator.swift; sourceTree = ""; }; - 310D80DD2732533300F15F4F /* GetPairIdUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetPairIdUseCase.swift; sourceTree = ""; }; - 310D80DF273253C900F15F4F /* GenerateMyIdUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GenerateMyIdUseCase.swift; sourceTree = ""; }; - 310D81232733C7FC00F15F4F /* SplashViewModelTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SplashViewModelTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 310D81252733C7FC00F15F4F /* SplashViewModelTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashViewModelTest.swift; sourceTree = ""; }; 310D81472733F56700F15F4F /* DiaryViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiaryViewController.swift; sourceTree = ""; }; - 3161EE932742A44100B922DD /* StickerPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPickerViewController.swift; sourceTree = ""; }; + 3161EE932742A44100B922DD /* StickerPickerBottomSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPickerBottomSheetViewController.swift; sourceTree = ""; }; 3161EE9727436EC600B922DD /* StickerPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPickerView.swift; sourceTree = ""; }; - 3161EE99274377E300B922DD /* PackedStickerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackedStickerCell.swift; sourceTree = ""; }; - 3161EE9B274377F400B922DD /* UnpackedStickerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnpackedStickerCell.swift; sourceTree = ""; }; - 3161EEDC2744C51000B922DD /* buddySticker_0.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = buddySticker_0.png; sourceTree = ""; }; - 3161EEE42744F57800B922DD /* buddySticker_cover.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = buddySticker_cover.png; sourceTree = ""; }; - 3161EEEB274663BB00B922DD /* boolbadaSticker_cover.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = boolbadaSticker_cover.png; sourceTree = ""; }; - 3161EEEC274663BC00B922DD /* boolbadaSticker_0.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = boolbadaSticker_0.png; sourceTree = ""; }; - 3161EEED274663BC00B922DD /* boolbadaSticker_2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = boolbadaSticker_2.png; sourceTree = ""; }; - 3161EEEE274663BC00B922DD /* boolbadaSticker_4.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = boolbadaSticker_4.png; sourceTree = ""; }; - 3161EEEF274663BC00B922DD /* boolbadaSticker_3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = boolbadaSticker_3.png; sourceTree = ""; }; - 3161EEF0274663BC00B922DD /* boolbadaSticker_1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = boolbadaSticker_1.png; sourceTree = ""; }; - 3161EEF82746644300B922DD /* htmlCoder_2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = htmlCoder_2.png; sourceTree = ""; }; - 3161EEF92746644300B922DD /* htmlCoder_3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = htmlCoder_3.png; sourceTree = ""; }; - 3161EEFA2746644400B922DD /* htmlCoder_10.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = htmlCoder_10.png; sourceTree = ""; }; - 3161EEFB2746644400B922DD /* htmlCoder_1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = htmlCoder_1.png; sourceTree = ""; }; - 3161EEFC2746644400B922DD /* htmlCoder_5.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = htmlCoder_5.png; sourceTree = ""; }; - 3161EEFD2746644400B922DD /* htmlCoder_6.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = htmlCoder_6.png; sourceTree = ""; }; - 3161EEFE2746644500B922DD /* htmlCoder_8.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = htmlCoder_8.png; sourceTree = ""; }; - 3161EEFF2746644500B922DD /* htmlCoder_9.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = htmlCoder_9.png; sourceTree = ""; }; - 3161EF002746644500B922DD /* htmlCoder_0.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = htmlCoder_0.png; sourceTree = ""; }; - 3161EF012746644500B922DD /* htmlCoder_7.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = htmlCoder_7.png; sourceTree = ""; }; - 3161EF022746644600B922DD /* htmlCoder_4.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = htmlCoder_4.png; sourceTree = ""; }; - 3161EF0E2746645E00B922DD /* htmlCoder_cover.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = htmlCoder_cover.png; sourceTree = ""; }; - 3161EF102746651000B922DD /* buddySticker_1.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = buddySticker_1.png; sourceTree = ""; }; - 3161EF112746651000B922DD /* buddySticker_3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = buddySticker_3.png; sourceTree = ""; }; - 3161EF122746651000B922DD /* buddySticker_4.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = buddySticker_4.png; sourceTree = ""; }; - 3161EF132746651100B922DD /* buddySticker_2.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = buddySticker_2.png; sourceTree = ""; }; - 319C21C9273B9703009CE65C /* FileDocuments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileDocuments.swift; sourceTree = ""; }; + 3161EE99274377E300B922DD /* PackedStickerCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackedStickerCollectionViewCell.swift; sourceTree = ""; }; + 3161EE9B274377F400B922DD /* UnpackedStickerCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnpackedStickerCollectionViewCell.swift; sourceTree = ""; }; + 317FB60A274A3850005DF3A3 /* StickerPickerCollectionViewFooter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPickerCollectionViewFooter.swift; sourceTree = ""; }; + 317FB60C274A8F59005DF3A3 /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; + 317FB610274A9175005DF3A3 /* SettingsViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewCoordinator.swift; sourceTree = ""; }; + 317FB612274AA879005DF3A3 /* SettingsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTableViewCell.swift; sourceTree = ""; }; + 317FB614274B9A92005DF3A3 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; + 317FB616274BAAEB005DF3A3 /* DooldaInfoType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DooldaInfoType.swift; sourceTree = ""; }; + 317FB618274BBC9D005DF3A3 /* SettingsTableViewHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTableViewHeader.swift; sourceTree = ""; }; + 317FB61A274BCE68005DF3A3 /* InformationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InformationViewController.swift; sourceTree = ""; }; + 318D9F5A274CC77800AA6F0E /* FontPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontPickerViewController.swift; sourceTree = ""; }; + 318D9F5D274CFBA100AA6F0E /* uhBeeMysen.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = uhBeeMysen.ttf; sourceTree = ""; }; + 318D9F5E274CFBEA00AA6F0E /* dungGeunMo.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = dungGeunMo.otf; sourceTree = ""; }; + 318D9F5F274CFC0800AA6F0E /* kotraHope.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = kotraHope.otf; sourceTree = ""; }; + 318D9F66274D4D5000AA6F0E /* darae.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = darae.ttf; sourceTree = ""; }; + 318D9F68274D4D9900AA6F0E /* kyoboHandwriting.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = kyoboHandwriting.otf; sourceTree = ""; }; + 318D9F6F274E12D900AA6F0E /* PageDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageDetailViewModel.swift; sourceTree = ""; }; + 318D9F71274E17AB00AA6F0E /* PageDetailViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageDetailViewCoordinator.swift; sourceTree = ""; }; + 318D9F75274E258D00AA6F0E /* PageDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageDetailViewController.swift; sourceTree = ""; }; 319C21CD273BA6D8009CE65C /* ImageRepositoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRepositoryProtocol.swift; sourceTree = ""; }; 319C21CF273BA7D4009CE65C /* ImageRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRepository.swift; sourceTree = ""; }; 319C21D5273BD764009CE65C /* URLSessionNetworkServiceTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = URLSessionNetworkServiceTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 319C21D7273BD764009CE65C /* URLSessionNetworkServiceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionNetworkServiceTest.swift; sourceTree = ""; }; 319C222C273CEFBB009CE65C /* CIImage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CIImage+Extension.swift"; sourceTree = ""; }; + 31BEC80A2754DD74009AF22A /* license.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = license.txt; sourceTree = ""; }; + 31BEC81E2755B154009AF22A /* Config.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Config.xcconfig; sourceTree = ""; }; + 31BEC8202755B160009AF22A /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 31BEC8312755EA89009AF22A /* ImageUseCaseTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ImageUseCaseTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 31BEC8332755EA89009AF22A /* ImageUseCaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUseCaseTest.swift; sourceTree = ""; }; + 31BEC8392755EBB7009AF22A /* DummyImageRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyImageRepository.swift; sourceTree = ""; }; 661A840327434FE10077E0E3 /* GetPageUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetPageUseCase.swift; sourceTree = ""; }; 661A8405274351230077E0E3 /* CheckMyTurnUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckMyTurnUseCase.swift; sourceTree = ""; }; 664893BD273007ED009F03E2 /* Doolda.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Doolda.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -371,11 +528,7 @@ 664893F72730272B009F03E2 /* AppCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCoordinator.swift; sourceTree = ""; }; 664893F927302905009F03E2 /* SplashViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashViewCoordinator.swift; sourceTree = ""; }; 664893FB27302947009F03E2 /* SplashViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashViewModel.swift; sourceTree = ""; }; - 664893FD273029AC009F03E2 /* SplashViewCoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashViewCoordinatorProtocol.swift; sourceTree = ""; }; 664894172730F1B0009F03E2 /* PairingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairingViewModel.swift; sourceTree = ""; }; - 6648941D27310165009F03E2 /* PairingViewModelTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PairingViewModelTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 6648945C27327F1D009F03E2 /* GeneratePairIdUseCaseTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GeneratePairIdUseCaseTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 6648945E27327F1D009F03E2 /* GeneratePairIdUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneratePairIdUseCaseTests.swift; sourceTree = ""; }; 6653039A2743B3FF00E3075C /* CoreDataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = CoreDataModel.xcdatamodel; sourceTree = ""; }; 6653039C2743B49E00E3075C /* CoreDataPersistenceService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataPersistenceService.swift; sourceTree = ""; }; 6653039E2743B7FE00E3075C /* CoreDataPageEntity+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CoreDataPageEntity+CoreDataClass.swift"; sourceTree = ""; }; @@ -383,13 +536,22 @@ 665303A52743E09A00E3075C /* CoreDataPersistenceServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataPersistenceServiceProtocol.swift; sourceTree = ""; }; 665303A72744C6F500E3075C /* CoreDataPageEntityPersistenceService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataPageEntityPersistenceService.swift; sourceTree = ""; }; 665303A92744CE3400E3075C /* CoreDataPageEntityPersistenceServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataPageEntityPersistenceServiceProtocol.swift; sourceTree = ""; }; - 669ADE322732E4A8007B529B /* RefreshPairIdUseCaseTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RefreshPairIdUseCaseTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 669ADE342732E4A8007B529B /* RefreshPairIdUseCaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshPairIdUseCaseTest.swift; sourceTree = ""; }; - 66B4ADFC2742BEFC0021EA27 /* Normal.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = Normal.jpg; sourceTree = ""; }; - 66B4ADFD2742BEFC0021EA27 /* Polaroid.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = Polaroid.jpg; sourceTree = ""; }; - 66B4ADFE2742BEFC0021EA27 /* LifeFourCuts.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = LifeFourCuts.jpg; sourceTree = ""; }; + 66B4ADFC2742BEFC0021EA27 /* normal.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = normal.jpg; sourceTree = ""; }; + 66B4ADFD2742BEFC0021EA27 /* polaroid.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = polaroid.jpg; sourceTree = ""; }; + 66B4ADFE2742BEFC0021EA27 /* lifeFourCuts.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = lifeFourCuts.jpg; sourceTree = ""; }; + 66C2CA852755E9F200AFE5EE /* PairUserUseCaseTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PairUserUseCaseTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 66C2CA872755E9F200AFE5EE /* PairUserUseCaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairUserUseCaseTest.swift; sourceTree = ""; }; + 66C2CAAB27561E1100AFE5EE /* RefreshUserUseCaseTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RefreshUserUseCaseTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 66C2CAAD27561E1200AFE5EE /* RefreshUserUseCaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshUserUseCaseTest.swift; sourceTree = ""; }; + 66C2CAC0275623F700AFE5EE /* CheckMyTurnUseCaseTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CheckMyTurnUseCaseTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 66C2CAC2275623F700AFE5EE /* CheckMyTurnUseCaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckMyTurnUseCaseTest.swift; sourceTree = ""; }; + 66C2CAD627562C1500AFE5EE /* GetPageUseCaseTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GetPageUseCaseTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 66C2CAD827562C1600AFE5EE /* GetPageUseCaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetPageUseCaseTest.swift; sourceTree = ""; }; + 66C2CAEC275640CA00AFE5EE /* GetRawPageUseCaseTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GetRawPageUseCaseTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 66C2CAEE275640CA00AFE5EE /* GetRawPageUseCaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetRawPageUseCaseTest.swift; sourceTree = ""; }; 66C482DA2746135500823151 /* GetRawPageUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetRawPageUseCase.swift; sourceTree = ""; }; 66CAFF232740F63A00A48DCF /* BackgroundTypePickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundTypePickerViewController.swift; sourceTree = ""; }; + 66CCB6F7275924AB00E3FD64 /* polaroid_long.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = polaroid_long.jpg; sourceTree = ""; }; 66CD644D273A490800E55C03 /* BottomSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomSheetViewController.swift; sourceTree = ""; }; 66CD644F273A5C4700E55C03 /* PhotoPickerBottomSheetViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPickerBottomSheetViewController.swift; sourceTree = ""; }; 66CD6455273BB88A00E55C03 /* PhotoFrameCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoFrameCollectionViewCell.swift; sourceTree = ""; }; @@ -397,6 +559,24 @@ 66CD6463273D125E00E55C03 /* CGImage+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGImage+Extensions.swift"; sourceTree = ""; }; 66ECCD39273E5342003990EE /* CarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselView.swift; sourceTree = ""; }; 66ECCD3B273F7C9E003990EE /* CustomActivityIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomActivityIndicator.swift; sourceTree = ""; }; + 66F1760427548260006ACB41 /* UnpairUserUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnpairUserUseCase.swift; sourceTree = ""; }; + 66F176082754C21B006ACB41 /* GetMyIdUseCaseProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetMyIdUseCaseProtocol.swift; sourceTree = ""; }; + 66F1760A2754C27C006ACB41 /* PairUserUseCaseProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairUserUseCaseProtocol.swift; sourceTree = ""; }; + 66F1760C2754C2CE006ACB41 /* GetUserUseCaseProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetUserUseCaseProtocol.swift; sourceTree = ""; }; + 66F1760E2754C334006ACB41 /* RefreshUserUseCaseProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshUserUseCaseProtocol.swift; sourceTree = ""; }; + 66F176102754C370006ACB41 /* RegisterUserUseCaseProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterUserUseCaseProtocol.swift; sourceTree = ""; }; + 66F176122754C3C3006ACB41 /* EditPageUseCaseProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditPageUseCaseProtocol.swift; sourceTree = ""; }; + 66F176142754C413006ACB41 /* ImageComposeUseCaseProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageComposeUseCaseProtocol.swift; sourceTree = ""; }; + 66F176162754C4F6006ACB41 /* ImageUseCaseProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageUseCaseProtocol.swift; sourceTree = ""; }; + 66F176182754C547006ACB41 /* CheckMyTurnUseCaseProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckMyTurnUseCaseProtocol.swift; sourceTree = ""; }; + 66F1761A2754C589006ACB41 /* GetPageUseCaseProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetPageUseCaseProtocol.swift; sourceTree = ""; }; + 66F1761C2754C5FF006ACB41 /* StickerUseCaseProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerUseCaseProtocol.swift; sourceTree = ""; }; + 66F1761E2754C656006ACB41 /* TextUseCaseProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextUseCaseProtocol.swift; sourceTree = ""; }; + 66F176202754C6A3006ACB41 /* PushNotificationStateUseCaseProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationStateUseCaseProtocol.swift; sourceTree = ""; }; + 66F176222754C6EE006ACB41 /* GlobalFontUseCaseProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalFontUseCaseProtocol.swift; sourceTree = ""; }; + 66F176242754C74C006ACB41 /* GetRawPageUseCaseProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetRawPageUseCaseProtocol.swift; sourceTree = ""; }; + 66F176262754C77C006ACB41 /* FCMTokenUseCaseProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FCMTokenUseCaseProtocol.swift; sourceTree = ""; }; + 66F176282754C7DA006ACB41 /* FirebaseMessageUseCaseProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseMessageUseCaseProtocol.swift; sourceTree = ""; }; 66F5D4792733E88F000FB471 /* PageEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageEntity.swift; sourceTree = ""; }; 66F5D47B2733EA32000FB471 /* ComponentEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComponentEntity.swift; sourceTree = ""; }; 66F5D47D2733EE8F000FB471 /* RawPageEntity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RawPageEntity.swift; sourceTree = ""; }; @@ -406,17 +586,34 @@ 66FE9370273671910036CED2 /* PairUserUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairUserUseCase.swift; sourceTree = ""; }; 66FE9372273688E10036CED2 /* RefreshUserUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RefreshUserUseCase.swift; sourceTree = ""; }; 66FE93CB27396B950036CED2 /* PhotoPickerBottomSheetViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPickerBottomSheetViewModel.swift; sourceTree = ""; }; - FC195989274284E200AB6059 /* StickerPickerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPickerViewModel.swift; sourceTree = ""; }; + FC195989274284E200AB6059 /* StickerPickerBottomSheetViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerPickerBottomSheetViewModel.swift; sourceTree = ""; }; + FC23C227274E17C200EBAE4C /* PageDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PageDetailViewController.swift; sourceTree = ""; }; FC28CB31274609F300BA47BB /* TextUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextUseCase.swift; sourceTree = ""; }; + FC44599B2755DF86004888B9 /* DummyPageRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyPageRepository.swift; sourceTree = ""; }; + FC44599E2755E0E1004888B9 /* DummyError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyError.swift; sourceTree = ""; }; + FC4459A12755E41C004888B9 /* DummyImageUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyImageUseCase.swift; sourceTree = ""; }; + FC4459A32755E434004888B9 /* DummyRawPageRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyRawPageRepository.swift; sourceTree = ""; }; + FC4459A52755E470004888B9 /* DummyPairRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyPairRepository.swift; sourceTree = ""; }; + FC4459AB2755E8C4004888B9 /* GetMyIdUseCaseTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GetMyIdUseCaseTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + FC4459AD2755E8C4004888B9 /* GetMyIdUseCaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetMyIdUseCaseTest.swift; sourceTree = ""; }; + FC4459B72755EBC5004888B9 /* DummyUserRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyUserRepository.swift; sourceTree = ""; }; + FC4459D827560731004888B9 /* GlobalFontUseCaseTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GlobalFontUseCaseTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + FC4459DA27560731004888B9 /* GlobalFontUseCaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalFontUseCaseTest.swift; sourceTree = ""; }; + FC4459E627560909004888B9 /* DummyGlobalFontRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyGlobalFontRepository.swift; sourceTree = ""; }; + FC4459EC27561A48004888B9 /* PushNotificationStateUseCaseTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PushNotificationStateUseCaseTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + FC4459EE27561A48004888B9 /* PushNotificationStateUseCaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationStateUseCaseTest.swift; sourceTree = ""; }; + FC4459F827561BB3004888B9 /* DummyPushNotificationStateRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DummyPushNotificationStateRepository.swift; sourceTree = ""; }; + FC4459FE275622CB004888B9 /* TextUseCaseTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TextUseCaseTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + FC445A00275622CB004888B9 /* TextUseCaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextUseCaseTest.swift; sourceTree = ""; }; + FC445A0C27562590004888B9 /* ComponentType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComponentType.swift; sourceTree = ""; }; + FC51FA66274BB9D60069D39C /* FontColorPickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontColorPickerView.swift; sourceTree = ""; }; + FC51FA68274BBB3C0069D39C /* FontColorCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontColorCollectionViewCell.swift; sourceTree = ""; }; + FC51FA6C274C89330069D39C /* FontSizeControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontSizeControlView.swift; sourceTree = ""; }; FC5FB78F27325C4D008AC3D2 /* UserDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDocument.swift; sourceTree = ""; }; FC5FB792273260C3008AC3D2 /* UserRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRepository.swift; sourceTree = ""; }; FC5FB7942732627C008AC3D2 /* PairDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairDocument.swift; sourceTree = ""; }; FC5FB799273284E2008AC3D2 /* UserDefaultsPersistenceService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsPersistenceService.swift; sourceTree = ""; }; FC5FB79B27328579008AC3D2 /* UserDefaultsPersistenceServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsPersistenceServiceProtocol.swift; sourceTree = ""; }; - FC5FB7A127328A19008AC3D2 /* UserDefaultsPersistenceServiceTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UserDefaultsPersistenceServiceTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - FC5FB7A327328A19008AC3D2 /* UserDefaultsPersistenceServiceTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefaultsPersistenceServiceTest.swift; sourceTree = ""; }; - FC5FB7C927339213008AC3D2 /* UserRepositoryTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UserRepositoryTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - FC5FB7CB27339213008AC3D2 /* UserRepositoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRepositoryTests.swift; sourceTree = ""; }; FC5FB7DB2733A4A9008AC3D2 /* UserDefaults+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Extensions.swift"; sourceTree = ""; }; FC5FB7DF2733A6D5008AC3D2 /* FirebaseCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseCollection.swift; sourceTree = ""; }; FC7AA338274345280059D0CD /* StickerUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerUseCase.swift; sourceTree = ""; }; @@ -426,17 +623,12 @@ FC822FB12736BF8400C918A0 /* URLSessionNetworkServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLSessionNetworkServiceProtocol.swift; sourceTree = ""; }; FC9F10572730F0060060FEB7 /* GetMyIdUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetMyIdUseCase.swift; sourceTree = ""; }; FC9F105A2730F1B20060FEB7 /* UserRepositoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRepositoryProtocol.swift; sourceTree = ""; }; - FC9F1062273102E30060FEB7 /* GetMyIdUseCaseTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GetMyIdUseCaseTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - FC9F1064273102E30060FEB7 /* GetMyIdUseCaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetMyIdUseCaseTests.swift; sourceTree = ""; }; - FC9F107F27315EB80060FEB7 /* FirebaseNetworkTest.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FirebaseNetworkTest.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - FC9F108127315EB80060FEB7 /* FirebaseNetworkTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirebaseNetworkTest.swift; sourceTree = ""; }; - FCC803F52744C22F00FFE4DC /* DoolDaFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoolDaFont.swift; sourceTree = ""; }; - FCC803F72744FEF800FFE4DC /* SettingViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingViewModel.swift; sourceTree = ""; }; - FCC803F927451A8000FFE4DC /* TextInputViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextInputViewController.swift; sourceTree = ""; }; - FCC803FB27451C9600FFE4DC /* TextInputViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextInputViewModel.swift; sourceTree = ""; }; + FCC803F52744C22F00FFE4DC /* FontType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontType.swift; sourceTree = ""; }; + FCC803F927451A8000FFE4DC /* TextEditViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEditViewController.swift; sourceTree = ""; }; + FCC803FB27451C9600FFE4DC /* TextEditViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextEditViewModel.swift; sourceTree = ""; }; FCCF022E273A12B400EAB00B /* EditPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditPageViewController.swift; sourceTree = ""; }; FCCF0230273A145400EAB00B /* EditPageViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditPageViewCoordinator.swift; sourceTree = ""; }; - FCCF0232273A719600EAB00B /* PageControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageControlView.swift; sourceTree = ""; }; + FCCF0232273A719600EAB00B /* PageComponentControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageComponentControlView.swift; sourceTree = ""; }; FCD24D7027437F31000203F7 /* UIFont+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Extensions.swift"; sourceTree = ""; }; FCD24D7227439268000203F7 /* GlobalFontRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalFontRepository.swift; sourceTree = ""; }; FCD24D74274392AB000203F7 /* GlobalFontRepositoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalFontRepositoryProtocol.swift; sourceTree = ""; }; @@ -447,32 +639,38 @@ FCE9D3DB2738C9A00001F155 /* PairRepositoryProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairRepositoryProtocol.swift; sourceTree = ""; }; FCE9D3DD2738CFB50001F155 /* PairRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairRepository.swift; sourceTree = ""; }; FCE9D3F327391F260001F155 /* EditPageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditPageViewModel.swift; sourceTree = ""; }; - FCE9D3F52739550C0001F155 /* EditPageViewCoordinatorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditPageViewCoordinatorProtocol.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 1D0C8BC62732B5F90091ACC0 /* Frameworks */ = { + 1D3925B7273BB4BE003A0B70 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - 1D3925B7273BB4BE003A0B70 /* Frameworks */ = { + 1D992E2D2755E8EC002C4457 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - 310125C62739A56600E43160 /* Frameworks */ = { + 1DAEB00C275603C80040E8C1 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - 310D81202733C7FC00F15F4F /* Frameworks */ = { + 1DAEB02127560E8F0040E8C1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1DAEB037275617010040E8C1 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( @@ -486,44 +684,80 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 31BEC82E2755EA89009AF22A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 664893BA273007ED009F03E2 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 664893F027300DB9009F03E2 /* Kingfisher in Frameworks */, - 664893ED27300D7D009F03E2 /* SnapKit in Frameworks */, + 1D3BAC0F274E307A00CD23EE /* Kingfisher in Frameworks */, + 1D3BAC08274E2F3B00CD23EE /* FirebaseMessaging in Frameworks */, + 1D3BAC0B274E2F6500CD23EE /* SnapKit in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 66C2CA822755E9F200AFE5EE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 66C2CAA827561E1100AFE5EE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 66C2CABD275623F700AFE5EE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 66C2CAD327562C1500AFE5EE /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - 669ADE2F2732E4A8007B529B /* Frameworks */ = { + 66C2CAE9275640CA00AFE5EE /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - FC5FB79E27328A19008AC3D2 /* Frameworks */ = { + FC4459A82755E8C4004888B9 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - FC5FB7C627339213008AC3D2 /* Frameworks */ = { + FC4459D527560731004888B9 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - FC9F105F273102E30060FEB7 /* Frameworks */ = { + FC4459E927561A48004888B9 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - FC9F107C27315EB80060FEB7 /* Frameworks */ = { + FC4459FB275622CB004888B9 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( @@ -533,289 +767,341 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 1D0C8BCA2732B5F90091ACC0 /* GetPairIdUseCaseTest */ = { + 1D3925BB273BB4BE003A0B70 /* EditPageUseCaseTest */ = { isa = PBXGroup; children = ( - 1D0C8BCB2732B5F90091ACC0 /* GetPairIdUseCaseTest.swift */, + 1D3925BC273BB4BE003A0B70 /* EditPageUseCaseTest.swift */, ); - path = GetPairIdUseCaseTest; + path = EditPageUseCaseTest; sourceTree = ""; }; - 1D3925BB273BB4BE003A0B70 /* EditPageUseCaseTest */ = { + 1D8646A4274D60AA00C4511D /* Frameworks */ = { isa = PBXGroup; children = ( - 1D3925BC273BB4BE003A0B70 /* EditPageUseCaseTest.swift */, + 1D0C7E8F274F7F3A00812C82 /* WidgetKit.framework */, + 1D0C7E91274F7F3A00812C82 /* SwiftUI.framework */, ); - path = EditPageUseCaseTest; + name = Frameworks; sourceTree = ""; }; - 1DD6AD3E27321E3700592AC8 /* Fonts */ = { + 1D992E312755E8EC002C4457 /* GetUserUseCaseTest */ = { isa = PBXGroup; children = ( - 1DD6AD3F27321E5800592AC8 /* dovemayo.otf */, + 1D992E322755E8EC002C4457 /* GetUserUseCaseTest.swift */, ); - path = Fonts; + path = GetUserUseCaseTest; sourceTree = ""; }; - 310125CA2739A56600E43160 /* ImageComposeUseCaseTest */ = { + 1D9DC09B2749F2AA0060587B /* FilterOptionScene */ = { isa = PBXGroup; children = ( - 310125CB2739A56600E43160 /* ImageComposeUseCaseTest.swift */, + 1D9DC09C2749F2BE0060587B /* FilterOptionBottomSheetViewController.swift */, + 1D9DC09E274A0E290060587B /* FilterOptionBottomSheetViewModel.swift */, ); - path = ImageComposeUseCaseTest; + path = FilterOptionScene; sourceTree = ""; }; - 310D80E32732841500F15F4F /* Recovered References */ = { + 1D9FAD482747B4A700198B93 /* BackgroundTypePickerScene */ = { isa = PBXGroup; children = ( - 310D80CE273106CA00F15F4F /* UIColor+Extensions.swift */, - 310D80DD2732533300F15F4F /* GetPairIdUseCase.swift */, - 310D80DF273253C900F15F4F /* GenerateMyIdUseCase.swift */, - 1DDAF0512730D267006CD027 /* GoogleService-Info.plist */, + 66CAFF232740F63A00A48DCF /* BackgroundTypePickerViewController.swift */, ); - name = "Recovered References"; + path = BackgroundTypePickerScene; + sourceTree = ""; + }; + 1D9FAD492747B6D400198B93 /* SettingsScene */ = { + isa = PBXGroup; + children = ( + 317FB60C274A8F59005DF3A3 /* SettingsViewController.swift */, + 317FB61A274BCE68005DF3A3 /* InformationViewController.swift */, + 318D9F5A274CC77800AA6F0E /* FontPickerViewController.swift */, + 317FB612274AA879005DF3A3 /* SettingsTableViewCell.swift */, + 317FB618274BBC9D005DF3A3 /* SettingsTableViewHeader.swift */, + 317FB614274B9A92005DF3A3 /* SettingsViewModel.swift */, + 317FB610274A9175005DF3A3 /* SettingsViewCoordinator.swift */, + ); + path = SettingsScene; sourceTree = ""; }; - 310D81242733C7FC00F15F4F /* SplashViewModelTest */ = { + 1D9FAD4A2747B82B00198B93 /* Tests */ = { isa = PBXGroup; children = ( - 310D81252733C7FC00F15F4F /* SplashViewModelTest.swift */, + FC44599A2755DF04004888B9 /* Dummies */, + 1D992E312755E8EC002C4457 /* GetUserUseCaseTest */, + 319C21D6273BD764009CE65C /* URLSessionNetworkServiceTest */, + 1D3925BB273BB4BE003A0B70 /* EditPageUseCaseTest */, + FC4459AC2755E8C4004888B9 /* GetMyIdUseCaseTest */, + 1DAEB010275603C80040E8C1 /* RegisterUserUseCaseTest */, + 66C2CA862755E9F200AFE5EE /* PairUserUseCaseTest */, + 31BEC8322755EA89009AF22A /* ImageUseCaseTest */, + 1DAEB02527560E8F0040E8C1 /* FCMTokenUseCaseTest */, + 66C2CAED275640CA00AFE5EE /* GetRawPageUseCaseTest */, + 66C2CAAC27561E1200AFE5EE /* RefreshUserUseCaseTest */, + 66C2CAD727562C1600AFE5EE /* GetPageUseCaseTest */, + 66C2CAC1275623F700AFE5EE /* CheckMyTurnUseCaseTest */, + FC4459FF275622CB004888B9 /* TextUseCaseTest */, + FC4459ED27561A48004888B9 /* PushNotificationStateUseCaseTest */, + FC4459D927560731004888B9 /* GlobalFontUseCaseTest */, + 1DAEB03B275617010040E8C1 /* FirebaseMessageUseCaseTest */, + ); + path = Tests; + sourceTree = ""; + }; + 1DAEB010275603C80040E8C1 /* RegisterUserUseCaseTest */ = { + isa = PBXGroup; + children = ( + 1DAEB011275603C80040E8C1 /* RegisterUserUseCaseTest.swift */, ); - path = SplashViewModelTest; + path = RegisterUserUseCaseTest; sourceTree = ""; }; - 3161EEC42744C37600B922DD /* StickerPacks */ = { + 1DAEB02527560E8F0040E8C1 /* FCMTokenUseCaseTest */ = { isa = PBXGroup; children = ( - 3161EEF72746641A00B922DD /* HtmlCoder */, - 3161EEEA2746636D00B922DD /* Boolbada */, - 3161EECF2744C46B00B922DD /* Buddy */, + 1DAEB02627560E8F0040E8C1 /* FCMTokenUseCaseTest.swift */, ); - path = StickerPacks; + path = FCMTokenUseCaseTest; sourceTree = ""; }; - 3161EECF2744C46B00B922DD /* Buddy */ = { + 1DAEB03B275617010040E8C1 /* FirebaseMessageUseCaseTest */ = { isa = PBXGroup; children = ( - 3161EEE42744F57800B922DD /* buddySticker_cover.png */, - 3161EEDC2744C51000B922DD /* buddySticker_0.png */, - 3161EF102746651000B922DD /* buddySticker_1.png */, - 3161EF132746651100B922DD /* buddySticker_2.png */, - 3161EF112746651000B922DD /* buddySticker_3.png */, - 3161EF122746651000B922DD /* buddySticker_4.png */, - ); - path = Buddy; + 1DAEB03C275617010040E8C1 /* FirebaseMessageUseCaseTest.swift */, + ); + path = FirebaseMessageUseCaseTest; sourceTree = ""; }; - 3161EEEA2746636D00B922DD /* Boolbada */ = { + 1DD0F35A2747AE8600FC5E65 /* SplashScene */ = { isa = PBXGroup; children = ( - 3161EEEB274663BB00B922DD /* boolbadaSticker_cover.png */, - 3161EEEC274663BC00B922DD /* boolbadaSticker_0.png */, - 3161EEF0274663BC00B922DD /* boolbadaSticker_1.png */, - 3161EEED274663BC00B922DD /* boolbadaSticker_2.png */, - 3161EEEF274663BC00B922DD /* boolbadaSticker_3.png */, - 3161EEEE274663BC00B922DD /* boolbadaSticker_4.png */, - ); - path = Boolbada; + 664893F3273023D3009F03E2 /* SplashViewController.swift */, + 664893F927302905009F03E2 /* SplashViewCoordinator.swift */, + 664893FB27302947009F03E2 /* SplashViewModel.swift */, + ); + path = SplashScene; sourceTree = ""; }; - 3161EEF72746641A00B922DD /* HtmlCoder */ = { + 1DD0F35E2747AF5A00FC5E65 /* PairingScene */ = { isa = PBXGroup; children = ( - 3161EF0E2746645E00B922DD /* htmlCoder_cover.png */, - 3161EF002746644500B922DD /* htmlCoder_0.png */, - 3161EEFB2746644400B922DD /* htmlCoder_1.png */, - 3161EEF82746644300B922DD /* htmlCoder_2.png */, - 3161EEF92746644300B922DD /* htmlCoder_3.png */, - 3161EF022746644600B922DD /* htmlCoder_4.png */, - 3161EEFC2746644400B922DD /* htmlCoder_5.png */, - 3161EEFD2746644400B922DD /* htmlCoder_6.png */, - 3161EF012746644500B922DD /* htmlCoder_7.png */, - 3161EEFE2746644500B922DD /* htmlCoder_8.png */, - 3161EEFF2746644500B922DD /* htmlCoder_9.png */, - 3161EEFA2746644400B922DD /* htmlCoder_10.png */, - ); - path = HtmlCoder; + 1D6232E62730F0DF00A9AD6B /* PairingViewController.swift */, + 1D6232F6273140CF00A9AD6B /* CopyableLabel.swift */, + 310D80D22731402F00F15F4F /* PairingViewCoordinator.swift */, + 664894172730F1B0009F03E2 /* PairingViewModel.swift */, + ); + path = PairingScene; sourceTree = ""; }; - 319C21D6273BD764009CE65C /* URLSessionNetworkServiceTest */ = { + 1DD0F35F2747AFC800FC5E65 /* DiaryScene */ = { isa = PBXGroup; children = ( - 319C21D7273BD764009CE65C /* URLSessionNetworkServiceTest.swift */, + 310D81472733F56700F15F4F /* DiaryViewController.swift */, + 1D8BE07A2742A3DB00201E3E /* DiaryCollectionViewCell.swift */, + 1D8BE07E2743846A00201E3E /* DiaryCollectionViewHeader.swift */, + 310D80D42731405B00F15F4F /* DiaryViewCoordinator.swift */, + 1D8BE07C2743437600201E3E /* DiaryViewModel.swift */, + 1D9DC0A2274B31940060587B /* DiaryBackgroundView.swift */, + 1D9DC09B2749F2AA0060587B /* FilterOptionScene */, ); - path = URLSessionNetworkServiceTest; + path = DiaryScene; sourceTree = ""; }; - 664893B4273007ED009F03E2 = { + 1DD0F3602747B10000FC5E65 /* Common */ = { isa = PBXGroup; children = ( - 3101258E2737987B00E43160 /* GoogleService-Info.plist */, - 664893BF273007ED009F03E2 /* Doolda */, - FC9F1063273102E30060FEB7 /* GetMyIdUseCaseTest */, - 6648945D27327F1D009F03E2 /* GeneratePairIdUseCaseTests */, - FC9F108027315EB80060FEB7 /* FirebaseNetworkTest */, - FC5FB7A227328A19008AC3D2 /* UserDefaultsPersistenceServiceTest */, - 669ADE332732E4A8007B529B /* RefreshPairIdUseCaseTest */, - FC5FB7CA27339213008AC3D2 /* UserRepositoryTests */, - 1D0C8BCA2732B5F90091ACC0 /* GetPairIdUseCaseTest */, - 310D81242733C7FC00F15F4F /* SplashViewModelTest */, - 319C21D6273BD764009CE65C /* URLSessionNetworkServiceTest */, - 310125CA2739A56600E43160 /* ImageComposeUseCaseTest */, - 1D3925BB273BB4BE003A0B70 /* EditPageUseCaseTest */, - 664893BE273007ED009F03E2 /* Products */, - 310D80E32732841500F15F4F /* Recovered References */, + 66CD644D273A490800E55C03 /* BottomSheetViewController.swift */, + 66ECCD3B273F7C9E003990EE /* CustomActivityIndicator.swift */, + 1DE5E5352738BBAA004F794F /* BaseCoordinator.swift */, + 1D9DC0A0274A47D50060587B /* DooldaButton.swift */, + 1DD58BD52754ACA600B93184 /* DiaryPageView.swift */, ); + path = Common; sourceTree = ""; }; - 664893BE273007ED009F03E2 /* Products */ = { + 1DD0F3612747B14500FC5E65 /* EditPageScene */ = { isa = PBXGroup; children = ( - 664893BD273007ED009F03E2 /* Doolda.app */, - 6648941D27310165009F03E2 /* PairingViewModelTests.xctest */, - FC9F1062273102E30060FEB7 /* GetMyIdUseCaseTest.xctest */, - 6648945C27327F1D009F03E2 /* GeneratePairIdUseCaseTests.xctest */, - FC9F107F27315EB80060FEB7 /* FirebaseNetworkTest.xctest */, - FC5FB7A127328A19008AC3D2 /* UserDefaultsPersistenceServiceTest.xctest */, - 669ADE322732E4A8007B529B /* RefreshPairIdUseCaseTest.xctest */, - 1D0C8BC92732B5F90091ACC0 /* GetPairIdUseCaseTest.xctest */, - FC5FB7C927339213008AC3D2 /* UserRepositoryTests.xctest */, - 310D81232733C7FC00F15F4F /* SplashViewModelTest.xctest */, - 319C21D5273BD764009CE65C /* URLSessionNetworkServiceTest.xctest */, - 310125C92739A56600E43160 /* ImageComposeUseCaseTest.xctest */, - 1D3925BA273BB4BE003A0B70 /* EditPageUseCaseTest.xctest */, + FCCF022E273A12B400EAB00B /* EditPageViewController.swift */, + FCCF0232273A719600EAB00B /* PageComponentControlView.swift */, + FCCF0230273A145400EAB00B /* EditPageViewCoordinator.swift */, + FCE9D3F327391F260001F155 /* EditPageViewModel.swift */, + 1DD0F3622747B31300FC5E65 /* PhotoPickerScene */, + 1DD0F3642747B36F00FC5E65 /* TextEditScene */, + 1DD0F3632747B33200FC5E65 /* StickerPickerScene */, + 1D9FAD482747B4A700198B93 /* BackgroundTypePickerScene */, ); - name = Products; + path = EditPageScene; sourceTree = ""; }; - 664893BF273007ED009F03E2 /* Doolda */ = { + 1DD0F3622747B31300FC5E65 /* PhotoPickerScene */ = { isa = PBXGroup; children = ( - 664893D4273008C2009F03E2 /* Application */, - 664893D52730090B009F03E2 /* Resources */, - 664893E927300B59009F03E2 /* Support */, - 664893D62730092E009F03E2 /* UI */, - 664893DA273009A9009F03E2 /* Presentation */, - 664893E227300A9E009F03E2 /* Domain */, - 664893DD27300A07009F03E2 /* Data */, - 664893E127300A62009F03E2 /* Service */, + 66CD644F273A5C4700E55C03 /* PhotoPickerBottomSheetViewController.swift */, + 66ECCD39273E5342003990EE /* CarouselView.swift */, + 66CD6455273BB88A00E55C03 /* PhotoFrameCollectionViewCell.swift */, + 66CD6459273BE5A000E55C03 /* PhotoPickerCollectionViewCell.swift */, + 66FE93CB27396B950036CED2 /* PhotoPickerBottomSheetViewModel.swift */, ); - path = Doolda; + path = PhotoPickerScene; sourceTree = ""; }; - 664893D4273008C2009F03E2 /* Application */ = { + 1DD0F3632747B33200FC5E65 /* StickerPickerScene */ = { isa = PBXGroup; children = ( - 664893C0273007ED009F03E2 /* AppDelegate.swift */, - 664893C2273007ED009F03E2 /* SceneDelegate.swift */, + 3161EE932742A44100B922DD /* StickerPickerBottomSheetViewController.swift */, + 3161EE9727436EC600B922DD /* StickerPickerView.swift */, + 3161EE99274377E300B922DD /* PackedStickerCollectionViewCell.swift */, + 3161EE9B274377F400B922DD /* UnpackedStickerCollectionViewCell.swift */, + 317FB60A274A3850005DF3A3 /* StickerPickerCollectionViewFooter.swift */, + FC195989274284E200AB6059 /* StickerPickerBottomSheetViewModel.swift */, ); - path = Application; + path = StickerPickerScene; sourceTree = ""; }; - 664893D52730090B009F03E2 /* Resources */ = { + 1DD0F3642747B36F00FC5E65 /* TextEditScene */ = { isa = PBXGroup; children = ( - 3161EEC42744C37600B922DD /* StickerPacks */, - 66CAFF38274136FE00A48DCF /* PhotoFrames */, - 1DD6AD3E27321E3700592AC8 /* Fonts */, - 664893C9273007F1009F03E2 /* Assets.xcassets */, - 664893CB273007F1009F03E2 /* LaunchScreen.storyboard */, - 664893CE273007F1009F03E2 /* Info.plist */, - 665303992743B3FF00E3075C /* CoreDataModel.xcdatamodeld */, + FCC803F927451A8000FFE4DC /* TextEditViewController.swift */, + FCC803FB27451C9600FFE4DC /* TextEditViewModel.swift */, + FC51FA66274BB9D60069D39C /* FontColorPickerView.swift */, + FC51FA6C274C89330069D39C /* FontSizeControlView.swift */, + FC51FA68274BBB3C0069D39C /* FontColorCollectionViewCell.swift */, ); - path = Resources; + path = TextEditScene; sourceTree = ""; }; - 664893D62730092E009F03E2 /* UI */ = { + 1DD6AD3E27321E3700592AC8 /* Fonts */ = { isa = PBXGroup; children = ( - 664893D727300936009F03E2 /* ViewControllers */, - 664893D827300972009F03E2 /* Views */, - 664893D927300980009F03E2 /* Coordinators */, + 318D9F68274D4D9900AA6F0E /* kyoboHandwriting.otf */, + 318D9F5F274CFC0800AA6F0E /* kotraHope.otf */, + 318D9F5E274CFBEA00AA6F0E /* dungGeunMo.otf */, + 318D9F5D274CFBA100AA6F0E /* uhBeeMysen.ttf */, + 318D9F66274D4D5000AA6F0E /* darae.ttf */, + 1DD6AD3F27321E5800592AC8 /* dovemayo.otf */, ); - path = UI; + path = Fonts; sourceTree = ""; }; - 664893D727300936009F03E2 /* ViewControllers */ = { + 318D9F6E274E129C00AA6F0E /* PageDetailScene */ = { isa = PBXGroup; children = ( - 66CD644D273A490800E55C03 /* BottomSheetViewController.swift */, - 664893F3273023D3009F03E2 /* SplashViewController.swift */, - 1D6232E62730F0DF00A9AD6B /* PairingViewController.swift */, - FCCF022E273A12B400EAB00B /* EditPageViewController.swift */, - 66CD644F273A5C4700E55C03 /* PhotoPickerBottomSheetViewController.swift */, - 66CAFF232740F63A00A48DCF /* BackgroundTypePickerViewController.swift */, - 3161EE932742A44100B922DD /* StickerPickerViewController.swift */, - FCC803F927451A8000FFE4DC /* TextInputViewController.swift */, - 310D81472733F56700F15F4F /* DiaryViewController.swift */, + 318D9F75274E258D00AA6F0E /* PageDetailViewController.swift */, + 318D9F6F274E12D900AA6F0E /* PageDetailViewModel.swift */, + FC23C227274E17C200EBAE4C /* PageDetailViewController.swift */, + 318D9F71274E17AB00AA6F0E /* PageDetailViewCoordinator.swift */, ); - path = ViewControllers; + path = PageDetailScene; sourceTree = ""; }; - 664893D827300972009F03E2 /* Views */ = { + 319C21D6273BD764009CE65C /* URLSessionNetworkServiceTest */ = { isa = PBXGroup; children = ( - 1D6232F6273140CF00A9AD6B /* CopyableLabel.swift */, - 1D8BE07A2742A3DB00201E3E /* DiaryPageView.swift */, - 66ECCD39273E5342003990EE /* CarouselView.swift */, - FCCF0232273A719600EAB00B /* PageControlView.swift */, - 66CD6455273BB88A00E55C03 /* PhotoFrameCollectionViewCell.swift */, - 66CD6459273BE5A000E55C03 /* PhotoPickerCollectionViewCell.swift */, - 66ECCD3B273F7C9E003990EE /* CustomActivityIndicator.swift */, - 3161EE9727436EC600B922DD /* StickerPickerView.swift */, - 3161EE99274377E300B922DD /* PackedStickerCell.swift */, - 3161EE9B274377F400B922DD /* UnpackedStickerCell.swift */, - 1D8BE07E2743846A00201E3E /* DiaryCollectionViewHeader.swift */, + 319C21D7273BD764009CE65C /* URLSessionNetworkServiceTest.swift */, ); - path = Views; + path = URLSessionNetworkServiceTest; sourceTree = ""; }; - 664893D927300980009F03E2 /* Coordinators */ = { + 31BEC81D2755AF57009AF22A /* Recovered References */ = { isa = PBXGroup; children = ( - 664893F72730272B009F03E2 /* AppCoordinator.swift */, - 664893F927302905009F03E2 /* SplashViewCoordinator.swift */, - 310D80D22731402F00F15F4F /* ParingViewCoordinator.swift */, - 310D80D42731405B00F15F4F /* DiaryViewCoordinator.swift */, - FCCF0230273A145400EAB00B /* EditPageViewCoordinator.swift */, + 1D8AFD0827506BE100822B89 /* GoogleService-Info.plist */, + 1D8AFD0727506BE100822B89 /* Config.xcconfig */, + FC4459B72755EBC5004888B9 /* DummyUserRepository.swift */, ); - path = Coordinators; + name = "Recovered References"; sourceTree = ""; }; - 664893DA273009A9009F03E2 /* Presentation */ = { + 31BEC8322755EA89009AF22A /* ImageUseCaseTest */ = { isa = PBXGroup; children = ( - 664893DC273009EC009F03E2 /* ViewModels */, - 664893DB273009CD009F03E2 /* Interfaces */, + 31BEC8332755EA89009AF22A /* ImageUseCaseTest.swift */, ); - path = Presentation; + name = ImageUseCaseTest; + path = ../ImageUseCaseTest; sourceTree = ""; }; - 664893DB273009CD009F03E2 /* Interfaces */ = { + 664893B4273007ED009F03E2 = { isa = PBXGroup; children = ( - 1DE5E5352738BBAA004F794F /* CoordinatorProtocol.swift */, - 664893FD273029AC009F03E2 /* SplashViewCoordinatorProtocol.swift */, - 1D58277C2732883E00FD0EA5 /* PairingViewCoordinatorProtocol.swift */, - 1DE5E5372738BCE0004F794F /* DiaryViewCoordinatorProtocol.swift */, - FCE9D3F52739550C0001F155 /* EditPageViewCoordinatorProtocol.swift */, + 664893BF273007ED009F03E2 /* Doolda */, + 1D9FAD4A2747B82B00198B93 /* Tests */, + 664893BE273007ED009F03E2 /* Products */, + 1D8646A4274D60AA00C4511D /* Frameworks */, + 31BEC81D2755AF57009AF22A /* Recovered References */, ); - path = Interfaces; sourceTree = ""; }; - 664893DC273009EC009F03E2 /* ViewModels */ = { + 664893BE273007ED009F03E2 /* Products */ = { isa = PBXGroup; children = ( - 664893FB27302947009F03E2 /* SplashViewModel.swift */, - 664894172730F1B0009F03E2 /* PairingViewModel.swift */, - FCE9D3F327391F260001F155 /* EditPageViewModel.swift */, - 66FE93CB27396B950036CED2 /* PhotoPickerBottomSheetViewModel.swift */, - 1D8BE07C2743437600201E3E /* DiaryViewModel.swift */, - FC195989274284E200AB6059 /* StickerPickerViewModel.swift */, - FCC803F72744FEF800FFE4DC /* SettingViewModel.swift */, - FCC803FB27451C9600FFE4DC /* TextInputViewModel.swift */, + 664893BD273007ED009F03E2 /* Doolda.app */, + 319C21D5273BD764009CE65C /* URLSessionNetworkServiceTest.xctest */, + 1D3925BA273BB4BE003A0B70 /* EditPageUseCaseTest.xctest */, + 1D992E302755E8EC002C4457 /* GetUserUseCaseTest.xctest */, + FC4459AB2755E8C4004888B9 /* GetMyIdUseCaseTest.xctest */, + 66C2CA852755E9F200AFE5EE /* PairUserUseCaseTest.xctest */, + 31BEC8312755EA89009AF22A /* ImageUseCaseTest.xctest */, + FC4459EC27561A48004888B9 /* PushNotificationStateUseCaseTest.xctest */, + 1DAEB02427560E8F0040E8C1 /* FCMTokenUseCaseTest.xctest */, + 66C2CAEC275640CA00AFE5EE /* GetRawPageUseCaseTest.xctest */, + 66C2CAAB27561E1100AFE5EE /* RefreshUserUseCaseTest.xctest */, + 66C2CAD627562C1500AFE5EE /* GetPageUseCaseTest.xctest */, + 66C2CAC0275623F700AFE5EE /* CheckMyTurnUseCaseTest.xctest */, + FC4459FE275622CB004888B9 /* TextUseCaseTest.xctest */, + FC4459D827560731004888B9 /* GlobalFontUseCaseTest.xctest */, + 1DAEB00F275603C80040E8C1 /* RegisterUserUseCaseTest.xctest */, + 1DAEB03A275617010040E8C1 /* FirebaseMessageUseCaseTest.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 664893BF273007ED009F03E2 /* Doolda */ = { + isa = PBXGroup; + children = ( + 1D99945E274C8BF600C72273 /* Doolda.entitlements */, + 664893D4273008C2009F03E2 /* Application */, + 664893D52730090B009F03E2 /* Resources */, + 664893E927300B59009F03E2 /* Support */, + 1DD0F3602747B10000FC5E65 /* Common */, + 1DD0F35A2747AE8600FC5E65 /* SplashScene */, + 1DD0F35E2747AF5A00FC5E65 /* PairingScene */, + 1DD0F35F2747AFC800FC5E65 /* DiaryScene */, + 318D9F6E274E129C00AA6F0E /* PageDetailScene */, + 1DD0F3612747B14500FC5E65 /* EditPageScene */, + 1D9FAD492747B6D400198B93 /* SettingsScene */, + 664893E227300A9E009F03E2 /* Domain */, + 664893DD27300A07009F03E2 /* Data */, + 664893E127300A62009F03E2 /* Service */, + ); + path = Doolda; + sourceTree = ""; + }; + 664893D4273008C2009F03E2 /* Application */ = { + isa = PBXGroup; + children = ( + 664893C0273007ED009F03E2 /* AppDelegate.swift */, + 664893C2273007ED009F03E2 /* SceneDelegate.swift */, + 664893F72730272B009F03E2 /* AppCoordinator.swift */, + ); + path = Application; + sourceTree = ""; + }; + 664893D52730090B009F03E2 /* Resources */ = { + isa = PBXGroup; + children = ( + 66CAFF38274136FE00A48DCF /* PhotoFrames */, + 1DD6AD3E27321E3700592AC8 /* Fonts */, + 664893C9273007F1009F03E2 /* Assets.xcassets */, + 664893CB273007F1009F03E2 /* LaunchScreen.storyboard */, + 664893CE273007F1009F03E2 /* Info.plist */, + 31BEC8202755B160009AF22A /* GoogleService-Info.plist */, + 665303992743B3FF00E3075C /* CoreDataModel.xcdatamodeld */, + 31BEC81E2755B154009AF22A /* Config.xcconfig */, + 31BEC80A2754DD74009AF22A /* license.txt */, + 1D0813602758C6C300DCF432 /* InfoPlist.strings */, ); - path = ViewModels; + path = Resources; sourceTree = ""; }; 664893DD27300A07009F03E2 /* Data */ = { @@ -838,6 +1124,8 @@ 1D992B41273D0EAB00B63954 /* PageRepository.swift */, FCD24D792743AACE000203F7 /* PushNotificationStateRepository.swift */, FCD24D7227439268000203F7 /* GlobalFontRepository.swift */, + 1D999465274CAB8900C72273 /* FCMTokenRepository.swift */, + 1D99946B274CEF2900C72273 /* FirebaseMessageRepository.swift */, ); path = Repositories; sourceTree = ""; @@ -849,6 +1137,7 @@ FC5FB7942732627C008AC3D2 /* PairDocument.swift */, 66FE936C2735655C0036CED2 /* DDIDDataTransferObject.swift */, 1D8BE0782740FE5E00201E3E /* PageDocument.swift */, + 1D999467274CAC4700C72273 /* FCMTokenDocument.swift */, ); path = DataTransferObjects; sourceTree = ""; @@ -890,6 +1179,7 @@ children = ( FC9F10572730F0060060FEB7 /* GetMyIdUseCase.swift */, 66FE9370273671910036CED2 /* PairUserUseCase.swift */, + 66F1760427548260006ACB41 /* UnpairUserUseCase.swift */, 1D0C8BC32732AF780091ACC0 /* GetUserUseCase.swift */, 66FE9372273688E10036CED2 /* RefreshUserUseCase.swift */, 1D3629FD2733C79300CFC069 /* RegisterUserUseCase.swift */, @@ -903,6 +1193,8 @@ FCD24D7D2743ADC1000203F7 /* PushNotificationStateUseCase.swift */, FCD24D7727439717000203F7 /* GlobalFontUseCase.swift */, 66C482DA2746135500823151 /* GetRawPageUseCase.swift */, + 1D999461274CA94600C72273 /* FCMTokenUseCase.swift */, + 1D99946D274CEFC500C72273 /* FirebaseMessageUseCase.swift */, ); path = UseCases; sourceTree = ""; @@ -919,9 +1211,13 @@ 1DBF5716273A328D00D28F5E /* PhotoFrameType.swift */, 3101259327383DED00E43160 /* PhotoComponentEntity.swift */, 31012595273842B300E43160 /* StickerComponentEntity.swift */, + FCC803F52744C22F00FFE4DC /* FontType.swift */, 1DBF5714273A2C5100D28F5E /* FontColorType.swift */, 31012597273842DA00E43160 /* TextComponentEntity.swift */, 3101259B2738430200E43160 /* StickerPackEntity.swift */, + 317FB616274BAAEB005DF3A3 /* DooldaInfoType.swift */, + 1D8646A2274D2DB600C4511D /* PushMessageEntity.swift */, + FC445A0C27562590004888B9 /* ComponentType.swift */, ); path = Entities; sourceTree = ""; @@ -929,13 +1225,8 @@ 664893E527300AB9009F03E2 /* Interfaces */ = { isa = PBXGroup; children = ( - FC9F105A2730F1B20060FEB7 /* UserRepositoryProtocol.swift */, - FCE9D3DB2738C9A00001F155 /* PairRepositoryProtocol.swift */, - 1DE5E53F273932B0004F794F /* PageRepositoryProtocol.swift */, - 1DE5E5412739352B004F794F /* RawPageRepositoryProtocol.swift */, - 319C21CD273BA6D8009CE65C /* ImageRepositoryProtocol.swift */, - FCD24D7B2743AAE2000203F7 /* PushNotificationStateRepositoryProtocol.swift */, - FCD24D74274392AB000203F7 /* GlobalFontRepositoryProtocol.swift */, + 66F176072754C1D1006ACB41 /* UseCases */, + 66F176062754C0E3006ACB41 /* Repositories */, ); path = Interfaces; sourceTree = ""; @@ -975,6 +1266,7 @@ 1D3925CF273C1263003A0B70 /* CGFloat+Extensions.swift */, 319C222C273CEFBB009CE65C /* CIImage+Extension.swift */, 66CD6463273D125E00E55C03 /* CGImage+Extensions.swift */, + 1D9DC0A4274B8A890060587B /* Collection+Extensions.swift */, ); path = Extensions; sourceTree = ""; @@ -993,20 +1285,11 @@ children = ( FC5FB7DF2733A6D5008AC3D2 /* FirebaseCollection.swift */, FC822FAF27367AFD00C918A0 /* API.swift */, - 319C21C9273B9703009CE65C /* FileDocuments.swift */, - FCC803F52744C22F00FFE4DC /* DoolDaFont.swift */, + 1DDADCB3274BCE63000330C9 /* Secrets.swift */, ); path = Constants; sourceTree = ""; }; - 6648945D27327F1D009F03E2 /* GeneratePairIdUseCaseTests */ = { - isa = PBXGroup; - children = ( - 6648945E27327F1D009F03E2 /* GeneratePairIdUseCaseTests.swift */, - ); - path = GeneratePairIdUseCaseTests; - sourceTree = ""; - }; 665303A22743B81800E3075C /* CoreData */ = { isa = PBXGroup; children = ( @@ -1018,126 +1301,234 @@ path = CoreData; sourceTree = ""; }; - 669ADE332732E4A8007B529B /* RefreshPairIdUseCaseTest */ = { + 66C2CA862755E9F200AFE5EE /* PairUserUseCaseTest */ = { + isa = PBXGroup; + children = ( + 66C2CA872755E9F200AFE5EE /* PairUserUseCaseTest.swift */, + ); + path = PairUserUseCaseTest; + sourceTree = ""; + }; + 66C2CAAC27561E1200AFE5EE /* RefreshUserUseCaseTest */ = { isa = PBXGroup; children = ( - 669ADE342732E4A8007B529B /* RefreshPairIdUseCaseTest.swift */, + 66C2CAAD27561E1200AFE5EE /* RefreshUserUseCaseTest.swift */, ); - path = RefreshPairIdUseCaseTest; + path = RefreshUserUseCaseTest; + sourceTree = ""; + }; + 66C2CAC1275623F700AFE5EE /* CheckMyTurnUseCaseTest */ = { + isa = PBXGroup; + children = ( + 66C2CAC2275623F700AFE5EE /* CheckMyTurnUseCaseTest.swift */, + ); + path = CheckMyTurnUseCaseTest; + sourceTree = ""; + }; + 66C2CAD727562C1600AFE5EE /* GetPageUseCaseTest */ = { + isa = PBXGroup; + children = ( + 66C2CAD827562C1600AFE5EE /* GetPageUseCaseTest.swift */, + ); + path = GetPageUseCaseTest; + sourceTree = ""; + }; + 66C2CAED275640CA00AFE5EE /* GetRawPageUseCaseTest */ = { + isa = PBXGroup; + children = ( + 66C2CAEE275640CA00AFE5EE /* GetRawPageUseCaseTest.swift */, + ); + path = GetRawPageUseCaseTest; sourceTree = ""; }; 66CAFF38274136FE00A48DCF /* PhotoFrames */ = { isa = PBXGroup; children = ( - 66B4ADFE2742BEFC0021EA27 /* LifeFourCuts.jpg */, - 66B4ADFC2742BEFC0021EA27 /* Normal.jpg */, - 66B4ADFD2742BEFC0021EA27 /* Polaroid.jpg */, + 66B4ADFE2742BEFC0021EA27 /* lifeFourCuts.jpg */, + 66B4ADFC2742BEFC0021EA27 /* normal.jpg */, + 66B4ADFD2742BEFC0021EA27 /* polaroid.jpg */, + 66CCB6F7275924AB00E3FD64 /* polaroid_long.jpg */, ); path = PhotoFrames; sourceTree = ""; }; - FC5FB7A227328A19008AC3D2 /* UserDefaultsPersistenceServiceTest */ = { + 66F176062754C0E3006ACB41 /* Repositories */ = { isa = PBXGroup; children = ( - FC5FB7A327328A19008AC3D2 /* UserDefaultsPersistenceServiceTest.swift */, + FC9F105A2730F1B20060FEB7 /* UserRepositoryProtocol.swift */, + FCE9D3DB2738C9A00001F155 /* PairRepositoryProtocol.swift */, + 1DE5E53F273932B0004F794F /* PageRepositoryProtocol.swift */, + 1DE5E5412739352B004F794F /* RawPageRepositoryProtocol.swift */, + 319C21CD273BA6D8009CE65C /* ImageRepositoryProtocol.swift */, + FCD24D7B2743AAE2000203F7 /* PushNotificationStateRepositoryProtocol.swift */, + FCD24D74274392AB000203F7 /* GlobalFontRepositoryProtocol.swift */, + 1D999463274CAAAC00C72273 /* FCMTokenRepositoryProtocol.swift */, + 1D999469274CE5F700C72273 /* FirebaseMessageRepositoryProtocol.swift */, ); - path = UserDefaultsPersistenceServiceTest; + path = Repositories; sourceTree = ""; }; - FC5FB7CA27339213008AC3D2 /* UserRepositoryTests */ = { + 66F176072754C1D1006ACB41 /* UseCases */ = { isa = PBXGroup; children = ( - FC5FB7CB27339213008AC3D2 /* UserRepositoryTests.swift */, + 66F176082754C21B006ACB41 /* GetMyIdUseCaseProtocol.swift */, + 66F1760A2754C27C006ACB41 /* PairUserUseCaseProtocol.swift */, + 66F1760C2754C2CE006ACB41 /* GetUserUseCaseProtocol.swift */, + 66F1760E2754C334006ACB41 /* RefreshUserUseCaseProtocol.swift */, + 66F176102754C370006ACB41 /* RegisterUserUseCaseProtocol.swift */, + 66F176122754C3C3006ACB41 /* EditPageUseCaseProtocol.swift */, + 66F176142754C413006ACB41 /* ImageComposeUseCaseProtocol.swift */, + 66F176162754C4F6006ACB41 /* ImageUseCaseProtocol.swift */, + 66F176182754C547006ACB41 /* CheckMyTurnUseCaseProtocol.swift */, + 66F1761A2754C589006ACB41 /* GetPageUseCaseProtocol.swift */, + 66F1761C2754C5FF006ACB41 /* StickerUseCaseProtocol.swift */, + 66F1761E2754C656006ACB41 /* TextUseCaseProtocol.swift */, + 66F176202754C6A3006ACB41 /* PushNotificationStateUseCaseProtocol.swift */, + 66F176222754C6EE006ACB41 /* GlobalFontUseCaseProtocol.swift */, + 66F176242754C74C006ACB41 /* GetRawPageUseCaseProtocol.swift */, + 66F176262754C77C006ACB41 /* FCMTokenUseCaseProtocol.swift */, + 66F176282754C7DA006ACB41 /* FirebaseMessageUseCaseProtocol.swift */, ); - path = UserRepositoryTests; + path = UseCases; + sourceTree = ""; + }; + FC44599A2755DF04004888B9 /* Dummies */ = { + isa = PBXGroup; + children = ( + FC44599E2755E0E1004888B9 /* DummyError.swift */, + FC44599B2755DF86004888B9 /* DummyPageRepository.swift */, + FC4459A12755E41C004888B9 /* DummyImageUseCase.swift */, + FC4459A32755E434004888B9 /* DummyRawPageRepository.swift */, + FC4459A52755E470004888B9 /* DummyPairRepository.swift */, + 1D1C8EC52755ECF10044AF95 /* DummyUserRepository.swift */, + 31BEC8392755EBB7009AF22A /* DummyImageRepository.swift */, + 1DAEB02E27560F5F0040E8C1 /* DummyFCMTokenRepository.swift */, + FC4459E627560909004888B9 /* DummyGlobalFontRepository.swift */, + FC4459F827561BB3004888B9 /* DummyPushNotificationStateRepository.swift */, + 1DAEB044275617A40040E8C1 /* DummyFirebaseMessageRepository.swift */, + ); + path = Dummies; sourceTree = ""; }; - FC9F1063273102E30060FEB7 /* GetMyIdUseCaseTest */ = { + FC4459AC2755E8C4004888B9 /* GetMyIdUseCaseTest */ = { isa = PBXGroup; children = ( - FC9F1064273102E30060FEB7 /* GetMyIdUseCaseTests.swift */, + FC4459AD2755E8C4004888B9 /* GetMyIdUseCaseTest.swift */, ); path = GetMyIdUseCaseTest; sourceTree = ""; }; - FC9F108027315EB80060FEB7 /* FirebaseNetworkTest */ = { + FC4459D927560731004888B9 /* GlobalFontUseCaseTest */ = { + isa = PBXGroup; + children = ( + FC4459DA27560731004888B9 /* GlobalFontUseCaseTest.swift */, + ); + path = GlobalFontUseCaseTest; + sourceTree = ""; + }; + FC4459ED27561A48004888B9 /* PushNotificationStateUseCaseTest */ = { + isa = PBXGroup; + children = ( + FC4459EE27561A48004888B9 /* PushNotificationStateUseCaseTest.swift */, + ); + path = PushNotificationStateUseCaseTest; + sourceTree = ""; + }; + FC4459FF275622CB004888B9 /* TextUseCaseTest */ = { isa = PBXGroup; children = ( - FC9F108127315EB80060FEB7 /* FirebaseNetworkTest.swift */, + FC445A00275622CB004888B9 /* TextUseCaseTest.swift */, + ); + path = TextUseCaseTest; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 1D3925B9273BB4BE003A0B70 /* EditPageUseCaseTest */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1D3925BE273BB4BE003A0B70 /* Build configuration list for PBXNativeTarget "EditPageUseCaseTest" */; + buildPhases = ( + 1D3925B6273BB4BE003A0B70 /* Sources */, + 1D3925B7273BB4BE003A0B70 /* Frameworks */, + 1D3925B8273BB4BE003A0B70 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( ); - path = FirebaseNetworkTest; - sourceTree = ""; + name = EditPageUseCaseTest; + productName = EditPageUseCaseTest; + productReference = 1D3925BA273BB4BE003A0B70 /* EditPageUseCaseTest.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; }; -/* End PBXGroup section */ - -/* Begin PBXNativeTarget section */ - 1D0C8BC82732B5F90091ACC0 /* GetPairIdUseCaseTest */ = { + 1D992E2F2755E8EC002C4457 /* GetUserUseCaseTest */ = { isa = PBXNativeTarget; - buildConfigurationList = 1D0C8BCD2732B5F90091ACC0 /* Build configuration list for PBXNativeTarget "GetPairIdUseCaseTest" */; + buildConfigurationList = 1D992E382755E8EC002C4457 /* Build configuration list for PBXNativeTarget "GetUserUseCaseTest" */; buildPhases = ( - 1D0C8BC52732B5F90091ACC0 /* Sources */, - 1D0C8BC62732B5F90091ACC0 /* Frameworks */, - 1D0C8BC72732B5F90091ACC0 /* Resources */, + 1D992E2C2755E8EC002C4457 /* Sources */, + 1D992E2D2755E8EC002C4457 /* Frameworks */, + 1D992E2E2755E8EC002C4457 /* Resources */, ); buildRules = ( ); dependencies = ( ); - name = GetPairIdUseCaseTest; - productName = GetPairIdUseCaseTest; - productReference = 1D0C8BC92732B5F90091ACC0 /* GetPairIdUseCaseTest.xctest */; + name = GetUserUseCaseTest; + productName = GetUserUseCaseTest; + productReference = 1D992E302755E8EC002C4457 /* GetUserUseCaseTest.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - 1D3925B9273BB4BE003A0B70 /* EditPageUseCaseTest */ = { + 1DAEB00E275603C80040E8C1 /* RegisterUserUseCaseTest */ = { isa = PBXNativeTarget; - buildConfigurationList = 1D3925BE273BB4BE003A0B70 /* Build configuration list for PBXNativeTarget "EditPageUseCaseTest" */; + buildConfigurationList = 1DAEB015275603C90040E8C1 /* Build configuration list for PBXNativeTarget "RegisterUserUseCaseTest" */; buildPhases = ( - 1D3925B6273BB4BE003A0B70 /* Sources */, - 1D3925B7273BB4BE003A0B70 /* Frameworks */, - 1D3925B8273BB4BE003A0B70 /* Resources */, + 1DAEB00B275603C80040E8C1 /* Sources */, + 1DAEB00C275603C80040E8C1 /* Frameworks */, + 1DAEB00D275603C80040E8C1 /* Resources */, ); buildRules = ( ); dependencies = ( + 1DAEB014275603C80040E8C1 /* PBXTargetDependency */, ); - name = EditPageUseCaseTest; - productName = EditPageUseCaseTest; - productReference = 1D3925BA273BB4BE003A0B70 /* EditPageUseCaseTest.xctest */; + name = RegisterUserUseCaseTest; + productName = RegisterUserUseCaseTest; + productReference = 1DAEB00F275603C80040E8C1 /* RegisterUserUseCaseTest.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - 310125C82739A56600E43160 /* ImageComposeUseCaseTest */ = { + 1DAEB02327560E8F0040E8C1 /* FCMTokenUseCaseTest */ = { isa = PBXNativeTarget; - buildConfigurationList = 310125D12739A56600E43160 /* Build configuration list for PBXNativeTarget "ImageComposeUseCaseTest" */; + buildConfigurationList = 1DAEB02C27560E8F0040E8C1 /* Build configuration list for PBXNativeTarget "FCMTokenUseCaseTest" */; buildPhases = ( - 310125C52739A56600E43160 /* Sources */, - 310125C62739A56600E43160 /* Frameworks */, - 310125C72739A56600E43160 /* Resources */, + 1DAEB02027560E8F0040E8C1 /* Sources */, + 1DAEB02127560E8F0040E8C1 /* Frameworks */, + 1DAEB02227560E8F0040E8C1 /* Resources */, ); buildRules = ( ); dependencies = ( - 310125CE2739A56600E43160 /* PBXTargetDependency */, ); - name = ImageComposeUseCaseTest; - productName = ImageComposeUseCaseTest; - productReference = 310125C92739A56600E43160 /* ImageComposeUseCaseTest.xctest */; + name = FCMTokenUseCaseTest; + productName = FCMTokenUseCaseTest; + productReference = 1DAEB02427560E8F0040E8C1 /* FCMTokenUseCaseTest.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - 310D81222733C7FC00F15F4F /* SplashViewModelTest */ = { + 1DAEB039275617010040E8C1 /* FirebaseMessageUseCaseTest */ = { isa = PBXNativeTarget; - buildConfigurationList = 310D812B2733C7FC00F15F4F /* Build configuration list for PBXNativeTarget "SplashViewModelTest" */; + buildConfigurationList = 1DAEB042275617010040E8C1 /* Build configuration list for PBXNativeTarget "FirebaseMessageUseCaseTest" */; buildPhases = ( - 310D811F2733C7FC00F15F4F /* Sources */, - 310D81202733C7FC00F15F4F /* Frameworks */, - 310D81212733C7FC00F15F4F /* Resources */, + 1DAEB036275617010040E8C1 /* Sources */, + 1DAEB037275617010040E8C1 /* Frameworks */, + 1DAEB038275617010040E8C1 /* Resources */, ); buildRules = ( ); dependencies = ( ); - name = SplashViewModelTest; - productName = SplashViewModelTest; - productReference = 310D81232733C7FC00F15F4F /* SplashViewModelTest.xctest */; + name = FirebaseMessageUseCaseTest; + productName = FirebaseMessageUseCaseTest; + productReference = 1DAEB03A275617010040E8C1 /* FirebaseMessageUseCaseTest.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 319C21D4273BD764009CE65C /* URLSessionNetworkServiceTest */ = { @@ -1151,13 +1542,29 @@ buildRules = ( ); dependencies = ( - 319C21DA273BD764009CE65C /* PBXTargetDependency */, ); name = URLSessionNetworkServiceTest; productName = URLSessionNetworkServiceTest; productReference = 319C21D5273BD764009CE65C /* URLSessionNetworkServiceTest.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + 31BEC8302755EA89009AF22A /* ImageUseCaseTest */ = { + isa = PBXNativeTarget; + buildConfigurationList = 31BEC8372755EA89009AF22A /* Build configuration list for PBXNativeTarget "ImageUseCaseTest" */; + buildPhases = ( + 31BEC82D2755EA89009AF22A /* Sources */, + 31BEC82E2755EA89009AF22A /* Frameworks */, + 31BEC82F2755EA89009AF22A /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ImageUseCaseTest; + productName = ImageUseCaseTest; + productReference = 31BEC8312755EA89009AF22A /* ImageUseCaseTest.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 664893BC273007ED009F03E2 /* Doolda */ = { isa = PBXNativeTarget; buildConfigurationList = 664893D1273007F1009F03E2 /* Build configuration list for PBXNativeTarget "Doolda" */; @@ -1166,6 +1573,7 @@ 664893BA273007ED009F03E2 /* Frameworks */, 664893BB273007ED009F03E2 /* Resources */, 664893F227300EDF009F03E2 /* ShellScript */, + 1D0C7E9C274F7F3D00812C82 /* Embed App Extensions */, ); buildRules = ( ); @@ -1173,130 +1581,168 @@ ); name = Doolda; packageProductDependencies = ( - 664893EC27300D7D009F03E2 /* SnapKit */, - 664893EF27300DB9009F03E2 /* Kingfisher */, + 1D3BAC07274E2F3B00CD23EE /* FirebaseMessaging */, + 1D3BAC0A274E2F6500CD23EE /* SnapKit */, + 1D3BAC0E274E307A00CD23EE /* Kingfisher */, ); productName = Doolda; productReference = 664893BD273007ED009F03E2 /* Doolda.app */; productType = "com.apple.product-type.application"; }; - 6648941C27310165009F03E2 /* PairingViewModelTests */ = { + 66C2CA842755E9F200AFE5EE /* PairUserUseCaseTest */ = { isa = PBXNativeTarget; - buildConfigurationList = 310D813E2733E56D00F15F4F /* Build configuration list for PBXNativeTarget "PairingViewModelTests" */; + buildConfigurationList = 66C2CA8D2755E9F200AFE5EE /* Build configuration list for PBXNativeTarget "PairUserUseCaseTest" */; buildPhases = ( + 66C2CA812755E9F200AFE5EE /* Sources */, + 66C2CA822755E9F200AFE5EE /* Frameworks */, + 66C2CA832755E9F200AFE5EE /* Resources */, ); buildRules = ( ); dependencies = ( ); - name = PairingViewModelTests; - productName = PairingViewModelTests; - productReference = 6648941D27310165009F03E2 /* PairingViewModelTests.xctest */; + name = PairUserUseCaseTest; + productName = PairUserUseCaseTest; + productReference = 66C2CA852755E9F200AFE5EE /* PairUserUseCaseTest.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - 6648945B27327F1D009F03E2 /* GeneratePairIdUseCaseTests */ = { + 66C2CAAA27561E1100AFE5EE /* RefreshUserUseCaseTest */ = { isa = PBXNativeTarget; - buildConfigurationList = 310D813F2733E56D00F15F4F /* Build configuration list for PBXNativeTarget "GeneratePairIdUseCaseTests" */; + buildConfigurationList = 66C2CAB327561E1200AFE5EE /* Build configuration list for PBXNativeTarget "RefreshUserUseCaseTest" */; buildPhases = ( + 66C2CAA727561E1100AFE5EE /* Sources */, + 66C2CAA827561E1100AFE5EE /* Frameworks */, + 66C2CAA927561E1100AFE5EE /* Resources */, ); buildRules = ( ); dependencies = ( - 6648946127327F1D009F03E2 /* PBXTargetDependency */, ); - name = GeneratePairIdUseCaseTests; - productName = GeneratePairIdUseCaseTests; - productReference = 6648945C27327F1D009F03E2 /* GeneratePairIdUseCaseTests.xctest */; + name = RefreshUserUseCaseTest; + productName = RefreshUserUseCaseTest; + productReference = 66C2CAAB27561E1100AFE5EE /* RefreshUserUseCaseTest.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - 669ADE312732E4A8007B529B /* RefreshPairIdUseCaseTest */ = { + 66C2CABF275623F700AFE5EE /* CheckMyTurnUseCaseTest */ = { isa = PBXNativeTarget; - buildConfigurationList = 669ADE3A2732E4A8007B529B /* Build configuration list for PBXNativeTarget "RefreshPairIdUseCaseTest" */; + buildConfigurationList = 66C2CAC8275623F700AFE5EE /* Build configuration list for PBXNativeTarget "CheckMyTurnUseCaseTest" */; buildPhases = ( - 669ADE2E2732E4A8007B529B /* Sources */, - 669ADE2F2732E4A8007B529B /* Frameworks */, - 669ADE302732E4A8007B529B /* Resources */, + 66C2CABC275623F700AFE5EE /* Sources */, + 66C2CABD275623F700AFE5EE /* Frameworks */, + 66C2CABE275623F700AFE5EE /* Resources */, ); buildRules = ( ); dependencies = ( - 669ADE372732E4A8007B529B /* PBXTargetDependency */, ); - name = RefreshPairIdUseCaseTest; - productName = RefreshPairIdUseCase; - productReference = 669ADE322732E4A8007B529B /* RefreshPairIdUseCaseTest.xctest */; + name = CheckMyTurnUseCaseTest; + productName = CheckMyTurnUseCaseTest; + productReference = 66C2CAC0275623F700AFE5EE /* CheckMyTurnUseCaseTest.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - FC5FB7A027328A19008AC3D2 /* UserDefaultsPersistenceServiceTest */ = { + 66C2CAD527562C1500AFE5EE /* GetPageUseCaseTest */ = { isa = PBXNativeTarget; - buildConfigurationList = FC5FB7A727328A19008AC3D2 /* Build configuration list for PBXNativeTarget "UserDefaultsPersistenceServiceTest" */; + buildConfigurationList = 66C2CADE27562C1600AFE5EE /* Build configuration list for PBXNativeTarget "GetPageUseCaseTest" */; buildPhases = ( - FC5FB79D27328A19008AC3D2 /* Sources */, - FC5FB79E27328A19008AC3D2 /* Frameworks */, - FC5FB79F27328A19008AC3D2 /* Resources */, + 66C2CAD227562C1500AFE5EE /* Sources */, + 66C2CAD327562C1500AFE5EE /* Frameworks */, + 66C2CAD427562C1500AFE5EE /* Resources */, ); buildRules = ( ); dependencies = ( - FC5FB7A627328A19008AC3D2 /* PBXTargetDependency */, ); - name = UserDefaultsPersistenceServiceTest; - productName = UserDefaultsPersistenceServiceTest; - productReference = FC5FB7A127328A19008AC3D2 /* UserDefaultsPersistenceServiceTest.xctest */; + name = GetPageUseCaseTest; + productName = GetPageUseCaseTest; + productReference = 66C2CAD627562C1500AFE5EE /* GetPageUseCaseTest.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - FC5FB7C827339213008AC3D2 /* UserRepositoryTests */ = { + 66C2CAEB275640CA00AFE5EE /* GetRawPageUseCaseTest */ = { isa = PBXNativeTarget; - buildConfigurationList = FC5FB7CF27339213008AC3D2 /* Build configuration list for PBXNativeTarget "UserRepositoryTests" */; + buildConfigurationList = 66C2CAF4275640CA00AFE5EE /* Build configuration list for PBXNativeTarget "GetRawPageUseCaseTest" */; buildPhases = ( - FC5FB7C527339213008AC3D2 /* Sources */, - FC5FB7C627339213008AC3D2 /* Frameworks */, - FC5FB7C727339213008AC3D2 /* Resources */, + 66C2CAE8275640CA00AFE5EE /* Sources */, + 66C2CAE9275640CA00AFE5EE /* Frameworks */, + 66C2CAEA275640CA00AFE5EE /* Resources */, ); buildRules = ( ); dependencies = ( - FC5FB7CE27339213008AC3D2 /* PBXTargetDependency */, ); - name = UserRepositoryTests; - productName = UserRepositoryTests; - productReference = FC5FB7C927339213008AC3D2 /* UserRepositoryTests.xctest */; + name = GetRawPageUseCaseTest; + productName = GetRawPageUseCaseTest; + productReference = 66C2CAEC275640CA00AFE5EE /* GetRawPageUseCaseTest.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - FC9F1061273102E30060FEB7 /* GetMyIdUseCaseTest */ = { + FC4459AA2755E8C4004888B9 /* GetMyIdUseCaseTest */ = { isa = PBXNativeTarget; - buildConfigurationList = FC9F1068273102E30060FEB7 /* Build configuration list for PBXNativeTarget "GetMyIdUseCaseTest" */; + buildConfigurationList = FC4459B12755E8C4004888B9 /* Build configuration list for PBXNativeTarget "GetMyIdUseCaseTest" */; buildPhases = ( - FC9F105E273102E30060FEB7 /* Sources */, - FC9F105F273102E30060FEB7 /* Frameworks */, - FC9F1060273102E30060FEB7 /* Resources */, + FC4459A72755E8C4004888B9 /* Sources */, + FC4459A82755E8C4004888B9 /* Frameworks */, + FC4459A92755E8C4004888B9 /* Resources */, ); buildRules = ( ); dependencies = ( - FC9F1067273102E30060FEB7 /* PBXTargetDependency */, ); name = GetMyIdUseCaseTest; - productName = DooldaTests; - productReference = FC9F1062273102E30060FEB7 /* GetMyIdUseCaseTest.xctest */; + productName = GetMyIdUseCaseTest; + productReference = FC4459AB2755E8C4004888B9 /* GetMyIdUseCaseTest.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + FC4459D727560731004888B9 /* GlobalFontUseCaseTest */ = { + isa = PBXNativeTarget; + buildConfigurationList = FC4459E027560731004888B9 /* Build configuration list for PBXNativeTarget "GlobalFontUseCaseTest" */; + buildPhases = ( + FC4459D427560731004888B9 /* Sources */, + FC4459D527560731004888B9 /* Frameworks */, + FC4459D627560731004888B9 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + FC4459DD27560731004888B9 /* PBXTargetDependency */, + ); + name = GlobalFontUseCaseTest; + productName = GlobalFontUseCaseTest; + productReference = FC4459D827560731004888B9 /* GlobalFontUseCaseTest.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + FC4459EB27561A48004888B9 /* PushNotificationStateUseCaseTest */ = { + isa = PBXNativeTarget; + buildConfigurationList = FC4459F427561A48004888B9 /* Build configuration list for PBXNativeTarget "PushNotificationStateUseCaseTest" */; + buildPhases = ( + FC4459E827561A48004888B9 /* Sources */, + FC4459E927561A48004888B9 /* Frameworks */, + FC4459EA27561A48004888B9 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + FC4459F127561A48004888B9 /* PBXTargetDependency */, + ); + name = PushNotificationStateUseCaseTest; + productName = PushNotificationStateUseCaseTest; + productReference = FC4459EC27561A48004888B9 /* PushNotificationStateUseCaseTest.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - FC9F107E27315EB80060FEB7 /* FirebaseNetworkTest */ = { + FC4459FD275622CB004888B9 /* TextUseCaseTest */ = { isa = PBXNativeTarget; - buildConfigurationList = FC9F108527315EB80060FEB7 /* Build configuration list for PBXNativeTarget "FirebaseNetworkTest" */; + buildConfigurationList = FC445A06275622CB004888B9 /* Build configuration list for PBXNativeTarget "TextUseCaseTest" */; buildPhases = ( - FC9F107B27315EB80060FEB7 /* Sources */, - FC9F107C27315EB80060FEB7 /* Frameworks */, - FC9F107D27315EB80060FEB7 /* Resources */, + FC4459FA275622CB004888B9 /* Sources */, + FC4459FB275622CB004888B9 /* Frameworks */, + FC4459FC275622CB004888B9 /* Resources */, ); buildRules = ( ); dependencies = ( - FC9F108427315EB80060FEB7 /* PBXTargetDependency */, + FC445A03275622CB004888B9 /* PBXTargetDependency */, ); - name = FirebaseNetworkTest; - productName = FirebaseNetworkTest; - productReference = FC9F107F27315EB80060FEB7 /* FirebaseNetworkTest.xctest */; + name = TextUseCaseTest; + productName = TextUseCaseTest; + productReference = FC4459FE275622CB004888B9 /* TextUseCaseTest.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ @@ -1309,45 +1755,62 @@ LastSwiftUpdateCheck = 1300; LastUpgradeCheck = 1300; TargetAttributes = { - 1D0C8BC82732B5F90091ACC0 = { + 1D3925B9273BB4BE003A0B70 = { CreatedOnToolsVersion = 13.0; }; - 1D3925B9273BB4BE003A0B70 = { + 1D992E2F2755E8EC002C4457 = { CreatedOnToolsVersion = 13.0; }; - 310125C82739A56600E43160 = { - CreatedOnToolsVersion = 13.1; - TestTargetID = 664893BC273007ED009F03E2; + 1DAEB00E275603C80040E8C1 = { + CreatedOnToolsVersion = 13.0; }; - 310D81222733C7FC00F15F4F = { - CreatedOnToolsVersion = 13.1; - TestTargetID = 664893BC273007ED009F03E2; + 1DAEB02327560E8F0040E8C1 = { + CreatedOnToolsVersion = 13.0; + }; + 1DAEB039275617010040E8C1 = { + CreatedOnToolsVersion = 13.0; }; 319C21D4273BD764009CE65C = { CreatedOnToolsVersion = 13.1; - TestTargetID = 664893BC273007ED009F03E2; + }; + 31BEC8302755EA89009AF22A = { + CreatedOnToolsVersion = 13.1; }; 664893BC273007ED009F03E2 = { CreatedOnToolsVersion = 13.0; }; - 669ADE312732E4A8007B529B = { + 66C2CA842755E9F200AFE5EE = { CreatedOnToolsVersion = 13.0; TestTargetID = 664893BC273007ED009F03E2; }; - FC5FB7A027328A19008AC3D2 = { + 66C2CAAA27561E1100AFE5EE = { CreatedOnToolsVersion = 13.0; + TestTargetID = 664893BC273007ED009F03E2; }; - FC5FB7C827339213008AC3D2 = { + 66C2CABF275623F700AFE5EE = { CreatedOnToolsVersion = 13.0; TestTargetID = 664893BC273007ED009F03E2; }; - FC9F1061273102E30060FEB7 = { + 66C2CAD527562C1500AFE5EE = { CreatedOnToolsVersion = 13.0; + TestTargetID = 664893BC273007ED009F03E2; }; - FC9F107E27315EB80060FEB7 = { + 66C2CAEB275640CA00AFE5EE = { CreatedOnToolsVersion = 13.0; TestTargetID = 664893BC273007ED009F03E2; }; + FC4459AA2755E8C4004888B9 = { + CreatedOnToolsVersion = 13.0; + }; + FC4459D727560731004888B9 = { + CreatedOnToolsVersion = 13.0; + }; + FC4459EB27561A48004888B9 = { + CreatedOnToolsVersion = 13.0; + }; + FC4459FD275622CB004888B9 = { + CreatedOnToolsVersion = 13.0; + }; }; }; buildConfigurationList = 664893B8273007ED009F03E2 /* Build configuration list for PBXProject "Doolda" */; @@ -1357,57 +1820,70 @@ knownRegions = ( en, Base, + ko, ); mainGroup = 664893B4273007ED009F03E2; packageReferences = ( - 664893EB27300D7D009F03E2 /* XCRemoteSwiftPackageReference "SnapKit" */, - 664893EE27300DB9009F03E2 /* XCRemoteSwiftPackageReference "Kingfisher" */, - 664893F127300E28009F03E2 /* XCRemoteSwiftPackageReference "SwiftLint" */, + 1D3BAC06274E2F3B00CD23EE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */, + 1D3BAC09274E2F6500CD23EE /* XCRemoteSwiftPackageReference "SnapKit" */, + 1D3BAC0C274E2FDA00CD23EE /* XCRemoteSwiftPackageReference "SwiftLint" */, + 1D3BAC0D274E307A00CD23EE /* XCRemoteSwiftPackageReference "Kingfisher" */, ); productRefGroup = 664893BE273007ED009F03E2 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 664893BC273007ED009F03E2 /* Doolda */, - 6648941C27310165009F03E2 /* PairingViewModelTests */, - FC9F1061273102E30060FEB7 /* GetMyIdUseCaseTest */, - 6648945B27327F1D009F03E2 /* GeneratePairIdUseCaseTests */, - FC9F107E27315EB80060FEB7 /* FirebaseNetworkTest */, - FC5FB7A027328A19008AC3D2 /* UserDefaultsPersistenceServiceTest */, - 669ADE312732E4A8007B529B /* RefreshPairIdUseCaseTest */, - 1D0C8BC82732B5F90091ACC0 /* GetPairIdUseCaseTest */, - FC5FB7C827339213008AC3D2 /* UserRepositoryTests */, - 310D81222733C7FC00F15F4F /* SplashViewModelTest */, 319C21D4273BD764009CE65C /* URLSessionNetworkServiceTest */, - 310125C82739A56600E43160 /* ImageComposeUseCaseTest */, 1D3925B9273BB4BE003A0B70 /* EditPageUseCaseTest */, + 1D992E2F2755E8EC002C4457 /* GetUserUseCaseTest */, + FC4459AA2755E8C4004888B9 /* GetMyIdUseCaseTest */, + 66C2CA842755E9F200AFE5EE /* PairUserUseCaseTest */, + 31BEC8302755EA89009AF22A /* ImageUseCaseTest */, + FC4459EB27561A48004888B9 /* PushNotificationStateUseCaseTest */, + 1DAEB02327560E8F0040E8C1 /* FCMTokenUseCaseTest */, + 66C2CAEB275640CA00AFE5EE /* GetRawPageUseCaseTest */, + 66C2CAAA27561E1100AFE5EE /* RefreshUserUseCaseTest */, + 66C2CAD527562C1500AFE5EE /* GetPageUseCaseTest */, + 66C2CABF275623F700AFE5EE /* CheckMyTurnUseCaseTest */, + FC4459FD275622CB004888B9 /* TextUseCaseTest */, + FC4459D727560731004888B9 /* GlobalFontUseCaseTest */, + 1DAEB00E275603C80040E8C1 /* RegisterUserUseCaseTest */, + 1DAEB039275617010040E8C1 /* FirebaseMessageUseCaseTest */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - 1D0C8BC72732B5F90091ACC0 /* Resources */ = { + 1D3925B8273BB4BE003A0B70 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - 1D3925B8273BB4BE003A0B70 /* Resources */ = { + 1D992E2E2755E8EC002C4457 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1DAEB00D275603C80040E8C1 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - 310125C72739A56600E43160 /* Resources */ = { + 1DAEB02227560E8F0040E8C1 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - 310D81212733C7FC00F15F4F /* Resources */ = { + 1DAEB038275617010040E8C1 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -1421,73 +1897,93 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 31BEC82F2755EA89009AF22A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 664893BB273007ED009F03E2 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 318D9F63274CFFA000AA6F0E /* dungGeunMo.otf in Resources */, 664893CD273007F1009F03E2 /* LaunchScreen.storyboard in Resources */, - 66B4ADFF2742BEFC0021EA27 /* Normal.jpg in Resources */, - 66B4AE002742BEFC0021EA27 /* Polaroid.jpg in Resources */, - 3161EF092746644600B922DD /* htmlCoder_8.png in Resources */, - 3161EEF6274663BD00B922DD /* boolbadaSticker_1.png in Resources */, - 3161EF0A2746644600B922DD /* htmlCoder_9.png in Resources */, - 3161EF062746644600B922DD /* htmlCoder_1.png in Resources */, - 3161EEE52744F57900B922DD /* buddySticker_cover.png in Resources */, - 66B4AE012742BEFC0021EA27 /* LifeFourCuts.jpg in Resources */, - 3161EF082746644600B922DD /* htmlCoder_6.png in Resources */, - 3101258F2737987B00E43160 /* GoogleService-Info.plist in Resources */, - 3161EF0C2746644600B922DD /* htmlCoder_7.png in Resources */, - 3161EEF3274663BD00B922DD /* boolbadaSticker_2.png in Resources */, - 3161EEF1274663BD00B922DD /* boolbadaSticker_cover.png in Resources */, - 3161EEF4274663BD00B922DD /* boolbadaSticker_4.png in Resources */, - 3161EF072746644600B922DD /* htmlCoder_5.png in Resources */, - 3161EEDD2744C51000B922DD /* buddySticker_0.png in Resources */, - 3161EF0F2746645E00B922DD /* htmlCoder_cover.png in Resources */, - 3161EF052746644600B922DD /* htmlCoder_10.png in Resources */, - 3161EEF2274663BD00B922DD /* boolbadaSticker_0.png in Resources */, - 3161EEF5274663BD00B922DD /* boolbadaSticker_3.png in Resources */, - 3161EF032746644600B922DD /* htmlCoder_2.png in Resources */, - 3161EF142746651100B922DD /* buddySticker_1.png in Resources */, - 3161EF152746651100B922DD /* buddySticker_3.png in Resources */, - 3161EF162746651100B922DD /* buddySticker_4.png in Resources */, + 318D9F62274CFF9E00AA6F0E /* uhBeeMysen.ttf in Resources */, + 318D9F64274CFFA200AA6F0E /* kotraHope.otf in Resources */, + 1D08135E2758C6C300DCF432 /* InfoPlist.strings in Resources */, + 66B4ADFF2742BEFC0021EA27 /* normal.jpg in Resources */, + 31BEC81F2755B154009AF22A /* Config.xcconfig in Resources */, + 66B4AE002742BEFC0021EA27 /* polaroid.jpg in Resources */, + 66B4AE012742BEFC0021EA27 /* lifeFourCuts.jpg in Resources */, + 66CCB6F8275924AB00E3FD64 /* polaroid_long.jpg in Resources */, 664893CA273007F1009F03E2 /* Assets.xcassets in Resources */, - 3161EF172746651100B922DD /* buddySticker_2.png in Resources */, 1DD6AD4027321F7800592AC8 /* dovemayo.otf in Resources */, - 3161EF0D2746644600B922DD /* htmlCoder_4.png in Resources */, - 3161EF042746644600B922DD /* htmlCoder_3.png in Resources */, - 3161EF0B2746644600B922DD /* htmlCoder_0.png in Resources */, + 31BEC80B2754DD74009AF22A /* license.txt in Resources */, + 318D9F67274D4D5000AA6F0E /* darae.ttf in Resources */, + 318D9F69274D4D9900AA6F0E /* kyoboHandwriting.otf in Resources */, + 31BEC8212755B160009AF22A /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 66C2CA832755E9F200AFE5EE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 66C2CAA927561E1100AFE5EE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 66C2CABE275623F700AFE5EE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 66C2CAD427562C1500AFE5EE /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - 669ADE302732E4A8007B529B /* Resources */ = { + 66C2CAEA275640CA00AFE5EE /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - FC5FB79F27328A19008AC3D2 /* Resources */ = { + FC4459A92755E8C4004888B9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - FC5FB7C727339213008AC3D2 /* Resources */ = { + FC4459D627560731004888B9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - FC9F1060273102E30060FEB7 /* Resources */ = { + FC4459EA27561A48004888B9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - FC9F107D27315EB80060FEB7 /* Resources */ = { + FC4459FC275622CB004888B9 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -1517,59 +2013,104 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - 1D0C8BC52732B5F90091ACC0 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 1D0C8BD02732B7780091ACC0 /* UserRepositoryProtocol.swift in Sources */, - 1D0C8BD12732B7BA0091ACC0 /* GetUserUseCase.swift in Sources */, - 1D0C8BCC2732B5F90091ACC0 /* GetPairIdUseCaseTest.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; 1D3925B6273BB4BE003A0B70 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 66CCB6E7275667EA00E3FD64 /* ComponentType.swift in Sources */, + FC4459A02755E12F004888B9 /* EditPageUseCaseProtocol.swift in Sources */, + FC44599D2755E02F004888B9 /* ImageUseCaseProtocol.swift in Sources */, + FC44599F2755E0E1004888B9 /* DummyError.swift in Sources */, 1D3925C9273BD6E2003A0B70 /* ComponentEntity.swift in Sources */, - 1D3925C2273BD500003A0B70 /* ImageUseCase.swift in Sources */, 1D3925CB273BDB15003A0B70 /* DDIDDataTransferObject.swift in Sources */, + 1DD0F34D2747AABA00FC5E65 /* FontColorType.swift in Sources */, 1D3925CD273BDB26003A0B70 /* CGColor+Extensions.swift in Sources */, + FC4459A42755E434004888B9 /* DummyRawPageRepository.swift in Sources */, 1D3925C1273BB513003A0B70 /* EditPageUseCase.swift in Sources */, 1D3925CA273BD8F6003A0B70 /* PhotoComponentEntity.swift in Sources */, 1D3925C5273BD62B003A0B70 /* User.swift in Sources */, 1D3925CC273BDB1D003A0B70 /* BackgroundType.swift in Sources */, + FC4459A62755E470004888B9 /* DummyPairRepository.swift in Sources */, + 1DD0F3502747AAE100FC5E65 /* ImageRepositoryProtocol.swift in Sources */, 1D3925C4273BD613003A0B70 /* RawPageRepositoryProtocol.swift in Sources */, + 1DD0F3522747AB4400FC5E65 /* CIImage+Extension.swift in Sources */, 1D3925C8273BD6DF003A0B70 /* RawPageEntity.swift in Sources */, + 1DD0F34E2747AAC700FC5E65 /* PairRepositoryProtocol.swift in Sources */, 1D3925C3273BD610003A0B70 /* PageRepositoryProtocol.swift in Sources */, + 1DD0F3512747AAEC00FC5E65 /* StickerComponentEntity.swift in Sources */, 1D3925C7273BD6DD003A0B70 /* PageEntity.swift in Sources */, 1D3925BD273BB4BE003A0B70 /* EditPageUseCaseTest.swift in Sources */, + FC44599C2755DF86004888B9 /* DummyPageRepository.swift in Sources */, 1D3925C6273BD6DB003A0B70 /* DDID.swift in Sources */, 1D3925D1273C12A8003A0B70 /* CGFloat+Extensions.swift in Sources */, 1D3925CE273BDB37003A0B70 /* DateFormatter+Extensions.swift in Sources */, + FC4459A22755E41C004888B9 /* DummyImageUseCase.swift in Sources */, + 1DD0F34F2747AAD800FC5E65 /* TextComponentEntity.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1D992E2C2755E8EC002C4457 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1D992E332755E8EC002C4457 /* GetUserUseCaseTest.swift in Sources */, + 1D1C8ECC2755EF080044AF95 /* DummyError.swift in Sources */, + 1D1C8EC42755ECBE0044AF95 /* GetUserUseCase.swift in Sources */, + 1D1C8ECA2755EE530044AF95 /* DDIDDataTransferObject.swift in Sources */, + 1D1C8EC72755ED690044AF95 /* UserRepositoryProtocol.swift in Sources */, + 1D1C8EC82755EDF20044AF95 /* DummyUserRepository.swift in Sources */, + 1D1C8ECD2755F6BB0044AF95 /* GetUserUseCaseProtocol.swift in Sources */, + 1D1C8EC92755EE420044AF95 /* DDID.swift in Sources */, + 1D1C8ECB2755EE8C0044AF95 /* User.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 1DAEB00B275603C80040E8C1 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1DAEB012275603C80040E8C1 /* RegisterUserUseCaseTest.swift in Sources */, + 1DAEB01E2756095C0040E8C1 /* DummyError.swift in Sources */, + 1DAEB01C275606BD0040E8C1 /* DummyUserRepository.swift in Sources */, + 1DAEB018275606960040E8C1 /* RegisterUserUseCase.swift in Sources */, + 1DAEB01B275606A70040E8C1 /* DDIDDataTransferObject.swift in Sources */, + 1DAEB01F275609610040E8C1 /* UserRepositoryProtocol.swift in Sources */, + 1DAEB01A275606A30040E8C1 /* DDID.swift in Sources */, + 1DAEB01D275609250040E8C1 /* RegisterUserUseCaseProtocol.swift in Sources */, + 1DAEB0192756069E0040E8C1 /* User.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - 310125C52739A56600E43160 /* Sources */ = { + 1DAEB02027560E8F0040E8C1 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 310125D22739A5D200E43160 /* ImageComposeUseCase.swift in Sources */, - 310125CC2739A56600E43160 /* ImageComposeUseCaseTest.swift in Sources */, + 1DAEB02727560E8F0040E8C1 /* FCMTokenUseCaseTest.swift in Sources */, + 1DAEB03027560F7A0040E8C1 /* FCMTokenRepositoryProtocol.swift in Sources */, + 1DAEB035275613EE0040E8C1 /* FCMTokenUseCaseProtocol.swift in Sources */, + 1DAEB034275612D30040E8C1 /* FCMTokenUseCase.swift in Sources */, + 1DAEB03327560FBB0040E8C1 /* DDIDDataTransferObject.swift in Sources */, + 1DAEB02D27560EA20040E8C1 /* DummyError.swift in Sources */, + 1DAEB03127560F960040E8C1 /* DummyFCMTokenRepository.swift in Sources */, + 1DAEB03227560FB80040E8C1 /* DDID.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - 310D811F2733C7FC00F15F4F /* Sources */ = { + 1DAEB036275617010040E8C1 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 310D81332733CD5400F15F4F /* UserRepositoryProtocol.swift in Sources */, - 310D81322733CC1900F15F4F /* RegisterUserUseCase.swift in Sources */, - 310D812E2733C86000F15F4F /* GetMyIdUseCase.swift in Sources */, - 310D812F2733C86000F15F4F /* GetUserUseCase.swift in Sources */, - 310D812D2733C84B00F15F4F /* SplashViewCoordinatorProtocol.swift in Sources */, - 310D812C2733C83B00F15F4F /* SplashViewModel.swift in Sources */, - 310D81262733C7FC00F15F4F /* SplashViewModelTest.swift in Sources */, + 1DAEB04C27561E790040E8C1 /* FirebaseMessageUseCaseProtocol.swift in Sources */, + 1DAEB04827561A260040E8C1 /* FirebaseMessageUseCase.swift in Sources */, + 1DF7094927562B5A0005FD92 /* DDIDDataTransferObject.swift in Sources */, + 1DAEB04D2756228F0040E8C1 /* FCMTokenRepositoryProtocol.swift in Sources */, + 1DAEB043275617940040E8C1 /* FirebaseMessageRepositoryProtocol.swift in Sources */, + 1DAEB04B27561E220040E8C1 /* DDID.swift in Sources */, + 1DAEB047275619FC0040E8C1 /* DummyError.swift in Sources */, + 1DAEB04927561D850040E8C1 /* DummyFCMTokenRepository.swift in Sources */, + 1DAEB03D275617010040E8C1 /* FirebaseMessageUseCaseTest.swift in Sources */, + 1DAEB04A27561DC00040E8C1 /* PushMessageEntity.swift in Sources */, + 1DAEB046275617CB0040E8C1 /* DummyFirebaseMessageRepository.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1577,34 +2118,73 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 1D1C8ECF2755F7270044AF95 /* User.swift in Sources */, + 1DD0F3552747ABE000FC5E65 /* PairDocument.swift in Sources */, 319C21E1273BD805009CE65C /* URLSessionNetworkServiceProtocol.swift in Sources */, + 1D1C8ECE2755F71F0044AF95 /* DDIDDataTransferObject.swift in Sources */, 319C21DE273BD791009CE65C /* API.swift in Sources */, + 1D1C8ED12755F7500044AF95 /* FCMTokenDocument.swift in Sources */, + 1DD0F3562747ABEC00FC5E65 /* PageDocument.swift in Sources */, + 1DD0F3572747ABFD00FC5E65 /* DDID.swift in Sources */, + 1DD0F3582747AC0300FC5E65 /* PageEntity.swift in Sources */, + 1DD0F3542747ABDA00FC5E65 /* UserDocument.swift in Sources */, + 1D1C8ED02755F7480044AF95 /* Secrets.swift in Sources */, + 1DD0F3532747ABD400FC5E65 /* DateFormatter+Extensions.swift in Sources */, 319C21DF273BD791009CE65C /* URLSessionNetworkService.swift in Sources */, 319C21E0273BD791009CE65C /* URLRequsetBuilder.swift in Sources */, 319C21D8273BD764009CE65C /* URLSessionNetworkServiceTest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; + 31BEC82D2755EA89009AF22A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 31BEC8422755F4F2009AF22A /* DDIDDataTransferObject.swift in Sources */, + 31BEC8402755ED64009AF22A /* DDID.swift in Sources */, + 31BEC8412755EF02009AF22A /* DummyError.swift in Sources */, + 31BEC83F2755ED60009AF22A /* User.swift in Sources */, + 31BEC83E2755ED57009AF22A /* CIImage+Extension.swift in Sources */, + 31BEC83D2755ED1C009AF22A /* ImageUseCase.swift in Sources */, + 31BEC83C2755EBFE009AF22A /* ImageRepositoryProtocol.swift in Sources */, + 31BEC83B2755EBCD009AF22A /* DummyImageRepository.swift in Sources */, + 31BEC8382755EB2C009AF22A /* ImageUseCaseProtocol.swift in Sources */, + 31BEC8342755EA89009AF22A /* ImageUseCaseTest.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 664893B9273007ED009F03E2 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( FC28CB32274609F300BA47BB /* TextUseCase.swift in Sources */, - 1D8BE07B2742A3DB00201E3E /* DiaryPageView.swift in Sources */, + 1D8BE07B2742A3DB00201E3E /* DiaryCollectionViewCell.swift in Sources */, + 1D9DC09F274A0E290060587B /* FilterOptionBottomSheetViewModel.swift in Sources */, 319C222D273CEFBB009CE65C /* CIImage+Extension.swift in Sources */, 661A8406274351230077E0E3 /* CheckMyTurnUseCase.swift in Sources */, + 66F176092754C21B006ACB41 /* GetMyIdUseCaseProtocol.swift in Sources */, + 317FB617274BAAEC005DF3A3 /* DooldaInfoType.swift in Sources */, + 66F176232754C6EE006ACB41 /* GlobalFontUseCaseProtocol.swift in Sources */, 1D3629FE2733C79300CFC069 /* RegisterUserUseCase.swift in Sources */, FCD24D7827439717000203F7 /* GlobalFontUseCase.swift in Sources */, + 1D999464274CAAAC00C72273 /* FCMTokenRepositoryProtocol.swift in Sources */, + 318D9F72274E17AB00AA6F0E /* PageDetailViewCoordinator.swift in Sources */, 310125D6273A6F3400E43160 /* FileManagerPersistenceService.swift in Sources */, FC5FB7E02733A6D5008AC3D2 /* FirebaseCollection.swift in Sources */, + 1D9DC09D2749F2BE0060587B /* FilterOptionBottomSheetViewController.swift in Sources */, + 66F1760F2754C334006ACB41 /* RefreshUserUseCaseProtocol.swift in Sources */, + 1DDADCB4274BCE63000330C9 /* Secrets.swift in Sources */, 3161EE9827436EC600B922DD /* StickerPickerView.swift in Sources */, 1D6232F527313D8E00A9AD6B /* UIView+Extensions.swift in Sources */, 66CD6464273D125F00E55C03 /* CGImage+Extensions.swift in Sources */, + FC51FA6D274C89330069D39C /* FontSizeControlView.swift in Sources */, 1D04F41A273954CD00CFCC3A /* CGColor+Extensions.swift in Sources */, 665303AA2744CE3400E3075C /* CoreDataPageEntityPersistenceServiceProtocol.swift in Sources */, 6653039D2743B49E00E3075C /* CoreDataPersistenceService.swift in Sources */, + 66F176272754C77C006ACB41 /* FCMTokenUseCaseProtocol.swift in Sources */, 1D0C8BC42732AF780091ACC0 /* GetUserUseCase.swift in Sources */, 1D8BE07F2743846A00201E3E /* DiaryCollectionViewHeader.swift in Sources */, + 66F176112754C370006ACB41 /* RegisterUserUseCaseProtocol.swift in Sources */, FC5FB79027325C4D008AC3D2 /* UserDocument.swift in Sources */, FCD24D7E2743ADC1000203F7 /* PushNotificationStateUseCase.swift in Sources */, 319C21D0273BA7D4009CE65C /* ImageRepository.swift in Sources */, @@ -1613,10 +2193,15 @@ 66F5D47A2733E88F000FB471 /* PageEntity.swift in Sources */, 1D8BE0792740FE5E00201E3E /* PageDocument.swift in Sources */, 66FE9371273671910036CED2 /* PairUserUseCase.swift in Sources */, - 1DE5E5362738BBAA004F794F /* CoordinatorProtocol.swift in Sources */, + 66F1761F2754C656006ACB41 /* TextUseCaseProtocol.swift in Sources */, + 317FB611274A9175005DF3A3 /* SettingsViewCoordinator.swift in Sources */, + 1DE5E5362738BBAA004F794F /* BaseCoordinator.swift in Sources */, + FC51FA67274BB9D60069D39C /* FontColorPickerView.swift in Sources */, 66CD645A273BE5A000E55C03 /* PhotoPickerCollectionViewCell.swift in Sources */, 1D3925B5273B9172003A0B70 /* DateFormatter+Extensions.swift in Sources */, - FCC803FA27451A8000FFE4DC /* TextInputViewController.swift in Sources */, + 66F176212754C6A3006ACB41 /* PushNotificationStateUseCaseProtocol.swift in Sources */, + FCC803FA27451A8000FFE4DC /* TextEditViewController.swift in Sources */, + 1D99946A274CE5F700C72273 /* FirebaseMessageRepositoryProtocol.swift in Sources */, FC822FAA2736088100C918A0 /* URLSessionNetworkService.swift in Sources */, FC822FAC27360F8600C918A0 /* URLRequsetBuilder.swift in Sources */, 310125922738325800E43160 /* UIAlertController+Extensions.swift in Sources */, @@ -1635,184 +2220,294 @@ 1DE5E5422739352B004F794F /* RawPageRepositoryProtocol.swift in Sources */, 66ECCD3C273F7C9F003990EE /* CustomActivityIndicator.swift in Sources */, 66FE936B273564DB0036CED2 /* DDID.swift in Sources */, - 3161EE9C274377F400B922DD /* UnpackedStickerCell.swift in Sources */, - 310D80D32731402F00F15F4F /* ParingViewCoordinator.swift in Sources */, + 3161EE9C274377F400B922DD /* UnpackedStickerCollectionViewCell.swift in Sources */, + 310D80D32731402F00F15F4F /* PairingViewCoordinator.swift in Sources */, FC822FB22736BF8400C918A0 /* URLSessionNetworkServiceProtocol.swift in Sources */, 1D6232F7273140CF00A9AD6B /* CopyableLabel.swift in Sources */, FC5FB79C27328579008AC3D2 /* UserDefaultsPersistenceServiceProtocol.swift in Sources */, 1D8BE07D2743437600201E3E /* DiaryViewModel.swift in Sources */, + FC51FA69274BBB3C0069D39C /* FontColorCollectionViewCell.swift in Sources */, 1D6232EF2731117100A9AD6B /* UIControl+Extensions.swift in Sources */, 66FE9373273688E10036CED2 /* RefreshUserUseCase.swift in Sources */, + 66F176292754C7DA006ACB41 /* FirebaseMessageUseCaseProtocol.swift in Sources */, 310D81482733F56700F15F4F /* DiaryViewController.swift in Sources */, - FC19598A274284E200AB6059 /* StickerPickerViewModel.swift in Sources */, + FC19598A274284E200AB6059 /* StickerPickerBottomSheetViewModel.swift in Sources */, 665303A82744C6F500E3075C /* CoreDataPageEntityPersistenceService.swift in Sources */, + 1D9DC0A5274B8A890060587B /* Collection+Extensions.swift in Sources */, 664893FA27302905009F03E2 /* SplashViewCoordinator.swift in Sources */, + 1D8646A3274D2DB600C4511D /* PushMessageEntity.swift in Sources */, FCE9D3DE2738CFB50001F155 /* PairRepository.swift in Sources */, + 317FB619274BBC9D005DF3A3 /* SettingsTableViewHeader.swift in Sources */, + 317FB60B274A3851005DF3A3 /* StickerPickerCollectionViewFooter.swift in Sources */, 66FE93CC27396B960036CED2 /* PhotoPickerBottomSheetViewModel.swift in Sources */, + FC445A0D27562590004888B9 /* ComponentType.swift in Sources */, 310125C427398DF600E43160 /* ImageUseCase.swift in Sources */, 66CD6450273A5C4700E55C03 /* PhotoPickerBottomSheetViewController.swift in Sources */, + 66F176132754C3C3006ACB41 /* EditPageUseCaseProtocol.swift in Sources */, FCD24D7C2743AAE2000203F7 /* PushNotificationStateRepositoryProtocol.swift in Sources */, FC9F10582730F0060060FEB7 /* GetMyIdUseCase.swift in Sources */, - 319C21CA273B9703009CE65C /* FileDocuments.swift in Sources */, - FCC803F82744FEF800FFE4DC /* SettingViewModel.swift in Sources */, 1DBF5717273A328D00D28F5E /* PhotoFrameType.swift in Sources */, - 3161EE9A274377E300B922DD /* PackedStickerCell.swift in Sources */, - 3161EE942742A44100B922DD /* StickerPickerViewController.swift in Sources */, - FCC803F62744C22F00FFE4DC /* DoolDaFont.swift in Sources */, + 3161EE9A274377E300B922DD /* PackedStickerCollectionViewCell.swift in Sources */, + 3161EE942742A44100B922DD /* StickerPickerBottomSheetViewController.swift in Sources */, + FCC803F62744C22F00FFE4DC /* FontType.swift in Sources */, FCE9D3DC2738C9A00001F155 /* PairRepositoryProtocol.swift in Sources */, + 317FB613274AA879005DF3A3 /* SettingsTableViewCell.swift in Sources */, + 318D9F5B274CC77800AA6F0E /* FontPickerViewController.swift in Sources */, + 1DD58BD62754ACA600B93184 /* DiaryPageView.swift in Sources */, + 317FB60D274A8F59005DF3A3 /* SettingsViewController.swift in Sources */, FC822FB027367AFD00C918A0 /* API.swift in Sources */, FCCF022F273A12B400EAB00B /* EditPageViewController.swift in Sources */, 6653039B2743B3FF00E3075C /* CoreDataModel.xcdatamodeld in Sources */, FCD24D75274392AB000203F7 /* GlobalFontRepositoryProtocol.swift in Sources */, 1D6232ED27310EE300A9AD6B /* UIImage+Extensions.swift in Sources */, 1D3925D0273C1263003A0B70 /* CGFloat+Extensions.swift in Sources */, + 66F176192754C547006ACB41 /* CheckMyTurnUseCaseProtocol.swift in Sources */, 66FE936D2735655C0036CED2 /* DDIDDataTransferObject.swift in Sources */, - FCE9D3F62739550C0001F155 /* EditPageViewCoordinatorProtocol.swift in Sources */, 665303A12743B7FE00E3075C /* CoreDataPageEntity+CoreDataProperties.swift in Sources */, + 1D9DC0A3274B31950060587B /* DiaryBackgroundView.swift in Sources */, 66F5D47C2733EA32000FB471 /* ComponentEntity.swift in Sources */, - 664893FE273029AC009F03E2 /* SplashViewCoordinatorProtocol.swift in Sources */, 66F5D47E2733EE8F000FB471 /* RawPageEntity.swift in Sources */, + 1D999468274CAC4700C72273 /* FCMTokenDocument.swift in Sources */, 1DE5E540273932B0004F794F /* PageRepositoryProtocol.swift in Sources */, 665303A62743E09A00E3075C /* CoreDataPersistenceServiceProtocol.swift in Sources */, + 317FB61B274BCE68005DF3A3 /* InformationViewController.swift in Sources */, 1D992B42273D0EAB00B63954 /* PageRepository.swift in Sources */, FC5FB79A273284E2008AC3D2 /* UserDefaultsPersistenceService.swift in Sources */, 664893FC27302947009F03E2 /* SplashViewModel.swift in Sources */, - FCCF0233273A719600EAB00B /* PageControlView.swift in Sources */, + FCCF0233273A719600EAB00B /* PageComponentControlView.swift in Sources */, 3101259427383DED00E43160 /* PhotoComponentEntity.swift in Sources */, 3101259C2738430200E43160 /* StickerPackEntity.swift in Sources */, 664893F82730272B009F03E2 /* AppCoordinator.swift in Sources */, 1D04F41C273957E600CFCC3A /* BackgroundType.swift in Sources */, - FCC803FC27451C9600FFE4DC /* TextInputViewModel.swift in Sources */, + FCC803FC27451C9600FFE4DC /* TextEditViewModel.swift in Sources */, 664894182730F1B1009F03E2 /* PairingViewModel.swift in Sources */, 66CD644E273A490800E55C03 /* BottomSheetViewController.swift in Sources */, + 66F1760D2754C2CF006ACB41 /* GetUserUseCaseProtocol.swift in Sources */, 66FE936F27356E160036CED2 /* User.swift in Sources */, + 66F1761B2754C589006ACB41 /* GetPageUseCaseProtocol.swift in Sources */, + 1D99946C274CEF2900C72273 /* FirebaseMessageRepository.swift in Sources */, FCD24D7327439268000203F7 /* GlobalFontRepository.swift in Sources */, FC5FB7DC2733A4AA008AC3D2 /* UserDefaults+Extensions.swift in Sources */, + 318D9F76274E258D00AA6F0E /* PageDetailViewController.swift in Sources */, FC5FB793273260C3008AC3D2 /* UserRepository.swift in Sources */, + FC23C228274E17C200EBAE4C /* PageDetailViewController.swift in Sources */, + 66F1760B2754C27C006ACB41 /* PairUserUseCaseProtocol.swift in Sources */, 664893C1273007ED009F03E2 /* AppDelegate.swift in Sources */, - 1DE5E5382738BCE0004F794F /* DiaryViewCoordinatorProtocol.swift in Sources */, + 66F1760527548260006ACB41 /* UnpairUserUseCase.swift in Sources */, FCD24D7127437F31000203F7 /* UIFont+Extensions.swift in Sources */, + 1D999462274CA94600C72273 /* FCMTokenUseCase.swift in Sources */, + 318D9F70274E12D900AA6F0E /* PageDetailViewModel.swift in Sources */, + 66F176152754C413006ACB41 /* ImageComposeUseCaseProtocol.swift in Sources */, + 66F176172754C4F6006ACB41 /* ImageUseCaseProtocol.swift in Sources */, 1D097E97273CFA5900A4810B /* RawPageRepository.swift in Sources */, + 1D9DC0A1274A47D50060587B /* DooldaButton.swift in Sources */, 310125A027393D3B00E43160 /* ImageComposeUseCase.swift in Sources */, + 317FB615274B9A92005DF3A3 /* SettingsViewModel.swift in Sources */, 310D80D52731405B00F15F4F /* DiaryViewCoordinator.swift in Sources */, + 66F176252754C74C006ACB41 /* GetRawPageUseCaseProtocol.swift in Sources */, 66ECCD3A273E5342003990EE /* CarouselView.swift in Sources */, 66CAFF242740F63A00A48DCF /* BackgroundTypePickerViewController.swift in Sources */, FC9F105B2730F1B20060FEB7 /* UserRepositoryProtocol.swift in Sources */, - 1D58277D2732883E00FD0EA5 /* PairingViewCoordinatorProtocol.swift in Sources */, + 1D99946E274CEFC500C72273 /* FirebaseMessageUseCase.swift in Sources */, 665303A02743B7FE00E3075C /* CoreDataPageEntity+CoreDataClass.swift in Sources */, 31012596273842B300E43160 /* StickerComponentEntity.swift in Sources */, 1D6232E72730F0DF00A9AD6B /* PairingViewController.swift in Sources */, 66CD6456273BB88A00E55C03 /* PhotoFrameCollectionViewCell.swift in Sources */, 1DBF5715273A2C5100D28F5E /* FontColorType.swift in Sources */, 664893C3273007ED009F03E2 /* SceneDelegate.swift in Sources */, + 1D999466274CAB8900C72273 /* FCMTokenRepository.swift in Sources */, FC5FB7952732627C008AC3D2 /* PairDocument.swift in Sources */, + 66F1761D2754C5FF006ACB41 /* StickerUseCaseProtocol.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - 669ADE2E2732E4A8007B529B /* Sources */ = { + 66C2CA812755E9F200AFE5EE /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 669ADE3E2732E55F007B529B /* UserRepositoryProtocol.swift in Sources */, - 669ADE3F2732E55F007B529B /* RefreshPairIdUseCaseTest.swift in Sources */, + 66C2CA9A2755EDB300AFE5EE /* UserRepositoryProtocol.swift in Sources */, + 66C2CA9B2755EDB300AFE5EE /* PairRepositoryProtocol.swift in Sources */, + 66C2CA982755ED7A00AFE5EE /* DDID.swift in Sources */, + 66C2CA992755ED7A00AFE5EE /* DDIDDataTransferObject.swift in Sources */, + 66C2CA972755ED7300AFE5EE /* User.swift in Sources */, + 66C2CA902755EB4800AFE5EE /* DummyPairRepository.swift in Sources */, + 66C2CA8E2755EA6D00AFE5EE /* PairUserUseCase.swift in Sources */, + 66C2CA882755E9F200AFE5EE /* PairUserUseCaseTest.swift in Sources */, + 66C2CA8F2755EA7A00AFE5EE /* PairUserUseCaseProtocol.swift in Sources */, + 66C2CA952755ECBC00AFE5EE /* DummyError.swift in Sources */, + 66C2CAA62756040000AFE5EE /* DummyUserRepository.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - FC5FB79D27328A19008AC3D2 /* Sources */ = { + 66C2CAA727561E1100AFE5EE /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - FC5FB7E12733A6D5008AC3D2 /* FirebaseCollection.swift in Sources */, - FC5FB7DD2733A4AA008AC3D2 /* UserDefaults+Extensions.swift in Sources */, + 66C2CABB27561F1100AFE5EE /* UserRepositoryProtocol.swift in Sources */, + 66C2CAB727561E9900AFE5EE /* RefreshUserUseCase.swift in Sources */, + 66C2CABA27561EF900AFE5EE /* DummyError.swift in Sources */, + 66C2CAB827561E9900AFE5EE /* RefreshUserUseCaseProtocol.swift in Sources */, + 66C2CAB527561E8E00AFE5EE /* DDID.swift in Sources */, + 66C2CAB927561EB400AFE5EE /* DummyUserRepository.swift in Sources */, + 66C2CAB627561E8E00AFE5EE /* DDIDDataTransferObject.swift in Sources */, + 66C2CAB427561E8500AFE5EE /* User.swift in Sources */, + 66C2CAAE27561E1200AFE5EE /* RefreshUserUseCaseTest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - FC5FB7C527339213008AC3D2 /* Sources */ = { + 66C2CABC275623F700AFE5EE /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - FC5FB7D22733927B008AC3D2 /* UserRepositoryProtocol.swift in Sources */, - FC5FB7D32733927B008AC3D2 /* UserRepository.swift in Sources */, - FC5FB7D42733927B008AC3D2 /* UserDocument.swift in Sources */, - FC5FB7D52733927B008AC3D2 /* PairDocument.swift in Sources */, - FC5FB7E22733A6D5008AC3D2 /* FirebaseCollection.swift in Sources */, - FC5FB7D82733927B008AC3D2 /* UserDefaultsPersistenceServiceProtocol.swift in Sources */, - FC5FB7D92733927B008AC3D2 /* UserDefaultsPersistenceService.swift in Sources */, - FC5FB7DE2733A4AA008AC3D2 /* UserDefaults+Extensions.swift in Sources */, - FC5FB7CC27339213008AC3D2 /* UserRepositoryTests.swift in Sources */, + 66C2CACE275624C500AFE5EE /* DDID.swift in Sources */, + 66C2CAD0275624D800AFE5EE /* DummyError.swift in Sources */, + 66C2CACF275624C500AFE5EE /* DDIDDataTransferObject.swift in Sources */, + 66C2CACD275624BF00AFE5EE /* User.swift in Sources */, + 66C2CACC275624B500AFE5EE /* PairRepositoryProtocol.swift in Sources */, + 66C2CACB2756248600AFE5EE /* DummyPairRepository.swift in Sources */, + 66C2CAC92756247200AFE5EE /* CheckMyTurnUseCase.swift in Sources */, + 66C2CACA2756247200AFE5EE /* CheckMyTurnUseCaseProtocol.swift in Sources */, + 66C2CAC3275623F700AFE5EE /* CheckMyTurnUseCaseTest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - FC9F105E273102E30060FEB7 /* Sources */ = { + 66C2CAD227562C1500AFE5EE /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - FC9F106B2731035E0060FEB7 /* GetMyIdUseCase.swift in Sources */, - FC9F106C2731035E0060FEB7 /* UserRepositoryProtocol.swift in Sources */, - FC9F1065273102E30060FEB7 /* GetMyIdUseCaseTests.swift in Sources */, + 66C2CAE527562CE000AFE5EE /* User.swift in Sources */, + 66C2CAE327562CB900AFE5EE /* DDID.swift in Sources */, + 66C2CAE427562CB900AFE5EE /* DDIDDataTransferObject.swift in Sources */, + 66C2CAE227562CAF00AFE5EE /* PageEntity.swift in Sources */, + 66C2CAE127562C9D00AFE5EE /* PageRepositoryProtocol.swift in Sources */, + 66C2CAE627562D8900AFE5EE /* DummyPageRepository.swift in Sources */, + 66C2CADF27562C6100AFE5EE /* GetPageUseCase.swift in Sources */, + 66C2CAE027562C6100AFE5EE /* GetPageUseCaseProtocol.swift in Sources */, + 66C2CAD927562C1600AFE5EE /* GetPageUseCaseTest.swift in Sources */, + 66C2CAE727562DB500AFE5EE /* DummyError.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - FC9F107B27315EB80060FEB7 /* Sources */ = { + 66C2CAE8275640CA00AFE5EE /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - FC5FB79627326306008AC3D2 /* PairDocument.swift in Sources */, - FC5FB79127325D2C008AC3D2 /* UserDocument.swift in Sources */, - FC9F108227315EB80060FEB7 /* FirebaseNetworkTest.swift in Sources */, + 66C2CB022756424D00AFE5EE /* FontColorType.swift in Sources */, + 66C2CB03275642C500AFE5EE /* DummyError.swift in Sources */, + 66C2CB04275642D000AFE5EE /* DummyRawPageRepository.swift in Sources */, + 66C2CB012756421800AFE5EE /* BackgroundType.swift in Sources */, + 66C2CB00275641EE00AFE5EE /* RawPageRepositoryProtocol.swift in Sources */, + 66C2CAFE275641D000AFE5EE /* DDID.swift in Sources */, + 66C2CAFF275641D000AFE5EE /* DDIDDataTransferObject.swift in Sources */, + 66C2CAFD275641CB00AFE5EE /* User.swift in Sources */, + 66C2CAFA275641A700AFE5EE /* PhotoComponentEntity.swift in Sources */, + 66C2CAFB275641A700AFE5EE /* StickerComponentEntity.swift in Sources */, + 66C2CAFC275641A700AFE5EE /* TextComponentEntity.swift in Sources */, + 66C2CAF92756419300AFE5EE /* ComponentEntity.swift in Sources */, + 66C2CAF82756415000AFE5EE /* PageEntity.swift in Sources */, + 66C2CAF72756414A00AFE5EE /* RawPageEntity.swift in Sources */, + 66C2CAF52756412E00AFE5EE /* GetRawPageUseCase.swift in Sources */, + 66CCB6E6275666AD00E3FD64 /* ComponentType.swift in Sources */, + 66C2CAF62756412E00AFE5EE /* GetRawPageUseCaseProtocol.swift in Sources */, + 66C2CAEF275640CA00AFE5EE /* GetRawPageUseCaseTest.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 310125CE2739A56600E43160 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 664893BC273007ED009F03E2 /* Doolda */; - targetProxy = 310125CD2739A56600E43160 /* PBXContainerItemProxy */; + FC4459A72755E8C4004888B9 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FC4459B92755EC0F004888B9 /* User.swift in Sources */, + FC4459BC2755ED0A004888B9 /* DDIDDataTransferObject.swift in Sources */, + FC4459BB2755ED05004888B9 /* DDID.swift in Sources */, + FC4459B62755EB96004888B9 /* UserRepositoryProtocol.swift in Sources */, + 1DAEB005275601760040E8C1 /* DummyUserRepository.swift in Sources */, + FC4459B42755EB47004888B9 /* GetMyIdUseCase.swift in Sources */, + FC4459B52755EB47004888B9 /* GetMyIdUseCaseProtocol.swift in Sources */, + FC4459AE2755E8C4004888B9 /* GetMyIdUseCaseTest.swift in Sources */, + FC4459BA2755EC71004888B9 /* DummyError.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; }; - 319C21DA273BD764009CE65C /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 664893BC273007ED009F03E2 /* Doolda */; - targetProxy = 1D097E94273CFA1200A4810B /* PBXContainerItemProxy */; + FC4459D427560731004888B9 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FC4459E127560782004888B9 /* GlobalFontUseCase.swift in Sources */, + FC4459E227560782004888B9 /* GlobalFontUseCaseProtocol.swift in Sources */, + FC4459E32756079E004888B9 /* FontType.swift in Sources */, + FC4459DB27560731004888B9 /* GlobalFontUseCaseTest.swift in Sources */, + FC4459E5275607AB004888B9 /* UIFont+Extensions.swift in Sources */, + FC4459E727560909004888B9 /* DummyGlobalFontRepository.swift in Sources */, + FC4459E4275607A3004888B9 /* GlobalFontRepositoryProtocol.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; }; - 6648946127327F1D009F03E2 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 664893BC273007ED009F03E2 /* Doolda */; - targetProxy = 6648946027327F1D009F03E2 /* PBXContainerItemProxy */; + FC4459E827561A48004888B9 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FC4459F527561ACB004888B9 /* PushNotificationStateUseCase.swift in Sources */, + FC4459F927561BB3004888B9 /* DummyPushNotificationStateRepository.swift in Sources */, + FC4459F627561ACB004888B9 /* PushNotificationStateUseCaseProtocol.swift in Sources */, + FC4459F727561ACB004888B9 /* PushNotificationStateRepositoryProtocol.swift in Sources */, + FC4459EF27561A48004888B9 /* PushNotificationStateUseCaseTest.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; }; - 669ADE372732E4A8007B529B /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 664893BC273007ED009F03E2 /* Doolda */; - targetProxy = 669ADE362732E4A8007B529B /* PBXContainerItemProxy */; + FC4459FA275622CB004888B9 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + FC445A0E27562590004888B9 /* ComponentType.swift in Sources */, + FC445A0B275624F2004888B9 /* ComponentEntity.swift in Sources */, + FC445A07275622E2004888B9 /* TextUseCase.swift in Sources */, + FC445A0A275624CA004888B9 /* TextComponentEntity.swift in Sources */, + FC445A08275622E2004888B9 /* TextUseCaseProtocol.swift in Sources */, + FC445A09275624C5004888B9 /* FontColorType.swift in Sources */, + FC445A01275622CB004888B9 /* TextUseCaseTest.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; }; - FC5FB7A627328A19008AC3D2 /* PBXTargetDependency */ = { +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 1DAEB014275603C80040E8C1 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 664893BC273007ED009F03E2 /* Doolda */; - targetProxy = FC5FB7A527328A19008AC3D2 /* PBXContainerItemProxy */; + targetProxy = 1DAEB013275603C80040E8C1 /* PBXContainerItemProxy */; }; - FC5FB7CE27339213008AC3D2 /* PBXTargetDependency */ = { + FC4459DD27560731004888B9 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 664893BC273007ED009F03E2 /* Doolda */; - targetProxy = FC5FB7CD27339213008AC3D2 /* PBXContainerItemProxy */; + targetProxy = FC4459DC27560731004888B9 /* PBXContainerItemProxy */; }; - FC9F1067273102E30060FEB7 /* PBXTargetDependency */ = { + FC4459F127561A48004888B9 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 664893BC273007ED009F03E2 /* Doolda */; - targetProxy = FC9F1066273102E30060FEB7 /* PBXContainerItemProxy */; + targetProxy = FC4459F027561A48004888B9 /* PBXContainerItemProxy */; }; - FC9F108427315EB80060FEB7 /* PBXTargetDependency */ = { + FC445A03275622CB004888B9 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 664893BC273007ED009F03E2 /* Doolda */; - targetProxy = FC9F108327315EB80060FEB7 /* PBXContainerItemProxy */; + targetProxy = FC445A02275622CB004888B9 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 1D0813602758C6C300DCF432 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 1D08135F2758C6C300DCF432 /* ko */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; 664893CB273007F1009F03E2 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 664893CC273007F1009F03E2 /* Base */, + 1D08135B2758C62900DCF432 /* ko */, ); name = LaunchScreen.storyboard; sourceTree = ""; @@ -1820,7 +2515,7 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ - 1D0C8BCE2732B5F90091ACC0 /* Debug */ = { + 1D3925BF273BB4BE003A0B70 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; @@ -1835,7 +2530,7 @@ ); MACOSX_DEPLOYMENT_TARGET = 11.3; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = Seunghun.GetPairIdUseCaseTest; + PRODUCT_BUNDLE_IDENTIFIER = Codesquad.EditPageUseCaseTest; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_EMIT_LOC_STRINGS = NO; @@ -1843,7 +2538,7 @@ }; name = Debug; }; - 1D0C8BCF2732B5F90091ACC0 /* Release */ = { + 1D3925C0273BB4BE003A0B70 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; @@ -1858,7 +2553,7 @@ ); MACOSX_DEPLOYMENT_TARGET = 11.3; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = Seunghun.GetPairIdUseCaseTest; + PRODUCT_BUNDLE_IDENTIFIER = Codesquad.EditPageUseCaseTest; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_EMIT_LOC_STRINGS = NO; @@ -1866,223 +2561,265 @@ }; name = Release; }; - 1D3925BF273BB4BE003A0B70 /* Debug */ = { + 1D992E362755E8EC002C4457 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "@executable_path/../Frameworks", - "@loader_path/../Frameworks", + "@executable_path/Frameworks", + "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.3; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = Codesquad.EditPageUseCaseTest; + PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.GetUserUseCaseTest; PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; - 1D3925C0273BB4BE003A0B70 /* Release */ = { + 1D992E372755E8EC002C4457 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; - COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "@executable_path/../Frameworks", - "@loader_path/../Frameworks", + "@executable_path/Frameworks", + "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.3; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = Codesquad.EditPageUseCaseTest; + PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.GetUserUseCaseTest; PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; - 310125CF2739A56600E43160 /* Debug */ = { + 1DAEB016275603C90040E8C1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = Codesquad.ImageComposeUseCaseTest; + PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.RegisterUserUseCaseTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Doolda.app/Doolda"; }; name = Debug; }; - 310125D02739A56600E43160 /* Release */ = { + 1DAEB017275603C90040E8C1 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = Codesquad.ImageComposeUseCaseTest; + PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.RegisterUserUseCaseTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Doolda.app/Doolda"; }; name = Release; }; - 310D81292733C7FC00F15F4F /* Debug */ = { + 1DAEB02A27560E8F0040E8C1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = Codesquad.SplashViewModelTest; + PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.FCMTokenUseCaseTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Doolda.app/Doolda"; }; name = Debug; }; - 310D812A2733C7FC00F15F4F /* Release */ = { + 1DAEB02B27560E8F0040E8C1 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = Codesquad.SplashViewModelTest; + PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.FCMTokenUseCaseTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Doolda.app/Doolda"; }; name = Release; }; - 310D81392733E47A00F15F4F /* Debug */ = { + 1DAEB040275617010040E8C1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - COPY_PHASE_STRIP = NO; - GCC_DYNAMIC_NO_PIC = NO; - GCC_OPTIMIZATION_LEVEL = 0; - PRODUCT_NAME = PairingViewModelTests; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B3PWYBKFUK; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.FirebaseMessageUseCaseTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; - 310D813A2733E47A00F15F4F /* Release */ = { + 1DAEB041275617010040E8C1 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - COPY_PHASE_STRIP = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - PRODUCT_NAME = PairingViewModelTests; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B3PWYBKFUK; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.FirebaseMessageUseCaseTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; - 310D813B2733E47A00F15F4F /* Debug */ = { + 319C21DB273BD764009CE65C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - COPY_PHASE_STRIP = NO; - GCC_DYNAMIC_NO_PIC = NO; - GCC_OPTIMIZATION_LEVEL = 0; - PRODUCT_NAME = GeneratePairIdUseCaseTests; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B3PWYBKFUK; + GENERATE_INFOPLIST_FILE = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = Codesquad.URLSessionNetworkServiceTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; - 310D813C2733E47A00F15F4F /* Release */ = { + 319C21DC273BD764009CE65C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - COPY_PHASE_STRIP = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - PRODUCT_NAME = GeneratePairIdUseCaseTests; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B3PWYBKFUK; + GENERATE_INFOPLIST_FILE = YES; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = Codesquad.URLSessionNetworkServiceTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; }; - 319C21DB273BD764009CE65C /* Debug */ = { + 31BEC8352755EA89009AF22A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = Codesquad.URLSessionNetworkServiceTest; + PRODUCT_BUNDLE_IDENTIFIER = Codesquad.ImageUseCaseTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Doolda.app/Doolda"; }; name = Debug; }; - 319C21DC273BD764009CE65C /* Release */ = { + 31BEC8362755EA89009AF22A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = Codesquad.URLSessionNetworkServiceTest; + PRODUCT_BUNDLE_IDENTIFIER = Codesquad.ImageUseCaseTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Doolda.app/Doolda"; }; name = Release; }; @@ -2090,6 +2827,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; @@ -2137,7 +2875,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -2151,6 +2889,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; @@ -2192,7 +2931,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -2204,16 +2943,21 @@ }; 664893D2273007F1009F03E2 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 31BEC81E2755B154009AF22A /* Config.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Doolda/Doolda.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Doolda/Resources/Info.plist; + INFOPLIST_KEY_NSPhotoLibraryUsageDescription = ""; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( @@ -2221,7 +2965,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.boolbada.Doolda; + PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.doolda; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -2231,16 +2975,21 @@ }; 664893D3273007F1009F03E2 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 31BEC81E2755B154009AF22A /* Config.xcconfig */; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Doolda/Doolda.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Doolda/Resources/Info.plist; + INFOPLIST_KEY_NSPhotoLibraryUsageDescription = ""; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; + INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; LD_RUNPATH_SEARCH_PATHS = ( @@ -2248,7 +2997,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.boolbada.Doolda; + PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.doolda; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -2256,7 +3005,7 @@ }; name = Release; }; - 669ADE382732E4A8007B529B /* Debug */ = { + 66C2CA8B2755E9F200AFE5EE /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -2264,13 +3013,14 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.boolbada.Doolda.RefreshPairIdUseCase; + PRODUCT_BUNDLE_IDENTIFIER = doolda.PairUserUseCaseTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; @@ -2279,7 +3029,7 @@ }; name = Debug; }; - 669ADE392732E4A8007B529B /* Release */ = { + 66C2CA8C2755E9F200AFE5EE /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -2287,13 +3037,14 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.boolbada.Doolda.RefreshPairIdUseCase; + PRODUCT_BUNDLE_IDENTIFIER = doolda.PairUserUseCaseTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; @@ -2302,49 +3053,55 @@ }; name = Release; }; - FC5FB7A827328A19008AC3D2 /* Debug */ = { + 66C2CAB127561E1200AFE5EE /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = test.UserDefaultsPersistenceServiceTest; + PRODUCT_BUNDLE_IDENTIFIER = doolda.RefreshUserUseCaseTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Doolda.app/Doolda"; }; name = Debug; }; - FC5FB7A927328A19008AC3D2 /* Release */ = { + 66C2CAB227561E1200AFE5EE /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = test.UserDefaultsPersistenceServiceTest; + PRODUCT_BUNDLE_IDENTIFIER = doolda.RefreshUserUseCaseTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Doolda.app/Doolda"; }; name = Release; }; - FC5FB7D027339213008AC3D2 /* Debug */ = { + 66C2CAC6275623F700AFE5EE /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -2352,13 +3109,14 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = test.UserRepositoryTests; + PRODUCT_BUNDLE_IDENTIFIER = doolda.CheckMyTurnUseCaseTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; @@ -2367,7 +3125,7 @@ }; name = Debug; }; - FC5FB7D127339213008AC3D2 /* Release */ = { + 66C2CAC7275623F700AFE5EE /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -2375,13 +3133,14 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = test.UserRepositoryTests; + PRODUCT_BUNDLE_IDENTIFIER = doolda.CheckMyTurnUseCaseTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; @@ -2390,49 +3149,55 @@ }; name = Release; }; - FC9F1069273102E30060FEB7 /* Debug */ = { + 66C2CADC27562C1600AFE5EE /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = test.DooldaTests; + PRODUCT_BUNDLE_IDENTIFIER = doolda.GetPageUseCaseTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Doolda.app/Doolda"; }; name = Debug; }; - FC9F106A273102E30060FEB7 /* Release */ = { + 66C2CADD27562C1600AFE5EE /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = test.DooldaTests; + PRODUCT_BUNDLE_IDENTIFIER = doolda.GetPageUseCaseTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Doolda.app/Doolda"; }; name = Release; }; - FC9F108627315EB80060FEB7 /* Debug */ = { + 66C2CAF2275640CA00AFE5EE /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -2440,13 +3205,14 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = test.FirebaseNetworkTest; + PRODUCT_BUNDLE_IDENTIFIER = doolda.GetRawPageUseCaseTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; @@ -2455,7 +3221,7 @@ }; name = Debug; }; - FC9F108727315EB80060FEB7 /* Release */ = { + 66C2CAF3275640CA00AFE5EE /* Release */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -2463,13 +3229,14 @@ CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = B3PWYBKFUK; GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = test.FirebaseNetworkTest; + PRODUCT_BUNDLE_IDENTIFIER = doolda.GetRawPageUseCaseTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; @@ -2478,18 +3245,185 @@ }; name = Release; }; + FC4459B22755E8C4004888B9 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B3PWYBKFUK; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.doolda.GetMyIdUseCaseTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + FC4459B32755E8C4004888B9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B3PWYBKFUK; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.doolda.GetMyIdUseCaseTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + FC4459DE27560731004888B9 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B3PWYBKFUK; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.doolda.GlobalFontUseCaseTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + FC4459DF27560731004888B9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B3PWYBKFUK; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.doolda.GlobalFontUseCaseTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + FC4459F227561A48004888B9 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B3PWYBKFUK; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.doolda.PushNotificationStateUseCaseTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + FC4459F327561A48004888B9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B3PWYBKFUK; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.doolda.PushNotificationStateUseCaseTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + FC445A04275622CB004888B9 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B3PWYBKFUK; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.doolda.TextUseCaseTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + FC445A05275622CB004888B9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = B3PWYBKFUK; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = kr.codesquad.doolda.TextUseCaseTest; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 1D0C8BCD2732B5F90091ACC0 /* Build configuration list for PBXNativeTarget "GetPairIdUseCaseTest" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 1D0C8BCE2732B5F90091ACC0 /* Debug */, - 1D0C8BCF2732B5F90091ACC0 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; 1D3925BE273BB4BE003A0B70 /* Build configuration list for PBXNativeTarget "EditPageUseCaseTest" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -2499,38 +3433,38 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 310125D12739A56600E43160 /* Build configuration list for PBXNativeTarget "ImageComposeUseCaseTest" */ = { + 1D992E382755E8EC002C4457 /* Build configuration list for PBXNativeTarget "GetUserUseCaseTest" */ = { isa = XCConfigurationList; buildConfigurations = ( - 310125CF2739A56600E43160 /* Debug */, - 310125D02739A56600E43160 /* Release */, + 1D992E362755E8EC002C4457 /* Debug */, + 1D992E372755E8EC002C4457 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 310D812B2733C7FC00F15F4F /* Build configuration list for PBXNativeTarget "SplashViewModelTest" */ = { + 1DAEB015275603C90040E8C1 /* Build configuration list for PBXNativeTarget "RegisterUserUseCaseTest" */ = { isa = XCConfigurationList; buildConfigurations = ( - 310D81292733C7FC00F15F4F /* Debug */, - 310D812A2733C7FC00F15F4F /* Release */, + 1DAEB016275603C90040E8C1 /* Debug */, + 1DAEB017275603C90040E8C1 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 310D813E2733E56D00F15F4F /* Build configuration list for PBXNativeTarget "PairingViewModelTests" */ = { + 1DAEB02C27560E8F0040E8C1 /* Build configuration list for PBXNativeTarget "FCMTokenUseCaseTest" */ = { isa = XCConfigurationList; buildConfigurations = ( - 310D81392733E47A00F15F4F /* Debug */, - 310D813A2733E47A00F15F4F /* Release */, + 1DAEB02A27560E8F0040E8C1 /* Debug */, + 1DAEB02B27560E8F0040E8C1 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 310D813F2733E56D00F15F4F /* Build configuration list for PBXNativeTarget "GeneratePairIdUseCaseTests" */ = { + 1DAEB042275617010040E8C1 /* Build configuration list for PBXNativeTarget "FirebaseMessageUseCaseTest" */ = { isa = XCConfigurationList; buildConfigurations = ( - 310D813B2733E47A00F15F4F /* Debug */, - 310D813C2733E47A00F15F4F /* Release */, + 1DAEB040275617010040E8C1 /* Debug */, + 1DAEB041275617010040E8C1 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -2544,6 +3478,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 31BEC8372755EA89009AF22A /* Build configuration list for PBXNativeTarget "ImageUseCaseTest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 31BEC8352755EA89009AF22A /* Debug */, + 31BEC8362755EA89009AF22A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 664893B8273007ED009F03E2 /* Build configuration list for PBXProject "Doolda" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -2562,47 +3505,83 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 669ADE3A2732E4A8007B529B /* Build configuration list for PBXNativeTarget "RefreshPairIdUseCaseTest" */ = { + 66C2CA8D2755E9F200AFE5EE /* Build configuration list for PBXNativeTarget "PairUserUseCaseTest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 66C2CA8B2755E9F200AFE5EE /* Debug */, + 66C2CA8C2755E9F200AFE5EE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 66C2CAB327561E1200AFE5EE /* Build configuration list for PBXNativeTarget "RefreshUserUseCaseTest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 66C2CAB127561E1200AFE5EE /* Debug */, + 66C2CAB227561E1200AFE5EE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 66C2CAC8275623F700AFE5EE /* Build configuration list for PBXNativeTarget "CheckMyTurnUseCaseTest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 66C2CAC6275623F700AFE5EE /* Debug */, + 66C2CAC7275623F700AFE5EE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 66C2CADE27562C1600AFE5EE /* Build configuration list for PBXNativeTarget "GetPageUseCaseTest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 66C2CADC27562C1600AFE5EE /* Debug */, + 66C2CADD27562C1600AFE5EE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 66C2CAF4275640CA00AFE5EE /* Build configuration list for PBXNativeTarget "GetRawPageUseCaseTest" */ = { isa = XCConfigurationList; buildConfigurations = ( - 669ADE382732E4A8007B529B /* Debug */, - 669ADE392732E4A8007B529B /* Release */, + 66C2CAF2275640CA00AFE5EE /* Debug */, + 66C2CAF3275640CA00AFE5EE /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - FC5FB7A727328A19008AC3D2 /* Build configuration list for PBXNativeTarget "UserDefaultsPersistenceServiceTest" */ = { + FC4459B12755E8C4004888B9 /* Build configuration list for PBXNativeTarget "GetMyIdUseCaseTest" */ = { isa = XCConfigurationList; buildConfigurations = ( - FC5FB7A827328A19008AC3D2 /* Debug */, - FC5FB7A927328A19008AC3D2 /* Release */, + FC4459B22755E8C4004888B9 /* Debug */, + FC4459B32755E8C4004888B9 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - FC5FB7CF27339213008AC3D2 /* Build configuration list for PBXNativeTarget "UserRepositoryTests" */ = { + FC4459E027560731004888B9 /* Build configuration list for PBXNativeTarget "GlobalFontUseCaseTest" */ = { isa = XCConfigurationList; buildConfigurations = ( - FC5FB7D027339213008AC3D2 /* Debug */, - FC5FB7D127339213008AC3D2 /* Release */, + FC4459DE27560731004888B9 /* Debug */, + FC4459DF27560731004888B9 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - FC9F1068273102E30060FEB7 /* Build configuration list for PBXNativeTarget "GetMyIdUseCaseTest" */ = { + FC4459F427561A48004888B9 /* Build configuration list for PBXNativeTarget "PushNotificationStateUseCaseTest" */ = { isa = XCConfigurationList; buildConfigurations = ( - FC9F1069273102E30060FEB7 /* Debug */, - FC9F106A273102E30060FEB7 /* Release */, + FC4459F227561A48004888B9 /* Debug */, + FC4459F327561A48004888B9 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - FC9F108527315EB80060FEB7 /* Build configuration list for PBXNativeTarget "FirebaseNetworkTest" */ = { + FC445A06275622CB004888B9 /* Build configuration list for PBXNativeTarget "TextUseCaseTest" */ = { isa = XCConfigurationList; buildConfigurations = ( - FC9F108627315EB80060FEB7 /* Debug */, - FC9F108727315EB80060FEB7 /* Release */, + FC445A04275622CB004888B9 /* Debug */, + FC445A05275622CB004888B9 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -2610,41 +3589,55 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 664893EB27300D7D009F03E2 /* XCRemoteSwiftPackageReference "SnapKit" */ = { + 1D3BAC06274E2F3B00CD23EE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/SnapKit/SnapKit.git"; + repositoryURL = "https://github.com/firebase/firebase-ios-sdk.git"; requirement = { - branch = develop; - kind = branch; + kind = versionRange; + maximumVersion = 8.0.0; + minimumVersion = 7.0.0; }; }; - 664893EE27300DB9009F03E2 /* XCRemoteSwiftPackageReference "Kingfisher" */ = { + 1D3BAC09274E2F6500CD23EE /* XCRemoteSwiftPackageReference "SnapKit" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/onevcat/Kingfisher.git"; + repositoryURL = "https://github.com/SnapKit/SnapKit.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 7.0.0; + minimumVersion = 5.0.0; }; }; - 664893F127300E28009F03E2 /* XCRemoteSwiftPackageReference "SwiftLint" */ = { + 1D3BAC0C274E2FDA00CD23EE /* XCRemoteSwiftPackageReference "SwiftLint" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/realm/SwiftLint"; + repositoryURL = "https://github.com/realm/SwiftLint.git"; requirement = { kind = upToNextMajorVersion; minimumVersion = 0.9.2; }; }; + 1D3BAC0D274E307A00CD23EE /* XCRemoteSwiftPackageReference "Kingfisher" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/onevcat/Kingfisher"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 7.0.0; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 664893EC27300D7D009F03E2 /* SnapKit */ = { + 1D3BAC07274E2F3B00CD23EE /* FirebaseMessaging */ = { + isa = XCSwiftPackageProductDependency; + package = 1D3BAC06274E2F3B00CD23EE /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */; + productName = FirebaseMessaging; + }; + 1D3BAC0A274E2F6500CD23EE /* SnapKit */ = { isa = XCSwiftPackageProductDependency; - package = 664893EB27300D7D009F03E2 /* XCRemoteSwiftPackageReference "SnapKit" */; + package = 1D3BAC09274E2F6500CD23EE /* XCRemoteSwiftPackageReference "SnapKit" */; productName = SnapKit; }; - 664893EF27300DB9009F03E2 /* Kingfisher */ = { + 1D3BAC0E274E307A00CD23EE /* Kingfisher */ = { isa = XCSwiftPackageProductDependency; - package = 664893EE27300DB9009F03E2 /* XCRemoteSwiftPackageReference "Kingfisher" */; + package = 1D3BAC0D274E307A00CD23EE /* XCRemoteSwiftPackageReference "Kingfisher" */; productName = Kingfisher; }; /* End XCSwiftPackageProductDependency section */ diff --git a/Doolda/Doolda.xcodeproj/xcshareddata/xcschemes/Doolda.xcscheme b/Doolda/Doolda.xcodeproj/xcshareddata/xcschemes/Doolda.xcscheme index 950d0bb2..867335fc 100644 --- a/Doolda/Doolda.xcodeproj/xcshareddata/xcschemes/Doolda.xcscheme +++ b/Doolda/Doolda.xcodeproj/xcshareddata/xcschemes/Doolda.xcscheme @@ -32,9 +32,9 @@ skipped = "NO"> @@ -42,9 +42,9 @@ skipped = "NO"> @@ -52,9 +52,9 @@ skipped = "NO"> @@ -62,9 +62,9 @@ skipped = "NO"> @@ -72,9 +72,9 @@ skipped = "NO"> @@ -82,9 +82,9 @@ skipped = "NO"> @@ -92,9 +92,9 @@ skipped = "NO"> @@ -102,9 +102,9 @@ skipped = "NO"> @@ -112,9 +112,9 @@ skipped = "NO"> @@ -122,9 +122,69 @@ skipped = "NO"> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Doolda/Doolda.xcodeproj/xcshareddata/xcschemes/GetMyIdUseCaseTest.xcscheme b/Doolda/Doolda.xcodeproj/xcshareddata/xcschemes/GetMyIdUseCaseTest.xcscheme new file mode 100644 index 00000000..d59fadf2 --- /dev/null +++ b/Doolda/Doolda.xcodeproj/xcshareddata/xcschemes/GetMyIdUseCaseTest.xcscheme @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Doolda/Doolda.xcodeproj/xcshareddata/xcschemes/GlobalFontUseCaseTest.xcscheme b/Doolda/Doolda.xcodeproj/xcshareddata/xcschemes/GlobalFontUseCaseTest.xcscheme new file mode 100644 index 00000000..013dd872 --- /dev/null +++ b/Doolda/Doolda.xcodeproj/xcshareddata/xcschemes/GlobalFontUseCaseTest.xcscheme @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Doolda/Doolda.xcodeproj/xcshareddata/xcschemes/ImageUseCaseTest.xcscheme b/Doolda/Doolda.xcodeproj/xcshareddata/xcschemes/ImageUseCaseTest.xcscheme new file mode 100644 index 00000000..9bb10baa --- /dev/null +++ b/Doolda/Doolda.xcodeproj/xcshareddata/xcschemes/ImageUseCaseTest.xcscheme @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Doolda/Doolda.xcodeproj/xcshareddata/xcschemes/UserDefaultsPersistenceServiceTest.xcscheme b/Doolda/Doolda.xcodeproj/xcshareddata/xcschemes/UserDefaultsPersistenceServiceTest.xcscheme new file mode 100644 index 00000000..32acf210 --- /dev/null +++ b/Doolda/Doolda.xcodeproj/xcshareddata/xcschemes/UserDefaultsPersistenceServiceTest.xcscheme @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Doolda/Doolda/Application/AppCoordinator.swift b/Doolda/Doolda/Application/AppCoordinator.swift new file mode 100644 index 00000000..f6024f09 --- /dev/null +++ b/Doolda/Doolda/Application/AppCoordinator.swift @@ -0,0 +1,61 @@ +// +// AppCoordinator.swift +// Doolda +// +// Created by 정지승 on 2021/11/01. +// + +import Combine +import UIKit + +final class AppCoordinator: BaseCoordinator { + + // MARK: - Nested enum + + enum Notifications { + static let coordinatorDidPop = Notification.Name("coordinatorDidPop") + static let appRestartSignal = Notification.Name("appRestartSignal") + } + + enum Keys { + static let coordinatorIdentifier = "coordinatorIdentifier" + } + + private var cancellables: Set = [] + + override init(identifier: UUID = UUID(), presenter: UINavigationController) { + super.init(identifier: identifier, presenter: presenter) + self.bind() + } + + private func bind() { + NotificationCenter.default.publisher(for: PushMessageEntity.Notifications.userDisconnected, object: nil) + .sink { [weak self] _ in + self?.start() + } + .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: Notifications.coordinatorDidPop, object: nil) + .compactMap { $0.userInfo?[Keys.coordinatorIdentifier] as? UUID } + .sink { [weak self] identifier in + self?.children.removeValue(forKey: identifier) + } + .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: Notifications.appRestartSignal, object: nil) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.children.removeAll() + self?.presenter.children.forEach { $0.removeFromParent() } + self?.start() + } + .store(in: &self.cancellables) + } + + func start() { + let splashIdentifier = UUID() + let splashViewCoordinator = SplashViewCoordinator(identifier: splashIdentifier, presenter: self.presenter) + self.children[splashIdentifier] = splashViewCoordinator + splashViewCoordinator.start() + } +} diff --git a/Doolda/Doolda/Application/AppDelegate.swift b/Doolda/Doolda/Application/AppDelegate.swift index fe5309f2..a2d1aef7 100644 --- a/Doolda/Doolda/Application/AppDelegate.swift +++ b/Doolda/Doolda/Application/AppDelegate.swift @@ -5,25 +5,113 @@ // Created by 정지승 on 2021/11/01. // +import Combine import UIKit +import Firebase +import FirebaseMessaging + @main class AppDelegate: UIResponder, UIApplicationDelegate { + + private var cancellables: Set = [] + func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { + FirebaseApp.configure() + Messaging.messaging().delegate = self + UNUserNotificationCenter.current().delegate = self + + UserDefaults.standard.register(defaults: [ + UserDefaults.Keys.globalFont: FontType.dovemayo.name, + UserDefaults.Keys.pushNotificationState: true + ]) + + let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] + UNUserNotificationCenter.current().requestAuthorization( + options: authOptions, + completionHandler: { _, _ in } + ) + + application.registerForRemoteNotifications() + return true } - func application( - _ application: UIApplication, - configurationForConnecting connectingSceneSession: UISceneSession, - options: UIScene.ConnectionOptions - ) -> UISceneConfiguration { - return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + Messaging.messaging().apnsToken = deviceToken } +} + +extension AppDelegate: UNUserNotificationCenterDelegate { + func userNotificationCenter( + _ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void + ) { + let userInfo = response.notification.request.content.userInfo + guard let notification = userInfo["notification"] as? String else { return } + + switch notification { + case "userRequestedNewPage": + NotificationCenter.default.post(name: PushMessageEntity.Notifications.userRequestedNewPage, object: nil) + default: break + } + } + + func userNotificationCenter( + _ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void + ) { + let userDefaultsPersistenceService = UserDefaultsPersistenceService.shared + let pushNotificationStateRepository = PushNotificationStateRepository(persistenceService: userDefaultsPersistenceService) + let pushNotificationStateUseCase = PushNotificationStateUseCase(pushNotificationStateRepository: pushNotificationStateRepository) + + guard let state = pushNotificationStateUseCase.getPushNotificationState(), state else { return } + completionHandler([.banner, .sound]) + let userInfo = notification.request.content.userInfo + guard let notification = userInfo["notification"] as? String else { return } + + switch notification { + case "userPairedWithFriend": + NotificationCenter.default.post(name: PushMessageEntity.Notifications.userPairedWithFriend, object: nil) + case "userPostedNewPage": + NotificationCenter.default.post(name: PushMessageEntity.Notifications.userPostedNewPage, object: nil) + case "userDisconnected": + NotificationCenter.default.post(name: PushMessageEntity.Notifications.userDisconnected, object: nil) + default: break + } + } +} - func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { +extension AppDelegate: MessagingDelegate { + func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) { + guard let token = fcmToken else { return } + + // FIXME: 더 좋은 방법 찾아보기 + let networkService = URLSessionNetworkService.shared + let persistenceService = UserDefaultsPersistenceService.shared + let userRepository: UserRepository = UserRepository(persistenceService: persistenceService, networkService: networkService) + let fcmTokenRepository: FCMTokenRepository = FCMTokenRepository(urlSessionNetworkService: networkService) + let getMyIdUseCase: GetMyIdUseCase = GetMyIdUseCase(userRepository: userRepository) + let fcmTokenUseCase: FCMTokenUseCase = FCMTokenUseCase(fcmTokenRepository: fcmTokenRepository) + + getMyIdUseCase.getMyId() + .compactMap { $0 } + .sink { [weak self] myId in + guard let self = self else { return } + fcmTokenUseCase.setToken(for: myId, with: token) + .sink { completion in + guard case .failure(let error) = completion else { return } + print(error.localizedDescription) + } receiveValue: { token in + print("SUCCESS: \(token)") + } + .store(in: &self.cancellables) + } + .store(in: &self.cancellables) } } diff --git a/Doolda/Doolda/Common/BaseCoordinator.swift b/Doolda/Doolda/Common/BaseCoordinator.swift new file mode 100644 index 00000000..e05887dd --- /dev/null +++ b/Doolda/Doolda/Common/BaseCoordinator.swift @@ -0,0 +1,45 @@ +// +// BaseCoordinator.swift +// Doolda +// +// Created by Seunghun Yang on 2021/11/08. +// + +import Combine +import UIKit + +class BaseCoordinator { + + // MARK: - Nested Enums + + enum Notifications { + static let coordinatorRemoveFromParent = Notification.Name("coordinatorRemoveFromParent") + } + + enum Keys { + static let sceneId = "sceneId" + } + + var identifier: UUID + var presenter: UINavigationController + var children: [UUID: BaseCoordinator] = [:] + + private var cancellables: Set = [] + + init(identifier: UUID, presenter: UINavigationController) { + self.identifier = identifier + self.presenter = presenter + self.bindRemoveRequest() + } + + func bindRemoveRequest() { + NotificationCenter.default.publisher(for: Notifications.coordinatorRemoveFromParent, object: nil) + .compactMap { $0.userInfo?[Keys.sceneId] as? UUID } + .filter { [weak self] identifier in + guard let self = self else { return false } + return self.children.contains { $0.key == identifier } + } + .sink { [weak self] identifier in self?.children.removeValue(forKey: identifier) } + .store(in: &self.cancellables) + } +} diff --git a/Doolda/Doolda/UI/ViewControllers/BottomSheetViewController.swift b/Doolda/Doolda/Common/BottomSheetViewController.swift similarity index 90% rename from Doolda/Doolda/UI/ViewControllers/BottomSheetViewController.swift rename to Doolda/Doolda/Common/BottomSheetViewController.swift index 93bee321..e82973a1 100644 --- a/Doolda/Doolda/UI/ViewControllers/BottomSheetViewController.swift +++ b/Doolda/Doolda/Common/BottomSheetViewController.swift @@ -17,6 +17,7 @@ class BottomSheetViewController: UIViewController { case medium case small case zero + case custom(CGFloat) func calculateHeight(baseView: UIView) -> CGFloat { switch self { @@ -30,6 +31,8 @@ class BottomSheetViewController: UIViewController { return baseView.frame.size.height * 0.25 case .zero: return .zero + case .custom(let height): + return height } } } @@ -41,10 +44,10 @@ class BottomSheetViewController: UIViewController { view.backgroundColor = .white view.layer.cornerRadius = 10 view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] - view.layer.shadowColor = UIColor.lightGray.cgColor + view.layer.shadowColor = UIColor.black.cgColor view.layer.shadowRadius = 10 - view.layer.shadowOpacity = 0.7 - view.layer.shadowOffset = CGSize(width: 0, height: -10) + view.layer.shadowOpacity = 0.1 + view.layer.shadowOffset = CGSize(width: 0, height: -3) return view }() @@ -93,11 +96,15 @@ class BottomSheetViewController: UIViewController { // MARK: - Override Methods - override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { - let duration = flag ? 0.25 : 0.0 + override func dismiss(animated: Bool, completion: (() -> Void)? = nil) { + let duration = animated ? 0.25 : 0.0 - hideBottomSheet(duration: duration) { - super.dismiss(animated: false, completion: completion) + if self.presentedViewController == nil { + hideBottomSheet(duration: duration) { + super.dismiss(animated: false, completion: completion) + } + } else { + super.dismiss(animated: animated, completion: completion) } } diff --git a/Doolda/Doolda/UI/Views/CustomActivityIndicator.swift b/Doolda/Doolda/Common/CustomActivityIndicator.swift similarity index 73% rename from Doolda/Doolda/UI/Views/CustomActivityIndicator.swift rename to Doolda/Doolda/Common/CustomActivityIndicator.swift index 9eb0072e..5320d10a 100644 --- a/Doolda/Doolda/UI/Views/CustomActivityIndicator.swift +++ b/Doolda/Doolda/Common/CustomActivityIndicator.swift @@ -14,25 +14,23 @@ final class CustomActivityIndicator: UIView { // MARK: - Subviews - private lazy var activityIndicatorImage: UIImageView = { + private lazy var loadingImageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFill - imageView.image = .hedgehog return imageView }() private lazy var activityIndicator: UIActivityIndicatorView = { let indicator = UIActivityIndicatorView() - indicator.color = .dooldaSubLabel + indicator.color = .dooldaSublabel indicator.hidesWhenStopped = false return indicator }() private lazy var subTitle: UILabel = { let label = UILabel() - label.font = .systemFont(ofSize: 14) label.textAlignment = .center - label.textColor = .dooldaSubLabel + label.textColor = .dooldaSublabel return label }() @@ -42,16 +40,18 @@ final class CustomActivityIndicator: UIView { // MARK: - Initializers - convenience init(subTitle: String?) { + convenience init(subTitle: String?, loadingImage: UIImage? = .hedgehog) { self.init(frame: .zero) self.subTitle.text = subTitle + self.loadingImageView.image = loadingImage configureUI() + configureFont() bindUI() } // MARK: - Helpers - func configureUI() { + private func configureUI() { self.backgroundColor = .dooldaActivityIndicatorBackground self.addSubview(self.activityIndicator) @@ -60,8 +60,8 @@ final class CustomActivityIndicator: UIView { make.center.equalToSuperview() } - self.addSubview(self.activityIndicatorImage) - self.activityIndicatorImage.snp.makeConstraints { make in + self.addSubview(self.loadingImageView) + self.loadingImageView.snp.makeConstraints { make in make.width.height.equalTo(100) make.centerX.equalToSuperview() make.bottom.equalTo(self.activityIndicator.snp.top).offset(-10) @@ -74,7 +74,11 @@ final class CustomActivityIndicator: UIView { } } - func bindUI() { + private func configureFont() { + self.subTitle.font = .systemFont(ofSize: 20) + } + + private func bindUI() { self.publisher(for: UITapGestureRecognizer()) .sink { _ in } .store(in: &self.cancellables) @@ -82,6 +86,12 @@ final class CustomActivityIndicator: UIView { self.publisher(for: UIPanGestureRecognizer()) .sink { _ in } .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: GlobalFontUseCase.Notifications.globalFontDidSet, object: nil) + .sink { [weak self] _ in + self?.configureFont() + } + .store(in: &self.cancellables) } // MARK: - Public Methods diff --git a/Doolda/Doolda/Common/DiaryPageView.swift b/Doolda/Doolda/Common/DiaryPageView.swift new file mode 100644 index 00000000..9d9b81dd --- /dev/null +++ b/Doolda/Doolda/Common/DiaryPageView.swift @@ -0,0 +1,145 @@ +// +// DiaryPageView.swift +// Doolda +// +// Created by Seunghun Yang on 2021/11/29. +// + +import Combine +import UIKit + +protocol DiaryPageViewDelegate: AnyObject { + func diaryPageDrawDidFinish(_ diaryPageView: DiaryPageView) +} + +class DiaryPageView: UIView { + + // MARK: - Subviews + + private lazy var componentCanvasView: UIView = UIView() + + // MARK: - Public Properties + + weak var delegate: DiaryPageViewDelegate? + @Published var pageBackgroundColor: UIColor? + @Published var components: [ComponentEntity]? + + // MARK: - Private Properties + + private var cancellables: Set = [] + private var widthRatioFromAbsolute: CGFloat { self.frame.size.width / 1700.0 } + private var heightRatioFromAbsolute: CGFloat { self.frame.size.height / 3000.0 } + + // MARK: - Initializers + + override init(frame: CGRect) { + super.init(frame: frame) + self.configureUI() + self.bindUI() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + self.configureUI() + self.bindUI() + } + + // MARK: - Override Methods + + override func layoutSubviews() { + super.layoutSubviews() + self.drawComponents(using: self.components, with: self.pageBackgroundColor) + } + + // MARK: - Helpers + + private func configureUI() { + self.addSubview(self.componentCanvasView) + self.componentCanvasView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + + private func bindUI() { + Publishers.CombineLatest(self.$components, self.$pageBackgroundColor) + .receive(on: DispatchQueue.main) + .sink { [weak self] components, pageBackgroundColor in + self?.drawComponents(using: components, with: pageBackgroundColor) + } + .store(in: &self.cancellables) + + + NotificationCenter.default.publisher(for: GlobalFontUseCase.Notifications.globalFontDidSet, object: nil) + .sink { [weak self] _ in + self?.configureFont() + } + .store(in: &self.cancellables) + } + + // MARK: - Private Methods + + private func drawComponents(using components: [ComponentEntity]?, with color: UIColor?) { + guard let components = components else { return } + self.backgroundColor = pageBackgroundColor + self.componentCanvasView.subviews.forEach { $0.removeFromSuperview() } + + for component in components { + guard let componentView = self.getComponentView(from: component) else { return } + componentView.frame = CGRect( + origin: self.computePointFromAbsolute(at: component.origin), + size: self.computeSizeFromAbsolute(with: component.frame.size) + ) + componentView.transform = CGAffineTransform.identity + .rotated(by: component.angle) + .scaledBy(x: component.scale, y: component.scale) + + self.componentCanvasView.addSubview(componentView) + } + self.delegate?.diaryPageDrawDidFinish(self) + } + + private func getComponentView(from componentEntity: ComponentEntity) -> UIView? { + switch componentEntity { + case let photoComponentEtitiy as PhotoComponentEntity: + let photoView = UIImageView() + photoView.kf.setImage(with: photoComponentEtitiy.imageUrl) + photoView.layer.shadowColor = UIColor.lightGray.cgColor + photoView.layer.shadowOpacity = 0.3 + photoView.layer.shadowRadius = 10 + photoView.layer.shadowOffset = CGSize(width: -5, height: -5) + return photoView + case let stickerComponentEntity as StickerComponentEntity: + let stickerView = UIImageView() + stickerView.image = UIImage(named: stickerComponentEntity.name) + stickerView.contentMode = .scaleAspectFit + return stickerView + case let textComponentEntity as TextComponentEntity: + let textView = UILabel() + textView.numberOfLines = 0 + textView.textAlignment = .center + textView.adjustsFontSizeToFitWidth = true + textView.adjustsFontForContentSizeCategory = true + textView.text = textComponentEntity.text + textView.textColor = UIColor(cgColor: textComponentEntity.fontColor.rawValue) + textView.font = .systemFont(ofSize: textComponentEntity.fontSize) + return textView + default: return nil + } + } + + private func computePointFromAbsolute(at point: CGPoint) -> CGPoint { + let computedX = point.x * self.widthRatioFromAbsolute + let computedY = point.y * self.heightRatioFromAbsolute + return CGPoint(x: computedX, y: computedY) + } + + private func computeSizeFromAbsolute(with size: CGSize) -> CGSize { + let computedWidth = size.width * self.widthRatioFromAbsolute + let computedHeight = size.height * self.widthRatioFromAbsolute + return CGSize(width: computedWidth, height: computedHeight) + } + + private func configureFont() { + self.drawComponents(using: self.components, with: self.backgroundColor) + } +} diff --git a/Doolda/Doolda/Common/DooldaButton.swift b/Doolda/Doolda/Common/DooldaButton.swift new file mode 100644 index 00000000..b9f1ccf9 --- /dev/null +++ b/Doolda/Doolda/Common/DooldaButton.swift @@ -0,0 +1,56 @@ +// +// DooldaButton.swift +// Doolda +// +// Created by Seunghun Yang on 2021/11/21. +// + +import Combine +import UIKit + +class DooldaButton: UIButton { + + // Private Properties + + private var cancellables: Set = [] + private lazy var hapticGenerator: UIImpactFeedbackGenerator = { + let generator = UIImpactFeedbackGenerator(style: .light) + return generator + }() + + // Override Properties + + override var isEnabled: Bool { + didSet { self.alpha = self.isEnabled ? 1.0 : 0.5 } + } + + // Initializers + + init() { + super.init(frame: .zero) + self.bindUI() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + self.bindUI() + } + + // LifeCycle Methods + + override func layoutSubviews() { + super.layoutSubviews() + self.layer.cornerRadius = self.frame.height / 2 + } + + // Private Methods + + private func bindUI() { + self.publisher(for: .touchUpInside) + .sink { [weak self] _ in + self?.hapticGenerator.prepare() + self?.hapticGenerator.impactOccurred() + } + .store(in: &self.cancellables) + } +} diff --git a/Doolda/Doolda/Config.xcconfig b/Doolda/Doolda/Config.xcconfig new file mode 100644 index 00000000..cbf95e1c --- /dev/null +++ b/Doolda/Doolda/Config.xcconfig @@ -0,0 +1,11 @@ +// +// Config.xcconfig +// Doolda +// +// Created by Seunghun Yang on 2021/11/22. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 + +FCM_SERVER_KEY = AAAABi_vS2s:APA91bGcfkGUsRD4r7ZgONyU987ZQKw5ifuzDIUdk35ohKKN1rROyK8zv_HG899-LZzIKePAtzYukrbYCj8rL_prOlGuqz7J07DJPaUsCTigaLe0yHClseGzaAuSiP-o6gyrmvQwSuNz diff --git a/Doolda/Doolda/Data/DataTransferObjects/FCMTokenDocument.swift b/Doolda/Doolda/Data/DataTransferObjects/FCMTokenDocument.swift new file mode 100644 index 00000000..f5957fcc --- /dev/null +++ b/Doolda/Doolda/Data/DataTransferObjects/FCMTokenDocument.swift @@ -0,0 +1,30 @@ +// +// FCMTokenDocument.swift +// Doolda +// +// Created by Seunghun Yang on 2021/11/23. +// + +import Foundation + +struct FCMTokenDocument: Codable { + var token: String? { return self.fields["token"]?["stringValue"] } + let fields: [String: [String: String]] + + init(token: String) { + self.fields = [ + "token": [ + "stringValue": token + ] + ] + } + + init?(document: [String: [String: String]]) { + guard let token = document["token"]?["stringValue"] else { return nil } + self.fields = [ + "token": [ + "stringValue": token + ] + ] + } +} diff --git a/Doolda/Doolda/Data/DataTransferObjects/PageDocument.swift b/Doolda/Doolda/Data/DataTransferObjects/PageDocument.swift index 4af79867..4aed3583 100644 --- a/Doolda/Doolda/Data/DataTransferObjects/PageDocument.swift +++ b/Doolda/Doolda/Data/DataTransferObjects/PageDocument.swift @@ -10,18 +10,23 @@ import Foundation struct PageDocument: Codable { var authorId: String? { return self.fields["author"]?["stringValue"] } var pairId: String? { return self.fields["pairId"]?["stringValue"] } - var timeStamp: String? { return self.fields["createdTime"]?["timestampValue"] } + var createdTime: String? { return self.fields["createdTime"]?["timestampValue"] } + var updatedTime: String? { return self.fields["updatedTime"]?["timestampValue"] } var jsonPath: String? { return self.fields["jsonPath"]?["stringValue"] } let fields: [String: [String: String]] - init(author: String, createdTime: Date, jsonPath: String, pairId: String) { - let formattedString = DateFormatter.firestoreFormatter.string(from: createdTime) + init(author: String, createdTime: Date, updatedTime: Date, jsonPath: String, pairId: String) { + let formattedCreatedTimeString = DateFormatter.firestoreFormatter.string(from: createdTime) + let formattedUpdatedTimeString = DateFormatter.firestoreFormatter.string(from: updatedTime) self.fields = [ "author": [ "stringValue": author ], "createdTime": [ - "timestampValue": formattedString + "timestampValue": formattedCreatedTimeString + ], + "updatedTime": [ + "timestampValue": formattedUpdatedTimeString ], "jsonPath": [ "stringValue": jsonPath @@ -35,6 +40,7 @@ struct PageDocument: Codable { init?(document: [String: [String: String]]) { guard let author = document["author"]?["stringValue"], let createdTime = document["createdTime"]?["timestampValue"], + let updatedTime = document["updatedTime"]?["timestampValue"], let jsonPath = document["jsonPath"]?["stringValue"], let pairId = document["pairId"]?["stringValue"] else { return nil } self.fields = [ @@ -44,6 +50,9 @@ struct PageDocument: Codable { "createdTime": [ "timestampValue": createdTime ], + "updatedTime": [ + "timestampValue": updatedTime + ], "jsonPath": [ "stringValue": jsonPath ], @@ -58,9 +67,16 @@ struct PageDocument: Codable { let pairId = self.pairId, let authorDDID = DDID(from: authorId), let pairDDID = DDID(from: pairId), - let timeStamp = self.timeStamp, - let formattedDate = DateFormatter.firestoreFormatter.date(from: timeStamp), + let createdTimeString = self.createdTime, + let formattedCreatedTime = DateFormatter.firestoreFormatter.date(from: createdTimeString), + let updatedTimeString = self.updatedTime, + let formattedUpdatedTime = DateFormatter.firestoreFormatter.date(from: updatedTimeString), let jsonPath = self.jsonPath else { return nil } - return PageEntity(author: User(id: authorDDID, pairId: pairDDID), timeStamp: formattedDate, jsonPath: jsonPath) + return PageEntity( + author: User(id: authorDDID, pairId: pairDDID), + createdTime: formattedCreatedTime, + updatedTime: formattedUpdatedTime, + jsonPath: jsonPath + ) } } diff --git a/Doolda/Doolda/Data/DataTransferObjects/UserDocument.swift b/Doolda/Doolda/Data/DataTransferObjects/UserDocument.swift index dd5328a9..e52439c1 100644 --- a/Doolda/Doolda/Data/DataTransferObjects/UserDocument.swift +++ b/Doolda/Doolda/Data/DataTransferObjects/UserDocument.swift @@ -14,15 +14,21 @@ struct UserDocument: Codable { var pairId: String? { return self.fields["pairId"]?["stringValue"] } + var friendId: String? { + return self.fields["friendId"]?["stringValue"] + } let name: String let fields: [String: [String: String]] - init(userId: String, pairId: String) { + init(userId: String, pairId: String, friendId: String) { self.name = userId self.fields = [ "pairId": [ "stringValue": pairId + ], + "friendId": [ + "stringValue": friendId ] ] } @@ -30,7 +36,8 @@ struct UserDocument: Codable { func toUser() -> User? { guard let userIdString = self.userId, let userDDID = DDID(from: userIdString), - let pairIdString = self.pairId else { return nil } - return User(id: userDDID, pairId: DDID(from: pairIdString)) + let pairIdString = self.pairId, + let friendIdString = self.friendId else { return nil } + return User(id: userDDID, pairId: DDID(from: pairIdString), friendId: DDID(from: friendIdString)) } } diff --git a/Doolda/Doolda/Data/Interfaces/CoreDataPageEntityPersistenceServiceProtocol.swift b/Doolda/Doolda/Data/Interfaces/CoreDataPageEntityPersistenceServiceProtocol.swift index f07af002..5df3ffd1 100644 --- a/Doolda/Doolda/Data/Interfaces/CoreDataPageEntityPersistenceServiceProtocol.swift +++ b/Doolda/Doolda/Data/Interfaces/CoreDataPageEntityPersistenceServiceProtocol.swift @@ -9,7 +9,8 @@ import Combine import Foundation protocol CoreDataPageEntityPersistenceServiceProtocol { + func isPageEntityUpToDate(_ pageEntity: PageEntity) -> AnyPublisher func fetchPageEntities() -> AnyPublisher<[PageEntity], Error> - func savePageEntity(_ pageEntity: PageEntity) -> AnyPublisher + func savePageEntity(_ pageEntity: PageEntity) -> AnyPublisher func removeAllPageEntity() -> AnyPublisher } diff --git a/Doolda/Doolda/Data/Repositories/FCMTokenRepository.swift b/Doolda/Doolda/Data/Repositories/FCMTokenRepository.swift new file mode 100644 index 00000000..fa93109b --- /dev/null +++ b/Doolda/Doolda/Data/Repositories/FCMTokenRepository.swift @@ -0,0 +1,33 @@ +// +// FCMRepository.swift +// Doolda +// +// Created by Seunghun Yang on 2021/11/23. +// + +import Combine +import Foundation + +class FCMTokenRepository: FCMTokenRepositoryProtocol { + private let urlSessionNetworkService: URLSessionNetworkServiceProtocol + + init(urlSessionNetworkService: URLSessionNetworkServiceProtocol) { + self.urlSessionNetworkService = urlSessionNetworkService + } + + func saveToken(for userId: DDID, with token: String) -> AnyPublisher { + let request = FirebaseAPIs.patchFCMTokenDocument(userId.ddidString, token) + let publisher: AnyPublisher = self.urlSessionNetworkService.request(request) + return publisher + .compactMap { $0.token } + .eraseToAnyPublisher() + } + + func fetchToken(for userId: DDID) -> AnyPublisher { + let request = FirebaseAPIs.getFCMTokenDocument(userId.ddidString) + let publisher: AnyPublisher = self.urlSessionNetworkService.request(request) + return publisher + .compactMap { $0.token } + .eraseToAnyPublisher() + } +} diff --git a/Doolda/Doolda/Data/Repositories/FirebaseMessageRepository.swift b/Doolda/Doolda/Data/Repositories/FirebaseMessageRepository.swift new file mode 100644 index 00000000..5e749587 --- /dev/null +++ b/Doolda/Doolda/Data/Repositories/FirebaseMessageRepository.swift @@ -0,0 +1,22 @@ +// +// FirebaseMessageRepository.swift +// Doolda +// +// Created by Seunghun Yang on 2021/11/23. +// + +import Combine +import Foundation + +class FirebaseMessageRepository: FirebaseMessageRepositoryProtocol { + private let urlSessionNetworkService: URLSessionNetworkServiceProtocol + + init(urlSessionNetworkService: URLSessionNetworkServiceProtocol) { + self.urlSessionNetworkService = urlSessionNetworkService + } + + func sendMessage(to token: String, title: String, body: String, data: [String : String]) -> AnyPublisher<[String : Any], Error> { + let request = FirebaseAPIs.sendFirebaseMessage(token, title, body, data) + return self.urlSessionNetworkService.request(request) + } +} diff --git a/Doolda/Doolda/Data/Repositories/PageRepository.swift b/Doolda/Doolda/Data/Repositories/PageRepository.swift index 0db9e8bd..7dd7c785 100644 --- a/Doolda/Doolda/Data/Repositories/PageRepository.swift +++ b/Doolda/Doolda/Data/Repositories/PageRepository.swift @@ -12,11 +12,13 @@ import OSLog enum PageRepositoryError: LocalizedError { case userNotPaired case failedToFetchPages + case failedToUpdatePage var errorDescription: String? { switch self { case .userNotPaired: return "페어링 된 유저가 없습니다." case .failedToFetchPages: return "페이지 패치에 실패했습니다." + case .failedToUpdatePage: return "페이지 업데이트에 실패했습니다." } } } @@ -37,7 +39,7 @@ class PageRepository: PageRepositoryProtocol { func savePage(_ page: PageEntity) -> AnyPublisher { guard let pairId = page.author.pairId?.ddidString else { return Fail(error: PageRepositoryError.userNotPaired).eraseToAnyPublisher() } - let request = FirebaseAPIs.createPageDocument(page.author.id.ddidString, page.timeStamp, page.jsonPath, pairId) + let request = FirebaseAPIs.createPageDocument(page.author.id.ddidString, page.createdTime, page.updatedTime, page.jsonPath, pairId) let publisher: AnyPublisher<[String: Any], Error> = self.urlSessionNetworkService.request(request) return publisher @@ -45,26 +47,34 @@ class PageRepository: PageRepositoryProtocol { .eraseToAnyPublisher() } - func fetchPages(for pair: DDID) -> AnyPublisher<[PageEntity], Error> { - return Future<[PageEntity], Error> { [weak self] promise in + func updatePage(_ page: PageEntity) -> AnyPublisher { + return Future { [weak self] promise in + guard let pairId = page.author.pairId?.ddidString else { + return promise(.failure(PageRepositoryError.userNotPaired)) + } + guard let self = self else { - return promise(.failure(PageRepositoryError.failedToFetchPages)) + return promise(.failure(PageRepositoryError.failedToUpdatePage)) } - self.pageEntityPersistenceService.fetchPageEntities() + let request = FirebaseAPIs.patchPageDocument(page.author.id.ddidString, page.createdTime, page.updatedTime, page.jsonPath, pairId) + let publisher: AnyPublisher<[String: Any], Error> = self.urlSessionNetworkService.request(request) + + publisher .sink { completion in guard case .failure(let error) = completion else { return } promise(.failure(error)) - } receiveValue: { cachedPages in - let latestPageEntity = cachedPages.first + } receiveValue: { [weak self] _ in + guard let self = self else { + return promise(.failure(PageRepositoryError.failedToUpdatePage)) + } - self.fetchPageFromServer(pairId: pair, after: latestPageEntity?.timeStamp) + self.savePageToCache(pages: [page]) .sink { completion in guard case .failure(let error) = completion else { return } promise(.failure(error)) - } receiveValue: { pages in - self.savePageToCache(pages: pages) - promise(.success(pages + cachedPages)) + } receiveValue: { _ in + promise(.success(page)) } .store(in: &self.cancellables) } @@ -73,6 +83,35 @@ class PageRepository: PageRepositoryProtocol { .eraseToAnyPublisher() } + func fetchPages(for pair: DDID) -> AnyPublisher<[PageEntity], Error> { + return Future { [weak self] promise in + guard let self = self else { + return promise(.failure(PageRepositoryError.failedToFetchPages)) + } + + self.fetchPageFromServer(pairId: pair, after: nil) + .sink(receiveCompletion: { completion in + guard case .failure(let error) = completion else { return } + promise(.failure(error)) + }, receiveValue: { [weak self] pages in + guard let self = self else { + return promise(.failure(PageRepositoryError.failedToFetchPages)) + } + + self.savePageToCache(pages: pages) + .sink(receiveCompletion: { completion in + guard case .failure(let error) = completion else { return } + promise(.failure(error)) + }, receiveValue: { _ in + promise(.success(pages)) + }) + .store(in: &self.cancellables) + }) + .store(in: &self.cancellables) + } + .eraseToAnyPublisher() + } + private func fetchPageFromServer(pairId: DDID, after: Date?) -> AnyPublisher<[PageEntity], Error> { let request = FirebaseAPIs.getPageDocuments(pairId.ddidString, after) let publisher: AnyPublisher<[[String: Any]], Error> = self.urlSessionNetworkService.request(request) @@ -91,15 +130,12 @@ class PageRepository: PageRepositoryProtocol { .eraseToAnyPublisher() } - private func savePageToCache(pages: [PageEntity]) { + private func savePageToCache(pages: [PageEntity]) -> AnyPublisher { let savePublishers = pages.map { self.pageEntityPersistenceService.savePageEntity($0) } - Publishers.MergeMany(savePublishers) + return Publishers.MergeMany(savePublishers) .collect() - .sink { completion in - guard case .failure = completion else { return } - os_log("PageEntity caching failure", type: .fault) - } receiveValue: { _ in } - .store(in: &self.cancellables) + .map { _ -> Void in () } + .eraseToAnyPublisher() } } diff --git a/Doolda/Doolda/Data/Repositories/PairRepository.swift b/Doolda/Doolda/Data/Repositories/PairRepository.swift index 7d69f96a..472ab787 100644 --- a/Doolda/Doolda/Data/Repositories/PairRepository.swift +++ b/Doolda/Doolda/Data/Repositories/PairRepository.swift @@ -34,8 +34,10 @@ final class PairRepository: PairRepositoryProtocol { guard let pairId = user.pairId else { return Fail(error: PairRepositoryError.nilUserPairId).eraseToAnyPublisher() } - let publisher: AnyPublisher = self.urlSessionNetworkService - .request(FirebaseAPIs.createPairDocument(pairId.ddidString, user.id.ddidString)) + + let request = FirebaseAPIs.createPairDocument(pairId.ddidString, user.id.ddidString) + let publisher: AnyPublisher = self.urlSessionNetworkService.request(request) + return publisher.tryMap { pairDocument in guard let pairIdString = pairDocument.pairId, let pairId = DDID(from: pairIdString) else { @@ -50,8 +52,10 @@ final class PairRepository: PairRepositoryProtocol { guard let pairId = user.pairId else { return Fail(error: PairRepositoryError.nilUserPairId).eraseToAnyPublisher() } - let publisher: AnyPublisher = self.urlSessionNetworkService - .request(FirebaseAPIs.patchPairDocument(pairId.ddidString, user.id.ddidString)) + + let request = FirebaseAPIs.patchPairDocument(pairId.ddidString, user.id.ddidString) + let publisher: AnyPublisher = self.urlSessionNetworkService.request(request) + return publisher.tryMap { pairDocument in guard let pairIdString = pairDocument.pairId, let pairId = DDID(from: pairIdString) else { @@ -66,8 +70,10 @@ final class PairRepository: PairRepositoryProtocol { guard let pairId = user.pairId else { return Fail(error: PairRepositoryError.nilUserPairId).eraseToAnyPublisher() } - let publisher: AnyPublisher = self.urlSessionNetworkService - .request(FirebaseAPIs.getPairDocument(pairId.ddidString)) + + let request = FirebaseAPIs.getPairDocument(pairId.ddidString) + let publisher: AnyPublisher = self.urlSessionNetworkService.request(request) + return publisher.tryMap { pairDocument in guard let recentlyEditedUserIdString = pairDocument.recentlyEditedUser, let recentlyEditedUser = DDID(from: recentlyEditedUserIdString) else { @@ -77,4 +83,18 @@ final class PairRepository: PairRepositoryProtocol { } .eraseToAnyPublisher() } + + func deletePair(with user: User) -> AnyPublisher { + guard let pairId = user.pairId else { + return Fail(error: PairRepositoryError.nilUserPairId).eraseToAnyPublisher() + } + + let request = FirebaseAPIs.deletePairDocument(pairId.ddidString) + let publisher: AnyPublisher<[String: Any], Error> = self.urlSessionNetworkService.request(request) + + return publisher.map { _ in + return User(id: user.id, pairId: nil, friendId: nil) + } + .eraseToAnyPublisher() + } } diff --git a/Doolda/Doolda/Data/Repositories/PushNotificationStateRepository.swift b/Doolda/Doolda/Data/Repositories/PushNotificationStateRepository.swift index 8fb163f0..a5cc2662 100644 --- a/Doolda/Doolda/Data/Repositories/PushNotificationStateRepository.swift +++ b/Doolda/Doolda/Data/Repositories/PushNotificationStateRepository.swift @@ -14,11 +14,11 @@ final class PushNotificationStateRepository: PushNotificationStateRepositoryProt self.userDefaultsPersistenceService = persistenceService } - func saveState(as state: Bool) { + func save(_ state: Bool) { self.userDefaultsPersistenceService.set(key: UserDefaults.Keys.pushNotificationState, value: state) } - func fetchState() -> Bool? { + func fetch() -> Bool? { guard let pushNotificationState: Bool = self.userDefaultsPersistenceService.get( key: UserDefaults.Keys.pushNotificationState ) else { diff --git a/Doolda/Doolda/Data/Repositories/RawPageRepository.swift b/Doolda/Doolda/Data/Repositories/RawPageRepository.swift index 9e64c8e4..afa07188 100644 --- a/Doolda/Doolda/Data/Repositories/RawPageRepository.swift +++ b/Doolda/Doolda/Data/Repositories/RawPageRepository.swift @@ -22,6 +22,7 @@ enum RawPageRepositoryError: LocalizedError { class RawPageRepository: RawPageRepositoryProtocol { private let networkService: URLSessionNetworkServiceProtocol + private let coreDataPageEntityPersistenceService: CoreDataPageEntityPersistenceServiceProtocol private let fileManagerPersistenceService: FileManagerPersistenceServiceProtocol private let encoder: JSONEncoder @@ -29,10 +30,12 @@ class RawPageRepository: RawPageRepositoryProtocol { init( networkService: URLSessionNetworkServiceProtocol, + coreDataPageEntityPersistenceService: CoreDataPageEntityPersistenceServiceProtocol, fileManagerPersistenceService: FileManagerPersistenceServiceProtocol, encoder: JSONEncoder = JSONEncoder() ) { self.networkService = networkService + self.coreDataPageEntityPersistenceService = coreDataPageEntityPersistenceService self.fileManagerPersistenceService = fileManagerPersistenceService self.encoder = encoder } @@ -51,21 +54,55 @@ class RawPageRepository: RawPageRepositoryProtocol { } } - func fetch(at folder: String, with name: String) -> AnyPublisher { + func fetch(metaData: PageEntity) -> AnyPublisher { + guard let folder = metaData.author.pairId?.ddidString else { + return Fail(error: RawPageRepositoryError.failedToFetchRawPage).eraseToAnyPublisher() + } + let name = metaData.jsonPath let fileName = "\(folder)\(name)" - if self.fileManagerPersistenceService.isFileExists(at: .cache, fileName: fileName) { - return self.fileManagerPersistenceService.fetch(at: .cache, fileName: fileName) - .decode(type: RawPageEntity.self, decoder: JSONDecoder()) - .eraseToAnyPublisher() - } else { - return self.fetchRawPageEntityFromServer(at: folder, with: name) - .map { [weak self] rawPageEntity in - self?.saveRawPageEntityToCache(rawPageEntity: rawPageEntity, fileName: fileName) - return rawPageEntity + return Future { [weak self] promise in + guard let self = self else { + return promise(.failure(RawPageRepositoryError.failedToFetchRawPage)) + } + + self.coreDataPageEntityPersistenceService.isPageEntityUpToDate(metaData) + .sink { completion in + guard case .failure(let error) = completion else { return } + promise(.failure(error)) + } receiveValue: { [weak self] isUpToDate in + guard let self = self else { + return promise(.failure(RawPageRepositoryError.failedToFetchRawPage)) + } + + let fetchPublisher: AnyPublisher + + if self.fileManagerPersistenceService.isFileExists(at: .cache, fileName: fileName), + isUpToDate { + fetchPublisher = self.fileManagerPersistenceService.fetch(at: .cache, fileName: fileName) + .decode(type: RawPageEntity.self, decoder: JSONDecoder()) + .eraseToAnyPublisher() + } else { + fetchPublisher = self.fetchRawPageEntityFromServer(at: folder, with: name) + .map { [weak self] rawPageEntity in + self?.saveRawPageEntityToCache(rawPageEntity: rawPageEntity, fileName: fileName) + return rawPageEntity + } + .eraseToAnyPublisher() + } + + fetchPublisher.sink { completion in + guard case .failure(let error) = completion else { return } + promise(.failure(error)) + } receiveValue: { [weak self] rawPageEntity in + self?.setIsUpToDateFlag(medaData: metaData) + promise(.success(rawPageEntity)) + } + .store(in: &self.cancellables) } - .eraseToAnyPublisher() + .store(in: &self.cancellables) } + .eraseToAnyPublisher() } private func fetchRawPageEntityFromServer(at folder: String, with name: String) -> AnyPublisher { @@ -97,4 +134,13 @@ class RawPageRepository: RawPageRepositoryProtocol { } receiveValue: { _ in } .store(in: &self.cancellables) } + + private func setIsUpToDateFlag(medaData: PageEntity) { + self.coreDataPageEntityPersistenceService.savePageEntity(medaData) + .sink { completion in + guard case .failure = completion else { return } + os_log("RawPageEntity caching failure", type: .fault) + } receiveValue: { _ in } + .store(in: &self.cancellables) + } } diff --git a/Doolda/Doolda/Data/Repositories/UserRepository.swift b/Doolda/Doolda/Data/Repositories/UserRepository.swift index 076d79e4..750f855a 100644 --- a/Doolda/Doolda/Data/Repositories/UserRepository.swift +++ b/Doolda/Doolda/Data/Repositories/UserRepository.swift @@ -9,6 +9,7 @@ import Foundation enum UserRepositoryError: LocalizedError { case nilUserId + case nilFriendId case DTOInitError case savePairIdFail @@ -16,6 +17,8 @@ enum UserRepositoryError: LocalizedError { switch self { case .nilUserId: return "유저의 아이디가 존재하지 않습니다." + case .nilFriendId: + return "친구의 아이디가 존재하지 않습니다." case .DTOInitError: return "DataTransferObjects가 올바르지 않습니다." case .savePairIdFail: @@ -60,8 +63,9 @@ class UserRepository: UserRepositoryProtocol { .eraseToAnyPublisher() } + guard let friendId = user.friendId else { return Fail(error: UserRepositoryError.nilFriendId).eraseToAnyPublisher() } let publisher: AnyPublisher = - self.urlSessionNetworkService.request(FirebaseAPIs.patchUserDocuement(user.id.ddidString, pairId.ddidString)) + self.urlSessionNetworkService.request(FirebaseAPIs.patchUserDocument(user.id.ddidString, pairId.ddidString, friendId.ddidString)) return publisher.tryMap { userDocument in guard let newUser = userDocument.toUser() else { throw UserRepositoryError.nilUserId @@ -71,6 +75,18 @@ class UserRepository: UserRepositoryProtocol { .eraseToAnyPublisher() } + func resetUser(_ user: User) -> AnyPublisher { + let publisher: AnyPublisher = + self.urlSessionNetworkService.request(FirebaseAPIs.patchUserDocument(user.id.ddidString, "", "")) + return publisher.tryMap { userDocument in + guard let user = userDocument.toUser() else { + throw UserRepositoryError.nilUserId + } + return user + } + .eraseToAnyPublisher() + } + func fetchUser(_ id: DDID) -> AnyPublisher { let publisher: AnyPublisher = self.urlSessionNetworkService.request(FirebaseAPIs.getUserDocuement(id.ddidString)) return publisher.tryMap { userDocument in diff --git a/Doolda/Doolda/DiaryScene/DiaryBackgroundView.swift b/Doolda/Doolda/DiaryScene/DiaryBackgroundView.swift new file mode 100644 index 00000000..ea49e5b9 --- /dev/null +++ b/Doolda/Doolda/DiaryScene/DiaryBackgroundView.swift @@ -0,0 +1,94 @@ +// +// DiaryBackgroundView.swift +// Doolda +// +// Created by Seunghun Yang on 2021/11/22. +// + +import UIKit + +class DiaryBackgroundView: UIView { + + // MARK: - Subviews + + private lazy var imageView: UIImageView = { + let imageView = UIImageView() + imageView.contentMode = .scaleAspectFit + return imageView + }() + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.textAlignment = .center + label.textColor = .dooldaLabel + return label + }() + + private lazy var subtitleLabel: UILabel = { + let label = UILabel() + label.textAlignment = .center + label.textColor = .dooldaSublabel + return label + }() + + private lazy var titleStackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [self.imageView, self.titleLabel, self.subtitleLabel]) + stackView.axis = .vertical + stackView.spacing = 16 + stackView.alignment = .center + return stackView + }() + + // MARK: - Public Properties + + var image: UIImage? { + get { return self.imageView.image } + set { self.imageView.image = newValue } + } + + var title: String? { + get { return self.titleLabel.text } + set { self.titleLabel.text = newValue } + } + + var titleFont: UIFont { + get { return self.titleLabel.font } + set { self.titleLabel.font = newValue } + } + + var subtitle: String? { + get { return self.subtitleLabel.text } + set { self.subtitleLabel.text = newValue } + } + + var subtitleFont: UIFont { + get { return self.subtitleLabel.font } + set { self.subtitleLabel.font = newValue } + } + + // MARK: - Initializers + + override init(frame: CGRect) { + super.init(frame: frame) + self.configureUI() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + self.configureUI() + } + + // MARK: - Helpers + + private func configureUI() { + self.addSubview(self.titleStackView) + self.titleStackView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.centerY.equalToSuperview().offset(-40) + } + + self.imageView.snp.makeConstraints { make in + make.width.height.equalTo(200) + } + } +} diff --git a/Doolda/Doolda/DiaryScene/DiaryCollectionViewCell.swift b/Doolda/Doolda/DiaryScene/DiaryCollectionViewCell.swift new file mode 100644 index 00000000..efe96d8a --- /dev/null +++ b/Doolda/Doolda/DiaryScene/DiaryCollectionViewCell.swift @@ -0,0 +1,160 @@ +// +// DiaryCollectionViewCell.swift +// Doolda +// +// Created by Seunghun Yang on 2021/11/15. +// + +import Combine +import UIKit + +import Kingfisher +import SnapKit + +class DiaryCollectionViewCell: UICollectionViewCell { + + // MARK: - Static Properties + + static let identifier: String = "DiaryCollectionViewCell" + + // MARK: - Subviews + + private lazy var diaryPageView: DiaryPageView = { + let view = DiaryPageView() + view.delegate = self + return view + }() + + private lazy var dayLabel: UILabel = { + let label = UILabel() + label.adjustsFontSizeToFitWidth = true + label.textColor = .black + return label + }() + + private lazy var monthLabel: UILabel = { + let label = UILabel() + label.adjustsFontSizeToFitWidth = true + label.textColor = .black + return label + }() + + private lazy var dayLabelUnderBar: UIView = { + let view = UIView() + view.backgroundColor = .black + return view + }() + + private lazy var activityIndicator: UIActivityIndicatorView = { + let activityIndicator = UIActivityIndicatorView() + activityIndicator.style = .large + return activityIndicator + }() + + // MARK: - Public Properties + + var timestamp: Date? { + didSet { + guard let timestamp = timestamp else { return } + self.dayLabel.text = DateFormatter.dayFormatter.string(from: timestamp) + self.monthLabel.text = DateFormatter.monthNameFormatter.string(from: timestamp).uppercased() + } + } + + // MARK: - Private Properties + + private var cancellables: Set = [] + private var rawPageEntityPublisherCancellable: Cancellable? + + // MARK: - Initializers + + override init(frame: CGRect) { + super.init(frame: frame) + self.configureUI() + self.configureFont() + self.bindUI() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + self.configureUI() + self.configureFont() + self.bindUI() + } + + // MARK: - Helpers + + private func configureUI() { + self.clipsToBounds = true + self.layer.borderWidth = 1 + self.layer.cornerRadius = 4 + self.layer.borderColor = UIColor.black.cgColor + + self.addSubview(self.diaryPageView) + self.diaryPageView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + self.addSubview(self.dayLabel) + self.dayLabel.snp.makeConstraints { make in + make.top.equalToSuperview().offset(16) + make.leading.equalToSuperview().offset(16) + make.width.equalTo(self.snp.width).dividedBy(7.0) + make.height.equalTo(self.dayLabel.snp.width) + } + + self.addSubview(self.dayLabelUnderBar) + self.dayLabelUnderBar.snp.makeConstraints { make in + make.height.equalTo(1) + make.width.equalTo(self.dayLabel.snp.width) + make.top.equalTo(self.dayLabel.snp.bottom) + make.centerX.equalTo(self.dayLabel.snp.centerX) + } + + self.addSubview(self.monthLabel) + self.monthLabel.snp.makeConstraints { make in + make.centerY.equalTo(self.dayLabel.snp.centerY) + make.leading.equalTo(self.dayLabel.snp.trailing).offset(5) + make.width.equalTo(self.snp.width).dividedBy(7.0) + make.height.equalTo(self.monthLabel.snp.width) + } + + self.addSubview(self.activityIndicator) + self.activityIndicator.snp.makeConstraints { make in + make.center.equalToSuperview() + } + } + + private func bindUI() { + NotificationCenter.default.publisher(for: GlobalFontUseCase.Notifications.globalFontDidSet, object: nil) + .sink { [weak self] _ in + self?.configureFont() + } + .store(in: &self.cancellables) + } + + private func configureFont() { + self.dayLabel.font = .systemFont(ofSize: 100) + self.monthLabel.font = .systemFont(ofSize: 100) + } + + func displayRawPage(with rawPageEntityPublisher: AnyPublisher) { + self.rawPageEntityPublisherCancellable?.cancel() + self.diaryPageView.isHidden = true + self.activityIndicator.startAnimating() + self.rawPageEntityPublisherCancellable = rawPageEntityPublisher + .receive(on: DispatchQueue.main) + .sink { _ in return + } receiveValue: { [weak self] rawPageEntity in + self?.diaryPageView.components = rawPageEntity.components + self?.diaryPageView.pageBackgroundColor = UIColor(cgColor: rawPageEntity.backgroundType.rawValue) + self?.diaryPageView.isHidden = false + } + } +} + +extension DiaryCollectionViewCell: DiaryPageViewDelegate { + func diaryPageDrawDidFinish(_ diaryPageView: DiaryPageView) { + self.activityIndicator.stopAnimating() + } +} diff --git a/Doolda/Doolda/UI/Views/DiaryCollectionViewHeader.swift b/Doolda/Doolda/DiaryScene/DiaryCollectionViewHeader.swift similarity index 92% rename from Doolda/Doolda/UI/Views/DiaryCollectionViewHeader.swift rename to Doolda/Doolda/DiaryScene/DiaryCollectionViewHeader.swift index 02d8c495..68d902a1 100644 --- a/Doolda/Doolda/UI/Views/DiaryCollectionViewHeader.swift +++ b/Doolda/Doolda/DiaryScene/DiaryCollectionViewHeader.swift @@ -18,7 +18,7 @@ class DiaryCollectionViewHeader: UICollectionReusableView { // MARK: - Static Properties - static let reusableViewIdentifier = "DiaryCollectionViewHeader" + static let identifier = "DiaryCollectionViewHeader" // MARK: - Subviews @@ -72,6 +72,11 @@ class DiaryCollectionViewHeader: UICollectionReusableView { return activityIndicator }() + private lazy var hapticGenerator: UIImpactFeedbackGenerator = { + let generator = UIImpactFeedbackGenerator(style: .light) + return generator + }() + // MARK: - Public Properties weak var delegate: DiaryCollectionViewHeaderDelegate? @@ -95,12 +100,14 @@ class DiaryCollectionViewHeader: UICollectionReusableView { override init(frame: CGRect) { super.init(frame: frame) self.configureUI() + self.configureFont() self.bindUI() } required init?(coder: NSCoder) { super.init(coder: coder) self.configureUI() + self.configureFont() self.bindUI() } @@ -162,6 +169,8 @@ class DiaryCollectionViewHeader: UICollectionReusableView { self.headerCardView.publisher(for: UITapGestureRecognizer()) .sink { [weak self] _ in guard let self = self else { return } + self.hapticGenerator.prepare() + self.hapticGenerator.impactOccurred() switch self.headerState { case .newPageAddable: self.delegate?.addPageButtonDidTap(self) case .waitingForOpponent: self.delegate?.refreshButtonDidTap(self) @@ -188,6 +197,12 @@ class DiaryCollectionViewHeader: UICollectionReusableView { } } .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: GlobalFontUseCase.Notifications.globalFontDidSet, object: nil) + .sink { [weak self] _ in + self?.configureFont() + } + .store(in: &self.cancellables) } private func updateView(with displayMode: DiaryDisplayMode) { @@ -285,6 +300,17 @@ class DiaryCollectionViewHeader: UICollectionReusableView { } } } + + private func configureFont() { + switch self.displayMode { + case .list: + self.titleLabel.font = .systemFont(ofSize: 20) + self.subtitleLabel.font = .systemFont(ofSize: 15) + case .carousel: + self.titleLabel.font = .systemFont(ofSize: 30) + self.subtitleLabel.font = .systemFont(ofSize: 20) + } + } } // MARK: - Delegate Protocol diff --git a/Doolda/Doolda/UI/ViewControllers/DiaryViewController.swift b/Doolda/Doolda/DiaryScene/DiaryViewController.swift similarity index 65% rename from Doolda/Doolda/UI/ViewControllers/DiaryViewController.swift rename to Doolda/Doolda/DiaryScene/DiaryViewController.swift index d0ffb14d..d5055135 100644 --- a/Doolda/Doolda/UI/ViewControllers/DiaryViewController.swift +++ b/Doolda/Doolda/DiaryScene/DiaryViewController.swift @@ -26,14 +26,15 @@ class DiaryViewController: UIViewController { collectionView.contentInset = UIEdgeInsets(top: 10, left: 16, bottom: 10, right: 16) collectionView.showsHorizontalScrollIndicator = false collectionView.showsVerticalScrollIndicator = false - collectionView.register(DiaryPageViewCell.self, forCellWithReuseIdentifier: DiaryPageViewCell.cellIdentifier) + collectionView.register(DiaryCollectionViewCell.self, forCellWithReuseIdentifier: DiaryCollectionViewCell.identifier) collectionView.register( DiaryCollectionViewHeader.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, - withReuseIdentifier: DiaryCollectionViewHeader.reusableViewIdentifier + withReuseIdentifier: DiaryCollectionViewHeader.identifier ) collectionView.backgroundColor = .clear collectionView.delegate = self + collectionView.backgroundView = self.pageCollectionBackgroundView return collectionView }() @@ -51,29 +52,37 @@ class DiaryViewController: UIViewController { return flowLayout }() + private lazy var pageCollectionBackgroundView: DiaryBackgroundView = { + let backgroundView = DiaryBackgroundView() + backgroundView.image = UIImage.hedgehogFrustrated + backgroundView.title = "표시될 페이지가 없어요!" + return backgroundView + }() + private lazy var displayModeToggleButton: UIButton = { let button = UIButton() button.setImage(.square, for: .normal) + button.setPreferredSymbolConfiguration(UIImage.SymbolConfiguration.init(pointSize: 21), forImageIn: .normal) return button }() private lazy var filterButton: UIButton = { let button = UIButton() button.setImage(.line3HorizontalDecrease, for: .normal) + button.setPreferredSymbolConfiguration(UIImage.SymbolConfiguration.init(pointSize: 21), forImageIn: .normal) return button }() private lazy var settingsButton: UIButton = { let button = UIButton() button.setImage(.gearshape, for: .normal) + button.setPreferredSymbolConfiguration(UIImage.SymbolConfiguration.init(pointSize: 21), forImageIn: .normal) return button }() - private let transparentNavigationBarAppearance: UINavigationBarAppearance = { - let appearance = UINavigationBarAppearance() - appearance.backgroundColor = .clear - appearance.configureWithTransparentBackground() - return appearance + private lazy var hapticGenerator: UIImpactFeedbackGenerator = { + let generator = UIImpactFeedbackGenerator(style: .light) + return generator }() private var headerView: DiaryCollectionViewHeader? @@ -98,30 +107,41 @@ class DiaryViewController: UIViewController { self.init(nibName: nil, bundle: nil) self.viewModel = viewModel } + + deinit { + print(#file, "DEINIT") + self.viewModel.deinitRequested() + } // MARK: - Lifecycle Methods override func viewDidLoad() { super.viewDidLoad() self.configureUI() + self.configureFont() self.configureDataSource() self.bindUI() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) + self.hapticGenerator.prepare() + self.hapticGenerator.impactOccurred() self.viewModel.diaryViewWillAppear() + self.updateView(with: self.viewModel.displayMode) } // MARK: - Helpers private func configureUI() { self.view.backgroundColor = .dooldaBackground - self.title = "둘다" + self.navigationController?.navigationBar.isHidden = false self.navigationController?.navigationBar.barTintColor = .dooldaBackground + self.navigationItem.backButtonTitle = "" self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: self.settingsButton) + self.navigationItem.leftBarButtonItems = [ UIBarButtonItem(customView: self.displayModeToggleButton), UIBarButtonItem(customView: self.filterButton) @@ -133,26 +153,39 @@ class DiaryViewController: UIViewController { } } + private func configureFont() { + self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)] + self.pageCollectionBackgroundView.titleFont = .systemFont(ofSize: 35) + self.pageCollectionBackgroundView.subtitleFont = .systemFont(ofSize: 20) + } + private func bindUI() { self.viewModel.filteredPageEntitiesPublisher .receive(on: DispatchQueue.main) .sink { [weak self] entities in - guard let self = self else { return } - self.applySnapshot(pageEntities: entities) + self?.applySnapshot(pageEntities: entities) } .store(in: &self.cancellables) - + self.viewModel.displayModePublisher .receive(on: DispatchQueue.main) .sink { [weak self] displayMode in self?.updateView(with: displayMode) } .store(in: &self.cancellables) + + Publishers.CombineLatest(self.viewModel.filteredPageEntitiesPublisher, self.viewModel.displayModePublisher) + .receive(on: DispatchQueue.main) + .sink { [weak self] entities, displayMode in + self?.pageCollectionBackgroundView.isHidden = !entities.isEmpty || (displayMode == .carousel) + } + .store(in: &self.cancellables) self.viewModel.isMyTurnPublisher .receive(on: DispatchQueue.main) .sink { [weak self] isMyTurn in self?.headerView?.isMyTurn = isMyTurn + self?.pageCollectionBackgroundView.subtitle = "헤더를 탭해 \(isMyTurn ? "새 글을 써" : "새로고침 해")보세요!" } .store(in: &self.cancellables) @@ -165,42 +198,72 @@ class DiaryViewController: UIViewController { self.displayModeToggleButton.publisher(for: .touchUpInside) .sink { [weak self] _ in + self?.hapticGenerator.prepare() + self?.hapticGenerator.impactOccurred() self?.viewModel.displayModeToggleButtonDidTap() } .store(in: &self.cancellables) self.filterButton.publisher(for: .touchUpInside) .sink { [weak self] _ in + self?.hapticGenerator.prepare() + self?.hapticGenerator.impactOccurred() self?.viewModel.filterButtonDidTap() } .store(in: &self.cancellables) self.settingsButton.publisher(for: .touchUpInside) .sink { [weak self] _ in + self?.hapticGenerator.prepare() + self?.hapticGenerator.impactOccurred() self?.viewModel.settingsButtonDidTap() } .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: PushMessageEntity.Notifications.userPostedNewPage, object: nil) + .sink { [weak self] _ in + self?.hapticGenerator.prepare() + self?.hapticGenerator.impactOccurred() + self?.scrollToPage(of: 0) + self?.viewModel.userPostedNewPageNotificationDidReceived() + } + .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: PushMessageEntity.Notifications.userRequestedNewPage, object: nil) + .sink { [weak self] _ in + self?.hapticGenerator.prepare() + self?.hapticGenerator.impactOccurred() + self?.scrollToPage(of: 0) + self?.viewModel.userRequestedNewPageNotificationDidReceived() + } + .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: GlobalFontUseCase.Notifications.globalFontDidSet, object: nil) + .sink { [weak self] _ in + self?.configureFont() + } + .store(in: &self.cancellables) } private func configureDataSource() { self.dataSource = DataSource( collectionView: self.pageCollectionView, - cellProvider: { [weak self] (collectionView, indexPath, pageEntity) -> DiaryPageViewCell? in + cellProvider: { [weak self] (collectionView, indexPath, pageEntity) -> DiaryCollectionViewCell? in guard let cell = collectionView.dequeueReusableCell( - withReuseIdentifier: DiaryPageViewCell.cellIdentifier, + withReuseIdentifier: DiaryCollectionViewCell.identifier, for: indexPath - ) as? DiaryPageViewCell else { return nil } + ) as? DiaryCollectionViewCell else { return nil } guard let viewModel = self?.viewModel else { return nil } - cell.displayRawPage(with: viewModel.pageDidDisplay(jsonPath: pageEntity.jsonPath)) - cell.timestamp = pageEntity.timeStamp + cell.displayRawPage(with: viewModel.pageDidDisplay(metaData: pageEntity)) + cell.timestamp = pageEntity.createdTime return cell }) self.dataSource?.supplementaryViewProvider = { collectionView, kind, indexPath in let view = collectionView.dequeueReusableSupplementaryView( ofKind: kind, - withReuseIdentifier: DiaryCollectionViewHeader.reusableViewIdentifier, + withReuseIdentifier: DiaryCollectionViewHeader.identifier, for: indexPath ) as? DiaryCollectionViewHeader view?.delegate = self @@ -228,6 +291,7 @@ class DiaryViewController: UIViewController { self.pageCollectionView.alwaysBounceVertical = true self.pageCollectionView.showsVerticalScrollIndicator = true self.navigationController?.hidesBarsOnSwipe = true + self.title = "모아보기" case .carousel: self.pageCollectionView.collectionViewLayout = self.carouselFlowLayout self.displayModeToggleButton.setImage(.squareGrid2x2, for: .normal) @@ -235,16 +299,27 @@ class DiaryViewController: UIViewController { self.pageCollectionView.alwaysBounceVertical = false self.pageCollectionView.showsVerticalScrollIndicator = false self.navigationController?.hidesBarsOnSwipe = false + self.navigationController?.isNavigationBarHidden = false self.scrollToPage(of: Int(self.pageCollectionView.contentOffset.x / self.pageOffset)) } } private func scrollToPage(of index: Int) { guard self.viewModel.displayMode == .carousel else { return } - let xOffset = CGFloat(index + 1) * self.pageOffset - 16 + let xOffset = CGFloat(min(self.viewModel.filteredEntityCount + 1, index)) * self.pageOffset - 16 let yOffset = self.pageCollectionView.contentOffset.y + self.setTitle(for: index - 1) self.pageCollectionView.setContentOffset(CGPoint(x: xOffset, y: yOffset), animated: false) } + + private func setTitle(for index: Int) { + if let date = self.viewModel.getDate(of: index), + let formattedDateString = try? DateFormatter.koreanFormatter.string(from: date) { + self.title = formattedDateString + } else { + self.title = "둘다" + } + } } // MARK: - UICOllectionViewDelegateFlowLayout @@ -278,6 +353,7 @@ extension DiaryViewController: UICollectionViewDelegateFlowLayout { actualIndex = Int(round(estimatedIndex)) } + self.setTitle(for: actualIndex - 1) targetContentOffset.pointee = CGPoint(x: CGFloat(actualIndex) * pageOffset - 16, y: 0) } @@ -306,6 +382,12 @@ extension DiaryViewController: UICollectionViewDelegateFlowLayout { } } +extension DiaryViewController: UICollectionViewDelegate { + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + self.viewModel?.pageDidTap(index: indexPath.item) + } +} + extension DiaryViewController: DiaryCollectionViewHeaderDelegate { func refreshButtonDidTap(_ diaryCollectionViewHeader: DiaryCollectionViewHeader) { self.viewModel?.refreshButtonDidTap() @@ -315,3 +397,25 @@ extension DiaryViewController: DiaryCollectionViewHeaderDelegate { self.viewModel?.addPageButtonDidTap() } } + +extension DiaryViewController: FilterOptionBottomSheetViewControllerDelegate { + func applyButtonDidTap( + _ filterOptionBottomSheetViewController: FilterOptionBottomSheetViewController, + authorFilter: DiaryAuthorFilter, + orderFilter: DiaryOrderFilter + ) { + self.viewModel.filterDidApply(author: authorFilter, orderBy: orderFilter) + } + + func filterOptionDidChange( + _ filterOptionBottomSheetViewController: FilterOptionBottomSheetViewController, + authorFilter: DiaryAuthorFilter, + orderFilter: DiaryOrderFilter + ) { + self.viewModel.filterOptionDidChange(author: authorFilter, orderBy: orderFilter) + } + + func filterBottomSheetWillDismiss(_ filteredOptionBottomSheetController: FilterOptionBottomSheetViewController) { + self.viewModel.filterBottomSheetDidDismiss() + } +} diff --git a/Doolda/Doolda/DiaryScene/DiaryViewCoordinator.swift b/Doolda/Doolda/DiaryScene/DiaryViewCoordinator.swift new file mode 100644 index 00000000..39be8a04 --- /dev/null +++ b/Doolda/Doolda/DiaryScene/DiaryViewCoordinator.swift @@ -0,0 +1,140 @@ +// +// DiaryViewCoordinator.swift +// Doolda +// +// Created by Dozzing on 2021/11/02. +// + +import Combine +import UIKit + +final class DiaryViewCoordinator: BaseCoordinator { + + // MARK: - Nested enum + + enum Notifications { + static let pageDetailRequested = Notification.Name("pageDetailRequested") + static let addPageRequested = Notification.Name("addPageRequested") + static let settingsPageRequested = Notification.Name("settingsPageRequested") + static let filteringSheetRequested = Notification.Name("filteringSheetRequested") + } + + enum Keys { + static let pageEntity = "pageEntity" + static let authorFilter = "authorFilter" + static let orderFilter = "orderFilter" + } + + private let user: User + private var cancellables: Set = [] + + init(identifier: UUID, presenter: UINavigationController, user: User) { + self.user = user + super.init(identifier: identifier, presenter: presenter) + self.bind() + } + + func start() { + let urlSessionNetworkService = URLSessionNetworkService.shared + let coreDataPersistenceService = CoreDataPersistenceService.shared + let coreDataPageEntityPersistenceService = CoreDataPageEntityPersistenceService(coreDataPersistenceService: coreDataPersistenceService) + let fileManagerPersistenceService = FileManagerPersistenceService.shared + + let pairRepository = PairRepository(networkService: urlSessionNetworkService) + let pageRepository = PageRepository( + urlSessionNetworkService: urlSessionNetworkService, + pageEntityPersistenceService: coreDataPageEntityPersistenceService + ) + + let rawPageRepository = RawPageRepository( + networkService: urlSessionNetworkService, + coreDataPageEntityPersistenceService: coreDataPageEntityPersistenceService, + fileManagerPersistenceService: fileManagerPersistenceService + ) + + let fcmTokenRepository = FCMTokenRepository(urlSessionNetworkService: urlSessionNetworkService) + let firebaseMessageRepository = FirebaseMessageRepository(urlSessionNetworkService: urlSessionNetworkService) + + let checkMyTurnUseCase = CheckMyTurnUseCase(pairRepository: pairRepository) + let getPageUseCase = GetPageUseCase(pageRepository: pageRepository) + let getRawPageUseCase = GetRawPageUseCase(rawPageRepository: rawPageRepository) + let firebaseMessageUseCase = FirebaseMessageUseCase( + fcmTokenRepository: fcmTokenRepository, + firebaseMessageRepository: firebaseMessageRepository + ) + + let viewModel = DiaryViewModel( + sceneId: self.identifier, + user: self.user, + checkMyTurnUseCase: checkMyTurnUseCase, + getPageUseCase: getPageUseCase, + getRawPageUseCase: getRawPageUseCase, + firebaseMessageUseCase: firebaseMessageUseCase + ) + + let viewController = DiaryViewController(viewModel: viewModel) + self.presenter.setViewControllers([viewController], animated: false) + } + + private func bind() { + NotificationCenter.default.publisher(for: Notifications.addPageRequested, object: nil) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.editPageRequested() + } + .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: Notifications.settingsPageRequested, object: nil) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.settingsPageRequested() + } + .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: Notifications.pageDetailRequested, object: nil) + .receive(on: DispatchQueue.main) + .compactMap { $0.userInfo?[Keys.pageEntity] as? PageEntity } + .sink { [weak self] pageEntity in + self?.pageDetailRequested(pageEntity: pageEntity) + } + .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: Notifications.filteringSheetRequested, object: nil) + .receive(on: DispatchQueue.main) + .compactMap { ($0.userInfo?[Keys.authorFilter] as? DiaryAuthorFilter, $0.userInfo?[Keys.orderFilter] as? DiaryOrderFilter) } + .sink { [weak self] filters in + guard let authorFilter = filters.0, + let orderFilter = filters.1 else { return } + self?.filteringSheetRequested(authorFilter: authorFilter, orderFilter: orderFilter) + } + .store(in: &self.cancellables) + } + + private func editPageRequested() { + let identifier = UUID() + let coordinator = EditPageViewCoordinator(identifier: identifier, presenter: self.presenter, user: self.user) + self.children[identifier] = coordinator + coordinator.start() + } + + private func settingsPageRequested() { + let identifier = UUID() + let coordinator = SettingsViewCoordinator(identifier: identifier, presenter: self.presenter, user: self.user) + self.children[identifier] = coordinator + coordinator.start() + } + + private func filteringSheetRequested(authorFilter: DiaryAuthorFilter, orderFilter: DiaryOrderFilter) { + let viewModel = FilterOptionBottomSheetViewModel(authorFilter: authorFilter, orderFilter: orderFilter) + let delegate = self.presenter.topViewController as? DiaryViewController + let viewController = FilterOptionBottomSheetViewController(viewModel: viewModel, delegate: delegate) + self.presenter.present(viewController, animated: false) + } + + private func pageDetailRequested(pageEntity: PageEntity) { + let identifier = UUID() + let coordinator = PageDetailViewCoordinator(identifier: identifier, presenter: self.presenter, user: self.user, pageEntity: pageEntity) + self.children[identifier] = coordinator + coordinator.start() + } +} diff --git a/Doolda/Doolda/DiaryScene/DiaryViewModel.swift b/Doolda/Doolda/DiaryScene/DiaryViewModel.swift new file mode 100644 index 00000000..9943bde5 --- /dev/null +++ b/Doolda/Doolda/DiaryScene/DiaryViewModel.swift @@ -0,0 +1,259 @@ +// +// DiaryViewModel.swift +// Doolda +// +// Created by Seunghun Yang on 2021/11/16. +// + +import Combine +import Foundation + +protocol DiaryViewModelInput { + func diaryViewWillAppear() + func filterButtonDidTap() + func displayModeToggleButtonDidTap() + func settingsButtonDidTap() + func addPageButtonDidTap() + func refreshButtonDidTap() + func filterDidApply(author: DiaryAuthorFilter, orderBy: DiaryOrderFilter) + func filterOptionDidChange(author: DiaryAuthorFilter, orderBy: DiaryOrderFilter) + func filterBottomSheetDidDismiss() + func pageDidDisplay(metaData: PageEntity) -> AnyPublisher + func pageDidTap(index: Int) + func getDate(of index: Int) -> Date? + func userPostedNewPageNotificationDidReceived() + func userRequestedNewPageNotificationDidReceived() + func deinitRequested() +} + +protocol DiaryViewModelOutput { + var errorPublisher: AnyPublisher { get } + var displayModePublisher: AnyPublisher { get } + var isMyTurnPublisher: AnyPublisher { get } + var filteredPageEntitiesPublisher: AnyPublisher<[PageEntity], Never> { get } + var isRefreshingPublisher: AnyPublisher { get } + var displayMode: DiaryDisplayMode { get } + var filteredEntityCount: Int { get } +} + +typealias DiaryViewModelProtocol = DiaryViewModelInput & DiaryViewModelOutput + +enum DiaryDisplayMode { + case carousel, list + + mutating func toggle() { + switch self { + case .carousel: self = .list + case .list: self = .carousel + } + } +} + +enum DiaryAuthorFilter: String, CaseIterable { + case both = "전체 보기" + case user = "내 것만 보기" + case friend = "친구 것만 보기" + + static var titles: [String] { DiaryAuthorFilter.allCases.map { $0.rawValue }} + + static subscript(index: Int) -> DiaryAuthorFilter? { + return DiaryAuthorFilter(rawValue: DiaryAuthorFilter.titles[index]) + } + + static func indexOf(authorFilter: DiaryAuthorFilter) -> Int { + return DiaryAuthorFilter.allCases.firstIndex(of: authorFilter) ?? 0 + } +} + +enum DiaryOrderFilter: String, CaseIterable { + case descending = "최신순" + case ascending = "오래된순" + + static var titles: [String] { DiaryOrderFilter.allCases.map { $0.rawValue }} + + static subscript(index: Int) -> DiaryOrderFilter? { + return DiaryOrderFilter(rawValue: DiaryOrderFilter.titles[index]) + } + + static func indexOf(orderFilter: DiaryOrderFilter) -> Int { + return DiaryOrderFilter.allCases.firstIndex(of: orderFilter) ?? 0 + } +} + +enum DiaryViewModelError: LocalizedError { + case userNotPaired + + var errorDescription: String? { + switch self { + case .userNotPaired: + return "연결된 짝이 없습니다." + } + } +} + +final class DiaryViewModel: DiaryViewModelProtocol { + var errorPublisher: AnyPublisher { self.$error.eraseToAnyPublisher() } + var displayModePublisher: AnyPublisher { self.$displayMode.eraseToAnyPublisher() } + var isMyTurnPublisher: AnyPublisher { self.$isMyTurn.eraseToAnyPublisher() } + var isRefreshingPublisher: AnyPublisher { self.$isRefreshing.eraseToAnyPublisher() } + var filteredPageEntitiesPublisher: AnyPublisher<[PageEntity], Never> { self.$filteredPageEntities.eraseToAnyPublisher() } + var filteredEntityCount: Int { self.filteredPageEntities.count } + + @Published var displayMode: DiaryDisplayMode = .carousel + @Published private var error: Error? + @Published private var isRefreshing: Bool = false + @Published private var isMyTurn: Bool = false + @Published private var filteredPageEntities: [PageEntity] = [] + @Published private var pageEntities: [PageEntity] = [] + @Published private var authorFilter: DiaryAuthorFilter = .both + @Published private var orderFilter: DiaryOrderFilter = .descending + + private var cancellables: Set = [] + + private let sceneId: UUID + private let user: User + private let checkMyTurnUseCase: CheckMyTurnUseCaseProtocol + private let getPageUseCase: GetPageUseCaseProtocol + private let getRawPageUseCase: GetRawPageUseCaseProtocol + private let firebaseMessageUseCase: FirebaseMessageUseCaseProtocol + + init( + sceneId: UUID, + user: User, + checkMyTurnUseCase: CheckMyTurnUseCaseProtocol, + getPageUseCase: GetPageUseCaseProtocol, + getRawPageUseCase: GetRawPageUseCaseProtocol, + firebaseMessageUseCase: FirebaseMessageUseCaseProtocol + ) { + self.sceneId = sceneId + self.user = user + self.checkMyTurnUseCase = checkMyTurnUseCase + self.getPageUseCase = getPageUseCase + self.getRawPageUseCase = getRawPageUseCase + self.firebaseMessageUseCase = firebaseMessageUseCase + self.bind() + } + + private func bind() { + self.$pageEntities + .sink { [weak self] entities in + guard let self = self else { return } + self.filterPageEntities(entities: entities, authorFilter: self.authorFilter, orderFilter: self.orderFilter) + } + .store(in: &self.cancellables) + } + + func diaryViewWillAppear() { + self.fetchPages() + } + + func pageDidDisplay(metaData: PageEntity) -> AnyPublisher { + return self.getRawPageUseCase.getRawPageEntity(metaData: metaData) + } + + func pageDidTap(index: Int) { + let selectedPageEntity = self.filteredPageEntities[index] + NotificationCenter.default.post( + name: DiaryViewCoordinator.Notifications.pageDetailRequested, + object: nil, + userInfo: [DiaryViewCoordinator.Keys.pageEntity: selectedPageEntity] + ) + } + + func displayModeToggleButtonDidTap() { + self.displayMode.toggle() + } + + func addPageButtonDidTap() { + NotificationCenter.default.post( + name: DiaryViewCoordinator.Notifications.addPageRequested, + object: nil + ) + } + + func refreshButtonDidTap() { + self.fetchPages() + + guard let friendId = user.friendId, + friendId != user.id else { return } + self.firebaseMessageUseCase.sendMessage(to: friendId, message: PushMessageEntity.userRequestedNewPage) + } + + func settingsButtonDidTap() { + NotificationCenter.default.post( + name: DiaryViewCoordinator.Notifications.settingsPageRequested, + object: nil + ) + } + + func filterButtonDidTap() { + NotificationCenter.default.post( + name: DiaryViewCoordinator.Notifications.filteringSheetRequested, + object: nil, + userInfo: [ + DiaryViewCoordinator.Keys.authorFilter: self.authorFilter, + DiaryViewCoordinator.Keys.orderFilter: self.orderFilter + ] + ) + } + + func filterDidApply(author: DiaryAuthorFilter, orderBy: DiaryOrderFilter) { + self.authorFilter = author + self.orderFilter = orderBy + } + + func filterBottomSheetDidDismiss() { + self.filterPageEntities(entities: self.pageEntities, authorFilter: self.authorFilter, orderFilter: self.orderFilter) + } + + func filterOptionDidChange(author: DiaryAuthorFilter, orderBy: DiaryOrderFilter) { + self.filterPageEntities(entities: self.pageEntities, authorFilter: author, orderFilter: orderBy) + } + + func getDate(of index: Int) -> Date? { + return filteredPageEntities[exist: index]?.createdTime + } + + func userPostedNewPageNotificationDidReceived() { + self.fetchPages() + } + + func userRequestedNewPageNotificationDidReceived() { + self.fetchPages() + } + + private func fetchPages() { + guard let pairId = self.user.pairId else { return } + self.isRefreshing = true + + Publishers.Zip( + self.checkMyTurnUseCase.checkTurn(for: self.user), + self.getPageUseCase.getPages(for: pairId) + ) + .delay(for: .seconds(1), scheduler: DispatchQueue.global()) + .sink { [weak self] completion in + guard case .failure(let error) = completion else { return } + self?.error = error + self?.isRefreshing = false + } receiveValue: { [weak self] isMyTurn, pages in + self?.isMyTurn = isMyTurn + self?.pageEntities = pages + self?.isRefreshing = false + } + .store(in: &self.cancellables) + } + + private func filterPageEntities(entities: [PageEntity], authorFilter: DiaryAuthorFilter, orderFilter: DiaryOrderFilter) { + let filtered = entities.filter { authorFilter == .both ? true : (authorFilter == .user ? ($0.author.id == self.user.id) : ($0.author.id != self.user.id)) } + let ordered = filtered.sorted { orderFilter == .descending ? ($0.createdTime >= $1.createdTime) : ($0.createdTime <= $1.createdTime) } + self.filteredPageEntities = ordered + } + + func deinitRequested() { + NotificationCenter.default.post( + name: BaseCoordinator.Notifications.coordinatorRemoveFromParent, + object: nil, + userInfo: [BaseCoordinator.Keys.sceneId: self.sceneId] + ) + } +} diff --git a/Doolda/Doolda/DiaryScene/FilterOptionScene/FilterOptionBottomSheetViewController.swift b/Doolda/Doolda/DiaryScene/FilterOptionScene/FilterOptionBottomSheetViewController.swift new file mode 100644 index 00000000..e3701682 --- /dev/null +++ b/Doolda/Doolda/DiaryScene/FilterOptionScene/FilterOptionBottomSheetViewController.swift @@ -0,0 +1,199 @@ +// +// FilterOptionBottomSheetViewController.swift +// Doolda +// +// Created by Seunghun Yang on 2021/11/21. +// + +import Combine +import UIKit + +import SnapKit + +protocol FilterOptionBottomSheetViewControllerDelegate: AnyObject { + func applyButtonDidTap( + _ filterOptionBottomSheetViewController: FilterOptionBottomSheetViewController, + authorFilter: DiaryAuthorFilter, + orderFilter: DiaryOrderFilter + ) + + func filterOptionDidChange( + _ filterOptionBottomSheetViewController: FilterOptionBottomSheetViewController, + authorFilter: DiaryAuthorFilter, + orderFilter: DiaryOrderFilter + ) + + func filterBottomSheetWillDismiss(_ filteredOptionBottomSheetController: FilterOptionBottomSheetViewController) +} + +class FilterOptionBottomSheetViewController: BottomSheetViewController { + + // MARK: - Subviews + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.text = "다이어리 필터" + return label + }() + + private lazy var authorFilterOptionSegmentedControl: UISegmentedControl = { + let segmentedControl = UISegmentedControl(items: DiaryAuthorFilter.titles) + return segmentedControl + }() + + private lazy var orderFilterOptionSegmentedControl: UISegmentedControl = { + let segmentedControl = UISegmentedControl(items: DiaryOrderFilter.titles) + return segmentedControl + }() + + private lazy var applyButton: UIButton = { + let button = UIButton() + button.setTitle("적용", for: .normal) + button.setTitleColor(.dooldaLabel, for: .normal) + button.backgroundColor = .dooldaHighlighted + button.layer.cornerRadius = 22 + return button + }() + + private lazy var hapticGenerator: UIImpactFeedbackGenerator = { + let generator = UIImpactFeedbackGenerator(style: .light) + return generator + }() + + // MARK: - Private Properties + + private var viewModel: FilterOptionBottomSheetViewModel! + private weak var delegate: FilterOptionBottomSheetViewControllerDelegate? + private var cancellables: Set = [] + + // MARK: - Initializers + + convenience init( + viewModel: FilterOptionBottomSheetViewModel, + delegate: FilterOptionBottomSheetViewControllerDelegate? + ) { + self.init(nibName: nil, bundle: nil) + self.viewModel = viewModel + self.delegate = delegate + } + + // MARK: - Lifecycle Methods + + override func viewDidLoad() { + super.viewDidLoad() + self.configureUI() + self.configureFont() + self.bindUI() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + self.delegate?.filterBottomSheetWillDismiss(self) + } + + // MARK: - Helpers + + private func configureUI() { + self.detent = .small + self.body.backgroundColor = .dooldaBackground + + self.body.addSubview(self.titleLabel) + self.titleLabel.snp.makeConstraints { make in + make.top.equalToSuperview().offset(16) + make.centerX.equalToSuperview() + } + + self.body.addSubview(self.authorFilterOptionSegmentedControl) + self.authorFilterOptionSegmentedControl.snp.makeConstraints { make in + make.top.equalTo(self.titleLabel.snp.bottom).offset(8) + make.height.equalTo(30) + make.leading.equalToSuperview().offset(16) + make.trailing.equalToSuperview().offset(-16) + } + + self.body.addSubview(self.orderFilterOptionSegmentedControl) + self.orderFilterOptionSegmentedControl.snp.makeConstraints { make in + make.top.equalTo(self.authorFilterOptionSegmentedControl.snp.bottom).offset(8) + make.height.equalTo(30) + make.leading.equalToSuperview().offset(16) + make.trailing.equalToSuperview().offset(-16) + } + + self.body.addSubview(self.applyButton) + self.applyButton.snp.makeConstraints { make in + make.top.equalTo(self.orderFilterOptionSegmentedControl.snp.bottom).offset(16) + make.height.equalTo(44) + make.leading.equalToSuperview().offset(16) + make.trailing.equalToSuperview().offset(-16) + make.bottom.equalToSuperview().offset(-32) + } + } + + private func configureFont() { + self.titleLabel.font = .systemFont(ofSize: 14) + self.authorFilterOptionSegmentedControl.setTitleTextAttributes([.font: UIFont.systemFont(ofSize: 14)], for: .normal) + self.orderFilterOptionSegmentedControl.setTitleTextAttributes([.font: UIFont.systemFont(ofSize: 14)], for: .normal) + self.applyButton.titleLabel?.font = .systemFont(ofSize: 14) + } + + private func bindUI() { + self.viewModel.authorFilterPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] authorFilter in + self?.authorFilterOptionSegmentedControl.selectedSegmentIndex = DiaryAuthorFilter.indexOf(authorFilter: authorFilter) + } + .store(in: &self.cancellables) + + self.viewModel.orderFilterPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] orderFilter in + self?.orderFilterOptionSegmentedControl.selectedSegmentIndex = DiaryOrderFilter.indexOf(orderFilter: orderFilter) + } + .store(in: &self.cancellables) + + self.authorFilterOptionSegmentedControl.publisher(for: .valueChanged) + .sink { [weak self] _ in + guard let index = self?.authorFilterOptionSegmentedControl.selectedSegmentIndex else { return } + self?.viewModel.authorFilterIndexValueDidChange(index) + } + .store(in: &self.cancellables) + + self.orderFilterOptionSegmentedControl.publisher(for: .valueChanged) + .sink { [weak self] _ in + guard let index = self?.orderFilterOptionSegmentedControl.selectedSegmentIndex else { return } + self?.viewModel.orderFilterIndexValueDidChange(index) + } + .store(in: &self.cancellables) + + Publishers.CombineLatest3( + self.applyButton.publisher(for: .touchUpInside), + self.viewModel.authorFilterPublisher, + self.viewModel.orderFilterPublisher + ) + .sink { [weak self] _, authorFilter, orderFilter in + guard let self = self else { return } + self.hapticGenerator.prepare() + self.hapticGenerator.impactOccurred() + self.delegate?.applyButtonDidTap(self, authorFilter: authorFilter, orderFilter: orderFilter) + self.dismiss(animated: true) + } + .store(in: &self.cancellables) + + Publishers.CombineLatest(self.viewModel.authorFilterPublisher, self.viewModel.orderFilterPublisher) + .sink { [weak self] authorFilter, orderFilter in + guard let self = self else { return } + self.delegate?.filterOptionDidChange(self, authorFilter: authorFilter, orderFilter: orderFilter) + } + .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: GlobalFontUseCase.Notifications.globalFontDidSet, object: nil) + .sink { [weak self] _ in + self?.configureFont() + } + .store(in: &self.cancellables) + } +} diff --git a/Doolda/Doolda/DiaryScene/FilterOptionScene/FilterOptionBottomSheetViewModel.swift b/Doolda/Doolda/DiaryScene/FilterOptionScene/FilterOptionBottomSheetViewModel.swift new file mode 100644 index 00000000..c014606c --- /dev/null +++ b/Doolda/Doolda/DiaryScene/FilterOptionScene/FilterOptionBottomSheetViewModel.swift @@ -0,0 +1,42 @@ +// +// FilterOptionBottomSheetViewModel.swift +// Doolda +// +// Created by Seunghun Yang on 2021/11/21. +// + +import Combine +import Foundation + +protocol FilterOptionBottomSheetViewModelInput { + func authorFilterIndexValueDidChange(_ index: Int) + func orderFilterIndexValueDidChange(_ index: Int) +} + +protocol FilterOptionBottomSheetViewModelOutput { + var authorFilterPublisher: AnyPublisher { get } + var orderFilterPublisher: AnyPublisher { get } +} + +typealias FilterOptionBottomSheetViewModelProtocol = FilterOptionBottomSheetViewModelInput & FilterOptionBottomSheetViewModelOutput + +class FilterOptionBottomSheetViewModel: FilterOptionBottomSheetViewModelInput, FilterOptionBottomSheetViewModelOutput { + var authorFilterPublisher: AnyPublisher { self.$authorFilter.eraseToAnyPublisher() } + var orderFilterPublisher: AnyPublisher { self.$orderFilter.eraseToAnyPublisher() } + + @Published private var authorFilter: DiaryAuthorFilter = .both + @Published private var orderFilter: DiaryOrderFilter = .descending + + init(authorFilter: DiaryAuthorFilter, orderFilter: DiaryOrderFilter) { + self.authorFilter = authorFilter + self.orderFilter = orderFilter + } + + func authorFilterIndexValueDidChange(_ index: Int) { + self.authorFilter = DiaryAuthorFilter[index] ?? .both + } + + func orderFilterIndexValueDidChange(_ index: Int) { + self.orderFilter = DiaryOrderFilter[index] ?? .descending + } +} diff --git a/Doolda/Doolda/Domain/Entities/ComponentType.swift b/Doolda/Doolda/Domain/Entities/ComponentType.swift new file mode 100644 index 00000000..bf91f7fa --- /dev/null +++ b/Doolda/Doolda/Domain/Entities/ComponentType.swift @@ -0,0 +1,12 @@ +// +// ComponentType.swift +// Doolda +// +// Created by 김민주 on 2021/11/30. +// + +import Foundation + +enum ComponentType: String, Codable { + case base, photo, sticker, text +} diff --git a/Doolda/Doolda/Domain/Entities/DooldaInfoType.swift b/Doolda/Doolda/Domain/Entities/DooldaInfoType.swift new file mode 100644 index 00000000..abdeb574 --- /dev/null +++ b/Doolda/Doolda/Domain/Entities/DooldaInfoType.swift @@ -0,0 +1,58 @@ +// +// DooldaInfoType.swift +// Doolda +// +// Created by Dozzing on 2021/11/22. +// + +import Foundation + +enum DooldaInfoType: String { + case appVersion + case openSourceLicense + case privacyPolicy + case contributor + + var rawValue: String { + switch self { + case .appVersion: + return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "" + case .openSourceLicense: + return "오픈소스 라이센스" + case .privacyPolicy: + return "개인정보 처리방침" + case .contributor: + return "만든 사람들" + } + } +} + +extension DooldaInfoType { + var title: String { + switch self { + case .appVersion: + return "앱 현재 버전" + case .openSourceLicense: + return "Open Source License" + case .privacyPolicy: + return "개인 정보 처리 방침" + case .contributor: + return "만든 사람들" + } + } + + var content: String { + switch self { + case .appVersion: + return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "" + case .openSourceLicense: + guard let path = Bundle.main.path(forResource: "license", ofType: "txt"), + let text = try? String(contentsOfFile: path) else { return "" } + return text + case .privacyPolicy: + return "개인 정보 처리 방침" + case .contributor: + return "" + } + } +} diff --git a/Doolda/Doolda/Domain/Entities/FontColorType.swift b/Doolda/Doolda/Domain/Entities/FontColorType.swift index e9de4d62..b617884d 100644 --- a/Doolda/Doolda/Domain/Entities/FontColorType.swift +++ b/Doolda/Doolda/Domain/Entities/FontColorType.swift @@ -13,6 +13,12 @@ enum FontColorType: RawRepresentable, CaseIterable, Codable { case black case dooldaLabel + case blue + case green + case yellow + case orange + case red + case purple init(rawValue: RawValue) { self = .dooldaLabel @@ -22,6 +28,12 @@ enum FontColorType: RawRepresentable, CaseIterable, Codable { switch self { case .black: return #colorLiteral(red: 0, green: 0, blue: 0, alpha: 1) case .dooldaLabel: return #colorLiteral(red: 0.3490196078, green: 0.3490196078, blue: 0.3490196078, alpha: 1) + case .blue: return #colorLiteral(red: 0.2392156869, green: 0.6745098233, blue: 0.9686274529, alpha: 1) + case .green: return #colorLiteral(red: 0.4666666687, green: 0.7647058964, blue: 0.2666666806, alpha: 1) + case .yellow: return #colorLiteral(red: 0.9607843161, green: 0.7058823705, blue: 0.200000003, alpha: 1) + case .orange: return #colorLiteral(red: 0.9372549057, green: 0.3490196168, blue: 0.1921568662, alpha: 1) + case .red: return #colorLiteral(red: 0.7450980544, green: 0.1568627506, blue: 0.07450980693, alpha: 1) + case .purple: return #colorLiteral(red: 0.5568627715, green: 0.3529411852, blue: 0.9686274529, alpha: 1) } } } diff --git a/Doolda/Doolda/Domain/Entities/FontType.swift b/Doolda/Doolda/Domain/Entities/FontType.swift new file mode 100644 index 00000000..8e1a742e --- /dev/null +++ b/Doolda/Doolda/Domain/Entities/FontType.swift @@ -0,0 +1,68 @@ +// +// DoolDaFont.swift +// Doolda +// +// Created by 김민주 on 2021/11/17. +// + +import Foundation + +enum FontType: CaseIterable { + case dovemayo + case darae + case kotraHope + case uhBeeMysen + case dungGeunMo + case kyoboHandwriting + case appleNeo + + init?(fontName: String) { + for fontType in FontType.allCases { + if fontType.name == fontName { + self = fontType + return + } + } + self = .dovemayo + } +} + +extension FontType { + var name: String { + switch self { + case .dovemayo: + return "dovemayo" + case .darae: + return "drfont_daraehand" + case .kotraHope: + return "kotraHope" + case .uhBeeMysen: + return "uhBeeMysen" + case .dungGeunMo: + return "dungGeunMo" + case .kyoboHandwriting: + return "Kyobo Handwriting 2019" + case .appleNeo: + return "Apple SD Gothic Neo" + } + } + + var displayName: String { + switch self { + case .dovemayo: + return "둘기 마요" + case .darae: + return "다래 손글씨체" + case .kotraHope: + return "코트라 희망체" + case .uhBeeMysen: + return "어비 마이센체" + case .dungGeunMo: + return "Neo 둥근모" + case .kyoboHandwriting: + return "교보 손글씨" + case .appleNeo: + return "애플 네오 고딕" + } + } +} diff --git a/Doolda/Doolda/Domain/Entities/PageEntity.swift b/Doolda/Doolda/Domain/Entities/PageEntity.swift index c7c8dd4c..35d42fff 100644 --- a/Doolda/Doolda/Domain/Entities/PageEntity.swift +++ b/Doolda/Doolda/Domain/Entities/PageEntity.swift @@ -9,12 +9,14 @@ import Foundation struct PageEntity: Hashable { let author: User - let timeStamp: Date + let createdTime: Date + let updatedTime: Date let jsonPath: String func hash(into hasher: inout Hasher) { hasher.combine(author) - hasher.combine(timeStamp) + hasher.combine(createdTime) + hasher.combine(updatedTime) hasher.combine(jsonPath) } } diff --git a/Doolda/Doolda/Domain/Entities/PhotoFrameType.swift b/Doolda/Doolda/Domain/Entities/PhotoFrameType.swift index 5a558eda..a1067f5e 100644 --- a/Doolda/Doolda/Domain/Entities/PhotoFrameType.swift +++ b/Doolda/Doolda/Domain/Entities/PhotoFrameType.swift @@ -14,6 +14,7 @@ enum PhotoFrameType: RawRepresentable, CaseIterable { case normal case polaroid + case polaroidLong case lifeFourCuts init?(rawValue: RawValue) { @@ -24,6 +25,7 @@ enum PhotoFrameType: RawRepresentable, CaseIterable { switch self { case .normal: return PhotoFrame.normal case .polaroid: return PhotoFrame.polaroid + case .polaroidLong: return PhotoFrame.polaroidLong case .lifeFourCuts: return PhotoFrame.lifeFourCuts } } @@ -45,21 +47,28 @@ struct PhotoFrame { static let normal = PhotoFrame( displayName: "일반 사진", - baseImageName: "Normal", + baseImageName: "normal", photoBounds: [ CGRect(x: 0, y: 0, width: 858, height: 803) ] ) static let polaroid = PhotoFrame( - displayName: "폴라로이드", - baseImageName: "Polaroid", + displayName: "폴라로이드1", + baseImageName: "polaroid", photoBounds: [ CGRect(x: 54, y: 60, width: 856, height: 804) ] ) + static let polaroidLong = PhotoFrame( + displayName: "폴라로이드2", + baseImageName: "polaroid_long", + photoBounds: [ + CGRect(x: 54, y: 54, width: 856, height: 1070) + ] + ) static let lifeFourCuts = PhotoFrame( displayName: "인생 네컷", - baseImageName: "LifeFourCuts", + baseImageName: "lifeFourCuts", photoBounds: [ CGRect(x: 26, y: 25, width: 576, height: 342), CGRect(x: 26, y: 387, width: 576, height: 342), diff --git a/Doolda/Doolda/Domain/Entities/PushMessageEntity.swift b/Doolda/Doolda/Domain/Entities/PushMessageEntity.swift new file mode 100644 index 00000000..e97ed37f --- /dev/null +++ b/Doolda/Doolda/Domain/Entities/PushMessageEntity.swift @@ -0,0 +1,45 @@ +// +// PushMessageEntity.swift +// Doolda +// +// Created by Seunghun Yang on 2021/11/23. +// + +import Foundation + +struct PushMessageEntity { + let title: String + let body: String + let data: [String: String] + + static let userPairedWithFriend: PushMessageEntity = PushMessageEntity( + title: "똑똑! 🙋‍♀️🙋‍♂️", + body: "누군가가 당신을 친구로 연결했어요!", + data: ["notification": "userPairedWithFriend"] + ) + + static let userPostedNewPage: PushMessageEntity = PushMessageEntity( + title: "띵동! 🔔", + body: "친구가 다이어리를 작성했어요!\n새 다이어리를 확인해볼까요?", + data: ["notification": "userPostedNewPage"] + ) + + static let userRequestedNewPage: PushMessageEntity = PushMessageEntity( + title: "쿡쿡! 🥺👉🏻👉🏻", + body: "친구가 다이어리 작성을 기다리고있어요.\n새 다이어리를 작성해볼까요?", + data: ["notification": "userRequestedNewPage"] + ) + + static let userDisconnected: PushMessageEntity = PushMessageEntity( + title: "연결 해제", + body: "상대방이 연결을 해제했습니다.", + data: ["notification": "userDisconnected"] + ) + + enum Notifications { + static let userPairedWithFriend = Notification.Name("userPairedWithFriend") + static let userPostedNewPage = Notification.Name("userPostedNewPage") + static let userRequestedNewPage = Notification.Name("userRequestedNewPage") + static let userDisconnected = Notification.Name("userDisconnected") + } +} diff --git a/Doolda/Doolda/Domain/Entities/RawPageEntity.swift b/Doolda/Doolda/Domain/Entities/RawPageEntity.swift index a4e5d405..6a72183c 100644 --- a/Doolda/Doolda/Domain/Entities/RawPageEntity.swift +++ b/Doolda/Doolda/Domain/Entities/RawPageEntity.swift @@ -8,10 +8,6 @@ import CoreGraphics import Foundation -enum ComponentType: String, Codable { - case base, photo, sticker, text -} - struct RawPageEntity: Codable { var components: [ComponentEntity] var backgroundType: BackgroundType diff --git a/Doolda/Doolda/Domain/Entities/StickerComponentEntity.swift b/Doolda/Doolda/Domain/Entities/StickerComponentEntity.swift index 30455d09..19c2d672 100644 --- a/Doolda/Doolda/Domain/Entities/StickerComponentEntity.swift +++ b/Doolda/Doolda/Domain/Entities/StickerComponentEntity.swift @@ -9,27 +9,27 @@ import CoreGraphics import Foundation class StickerComponentEntity: ComponentEntity { - var stickerUrl: URL + var name: String private enum CodingKeys: String, CodingKey { - case stickerUrl, type + case name, type } - init(frame: CGRect, scale: CGFloat, angle: CGFloat, aspectRatio: CGFloat, stickerUrl: URL) { - self.stickerUrl = stickerUrl + init(frame: CGRect, scale: CGFloat, angle: CGFloat, aspectRatio: CGFloat, name: String) { + self.name = name super.init(frame: frame, scale: scale, angle: angle, aspectRatio: aspectRatio) } required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - self.stickerUrl = try container.decode(URL.self, forKey: .stickerUrl) + self.name = try container.decode(String.self, forKey: .name) try super.init(from: decoder) } override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(stickerUrl, forKey: .stickerUrl) + try container.encode(name, forKey: .name) try container.encode(ComponentType.sticker, forKey: .type) } } diff --git a/Doolda/Doolda/Domain/Entities/StickerPackEntity.swift b/Doolda/Doolda/Domain/Entities/StickerPackEntity.swift index 0bba416f..f3afaaa3 100644 --- a/Doolda/Doolda/Domain/Entities/StickerPackEntity.swift +++ b/Doolda/Doolda/Domain/Entities/StickerPackEntity.swift @@ -28,26 +28,37 @@ enum StickerPackType: RawRepresentable, CaseIterable { } class StickerPackEntity { - let name: String - var sealingImageUrl: URL - var stickersUrl: [URL] - var isUnpacked: Bool = false + let displayName: String + let familyName: String + let stickerCount: Int - private let maximumStickerCount = 16 - - init?(name: String) { - self.name = name - guard let coverUrl = Bundle.main.url(forResource: name + "_cover", withExtension: "png") else { return nil } - self.sealingImageUrl = coverUrl - self.stickersUrl = [] - for index in 0 ..< self.maximumStickerCount { - guard let stickerUrl = Bundle.main.url(forResource: name + "_\(index)", withExtension: "png") else { break } - self.stickersUrl.append(stickerUrl) + var isUnpacked: Bool = false + var coverStickerName: String { self.familyName + "_cover" } + var stickersName: [String] { + (0.. AnyPublisher + func fetchToken(for userId: DDID) -> AnyPublisher +} diff --git a/Doolda/Doolda/Domain/Interfaces/Repositories/FirebaseMessageRepositoryProtocol.swift b/Doolda/Doolda/Domain/Interfaces/Repositories/FirebaseMessageRepositoryProtocol.swift new file mode 100644 index 00000000..2ace6c27 --- /dev/null +++ b/Doolda/Doolda/Domain/Interfaces/Repositories/FirebaseMessageRepositoryProtocol.swift @@ -0,0 +1,13 @@ +// +// FirebaseMessageRepository.swift +// Doolda +// +// Created by Seunghun Yang on 2021/11/23. +// + +import Combine +import Foundation + +protocol FirebaseMessageRepositoryProtocol { + func sendMessage(to token: String, title: String, body: String, data: [String: String]) -> AnyPublisher<[String: Any], Error> +} diff --git a/Doolda/Doolda/Domain/Interfaces/GlobalFontRepositoryProtocol.swift b/Doolda/Doolda/Domain/Interfaces/Repositories/GlobalFontRepositoryProtocol.swift similarity index 100% rename from Doolda/Doolda/Domain/Interfaces/GlobalFontRepositoryProtocol.swift rename to Doolda/Doolda/Domain/Interfaces/Repositories/GlobalFontRepositoryProtocol.swift diff --git a/Doolda/Doolda/Domain/Interfaces/ImageRepositoryProtocol.swift b/Doolda/Doolda/Domain/Interfaces/Repositories/ImageRepositoryProtocol.swift similarity index 100% rename from Doolda/Doolda/Domain/Interfaces/ImageRepositoryProtocol.swift rename to Doolda/Doolda/Domain/Interfaces/Repositories/ImageRepositoryProtocol.swift diff --git a/Doolda/Doolda/Domain/Interfaces/PageRepositoryProtocol.swift b/Doolda/Doolda/Domain/Interfaces/Repositories/PageRepositoryProtocol.swift similarity index 80% rename from Doolda/Doolda/Domain/Interfaces/PageRepositoryProtocol.swift rename to Doolda/Doolda/Domain/Interfaces/Repositories/PageRepositoryProtocol.swift index e18002dd..236c580c 100644 --- a/Doolda/Doolda/Domain/Interfaces/PageRepositoryProtocol.swift +++ b/Doolda/Doolda/Domain/Interfaces/Repositories/PageRepositoryProtocol.swift @@ -10,5 +10,6 @@ import Foundation protocol PageRepositoryProtocol { func savePage(_ page: PageEntity) -> AnyPublisher + func updatePage(_ page: PageEntity) -> AnyPublisher func fetchPages(for pair: DDID) -> AnyPublisher<[PageEntity], Error> } diff --git a/Doolda/Doolda/Domain/Interfaces/PairRepositoryProtocol.swift b/Doolda/Doolda/Domain/Interfaces/Repositories/PairRepositoryProtocol.swift similarity index 85% rename from Doolda/Doolda/Domain/Interfaces/PairRepositoryProtocol.swift rename to Doolda/Doolda/Domain/Interfaces/Repositories/PairRepositoryProtocol.swift index c39b87b8..9de22c96 100644 --- a/Doolda/Doolda/Domain/Interfaces/PairRepositoryProtocol.swift +++ b/Doolda/Doolda/Domain/Interfaces/Repositories/PairRepositoryProtocol.swift @@ -10,7 +10,7 @@ import Foundation protocol PairRepositoryProtocol { func setPairId(with user: User) -> AnyPublisher - func setRecentlyEditedUser(with user: User) -> AnyPublisher func fetchRecentlyEditedUser(with user: User) -> AnyPublisher + func deletePair(with user: User) -> AnyPublisher } diff --git a/Doolda/Doolda/Domain/Interfaces/PushNotificationStateRepositoryProtocol.swift b/Doolda/Doolda/Domain/Interfaces/Repositories/PushNotificationStateRepositoryProtocol.swift similarity index 73% rename from Doolda/Doolda/Domain/Interfaces/PushNotificationStateRepositoryProtocol.swift rename to Doolda/Doolda/Domain/Interfaces/Repositories/PushNotificationStateRepositoryProtocol.swift index 9801ddee..c2e33021 100644 --- a/Doolda/Doolda/Domain/Interfaces/PushNotificationStateRepositoryProtocol.swift +++ b/Doolda/Doolda/Domain/Interfaces/Repositories/PushNotificationStateRepositoryProtocol.swift @@ -8,6 +8,6 @@ import Foundation protocol PushNotificationStateRepositoryProtocol { - func saveState(as state: Bool) - func fetchState() -> Bool? + func save(_ state: Bool) + func fetch() -> Bool? } diff --git a/Doolda/Doolda/Domain/Interfaces/RawPageRepositoryProtocol.swift b/Doolda/Doolda/Domain/Interfaces/Repositories/RawPageRepositoryProtocol.swift similarity index 75% rename from Doolda/Doolda/Domain/Interfaces/RawPageRepositoryProtocol.swift rename to Doolda/Doolda/Domain/Interfaces/Repositories/RawPageRepositoryProtocol.swift index e1e32848..3f80eb6b 100644 --- a/Doolda/Doolda/Domain/Interfaces/RawPageRepositoryProtocol.swift +++ b/Doolda/Doolda/Domain/Interfaces/Repositories/RawPageRepositoryProtocol.swift @@ -10,5 +10,5 @@ import Foundation protocol RawPageRepositoryProtocol { func save(rawPage: RawPageEntity, at folder: String, with name: String) -> AnyPublisher - func fetch(at folder: String, with name: String) -> AnyPublisher + func fetch(metaData: PageEntity) -> AnyPublisher } diff --git a/Doolda/Doolda/Domain/Interfaces/UserRepositoryProtocol.swift b/Doolda/Doolda/Domain/Interfaces/Repositories/UserRepositoryProtocol.swift similarity index 88% rename from Doolda/Doolda/Domain/Interfaces/UserRepositoryProtocol.swift rename to Doolda/Doolda/Domain/Interfaces/Repositories/UserRepositoryProtocol.swift index 8b6aa839..23bf6de0 100644 --- a/Doolda/Doolda/Domain/Interfaces/UserRepositoryProtocol.swift +++ b/Doolda/Doolda/Domain/Interfaces/Repositories/UserRepositoryProtocol.swift @@ -13,6 +13,7 @@ protocol UserRepositoryProtocol { func getMyId() -> AnyPublisher func setUser(_ user: User) -> AnyPublisher + func resetUser(_ user: User) -> AnyPublisher func fetchUser(_ id: DDID) -> AnyPublisher func fetchUser(_ user: User) -> AnyPublisher diff --git a/Doolda/Doolda/Domain/Interfaces/UseCases/CheckMyTurnUseCaseProtocol.swift b/Doolda/Doolda/Domain/Interfaces/UseCases/CheckMyTurnUseCaseProtocol.swift new file mode 100644 index 00000000..ad32995e --- /dev/null +++ b/Doolda/Doolda/Domain/Interfaces/UseCases/CheckMyTurnUseCaseProtocol.swift @@ -0,0 +1,13 @@ +// +// CheckMyTurnUseCaseProtocol.swift +// Doolda +// +// Created by 정지승 on 2021/11/29. +// + +import Combine +import Foundation + +protocol CheckMyTurnUseCaseProtocol { + func checkTurn(for user: User) -> AnyPublisher +} diff --git a/Doolda/Doolda/Domain/Interfaces/UseCases/EditPageUseCaseProtocol.swift b/Doolda/Doolda/Domain/Interfaces/UseCases/EditPageUseCaseProtocol.swift new file mode 100644 index 00000000..d68cf4cf --- /dev/null +++ b/Doolda/Doolda/Domain/Interfaces/UseCases/EditPageUseCaseProtocol.swift @@ -0,0 +1,31 @@ +// +// EditPageUseCaseProtocol.swift +// Doolda +// +// Created by 정지승 on 2021/11/29. +// + +import Combine +import CoreGraphics +import Foundation + +protocol EditPageUseCaseProtocol { + var selectedComponentPublisher: AnyPublisher { get } + var rawPagePublisher: AnyPublisher { get } + var errorPublisher: AnyPublisher { get } + var resultPublisher: AnyPublisher { get } + + func selectComponent(at point: CGPoint) + func moveComponent(to point: CGPoint) + func rotateComponent(by angle: CGFloat) + func scaleComponent(by scale: CGFloat) + func bringComponentFront() + func sendComponentBack() + func removeComponent() + func addComponent(_ component: ComponentEntity) + + func changeTextComponent(into content: TextComponentEntity) + + func changeBackgroundType(_ backgroundType: BackgroundType) + func savePage(author: User, metaData: PageEntity?) +} diff --git a/Doolda/Doolda/Domain/Interfaces/UseCases/FCMTokenUseCaseProtocol.swift b/Doolda/Doolda/Domain/Interfaces/UseCases/FCMTokenUseCaseProtocol.swift new file mode 100644 index 00000000..cd5cb738 --- /dev/null +++ b/Doolda/Doolda/Domain/Interfaces/UseCases/FCMTokenUseCaseProtocol.swift @@ -0,0 +1,14 @@ +// +// FCMTokenUseCaseProtocol.swift +// Doolda +// +// Created by 정지승 on 2021/11/29. +// + +import Combine +import Foundation + +protocol FCMTokenUseCaseProtocol { + func setToken(for userId: DDID, with token: String) -> AnyPublisher + func getToken(for userId: DDID) -> AnyPublisher +} diff --git a/Doolda/Doolda/Domain/Interfaces/UseCases/FirebaseMessageUseCaseProtocol.swift b/Doolda/Doolda/Domain/Interfaces/UseCases/FirebaseMessageUseCaseProtocol.swift new file mode 100644 index 00000000..ca1c9fb6 --- /dev/null +++ b/Doolda/Doolda/Domain/Interfaces/UseCases/FirebaseMessageUseCaseProtocol.swift @@ -0,0 +1,15 @@ +// +// FirebaseMessageUseCaseProtocol.swift +// Doolda +// +// Created by 정지승 on 2021/11/29. +// + +import Combine +import Foundation + +protocol FirebaseMessageUseCaseProtocol { + var errorPublisher: AnyPublisher { get } + + func sendMessage(to user: DDID, message: PushMessageEntity) +} diff --git a/Doolda/Doolda/Domain/Interfaces/UseCases/GetMyIdUseCaseProtocol.swift b/Doolda/Doolda/Domain/Interfaces/UseCases/GetMyIdUseCaseProtocol.swift new file mode 100644 index 00000000..81faf7cb --- /dev/null +++ b/Doolda/Doolda/Domain/Interfaces/UseCases/GetMyIdUseCaseProtocol.swift @@ -0,0 +1,13 @@ +// +// GetMyIdUseCaseProtocol.swift +// Doolda +// +// Created by 정지승 on 2021/11/29. +// + +import Combine +import Foundation + +protocol GetMyIdUseCaseProtocol { + func getMyId() -> AnyPublisher +} diff --git a/Doolda/Doolda/Domain/Interfaces/UseCases/GetPageUseCaseProtocol.swift b/Doolda/Doolda/Domain/Interfaces/UseCases/GetPageUseCaseProtocol.swift new file mode 100644 index 00000000..d8fcf4ca --- /dev/null +++ b/Doolda/Doolda/Domain/Interfaces/UseCases/GetPageUseCaseProtocol.swift @@ -0,0 +1,13 @@ +// +// GetPageUseCaseProtocol.swift +// Doolda +// +// Created by 정지승 on 2021/11/29. +// + +import Combine +import Foundation + +protocol GetPageUseCaseProtocol { + func getPages(for pair: DDID) -> AnyPublisher<[PageEntity], Error> +} diff --git a/Doolda/Doolda/Domain/Interfaces/UseCases/GetRawPageUseCaseProtocol.swift b/Doolda/Doolda/Domain/Interfaces/UseCases/GetRawPageUseCaseProtocol.swift new file mode 100644 index 00000000..eb2f0068 --- /dev/null +++ b/Doolda/Doolda/Domain/Interfaces/UseCases/GetRawPageUseCaseProtocol.swift @@ -0,0 +1,13 @@ +// +// GetRawPageUseCaseProtocol.swift +// Doolda +// +// Created by 정지승 on 2021/11/29. +// + +import Combine +import Foundation + +protocol GetRawPageUseCaseProtocol { + func getRawPageEntity(metaData: PageEntity) -> AnyPublisher +} diff --git a/Doolda/Doolda/Domain/Interfaces/UseCases/GetUserUseCaseProtocol.swift b/Doolda/Doolda/Domain/Interfaces/UseCases/GetUserUseCaseProtocol.swift new file mode 100644 index 00000000..76a3a604 --- /dev/null +++ b/Doolda/Doolda/Domain/Interfaces/UseCases/GetUserUseCaseProtocol.swift @@ -0,0 +1,13 @@ +// +// GetUserUseCaseProtocol.swift +// Doolda +// +// Created by 정지승 on 2021/11/29. +// + +import Combine +import Foundation + +protocol GetUserUseCaseProtocol { + func getUser(for id: DDID) -> AnyPublisher +} diff --git a/Doolda/Doolda/Domain/Interfaces/UseCases/GlobalFontUseCaseProtocol.swift b/Doolda/Doolda/Domain/Interfaces/UseCases/GlobalFontUseCaseProtocol.swift new file mode 100644 index 00000000..229abcc9 --- /dev/null +++ b/Doolda/Doolda/Domain/Interfaces/UseCases/GlobalFontUseCaseProtocol.swift @@ -0,0 +1,14 @@ +// +// GlobalFontUseCaseProtocol.swift +// Doolda +// +// Created by 정지승 on 2021/11/29. +// + +import UIKit + +protocol GlobalFontUseCaseProtocol { + func setGlobalFont(with fontName: String) + func saveGlobalFont(as fontName: String) + func getGlobalFont() -> FontType? +} diff --git a/Doolda/Doolda/Domain/Interfaces/UseCases/ImageComposeUseCaseProtocol.swift b/Doolda/Doolda/Domain/Interfaces/UseCases/ImageComposeUseCaseProtocol.swift new file mode 100644 index 00000000..7f96058e --- /dev/null +++ b/Doolda/Doolda/Domain/Interfaces/UseCases/ImageComposeUseCaseProtocol.swift @@ -0,0 +1,15 @@ +// +// ImageComposeUseCaseProtocol.swift +// Doolda +// +// Created by 정지승 on 2021/11/29. +// + +import Combine +import CoreImage +import Foundation + +protocol ImageComposeUseCaseProtocol { + func isComposable(photoFrameType: PhotoFrameType?, numberOfPhotos: Int) -> Bool + func compose(photoFrameType: PhotoFrameType, images: [CIImage]) -> AnyPublisher +} diff --git a/Doolda/Doolda/Domain/Interfaces/UseCases/ImageUseCaseProtocol.swift b/Doolda/Doolda/Domain/Interfaces/UseCases/ImageUseCaseProtocol.swift new file mode 100644 index 00000000..23af7d7a --- /dev/null +++ b/Doolda/Doolda/Domain/Interfaces/UseCases/ImageUseCaseProtocol.swift @@ -0,0 +1,15 @@ +// +// ImageUseCaseProtocol.swift +// Doolda +// +// Created by 정지승 on 2021/11/29. +// + +import Combine +import CoreImage +import Foundation + +protocol ImageUseCaseProtocol { + func saveLocal(image: CIImage) -> AnyPublisher + func saveRemote(for user: User, localUrl: URL) -> AnyPublisher +} diff --git a/Doolda/Doolda/Domain/Interfaces/UseCases/PairUserUseCaseProtocol.swift b/Doolda/Doolda/Domain/Interfaces/UseCases/PairUserUseCaseProtocol.swift new file mode 100644 index 00000000..74da396c --- /dev/null +++ b/Doolda/Doolda/Domain/Interfaces/UseCases/PairUserUseCaseProtocol.swift @@ -0,0 +1,17 @@ +// +// PairUserUseCaseProtocol.swift +// Doolda +// +// Created by 정지승 on 2021/11/29. +// + +import Combine +import Foundation + +protocol PairUserUseCaseProtocol { + var pairedUserPublisher: AnyPublisher { get } + var errorPublisher: AnyPublisher { get } + + func pair(user: User, friendId: DDID) + func pair(user: User) +} diff --git a/Doolda/Doolda/Domain/Interfaces/UseCases/PushNotificationStateUseCaseProtocol.swift b/Doolda/Doolda/Domain/Interfaces/UseCases/PushNotificationStateUseCaseProtocol.swift new file mode 100644 index 00000000..1907058e --- /dev/null +++ b/Doolda/Doolda/Domain/Interfaces/UseCases/PushNotificationStateUseCaseProtocol.swift @@ -0,0 +1,13 @@ +// +// PushNotificationStateUseCaseProtocol.swift +// Doolda +// +// Created by 정지승 on 2021/11/29. +// + +import Foundation + +protocol PushNotificationStateUseCaseProtocol { + func getPushNotificationState() -> Bool? + func setPushNotificationState(as state: Bool) +} diff --git a/Doolda/Doolda/Domain/Interfaces/UseCases/RefreshUserUseCaseProtocol.swift b/Doolda/Doolda/Domain/Interfaces/UseCases/RefreshUserUseCaseProtocol.swift new file mode 100644 index 00000000..9163fbbe --- /dev/null +++ b/Doolda/Doolda/Domain/Interfaces/UseCases/RefreshUserUseCaseProtocol.swift @@ -0,0 +1,16 @@ +// +// RefreshUserUseCaseProtocol.swift +// Doolda +// +// Created by 정지승 on 2021/11/29. +// + +import Combine +import Foundation + +protocol RefreshUserUseCaseProtocol { + var refreshedUserPublisher: AnyPublisher { get } + var errorPublisher: AnyPublisher { get } + + func refresh(for user: User) +} diff --git a/Doolda/Doolda/Domain/Interfaces/UseCases/RegisterUserUseCaseProtocol.swift b/Doolda/Doolda/Domain/Interfaces/UseCases/RegisterUserUseCaseProtocol.swift new file mode 100644 index 00000000..2ae8c93c --- /dev/null +++ b/Doolda/Doolda/Domain/Interfaces/UseCases/RegisterUserUseCaseProtocol.swift @@ -0,0 +1,16 @@ +// +// RegisterUserUseCaseProtocol.swift +// Doolda +// +// Created by 정지승 on 2021/11/29. +// + +import Combine +import Foundation + +protocol RegisterUserUseCaseProtocol { + var registeredUserPublisher: AnyPublisher { get } + var errorPublisher: AnyPublisher { get } + + func register() +} diff --git a/Doolda/Doolda/Domain/Interfaces/UseCases/StickerUseCaseProtocol.swift b/Doolda/Doolda/Domain/Interfaces/UseCases/StickerUseCaseProtocol.swift new file mode 100644 index 00000000..e0ee2b9e --- /dev/null +++ b/Doolda/Doolda/Domain/Interfaces/UseCases/StickerUseCaseProtocol.swift @@ -0,0 +1,16 @@ +// +// StickerUseCaseProtocol.swift +// Doolda +// +// Created by 정지승 on 2021/11/29. +// + +import CoreGraphics +import Foundation + +protocol StickerUseCaseProtocol { + var stickerPacks: [StickerPackType] { get } + func getStickerPackEntity(at index: Int) -> StickerPackEntity? + func getStickerName(at indexPath: IndexPath) -> String? + func selectSticker(at indexPath: IndexPath) -> StickerComponentEntity? +} diff --git a/Doolda/Doolda/Domain/Interfaces/UseCases/TextUseCaseProtocol.swift b/Doolda/Doolda/Domain/Interfaces/UseCases/TextUseCaseProtocol.swift new file mode 100644 index 00000000..9ccf982b --- /dev/null +++ b/Doolda/Doolda/Domain/Interfaces/UseCases/TextUseCaseProtocol.swift @@ -0,0 +1,14 @@ +// +// TextUseCaseProtocol.swift +// Doolda +// +// Created by 정지승 on 2021/11/29. +// + +import CoreGraphics +import Foundation + +protocol TextUseCaseProtocol { + func changeTextComponent(from textComponent: TextComponentEntity, with input: String, contentSize: CGSize, fontSize:CGFloat, color: FontColorType) -> TextComponentEntity + func getTextComponent(with input: String, contentSize: CGSize, fontSize:CGFloat, color: FontColorType) -> TextComponentEntity +} diff --git a/Doolda/Doolda/Domain/UseCases/CheckMyTurnUseCase.swift b/Doolda/Doolda/Domain/UseCases/CheckMyTurnUseCase.swift index b035f045..8efa6916 100644 --- a/Doolda/Doolda/Domain/UseCases/CheckMyTurnUseCase.swift +++ b/Doolda/Doolda/Domain/UseCases/CheckMyTurnUseCase.swift @@ -8,11 +8,7 @@ import Combine import Foundation -protocol CheckMyTurnUseCaseProtocol { - func checkTurn(for user: User) -> AnyPublisher -} - -class CheckMyTurnUseCase: CheckMyTurnUseCaseProtocol { +final class CheckMyTurnUseCase: CheckMyTurnUseCaseProtocol { private let pairRepository: PairRepositoryProtocol private var cancellables = Set() diff --git a/Doolda/Doolda/Domain/UseCases/EditPageUseCase.swift b/Doolda/Doolda/Domain/UseCases/EditPageUseCase.swift index d5b12886..7425a268 100644 --- a/Doolda/Doolda/Domain/UseCases/EditPageUseCase.swift +++ b/Doolda/Doolda/Domain/UseCases/EditPageUseCase.swift @@ -11,42 +11,21 @@ import Foundation enum EditPageUseCaseError: LocalizedError { case rawPageNotFound + case failedToSavePage var errorDescription: String? { switch self { case .rawPageNotFound: return "편집중인 페이지를 찾을 수 없습니다." + case .failedToSavePage: return "페이지 저장에 실패 했습니다." } } } -protocol EditPageUseCaseProtocol { - var selectedComponentPublisher: Published.Publisher { get } - var rawPagePublisher: Published.Publisher { get } - var errorPublisher: Published.Publisher { get } - var resultPublisher: Published.Publisher { get } - - func selectComponent(at point: CGPoint) - func moveComponent(to point: CGPoint) - func rotateComponent(by angle: CGFloat) - func scaleComponent(by scale: CGFloat) - func bringComponentFront() - func sendComponentBack() - func removeComponent() - func addComponent(_ component: ComponentEntity) - - func changeTextComponentColor(into color: FontColorType) - func changeTextCompnentFontSize(into size: CGFloat) - func changeTextComponentContent(into content: String) - - func changeBackgroundType(_ backgroundType: BackgroundType) - func savePage(author: User) -} - -class EditPageUseCase: EditPageUseCaseProtocol { - var selectedComponentPublisher: Published.Publisher { self.$selectedComponent } - var rawPagePublisher: Published.Publisher { self.$rawPage } - var errorPublisher: Published.Publisher { self.$error } - var resultPublisher: Published.Publisher { self.$result } +final class EditPageUseCase: EditPageUseCaseProtocol { + var selectedComponentPublisher: AnyPublisher { self.$selectedComponent.eraseToAnyPublisher() } + var rawPagePublisher: AnyPublisher { self.$rawPage.eraseToAnyPublisher() } + var errorPublisher: AnyPublisher { self.$error.eraseToAnyPublisher() } + var resultPublisher: AnyPublisher { self.$result.eraseToAnyPublisher() } private let user: User private let imageUseCase: ImageUseCaseProtocol @@ -134,39 +113,33 @@ class EditPageUseCase: EditPageUseCaseProtocol { self.selectedComponent = component self.selectedComponent = self.selectedComponent } - - func changeTextComponentColor(into color: FontColorType) { - guard let selectedComponent = self.selectedComponent as? TextComponentEntity else { return } - selectedComponent.fontColor = color - self.selectedComponent = selectedComponent - } - func changeTextCompnentFontSize(into size: CGFloat) { - guard let selectedComponent = self.selectedComponent as? TextComponentEntity else { return } - selectedComponent.fontSize = size - self.selectedComponent = selectedComponent - } - - func changeTextComponentContent(into content: String) { - guard let selectedComponent = self.selectedComponent as? TextComponentEntity else { return } - selectedComponent.text = content - self.selectedComponent = selectedComponent + func changeTextComponent(into content: TextComponentEntity) { + self.selectedComponent = content } func changeBackgroundType(_ backgroundType: BackgroundType) { self.rawPage?.backgroundType = backgroundType } - func savePage(author: User) { + func savePage(author: User, metaData: PageEntity?) { guard let page = self.rawPage else { return self.error = EditPageUseCaseError.rawPageNotFound } guard let pairId = author.pairId?.ddidString else { return } + let isNewPage = metaData == nil let currentTime = Date() let path = DateFormatter.jsonPathFormatter.string(from: currentTime) - let metaData = PageEntity(author: author, timeStamp: currentTime, jsonPath: path) + + let pageEntity = PageEntity( + author: metaData?.author ?? author, + createdTime: metaData?.createdTime ?? currentTime, + updatedTime: currentTime, + jsonPath: metaData?.jsonPath ?? path + ) let imageUploadPublishers = page.components .compactMap { $0 as? PhotoComponentEntity } + .filter { $0.imageUrl.scheme == "file" } .map { [weak self] photoComponent -> AnyPublisher in guard let self = self else { return Fail(error: EditPageUseCaseError.rawPageNotFound).eraseToAnyPublisher() } return self.imageUseCase.saveRemote(for: author, localUrl: photoComponent.imageUrl) @@ -181,15 +154,31 @@ class EditPageUseCase: EditPageUseCaseProtocol { .collect() .eraseToAnyPublisher() .sink { [weak self] completion in - guard case .failure(let error) = completion else { return } + guard case .failure(let error) = completion else {return } self?.error = error } receiveValue: { [weak self] _ in guard let self = self else { return } - Publishers.Zip3( - self.pageRepository.savePage(metaData), - self.rawPageRepository.save(rawPage: page, at: pairId, with: path), - self.pairRepository.setRecentlyEditedUser(with: self.user) - ) + let savePublisher: AnyPublisher + if isNewPage { + savePublisher = Publishers.Zip3( + self.pageRepository.savePage(pageEntity), + self.rawPageRepository.save(rawPage: page, at: pairId, with: pageEntity.jsonPath), + self.pairRepository.setRecentlyEditedUser(with: self.user) + ) + .mapError { _ -> Error in EditPageUseCaseError.failedToSavePage } + .map { _ -> Void in () } + .eraseToAnyPublisher() + } else { + savePublisher = Publishers.Zip( + self.pageRepository.updatePage(pageEntity), + self.rawPageRepository.save(rawPage: page, at: pairId, with: pageEntity.jsonPath) + ) + .mapError { _ -> Error in EditPageUseCaseError.failedToSavePage } + .map { _ -> Void in () } + .eraseToAnyPublisher() + } + + savePublisher .sink { [weak self] completion in guard case .failure(let error) = completion else { return } self?.error = error diff --git a/Doolda/Doolda/Domain/UseCases/FCMTokenUseCase.swift b/Doolda/Doolda/Domain/UseCases/FCMTokenUseCase.swift new file mode 100644 index 00000000..f621e04b --- /dev/null +++ b/Doolda/Doolda/Domain/UseCases/FCMTokenUseCase.swift @@ -0,0 +1,25 @@ +// +// FCMTokenUseCase.swift +// Doolda +// +// Created by Seunghun Yang on 2021/11/23. +// + +import Combine +import Foundation + +final class FCMTokenUseCase: FCMTokenUseCaseProtocol { + private let fcmTokenRepository: FCMTokenRepositoryProtocol + + init(fcmTokenRepository: FCMTokenRepositoryProtocol) { + self.fcmTokenRepository = fcmTokenRepository + } + + func setToken(for userId: DDID, with token: String) -> AnyPublisher { + return self.fcmTokenRepository.saveToken(for: userId, with: token) + } + + func getToken(for userId: DDID) -> AnyPublisher { + return self.fcmTokenRepository.fetchToken(for: userId) + } +} diff --git a/Doolda/Doolda/Domain/UseCases/FirebaseMessageUseCase.swift b/Doolda/Doolda/Domain/UseCases/FirebaseMessageUseCase.swift new file mode 100644 index 00000000..a828ee58 --- /dev/null +++ b/Doolda/Doolda/Domain/UseCases/FirebaseMessageUseCase.swift @@ -0,0 +1,47 @@ +// +// FirebaseMessageUseCase.swift +// Doolda +// +// Created by Seunghun Yang on 2021/11/23. +// + +import Combine +import Foundation + +final class FirebaseMessageUseCase: FirebaseMessageUseCaseProtocol { + var errorPublisher: AnyPublisher { self.$error.eraseToAnyPublisher() } + + private let fcmTokenRepository: FCMTokenRepositoryProtocol + private let firebaseMessageRepository: FirebaseMessageRepositoryProtocol + + private var cancellables: Set = [] + + @Published private var error: Error? + + init(fcmTokenRepository: FCMTokenRepositoryProtocol, firebaseMessageRepository: FirebaseMessageRepositoryProtocol) { + self.fcmTokenRepository = fcmTokenRepository + self.firebaseMessageRepository = firebaseMessageRepository + } + + func sendMessage(to user: DDID, message: PushMessageEntity) { + self.fcmTokenRepository.fetchToken(for: user) + .sink { [weak self] completion in + guard case .failure(let error) = completion else { return } + self?.error = error + } receiveValue: { [weak self] token in + guard let self = self else { return } + self.firebaseMessageRepository.sendMessage( + to: token, + title: message.title, + body: message.body, + data: message.data + ) + .sink { [weak self] completion in + guard case .failure(let error) = completion else { return } + self?.error = error + } receiveValue: { _ in } + .store(in: &self.cancellables) + } + .store(in: &self.cancellables) + } +} diff --git a/Doolda/Doolda/Domain/UseCases/GetMyIdUseCase.swift b/Doolda/Doolda/Domain/UseCases/GetMyIdUseCase.swift index 42a0f550..704c92b3 100644 --- a/Doolda/Doolda/Domain/UseCases/GetMyIdUseCase.swift +++ b/Doolda/Doolda/Domain/UseCases/GetMyIdUseCase.swift @@ -8,10 +8,6 @@ import Combine import Foundation -protocol GetMyIdUseCaseProtocol { - func getMyId() -> AnyPublisher -} - final class GetMyIdUseCase: GetMyIdUseCaseProtocol { private let userRepository: UserRepositoryProtocol diff --git a/Doolda/Doolda/Domain/UseCases/GetPageUseCase.swift b/Doolda/Doolda/Domain/UseCases/GetPageUseCase.swift index 2b259097..a684f31e 100644 --- a/Doolda/Doolda/Domain/UseCases/GetPageUseCase.swift +++ b/Doolda/Doolda/Domain/UseCases/GetPageUseCase.swift @@ -8,11 +8,7 @@ import Combine import Foundation -protocol GetPageUseCaseProtocol { - func getPages(for pair: DDID) -> AnyPublisher<[PageEntity], Error> -} - -class GetPageUseCase: GetPageUseCaseProtocol { +final class GetPageUseCase: GetPageUseCaseProtocol { private let pageRepository: PageRepositoryProtocol init(pageRepository: PageRepositoryProtocol) { diff --git a/Doolda/Doolda/Domain/UseCases/GetRawPageUseCase.swift b/Doolda/Doolda/Domain/UseCases/GetRawPageUseCase.swift index 82ae00ac..dab6ea2e 100644 --- a/Doolda/Doolda/Domain/UseCases/GetRawPageUseCase.swift +++ b/Doolda/Doolda/Domain/UseCases/GetRawPageUseCase.swift @@ -8,18 +8,14 @@ import Combine import Foundation -protocol GetRawPageUseCaseProtocol { - func getRawPageEntity(for pairId: DDID, jsonPath: String) -> AnyPublisher -} - -class GetRawPageUseCase: GetRawPageUseCaseProtocol { +final class GetRawPageUseCase: GetRawPageUseCaseProtocol { private let rawPageRepository: RawPageRepositoryProtocol init(rawPageRepository: RawPageRepositoryProtocol) { self.rawPageRepository = rawPageRepository } - func getRawPageEntity(for pairId: DDID, jsonPath: String) -> AnyPublisher { - return self.rawPageRepository.fetch(at: pairId.ddidString, with: jsonPath) + func getRawPageEntity(metaData: PageEntity) -> AnyPublisher { + return self.rawPageRepository.fetch(metaData: metaData) } } diff --git a/Doolda/Doolda/Domain/UseCases/GetUserUseCase.swift b/Doolda/Doolda/Domain/UseCases/GetUserUseCase.swift index b1fce52d..5702d4a1 100644 --- a/Doolda/Doolda/Domain/UseCases/GetUserUseCase.swift +++ b/Doolda/Doolda/Domain/UseCases/GetUserUseCase.swift @@ -8,10 +8,6 @@ import Combine import Foundation -protocol GetUserUseCaseProtocol { - func getUser(for id: DDID) -> AnyPublisher -} - final class GetUserUseCase: GetUserUseCaseProtocol { private let userRepository: UserRepositoryProtocol diff --git a/Doolda/Doolda/Domain/UseCases/GlobalFontUseCase.swift b/Doolda/Doolda/Domain/UseCases/GlobalFontUseCase.swift index 8fa62a99..3afa9658 100644 --- a/Doolda/Doolda/Domain/UseCases/GlobalFontUseCase.swift +++ b/Doolda/Doolda/Domain/UseCases/GlobalFontUseCase.swift @@ -7,13 +7,11 @@ import UIKit -protocol GlobalFontUseCaseProtocol { - func setGlobalFont(with fontName: String) - func saveGlobalFont(as fontName: String) - func getGlobalFont() -> String? -} - -class GlobalFontUseCase: GlobalFontUseCaseProtocol { +final class GlobalFontUseCase: GlobalFontUseCaseProtocol { + enum Notifications { + static let globalFontDidSet = Notification.Name("globalFontDidSet") + } + private let globalFontRepository: GlobalFontRepositoryProtocol init(globalFontRepository: GlobalFontRepositoryProtocol) { @@ -22,13 +20,15 @@ class GlobalFontUseCase: GlobalFontUseCaseProtocol { func setGlobalFont(with fontName: String) { UIFont.globalFontFamily = fontName + NotificationCenter.default.post(name: Notifications.globalFontDidSet, object: nil) } func saveGlobalFont(as fontName: String) { self.globalFontRepository.saveGlobalFont(as: fontName) } - func getGlobalFont() -> String? { - return self.globalFontRepository.getGlobalFont() + func getGlobalFont() -> FontType? { + guard let fontName = self.globalFontRepository.getGlobalFont() else { return nil } + return FontType(fontName: fontName) } } diff --git a/Doolda/Doolda/Domain/UseCases/ImageComposeUseCase.swift b/Doolda/Doolda/Domain/UseCases/ImageComposeUseCase.swift index 1fecdc63..23a54b06 100644 --- a/Doolda/Doolda/Domain/UseCases/ImageComposeUseCase.swift +++ b/Doolda/Doolda/Domain/UseCases/ImageComposeUseCase.swift @@ -23,12 +23,7 @@ enum ImageComposeUseCaseError: LocalizedError { } } -protocol ImageComposeUseCaseProtocol { - func isComposable(photoFrameType: PhotoFrameType?, numberOfPhotos: Int) -> Bool - func compose(photoFrameType: PhotoFrameType, images: [CIImage]) -> AnyPublisher -} - -class ImageComposeUseCase: ImageComposeUseCaseProtocol { +final class ImageComposeUseCase: ImageComposeUseCaseProtocol { func isComposable(photoFrameType: PhotoFrameType?, numberOfPhotos: Int) -> Bool { guard let photoFrame = photoFrameType?.rawValue else { return false } diff --git a/Doolda/Doolda/Domain/UseCases/ImageUseCase.swift b/Doolda/Doolda/Domain/UseCases/ImageUseCase.swift index dff2e6ec..5a923c13 100644 --- a/Doolda/Doolda/Domain/UseCases/ImageUseCase.swift +++ b/Doolda/Doolda/Domain/UseCases/ImageUseCase.swift @@ -23,12 +23,7 @@ enum ImageUseCaseError: LocalizedError { } } -protocol ImageUseCaseProtocol { - func saveLocal(image: CIImage) -> AnyPublisher - func saveRemote(for user: User, localUrl: URL) -> AnyPublisher -} - -class ImageUseCase: ImageUseCaseProtocol { +final class ImageUseCase: ImageUseCaseProtocol { private let imageRepository: ImageRepositoryProtocol init(imageRepository: ImageRepositoryProtocol) { diff --git a/Doolda/Doolda/Domain/UseCases/PairUserUseCase.swift b/Doolda/Doolda/Domain/UseCases/PairUserUseCase.swift index aacfb765..fbacf606 100644 --- a/Doolda/Doolda/Domain/UseCases/PairUserUseCase.swift +++ b/Doolda/Doolda/Domain/UseCases/PairUserUseCase.swift @@ -28,17 +28,9 @@ enum PairUserUseCaseError: LocalizedError { } } -protocol PairUserUseCaseProtocol { - var pairedUserPublisher: Published.Publisher { get } - var errorPublisher: Published.Publisher { get } - - func pair(user: User, friendId: DDID) - func pair(user: User) -} - final class PairUserUseCase: PairUserUseCaseProtocol { - var pairedUserPublisher: Published.Publisher { self.$pairedUser } - var errorPublisher: Published.Publisher { self.$error } + var pairedUserPublisher: AnyPublisher { self.$pairedUser.eraseToAnyPublisher() } + var errorPublisher: AnyPublisher { self.$error.eraseToAnyPublisher() } private let userRepository: UserRepositoryProtocol private let pairRepository: PairRepositoryProtocol @@ -76,18 +68,17 @@ final class PairUserUseCase: PairUserUseCaseProtocol { } func pair(user: User) { - let pairId = user.id - let user = User(id: user.id, pairId: pairId) + let user = User(id: user.id, pairId: user.id, friendId: user.id) Publishers.Zip( self.userRepository.setUser(user), self.pairRepository.setPairId(with: user) ) - .sink { completion in + .sink { [weak self] completion in guard case .failure(let error) = completion else { return } - self.error = error - } receiveValue: { user, _ in - self.pairedUser = user + self?.error = error + } receiveValue: { [weak self] user, _ in + self?.pairedUser = user } .store(in: &self.cancellables) } @@ -117,19 +108,19 @@ final class PairUserUseCase: PairUserUseCaseProtocol { case .failure = completion else { return } let pairId = DDID() - let user = User(id: user.id, pairId: pairId) - let friend = User(id: friend.id, pairId: pairId) + let user = User(id: user.id, pairId: pairId, friendId: friend.id) + let friend = User(id: friend.id, pairId: pairId, friendId: user.id) Publishers.Zip3( self.userRepository.setUser(user), self.userRepository.setUser(friend), self.pairRepository.setPairId(with: friend) ) - .sink { completion in + .sink { [weak self] completion in guard case .failure(let error) = completion else { return } - self.error = error - } receiveValue: { user, _, _ in - self.pairedUser = user + self?.error = error + } receiveValue: { [weak self] user, _, _ in + self?.pairedUser = user } .store(in: &self.cancellables) } receiveValue: { [weak self] _ in diff --git a/Doolda/Doolda/Domain/UseCases/PushNotificationStateUseCase.swift b/Doolda/Doolda/Domain/UseCases/PushNotificationStateUseCase.swift index fdf81d81..b21e33d2 100644 --- a/Doolda/Doolda/Domain/UseCases/PushNotificationStateUseCase.swift +++ b/Doolda/Doolda/Domain/UseCases/PushNotificationStateUseCase.swift @@ -7,11 +7,6 @@ import Foundation -protocol PushNotificationStateUseCaseProtocol { - func getPushNotificationState() -> Bool? - func setPushNotificationState(as state: Bool) -} - final class PushNotificationStateUseCase: PushNotificationStateUseCaseProtocol { private let pushNotificationStateRepository: PushNotificationStateRepositoryProtocol @@ -20,10 +15,10 @@ final class PushNotificationStateUseCase: PushNotificationStateUseCaseProtocol { } func getPushNotificationState() -> Bool? { - return self.pushNotificationStateRepository.fetchState() + return self.pushNotificationStateRepository.fetch() } func setPushNotificationState(as state: Bool) { - self.pushNotificationStateRepository.saveState(as: state) + self.pushNotificationStateRepository.save(state) } } diff --git a/Doolda/Doolda/Domain/UseCases/RefreshUserUseCase.swift b/Doolda/Doolda/Domain/UseCases/RefreshUserUseCase.swift index ddc36e4b..2ebe9d21 100644 --- a/Doolda/Doolda/Domain/UseCases/RefreshUserUseCase.swift +++ b/Doolda/Doolda/Domain/UseCases/RefreshUserUseCase.swift @@ -8,16 +8,9 @@ import Combine import Foundation -protocol RefreshUserUseCaseProtocol { - var refreshedUserPublisher: Published.Publisher { get } - var errorPublisher: Published.Publisher { get } - - func refresh(for user: User) -} - final class RefreshUserUseCase: RefreshUserUseCaseProtocol { - var refreshedUserPublisher: Published.Publisher { self.$refreshedUser } - var errorPublisher: Published.Publisher { self.$error } + var refreshedUserPublisher: AnyPublisher { self.$refreshedUser.eraseToAnyPublisher() } + var errorPublisher: AnyPublisher { self.$error.eraseToAnyPublisher() } private let userRepository: UserRepositoryProtocol @@ -31,11 +24,11 @@ final class RefreshUserUseCase: RefreshUserUseCaseProtocol { func refresh(for user: User) { self.userRepository.fetchUser(user) - .sink { completion in + .sink { [weak self] completion in guard case .failure(let error) = completion else { return } - self.error = error - } receiveValue: { user in - self.refreshedUser = user + self?.error = error + } receiveValue: { [weak self] user in + self?.refreshedUser = user } .store(in: &cancellables) } diff --git a/Doolda/Doolda/Domain/UseCases/RegisterUserUseCase.swift b/Doolda/Doolda/Domain/UseCases/RegisterUserUseCase.swift index 14f226cf..1c9cf884 100644 --- a/Doolda/Doolda/Domain/UseCases/RegisterUserUseCase.swift +++ b/Doolda/Doolda/Domain/UseCases/RegisterUserUseCase.swift @@ -8,16 +8,9 @@ import Combine import Foundation -protocol RegisterUserUseCaseProtocol { - var registeredUserPublisher: Published.Publisher { get } - var errorPublisher: Published.Publisher { get } - - func register() -} - final class RegisterUserUseCase: RegisterUserUseCaseProtocol { - var registeredUserPublisher: Published.Publisher { self.$registeredUser } - var errorPublisher: Published.Publisher { self.$error } + var registeredUserPublisher: AnyPublisher { self.$registeredUser.eraseToAnyPublisher() } + var errorPublisher: AnyPublisher { self.$error.eraseToAnyPublisher() } private let userRepository: UserRepositoryProtocol private var cancellables: Set = [] diff --git a/Doolda/Doolda/Domain/UseCases/StickerUseCase.swift b/Doolda/Doolda/Domain/UseCases/StickerUseCase.swift index de99de9b..4302ff4e 100644 --- a/Doolda/Doolda/Domain/UseCases/StickerUseCase.swift +++ b/Doolda/Doolda/Domain/UseCases/StickerUseCase.swift @@ -8,14 +8,7 @@ import CoreGraphics import Foundation -protocol StickerUseCaseProtocol { - var stickerPacks: [StickerPackType] { get } - func getStickerPackEntity(at index: Int) -> StickerPackEntity? - func getStickerUrl(at indexPath: IndexPath) -> URL? - func selectSticker(at indexPath: IndexPath) -> StickerComponentEntity? -} - -class StickerUseCase: StickerUseCaseProtocol { +final class StickerUseCase: StickerUseCaseProtocol { let stickerPacks: [StickerPackType] init() { @@ -27,10 +20,10 @@ class StickerUseCase: StickerUseCaseProtocol { return StickerPackType.allCases[index].rawValue } - func getStickerUrl(at indexPath: IndexPath) -> URL? { + func getStickerName(at indexPath: IndexPath) -> String? { guard let stickerPack = self.getStickerPackEntity(at: indexPath.section) else { return nil } - if stickerPack.stickersUrl.count <= indexPath.item { return nil } - return stickerPack.stickersUrl[indexPath.item] + if stickerPack.stickerCount <= indexPath.item { return nil } + return stickerPack.stickersName[indexPath.item] } func selectSticker(at indexPath: IndexPath) -> StickerComponentEntity? { @@ -41,7 +34,7 @@ class StickerUseCase: StickerUseCaseProtocol { scale: 1.0, angle: 0, aspectRatio: 1, - stickerUrl: selectedStickerPack.stickersUrl[indexPath.item] + name: selectedStickerPack.stickersName[indexPath.item] ) return stickerComponentEntity } diff --git a/Doolda/Doolda/Domain/UseCases/TextUseCase.swift b/Doolda/Doolda/Domain/UseCases/TextUseCase.swift index 6fb698aa..d1030852 100644 --- a/Doolda/Doolda/Domain/UseCases/TextUseCase.swift +++ b/Doolda/Doolda/Domain/UseCases/TextUseCase.swift @@ -8,11 +8,21 @@ import CoreGraphics import Foundation -protocol TextUseCaseProtocol { - func getTextComponent(with input: String, contentSize: CGSize, fontSize:CGFloat, color: FontColorType) -> TextComponentEntity -} +final class TextUseCase: TextUseCaseProtocol { + func changeTextComponent( + from textComponent: TextComponentEntity, + with input: String, + contentSize: CGSize, + fontSize:CGFloat, + color: FontColorType + ) -> TextComponentEntity { + textComponent.text = input + textComponent.frame.size = contentSize + textComponent.fontSize = fontSize + textComponent.fontColor = color + return textComponent + } -class TextUseCase: TextUseCaseProtocol { func getTextComponent(with input: String, contentSize: CGSize, fontSize: CGFloat, color: FontColorType) -> TextComponentEntity { let componentOrigin = CGPoint(x: 850 - contentSize.width/2, y: 1500 - contentSize.height/2) return TextComponentEntity( diff --git a/Doolda/Doolda/Domain/UseCases/UnpairUserUseCase.swift b/Doolda/Doolda/Domain/UseCases/UnpairUserUseCase.swift new file mode 100644 index 00000000..26054d52 --- /dev/null +++ b/Doolda/Doolda/Domain/UseCases/UnpairUserUseCase.swift @@ -0,0 +1,52 @@ +// +// UnpairUserUseCase.swift +// Doolda +// +// Created by 정지승 on 2021/11/29. +// + +import Combine +import Foundation + +protocol UnpairUserUseCaseProtocol { + func unpair(user: User) -> AnyPublisher +} + +final class UnpairUserUseCase: UnpairUserUseCaseProtocol { + private let userRepository: UserRepositoryProtocol + private let pairRepository: PairRepositoryProtocol + + init(userRepository: UserRepositoryProtocol, pairRepository: PairRepositoryProtocol) { + self.userRepository = userRepository + self.pairRepository = pairRepository + } + + func unpair(user: User) -> AnyPublisher { + let resetUser = User(id: user.id, pairId: nil, friendId: nil) + let publisher: AnyPublisher + + if let friendId = user.friendId, + resetUser.id != friendId { + publisher = Publishers.Zip3( + self.userRepository.resetUser(resetUser), + self.userRepository.resetUser(User(id: friendId, pairId: nil, friendId: nil)), + self.pairRepository.deletePair(with: user) + ) + .map { user, _, _ in + return user + } + .eraseToAnyPublisher() + } else { + publisher = Publishers.Zip( + self.userRepository.resetUser(resetUser), + self.pairRepository.deletePair(with: user) + ) + .map { user, _ in + return user + } + .eraseToAnyPublisher() + } + + return publisher + } +} diff --git a/Doolda/Doolda/Doolda.entitlements b/Doolda/Doolda/Doolda.entitlements new file mode 100644 index 00000000..903def2a --- /dev/null +++ b/Doolda/Doolda/Doolda.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/Doolda/Doolda/UI/ViewControllers/BackgroundTypePickerViewController.swift b/Doolda/Doolda/EditPageScene/BackgroundTypePickerScene/BackgroundTypePickerViewController.swift similarity index 96% rename from Doolda/Doolda/UI/ViewControllers/BackgroundTypePickerViewController.swift rename to Doolda/Doolda/EditPageScene/BackgroundTypePickerScene/BackgroundTypePickerViewController.swift index cd60e42d..008c8506 100644 --- a/Doolda/Doolda/UI/ViewControllers/BackgroundTypePickerViewController.swift +++ b/Doolda/Doolda/EditPageScene/BackgroundTypePickerScene/BackgroundTypePickerViewController.swift @@ -16,7 +16,7 @@ final class BackgroundTypePickerViewController: BottomSheetViewController { // MARK: - Static Properties - static let backgroundTypeCellIdentifier = "backgroundTypeCellIdentifier" + static let identifier = "backgroundTypeCellIdentifier" // MARK: - Subviews @@ -54,7 +54,7 @@ final class BackgroundTypePickerViewController: BottomSheetViewController { layout.sectionInset = .zero let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) collectionView.backgroundColor = .clear - collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: Self.backgroundTypeCellIdentifier) + collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: Self.identifier) collectionView.contentInset = UIEdgeInsets(top: 10, left: 20, bottom: 10, right: 20) collectionView.delegate = self collectionView.dataSource = self @@ -129,7 +129,7 @@ extension BackgroundTypePickerViewController: UICollectionViewDelegateFlowLayout } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Self.backgroundTypeCellIdentifier, for: indexPath) + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Self.identifier, for: indexPath) cell.layer.cornerRadius = 10 cell.layer.borderWidth = 1 diff --git a/Doolda/Doolda/UI/ViewControllers/EditPageViewController.swift b/Doolda/Doolda/EditPageScene/EditPageViewController.swift similarity index 68% rename from Doolda/Doolda/UI/ViewControllers/EditPageViewController.swift rename to Doolda/Doolda/EditPageScene/EditPageViewController.swift index 9a669473..b83148cc 100644 --- a/Doolda/Doolda/UI/ViewControllers/EditPageViewController.swift +++ b/Doolda/Doolda/EditPageScene/EditPageViewController.swift @@ -15,18 +15,17 @@ class EditPageViewController: UIViewController { // MARK: - Subviews - private lazy var scrollView: UIScrollView = UIScrollView() - private lazy var contentView: UIView = UIView() - private lazy var cancelButton: UIButton = { var button = UIButton() button.setImage(.xmark, for: .normal) + button.setPreferredSymbolConfiguration(UIImage.SymbolConfiguration.init(pointSize: 21), forImageIn: .normal) return button }() private lazy var saveButton: UIButton = { var button = UIButton() button.setImage(.checkmark, for: .normal) + button.setPreferredSymbolConfiguration(UIImage.SymbolConfiguration.init(pointSize: 21), forImageIn: .normal) return button }() @@ -37,32 +36,36 @@ class EditPageViewController: UIViewController { return view }() - private lazy var pageControlView: PageControlView = { - var controlView = PageControlView(frame: .zero, delegate: self) + private lazy var pageControlView: PageComponentControlView = { + var controlView = PageComponentControlView(frame: .zero, delegate: self) return controlView }() private lazy var addPhotoComponentButton: UIButton = { var button = UIButton(frame: CGRect(x: .zero, y: .zero, width: 24, height: 24)) button.setImage(.photo, for: .normal) + button.setPreferredSymbolConfiguration(UIImage.SymbolConfiguration.init(pointSize: 21), forImageIn: .normal) return button }() private lazy var addTextComponentButton: UIButton = { var button = UIButton(frame: CGRect(x: .zero, y: .zero, width: 24, height: 24)) button.setImage(.textformat, for: .normal) + button.setPreferredSymbolConfiguration(UIImage.SymbolConfiguration.init(pointSize: 21), forImageIn: .normal) return button }() private lazy var addStickerComponentButton: UIButton = { var button = UIButton(frame: CGRect(x: .zero, y: .zero, width: 24, height: 24)) button.setImage(.sticker, for: .normal) + button.setPreferredSymbolConfiguration(UIImage.SymbolConfiguration.init(pointSize: 21), forImageIn: .normal) return button }() private lazy var changeBackgroundTypeButton: UIButton = { var button = UIButton(frame: CGRect(x: .zero, y: .zero, width: 24, height: 24)) button.setImage(.background, for: .normal) + button.setPreferredSymbolConfiguration(UIImage.SymbolConfiguration.init(pointSize: 21), forImageIn: .normal) return button }() @@ -81,11 +84,16 @@ class EditPageViewController: UIViewController { }() private lazy var activityIndicator: CustomActivityIndicator = { - let customActivityIndicator = CustomActivityIndicator(subTitle: "페이지 저장중이에요!!🦔🦔") + let customActivityIndicator = CustomActivityIndicator(subTitle: "페이지 저장중이에요!!🦔🦔", loadingImage: .hedgehogWriting) customActivityIndicator.isHidden = true return customActivityIndicator }() + private lazy var hapticGenerator: UIImpactFeedbackGenerator = { + let generator = UIImpactFeedbackGenerator(style: .light) + return generator + }() + // MARK: - Override Properties override var prefersStatusBarHidden: Bool { return true } @@ -93,7 +101,7 @@ class EditPageViewController: UIViewController { // MARK: - Private Properties private var cancellables: Set = [] - private var viewModel: EditPageViewModelProtocol? + private var viewModel: EditPageViewModelProtocol! private var componentViewDictionary: [ComponentEntity: UIView] = [:] var widthRatioFromAbsolute: CGFloat { @@ -121,71 +129,66 @@ class EditPageViewController: UIViewController { self.viewModel = viewModel } + deinit { + print(#file, "DEINIT") + self.viewModel.deinitRequested() + } + // MARK: - Lifecycle Methods override func viewDidLoad() { super.viewDidLoad() self.configureUI() + self.configureFont() self.bindUI() self.bindViewModel() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - self.configureFont() + self.navigationController?.isNavigationBarHidden = false + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + self.viewModel?.editPageViewDidAppear() } // MARK: - Helpers private func configureUI() { self.view.backgroundColor = .dooldaBackground - self.title = "새 페이지" + self.title = "페이지 편집" self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: self.cancelButton) self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: self.saveButton) - - self.view.addSubview(self.scrollView) - self.scrollView.snp.makeConstraints { make in - make.top.equalTo(view.safeAreaLayoutGuide) - make.bottom.leading.trailing.equalToSuperview() - } - - self.scrollView.addSubview(self.contentView) - self.contentView.snp.makeConstraints { make in - make.top.leading.trailing.equalToSuperview() - make.centerX.equalToSuperview() - make.bottom.equalToSuperview().priority(.low) - make.centerY.equalToSuperview().priority(.low) - } - self.contentView.addSubview(self.pageView) + self.view.addSubview(self.pageView) self.pageView.isUserInteractionEnabled = true self.pageView.clipsToBounds = true self.pageView.layer.cornerRadius = 4 self.pageView.snp.makeConstraints { make in make.centerX.equalToSuperview() - make.top.equalTo(self.scrollView.snp.top).offset(12) + make.top.equalTo(self.view.safeAreaLayoutGuide).offset(12) make.width.equalTo(self.pageView.snp.height).multipliedBy(17.0 / 30.0) let screenHeight = UIScreen.main.bounds.size.height - print(screenHeight) if screenHeight > 750 { - make.height.equalTo(self.scrollView.snp.height).offset(-100) + make.height.equalTo(self.view.safeAreaLayoutGuide).offset(-65) } else { - make.height.equalTo(self.scrollView.snp.height).offset(-45) + make.height.equalTo(self.view.safeAreaLayoutGuide).offset(-45) } } - self.contentView.addSubview(self.pageControlView) + self.view.addSubview(self.pageControlView) self.pageControlView.clipsToBounds = true self.pageControlView.isUserInteractionEnabled = true self.pageControlView.snp.makeConstraints { make in make.edges.equalTo(self.pageView) } - self.contentView.addSubview(self.componentsStackView) + self.view.addSubview(self.componentsStackView) self.componentsStackView.snp.makeConstraints { make in make.leading.equalTo(self.pageView) make.top.equalTo(self.pageView.snp.bottom).offset(5) - make.bottom.equalToSuperview().offset(-5) make.width.equalTo(135) } @@ -196,15 +199,15 @@ class EditPageViewController: UIViewController { } private func configureFont() { - self.navigationController?.navigationBar.titleTextAttributes = [ - NSAttributedString.Key.font: UIFont.systemFont(ofSize: 17) as Any - ] + self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)] } private func bindUI() { self.cancelButton.publisher(for: .touchUpInside) .sink { [weak self] _ in guard let self = self else { return } + self.hapticGenerator.prepare() + self.hapticGenerator.impactOccurred() let alert = UIAlertController.selectAlert( title: "편집 나가기", message: "페이지를 저장하지 않고 나갈 시, 작성한 내용은 저장되지 않습니다.", @@ -219,6 +222,8 @@ class EditPageViewController: UIViewController { self.saveButton.publisher(for: .touchUpInside) .sink { [weak self] _ in guard let self = self else { return } + self.hapticGenerator.prepare() + self.hapticGenerator.impactOccurred() let alert = UIAlertController.selectAlert( title: "편집 저장하기", message: "페이지를 저장하시겠습니까?, 저장 후 더 이상 편집할 수 없습니다.", @@ -246,7 +251,7 @@ class EditPageViewController: UIViewController { case .began: let touchCGPoint = panGestrue.location(in: self.pageControlView) self.viewModel?.canvasDidTap(at: self.computePointToAbsolute(at: touchCGPoint)) - self.initialOrigin = self.pageControlView.componentSpaceView.frame.origin + self.initialOrigin = self.selectedComponentInitialRect.origin fallthrough case .changed: let translation = panGestrue.translation(in: self.pageControlView) @@ -265,26 +270,40 @@ class EditPageViewController: UIViewController { self.addPhotoComponentButton.publisher(for: .touchUpInside) .sink { [weak self] _ in guard let self = self else { return } + self.hapticGenerator.prepare() + self.hapticGenerator.impactOccurred() self.viewModel?.photoComponentAddButtonDidTap() }.store(in: &self.cancellables) self.addTextComponentButton.publisher(for: .touchUpInside) .sink { [weak self] _ in guard let self = self else { return } + self.hapticGenerator.prepare() + self.hapticGenerator.impactOccurred() self.viewModel?.textComponentAddButtonDidTap() }.store(in: &self.cancellables) self.addStickerComponentButton.publisher(for: .touchUpInside) .sink { [weak self] _ in guard let self = self else { return } + self.hapticGenerator.prepare() + self.hapticGenerator.impactOccurred() self.viewModel?.stickerComponentAddButtonDidTap() }.store(in: &self.cancellables) self.changeBackgroundTypeButton.publisher(for: .touchUpInside) .sink { [weak self] _ in guard let self = self else { return } + self.hapticGenerator.prepare() + self.hapticGenerator.impactOccurred() self.viewModel?.backgroundTypeButtonDidTap() }.store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: GlobalFontUseCase.Notifications.globalFontDidSet, object: nil) + .sink { [weak self] _ in + self?.configureFont() + } + .store(in: &self.cancellables) } private func bindViewModel() { @@ -309,6 +328,7 @@ class EditPageViewController: UIViewController { componentView.layer.frame = computedCGRect self.pageControlView.componentSpaceView.frame = computedCGRect + let transform = CGAffineTransform.identity.rotated( by: componentEntity.angle ).scaledBy( @@ -316,6 +336,14 @@ class EditPageViewController: UIViewController { y: componentEntity.scale ) componentView.transform = transform + + if let textComponentEntity = componentEntity as? TextComponentEntity, + let textComponentView = componentView as? UILabel { + textComponentView.text = textComponentEntity.text + textComponentView.textColor = UIColor(cgColor: textComponentEntity.fontColor.rawValue) + textComponentView.font = .systemFont(ofSize: textComponentEntity.fontSize) + } + self.pageControlView.componentSpaceView.transform = transform self.pageControlView.controlsView.transform = transform @@ -332,65 +360,67 @@ class EditPageViewController: UIViewController { value.removeFromSuperview() self.componentViewDictionary[key] = nil } - //MARK: fixme: 다른 컴포넌트들에 알맞게 for componentEntity in componenets { - let computedCGRect = CGRect( + guard let componentView = self.getComponentView(from: componentEntity) else { return } + componentView.frame = CGRect( origin: self.computePointFromAbsolute(at: componentEntity.origin), size: self.computeSizeFromAbsolute(with: componentEntity.frame.size) ) - - switch componentEntity { - case let photoComponentEtitiy as PhotoComponentEntity: - let photoComponentView = UIImageView(frame: computedCGRect) - photoComponentView.kf.setImage(with: photoComponentEtitiy.imageUrl) - self.componentViewDictionary[photoComponentEtitiy] = photoComponentView - self.pageView.addSubview(photoComponentView) - let transform = CGAffineTransform.identity - .rotated(by: componentEntity.angle) - .scaledBy(x: componentEntity.scale, y: componentEntity.scale) - photoComponentView.transform = transform - photoComponentView.layer.shadowColor = UIColor.lightGray.cgColor - photoComponentView.layer.shadowOpacity = 0.3 - photoComponentView.layer.shadowRadius = 10 - photoComponentView.layer.shadowOffset = CGSize(width: -5, height: -5) - case let stickerComponentEntity as StickerComponentEntity: - let stickerComponentView = UIImageView(frame: computedCGRect) - stickerComponentView.kf.setImage(with: stickerComponentEntity.stickerUrl) - self.componentViewDictionary[stickerComponentEntity] = stickerComponentView - self.pageView.addSubview(stickerComponentView) - let transform = CGAffineTransform.identity - .rotated(by: componentEntity.angle) - .scaledBy(x: componentEntity.scale, y: componentEntity.scale) - stickerComponentView.transform = transform - case let textComponentEntity as TextComponentEntity: - let textComponentView = UITextView(frame: computedCGRect) - textComponentView.backgroundColor = .clear - textComponentView.text = textComponentEntity.text - textComponentView.font = .systemFont(ofSize: textComponentEntity.fontSize) - textComponentView.textColor = UIColor(cgColor: textComponentEntity.fontColor.rawValue) - textComponentView.isScrollEnabled = false - textComponentView.textAlignment = .center - - self.componentViewDictionary[textComponentEntity] = textComponentView - self.pageView.addSubview(textComponentView) - let transform = CGAffineTransform.identity - .rotated(by: componentEntity.angle) - .scaledBy(x: componentEntity.scale, y: componentEntity.scale) - textComponentView.transform = transform - - default: - break - } + componentView.transform = CGAffineTransform.identity + .rotated(by: componentEntity.angle) + .scaledBy(x: componentEntity.scale, y: componentEntity.scale) + self.pageView.addSubview(componentView) + self.componentViewDictionary[componentEntity] = componentView } }.store(in: &self.cancellables) self.viewModel?.backgroundPublisher - .sink { backgroundType in - self.pageView.backgroundColor = UIColor(cgColor: backgroundType.rawValue) + .sink { [weak self] backgroundType in + self?.pageView.backgroundColor = UIColor(cgColor: backgroundType.rawValue) }.store(in: &self.cancellables) + + self.viewModel?.errorPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] error in + guard let error = error as? LocalizedError else { return } + let alert = UIAlertController.defaultAlert(title: "알림", message: error.localizedDescription, handler: { _ in + self?.activityIndicator.stopAnimating() + }) + self?.present(alert, animated: true, completion: nil) + } + .store(in: &self.cancellables) } // MARK: - Private Methods + + private func getComponentView(from componentEntity: ComponentEntity) -> UIView? { + switch componentEntity { + case let photoComponentEtitiy as PhotoComponentEntity: + let photoView = UIImageView() + photoView.kf.setImage(with: photoComponentEtitiy.imageUrl) + photoView.layer.shadowColor = UIColor.lightGray.cgColor + photoView.layer.shadowOpacity = 0.3 + photoView.layer.shadowRadius = 10 + photoView.layer.shadowOffset = CGSize(width: -5, height: -5) + return photoView + case let stickerComponentEntity as StickerComponentEntity: + let stickerView = UIImageView() + stickerView.image = UIImage(named: stickerComponentEntity.name) + stickerView.contentMode = .scaleAspectFit + return stickerView + case let textComponentEntity as TextComponentEntity: + let textView = UILabel() + textView.numberOfLines = 0 + textView.textAlignment = .center + textView.adjustsFontSizeToFitWidth = true + textView.adjustsFontForContentSizeCategory = true + textView.text = textComponentEntity.text + textView.textColor = UIColor(cgColor: textComponentEntity.fontColor.rawValue) + textView.font = .systemFont(ofSize: textComponentEntity.fontSize) + return textView + default: return nil + } + } private func computePointToAbsolute(at point: CGPoint) -> CGPoint { let computedX = point.x / self.widthRatioFromAbsolute @@ -409,24 +439,26 @@ class EditPageViewController: UIViewController { let computedHeight = size.height * self.heightRatioFromAbsolute return CGSize(width: computedWidth, height: computedHeight) } - } -extension EditPageViewController: ControlViewDelegate { +extension EditPageViewController: PageComponentControlViewDelegate { + func controlViewDidTap(_ pageComponentControlView: PageComponentControlView, with gesture: UITapGestureRecognizer) { + self.viewModel?.componentDidTap() + } - func leftTopControlDidTap(_ pageControlView: PageControlView, with gesture: UITapGestureRecognizer) { + func leftTopControlDidTap(_ pageControlView: PageComponentControlView, with gesture: UITapGestureRecognizer) { self.viewModel?.componentBringFrontControlDidTap() } - func leftBottomControlDidTap(_ pageControlView: PageControlView, with gesture: UITapGestureRecognizer) { + func leftBottomControlDidTap(_ pageControlView: PageComponentControlView, with gesture: UITapGestureRecognizer) { self.viewModel?.componentSendBackControlDidTap() } - func rightTopControlDidTap(_ pageControlView: PageControlView, with gesture: UITapGestureRecognizer) { + func rightTopControlDidTap(_ pageControlView: PageComponentControlView, with gesture: UITapGestureRecognizer) { self.viewModel?.componentRemoveControlDidTap() } - func rightBottomcontrolDidPan(_ pageControlView: PageControlView, with gesture: UIPanGestureRecognizer) { + func rightBottomcontrolDidPan(_ pageControlView: PageComponentControlView, with gesture: UIPanGestureRecognizer) { let touchLocation = gesture.location(in: self.view) let center = CGPoint(x: self.selectedComponentInitialRect.midX, y: self.selectedComponentInitialRect.midY) let xDifference = (center.x - touchLocation.x) @@ -456,7 +488,7 @@ extension EditPageViewController: ControlViewDelegate { } } - func controlViewDidPan(_ pageControlView: PageControlView, with gesture: UIPanGestureRecognizer) { + func controlViewDidPan(_ pageControlView: PageComponentControlView, with gesture: UIPanGestureRecognizer) { switch gesture.state { case .began: let touchCGPoint = gesture.location(in: self.pageControlView) @@ -479,24 +511,36 @@ extension EditPageViewController: ControlViewDelegate { extension EditPageViewController: PhotoPickerBottomSheetViewControllerDelegate { func composedPhotoDidMake(_ photoComponentEntity: PhotoComponentEntity) { + self.hapticGenerator.prepare() + self.hapticGenerator.impactOccurred() self.viewModel?.componentEntityDidAdd(photoComponentEntity) } } extension EditPageViewController: BackgroundTypePickerViewControllerDelegate { func backgroundTypeDidSelect(_ backgroundType: BackgroundType) { + self.hapticGenerator.prepare() + self.hapticGenerator.impactOccurred() self.viewModel?.backgroundColorDidChange(backgroundType) } } -extension EditPageViewController: StickerPickerViewControllerDelegate { +extension EditPageViewController: StickerPickerBottomSheetViewControllerDelegate { func stickerDidSelected(_ stickerComponentEntity: StickerComponentEntity) { + self.hapticGenerator.prepare() + self.hapticGenerator.impactOccurred() self.viewModel?.componentEntityDidAdd(stickerComponentEntity) } } -extension EditPageViewController: TextInputViewControllerDelegate { - func textInputDidEndEditing(_ textComponentEntity: TextComponentEntity) { +extension EditPageViewController: TextEditViewControllerDelegate { + func textInputDidEndAdd(_ textComponentEntity: TextComponentEntity) { + self.hapticGenerator.prepare() + self.hapticGenerator.impactOccurred() self.viewModel?.componentEntityDidAdd(textComponentEntity) } + + func textInputDidEndEditing(_ textComponentEntity: TextComponentEntity) { + self.viewModel?.textComponentDidChange(to: textComponentEntity) + } } diff --git a/Doolda/Doolda/EditPageScene/EditPageViewCoordinator.swift b/Doolda/Doolda/EditPageScene/EditPageViewCoordinator.swift new file mode 100644 index 00000000..a6605065 --- /dev/null +++ b/Doolda/Doolda/EditPageScene/EditPageViewCoordinator.swift @@ -0,0 +1,218 @@ +// +// EditPageViewCoordinator.swift +// Doolda +// +// Created by 김민주 on 2021/11/09. +// + +import Combine +import UIKit + +final class EditPageViewCoordinator: BaseCoordinator { + + // MARK: - Nested Enums + + enum Notifications { + static let editPageSaved = Notification.Name("editPageSaved") + static let editingPageCanceled = Notification.Name("editingPageCanceled") + static let addPhotoComponent = Notification.Name("addPhotoComponent") + static let editTextComponent = Notification.Name("editTextComponent") + static let addStickerComponent = Notification.Name("addStickerComponent") + static let changeBackgroundType = Notification.Name("changeBackgroundType") + } + + enum Keys { + static let textComponent = "textComponent" + } + + // MARK: - Private Properties + + private let user: User + private let pageEntity: PageEntity? + private let rawPageEntity: RawPageEntity? + private var cancellables: Set = [] + + // MARK: - Initializers + + init( + identifier: UUID, + presenter: UINavigationController, + user: User, + pageEntity: PageEntity? = nil, + rawPageEntity: RawPageEntity? = nil + ) { + self.user = user + self.pageEntity = pageEntity + self.rawPageEntity = rawPageEntity + super.init(identifier: identifier, presenter: presenter) + self.bind() + } + + // MARK: - Helpers + + private func bind() { + NotificationCenter.default.publisher(for: Notifications.editPageSaved, object: nil) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.editingPageSaved() + } + .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: Notifications.editingPageCanceled, object: nil) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.editingPageCanceled() + } + .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: Notifications.addPhotoComponent, object: nil) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.addPhotoComponent() + } + .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: Notifications.editTextComponent, object: nil) + .map { $0.userInfo?[EditPageViewCoordinator.Keys.textComponent] as? TextComponentEntity } + .receive(on: DispatchQueue.main) + .sink { [weak self] textComponent in + self?.editTextComponent(with: textComponent) + } + .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: Notifications.addStickerComponent, object: nil) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.addStickerComponent() + } + .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: Notifications.changeBackgroundType, object: nil) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.changeBackgroundType() + } + .store(in: &self.cancellables) + } + + // MARK: - Public Methods + + func start() { + DispatchQueue.main.async { + let fileManagerPersistenceService = FileManagerPersistenceService.shared + let urlSessionNetworkService = URLSessionNetworkService.shared + let coreDataPersistenceService = CoreDataPersistenceService.shared + let coreDataPageEntityPersistenceService = CoreDataPageEntityPersistenceService( + coreDataPersistenceService: coreDataPersistenceService + ) + + let pairRepository = PairRepository(networkService: urlSessionNetworkService) + let imageRepository = ImageRepository( + fileManagerService: fileManagerPersistenceService, + networkService: urlSessionNetworkService + ) + let pageRepository = PageRepository( + urlSessionNetworkService: urlSessionNetworkService, + pageEntityPersistenceService: coreDataPageEntityPersistenceService + ) + let rawPageRepository = RawPageRepository( + networkService: urlSessionNetworkService, + coreDataPageEntityPersistenceService: coreDataPageEntityPersistenceService, + fileManagerPersistenceService: fileManagerPersistenceService + ) + let fcmTokenRepository = FCMTokenRepository(urlSessionNetworkService: urlSessionNetworkService) + let firebaseMessageRepository = FirebaseMessageRepository(urlSessionNetworkService: urlSessionNetworkService) + + let imageUseCase = ImageUseCase(imageRepository: imageRepository) + let editPageUseCase = EditPageUseCase( + user: self.user, + imageUseCase: imageUseCase, + pageRepository: pageRepository, + rawPageRepository: rawPageRepository, + pairRepository: pairRepository + ) + let firebaseMessageUseCase = FirebaseMessageUseCase( + fcmTokenRepository: fcmTokenRepository, + firebaseMessageRepository: firebaseMessageRepository + ) + + let editPageViewModel = EditPageViewModel( + sceneId: self.identifier, + user: self.user, + pageEntity: self.pageEntity, + rawPageEntity: self.rawPageEntity, + editPageUseCase: editPageUseCase, + firebaseMessageUseCase: firebaseMessageUseCase + ) + + let viewController = EditPageViewController(viewModel: editPageViewModel) + self.presenter.pushViewController(viewController, animated: true) + } + } + + // MARK: - Private Methods + + private func editingPageSaved() { + self.presenter.popViewController(animated: true) + } + + private func editingPageCanceled() { + self.presenter.popViewController(animated: true) + } + + private func addPhotoComponent() { + let fileManagerPersistenceService = FileManagerPersistenceService.shared + let urlSessionNetworkService = URLSessionNetworkService.shared + + let imageRepository = ImageRepository(fileManagerService: fileManagerPersistenceService, networkService: urlSessionNetworkService) + let imageUseCase = ImageUseCase(imageRepository: imageRepository) + let imageComposeUseCaes = ImageComposeUseCase() + + let photoPickerBottomSheetViewModel = PhotoPickerBottomSheetViewModel( + imageUseCase: imageUseCase, + imageComposeUseCase: imageComposeUseCaes + ) + + let delegatedViewController = self.presenter.topViewController as? EditPageViewController + let viewController = PhotoPickerBottomSheetViewController( + photoPickerViewModel: photoPickerBottomSheetViewModel, + delegate: delegatedViewController + ) + + self.presenter.topViewController?.present(viewController, animated: false, completion: nil) + } + + private func editTextComponent(with textComponent: TextComponentEntity? = nil) { + let delegatedViewController = self.presenter.topViewController as? EditPageViewController + + let textEditViewModel = TextEditViewModel( + textUseCase: TextUseCase(), + selectedTextComponent: textComponent) + let viewController = TextEditViewController( + textEditViewModel: textEditViewModel, + delegate: delegatedViewController, + widthRatioFromAbsolute: delegatedViewController?.widthRatioFromAbsolute, + heightRatioFromAbsolute: delegatedViewController?.heightRatioFromAbsolute + ) + + self.presenter.topViewController?.present(viewController, animated: false, completion: nil) + } + + private func addStickerComponent() { + let stickerUseCase = StickerUseCase() + let stickerPickerBottomSheetViewModel = StickerPickerBottomSheetViewModel(stickerUseCase: stickerUseCase) + let delegatedViewController = self.presenter.topViewController as? EditPageViewController + let viewController = StickerPickerBottomSheetViewController( + stickerPickerBottomSheetViewModel: stickerPickerBottomSheetViewModel, + delegate: delegatedViewController + ) + self.presenter.topViewController?.present(viewController, animated: false, completion: nil) + } + + private func changeBackgroundType() { + let delegatedViewController = self.presenter.topViewController as? EditPageViewController + let viewController = BackgroundTypePickerViewController(delegate: delegatedViewController) + + delegatedViewController?.present(viewController, animated: false, completion: nil) + } +} diff --git a/Doolda/Doolda/EditPageScene/EditPageViewModel.swift b/Doolda/Doolda/EditPageScene/EditPageViewModel.swift new file mode 100644 index 00000000..91609ede --- /dev/null +++ b/Doolda/Doolda/EditPageScene/EditPageViewModel.swift @@ -0,0 +1,202 @@ +// +// EditPageViewModel.swift +// Doolda +// +// Created by 김민주 on 2021/11/08. +// + +import Combine +import CoreGraphics +import Foundation + +protocol EditPageViewModelInput { + func editPageViewDidAppear() + + func canvasDidTap(at point: CGPoint) + + func componentDidTap() + func componentDidDrag(at point: CGPoint) + func componentDidRotate(by angle: CGFloat) + func componentDidScale(by scale: CGFloat) + func componentBringFrontControlDidTap() + func componentSendBackControlDidTap() + func componentRemoveControlDidTap() + + func textComponentDidChange(to textComponent: TextComponentEntity) + + func photoComponentAddButtonDidTap() + func textComponentAddButtonDidTap() + func stickerComponentAddButtonDidTap() + func backgroundTypeButtonDidTap() + + func componentEntityDidAdd(_ component: ComponentEntity) + func backgroundColorDidChange(_ backgroundColor: BackgroundType) + func saveEditingPageButtonDidTap() + func cancelEditingPageButtonDidTap() + func deinitRequested() +} + +protocol EditPageViewModelOutput { + var selectedComponentPublisher: AnyPublisher { get } + var componentsPublisher: AnyPublisher<[ComponentEntity], Never> { get } + var backgroundPublisher: AnyPublisher { get } + var errorPublisher: AnyPublisher { get } +} + +typealias EditPageViewModelProtocol = EditPageViewModelInput & EditPageViewModelOutput + +final class EditPageViewModel: EditPageViewModelProtocol { + var selectedComponentPublisher: AnyPublisher { self.$selectedComponent.eraseToAnyPublisher() } + var componentsPublisher: AnyPublisher<[ComponentEntity], Never> { self.$components.eraseToAnyPublisher() } + var backgroundPublisher: AnyPublisher { self.$background.eraseToAnyPublisher() } + var errorPublisher: AnyPublisher { self.$error.eraseToAnyPublisher() } + + private let sceneId: UUID + private let user: User + private let pageEntity: PageEntity? + private let rawPageEntity: RawPageEntity? + private let editPageUseCase: EditPageUseCaseProtocol + private let firebaseMessageUseCase: FirebaseMessageUseCaseProtocol + + private var cancellables: Set = [] + + @Published private var selectedComponent: ComponentEntity? = nil + @Published private var components: [ComponentEntity] = [] + @Published private var background: BackgroundType = .dooldaBackground + @Published private var error: Error? + + init( + sceneId: UUID, + user: User, + pageEntity: PageEntity? = nil, + rawPageEntity: RawPageEntity? = nil, + editPageUseCase: EditPageUseCaseProtocol, + firebaseMessageUseCase: FirebaseMessageUseCaseProtocol + ) { + self.sceneId = sceneId + self.user = user + self.pageEntity = pageEntity + self.rawPageEntity = rawPageEntity + self.editPageUseCase = editPageUseCase + self.firebaseMessageUseCase = firebaseMessageUseCase + bind() + } + + private func bind() { + self.editPageUseCase.selectedComponentPublisher + .sink { [weak self] selectedComponent in + self?.selectedComponent = selectedComponent + }.store(in : &self.cancellables) + + self.editPageUseCase.rawPagePublisher + .sink { [weak self] rawPageEntity in + guard let self = self, + let rawPageEntity = rawPageEntity else { return } + self.components = rawPageEntity.components + self.background = rawPageEntity.backgroundType + }.store(in : &self.cancellables) + + self.editPageUseCase.resultPublisher + .dropFirst() + .sink { [weak self] _ in + guard let self = self else { return } + if let friendId = self.user.friendId, friendId != self.user.id { + self.firebaseMessageUseCase.sendMessage(to: friendId, message: PushMessageEntity.userPostedNewPage) + } + NotificationCenter.default.post(name: EditPageViewCoordinator.Notifications.editPageSaved, object: nil) + }.store(in: &self.cancellables) + + self.editPageUseCase.errorPublisher + .assign(to: &$error) + } + + func editPageViewDidAppear() { + guard let rawPageEntity = self.rawPageEntity else { return } + rawPageEntity.components.forEach { component in + self.componentEntityDidAdd(component) + } + self.backgroundColorDidChange(rawPageEntity.backgroundType) + } + + func componentDidTap() { + if let selectedComponent = self.selectedComponent as? TextComponentEntity { + NotificationCenter.default.post( + name: EditPageViewCoordinator.Notifications.editTextComponent, + object: nil, + userInfo: [EditPageViewCoordinator.Keys.textComponent: selectedComponent] + ) + } + } + + func canvasDidTap(at point: CGPoint) { + self.editPageUseCase.selectComponent(at: point) + } + + func componentDidDrag(at point: CGPoint) { + self.editPageUseCase.moveComponent(to: point) + } + + func componentDidRotate(by angle: CGFloat) { + self.editPageUseCase.rotateComponent(by: angle) + } + + func componentDidScale(by scale: CGFloat) { + self.editPageUseCase.scaleComponent(by: scale) + } + + func componentBringFrontControlDidTap() { + self.editPageUseCase.bringComponentFront() + } + + func componentSendBackControlDidTap() { + self.editPageUseCase.sendComponentBack() + } + + func componentRemoveControlDidTap() { + self.editPageUseCase.removeComponent() + } + + func textComponentDidChange(to textComponent: TextComponentEntity) { + self.editPageUseCase.changeTextComponent(into: textComponent) + } + + func photoComponentAddButtonDidTap() { + NotificationCenter.default.post(name: EditPageViewCoordinator.Notifications.addPhotoComponent, object: nil) + } + + func textComponentAddButtonDidTap() { + NotificationCenter.default.post(name: EditPageViewCoordinator.Notifications.editTextComponent, object: nil) + } + + func stickerComponentAddButtonDidTap() { + NotificationCenter.default.post(name: EditPageViewCoordinator.Notifications.addStickerComponent, object: nil) + } + + func backgroundTypeButtonDidTap() { + NotificationCenter.default.post(name: EditPageViewCoordinator.Notifications.changeBackgroundType, object: nil) + } + + func componentEntityDidAdd(_ component: ComponentEntity) { + self.editPageUseCase.addComponent(component) + } + + func backgroundColorDidChange(_ backgroundColor: BackgroundType) { + self.editPageUseCase.changeBackgroundType(backgroundColor) + } + + func saveEditingPageButtonDidTap() { + self.editPageUseCase.savePage(author: self.user, metaData: self.pageEntity) + } + + func cancelEditingPageButtonDidTap() { + NotificationCenter.default.post(name: EditPageViewCoordinator.Notifications.editingPageCanceled, object: nil) + } + + func deinitRequested() { + NotificationCenter.default.post( + name: BaseCoordinator.Notifications.coordinatorRemoveFromParent, + object: nil, + userInfo: [BaseCoordinator.Keys.sceneId: self.sceneId] + ) + } +} diff --git a/Doolda/Doolda/UI/Views/PageControlView.swift b/Doolda/Doolda/EditPageScene/PageComponentControlView.swift similarity index 82% rename from Doolda/Doolda/UI/Views/PageControlView.swift rename to Doolda/Doolda/EditPageScene/PageComponentControlView.swift index 1a9ce996..f6e1cfc7 100644 --- a/Doolda/Doolda/UI/Views/PageControlView.swift +++ b/Doolda/Doolda/EditPageScene/PageComponentControlView.swift @@ -10,15 +10,16 @@ import UIKit import SnapKit -protocol ControlViewDelegate: AnyObject { - func controlViewDidPan(_ pageControlView: PageControlView, with gesture: UIPanGestureRecognizer) - func leftTopControlDidTap(_ pageControlView: PageControlView, with gesture: UITapGestureRecognizer) - func leftBottomControlDidTap(_ pageControlView: PageControlView, with gesture: UITapGestureRecognizer) - func rightTopControlDidTap(_ pageControlView: PageControlView, with gesture: UITapGestureRecognizer) - func rightBottomcontrolDidPan(_ pageControlView: PageControlView, with gesture: UIPanGestureRecognizer) +protocol PageComponentControlViewDelegate: AnyObject { + func controlViewDidTap(_ pageComponentControlView: PageComponentControlView, with gesture: UITapGestureRecognizer) + func controlViewDidPan(_ pageComponentControlView: PageComponentControlView, with gesture: UIPanGestureRecognizer) + func leftTopControlDidTap(_ pageComponentControlView: PageComponentControlView, with gesture: UITapGestureRecognizer) + func leftBottomControlDidTap(_ pageComponentControlView: PageComponentControlView, with gesture: UITapGestureRecognizer) + func rightTopControlDidTap(_ pageComponentControlView: PageComponentControlView, with gesture: UITapGestureRecognizer) + func rightBottomcontrolDidPan(_ pageComponentControlView: PageComponentControlView, with gesture: UIPanGestureRecognizer) } -class PageControlView: UIView { +class PageComponentControlView: UIView { // MARK: - Subviews lazy var controlsView: UIView = { @@ -54,12 +55,12 @@ class PageControlView: UIView { // MARK: - Private Properties - private weak var delegate: ControlViewDelegate? + private weak var delegate: PageComponentControlViewDelegate? private var cancellables: Set = [] // MARK: - Initializers - convenience init(frame: CGRect, delegate: ControlViewDelegate) { + convenience init(frame: CGRect, delegate: PageComponentControlViewDelegate) { self.init(frame: frame) self.delegate = delegate } @@ -154,6 +155,14 @@ class PageControlView: UIView { self.delegate?.controlViewDidPan(self, with: panGesture) } .store(in: &cancellables) + self.controlsView.publisher(for: UITapGestureRecognizer()) + .receive(on: DispatchQueue.main) + .sink { [weak self] gesture in + guard let self = self, + let tapGesture = gesture as? UITapGestureRecognizer else { return } + self.delegate?.controlViewDidTap(self, with: tapGesture) + } + .store(in: &cancellables) self.leftTopControl.publisher(for: UITapGestureRecognizer()) .receive(on: DispatchQueue.main) @@ -209,6 +218,7 @@ class PageControlView: UIView { } else { self.componentSpaceView.layer.borderWidth = 0 } + self.controlsView.isHidden = !selectedState self.leftTopControl.isHidden = !selectedState self.leftBottomControl.isHidden = !selectedState self.rightTopControl.isHidden = !selectedState diff --git a/Doolda/Doolda/UI/Views/CarouselView.swift b/Doolda/Doolda/EditPageScene/PhotoPickerScene/CarouselView.swift similarity index 100% rename from Doolda/Doolda/UI/Views/CarouselView.swift rename to Doolda/Doolda/EditPageScene/PhotoPickerScene/CarouselView.swift diff --git a/Doolda/Doolda/UI/Views/PhotoFrameCollectionViewCell.swift b/Doolda/Doolda/EditPageScene/PhotoPickerScene/PhotoFrameCollectionViewCell.swift similarity index 93% rename from Doolda/Doolda/UI/Views/PhotoFrameCollectionViewCell.swift rename to Doolda/Doolda/EditPageScene/PhotoPickerScene/PhotoFrameCollectionViewCell.swift index 0f988b2c..dd1bbfb9 100644 --- a/Doolda/Doolda/UI/Views/PhotoFrameCollectionViewCell.swift +++ b/Doolda/Doolda/EditPageScene/PhotoPickerScene/PhotoFrameCollectionViewCell.swift @@ -22,7 +22,6 @@ class PhotoFrameCollectionViewCell: UICollectionViewCell { imageView.layer.shadowColor = UIColor.lightGray.cgColor imageView.layer.shadowRadius = 10 imageView.layer.shadowOpacity = 0.3 - // FIXME : scaleAspectFit으로 생기는 이미지 여백의 그림자 처리를 dynamic shadow방식말고 사용할 수 있도록 개선 imageView.contentMode = .scaleAspectFit return imageView }() diff --git a/Doolda/Doolda/UI/ViewControllers/PhotoPickerBottomSheetViewController.swift b/Doolda/Doolda/EditPageScene/PhotoPickerScene/PhotoPickerBottomSheetViewController.swift similarity index 67% rename from Doolda/Doolda/UI/ViewControllers/PhotoPickerBottomSheetViewController.swift rename to Doolda/Doolda/EditPageScene/PhotoPickerScene/PhotoPickerBottomSheetViewController.swift index a46a54d3..64dfe99e 100644 --- a/Doolda/Doolda/UI/ViewControllers/PhotoPickerBottomSheetViewController.swift +++ b/Doolda/Doolda/EditPageScene/PhotoPickerScene/PhotoPickerBottomSheetViewController.swift @@ -64,7 +64,7 @@ final class PhotoPickerBottomSheetViewController: BottomSheetViewController { }() private lazy var activityIndicator: CustomActivityIndicator = { - let customActivityIndicator = CustomActivityIndicator(subTitle: "이미지 합성중이에요!🦔🦔") + let customActivityIndicator = CustomActivityIndicator(subTitle: "이미지 합성중이에요!🦔🦔", loadingImage: .hedgehogWriting) customActivityIndicator.isHidden = true return customActivityIndicator }() @@ -89,25 +89,17 @@ final class PhotoPickerBottomSheetViewController: BottomSheetViewController { }() private lazy var nextButton: UIButton = { - var configuration = UIButton.Configuration.filled() - configuration.cornerStyle = .capsule - configuration.baseForegroundColor = .dooldaLabel - configuration.baseBackgroundColor = .dooldaHighlighted - configuration.attributedTitle = AttributedString("다음", attributes: self.fontContainer) - let button = UIButton(configuration: configuration) + let button = DooldaButton() + button.setTitleColor(.dooldaLabel, for: .normal) + button.backgroundColor = .dooldaHighlighted + button.setTitle("다음", for: .normal) button.isEnabled = false return button }() // MARK: - Private Properties - - private var fontContainer: AttributeContainer { - var container = AttributeContainer() - container.font = .systemFont(ofSize: 16) - return container - } - - private var viewModel: PhotoPickerBottomSheetViewModel? + + private var viewModel: PhotoPickerBottomSheetViewModelProtocol! private var cancellables = Set() private var currentContentView: UIView? private weak var delegate: PhotoPickerBottomSheetViewControllerDelegate? @@ -115,7 +107,7 @@ final class PhotoPickerBottomSheetViewController: BottomSheetViewController { // MARK: - Initializers convenience init( - photoPickerViewModel: PhotoPickerBottomSheetViewModel, + photoPickerViewModel: PhotoPickerBottomSheetViewModelProtocol, delegate: PhotoPickerBottomSheetViewControllerDelegate? ) { self.init(nibName: nil, bundle: nil) @@ -127,13 +119,18 @@ final class PhotoPickerBottomSheetViewController: BottomSheetViewController { override func viewDidLoad() { super.viewDidLoad() - configureUI() + self.configureFont() bindUI() + bindViewModel() setContentView(self.framePicker) } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + } + // MARK: - Helpers private func configureUI() { @@ -168,19 +165,21 @@ final class PhotoPickerBottomSheetViewController: BottomSheetViewController { } } + private func configureFont() { + self.bottomSheetTitle.font = .systemFont(ofSize: 16) + self.nextButton.titleLabel?.font = .systemFont(ofSize: 16) + } + private func bindUI() { - guard let viewModel = viewModel else { return } - self.nextButton.publisher(for: .touchUpInside) .receive(on: DispatchQueue.main) .sink { [weak self] _ in guard let self = self else { return } if self.currentContentView == self.framePicker { - self.photoFrameDidSelect() + self.viewModel.nextButtonDidTap() } else if self.currentContentView == self.photoPickerCollectionView { self.activityIndicator.startAnimating() - - self.viewModel?.completeButtonDidTap() + self.viewModel.completeButtonDidTap() } } .store(in: &self.cancellables) @@ -191,8 +190,29 @@ final class PhotoPickerBottomSheetViewController: BottomSheetViewController { self?.dismiss(animated: true, completion: nil) } .store(in: &self.cancellables) + } + + private func bindViewModel() { + self.viewModel.isPhotoAccessiblePublisher + .compactMap { $0 } + .sink { [weak self] result in + guard !result else { return } + self?.requestAuthorization() + } + .store(in: &self.cancellables) - viewModel.isReadyToCompose + self.viewModel.isReadyToSelectPhoto + .receive(on: DispatchQueue.main) + .sink { [weak self] result in + guard result, + let self = self else { return } + self.setContentView(self.photoPickerCollectionView) + self.nextButton.setTitle("완료", for: .normal) + self.nextButton.isEnabled = false + } + .store(in: &self.cancellables) + + self.viewModel.isReadyToComposePhoto .receive(on: DispatchQueue.main) .sink { [weak self] result in self?.nextButton.isEnabled = self?.currentContentView == self?.framePicker || @@ -200,7 +220,7 @@ final class PhotoPickerBottomSheetViewController: BottomSheetViewController { } .store(in: &self.cancellables) - viewModel.composedResultPublisher + self.viewModel.composedResultPublisher .receive(on: DispatchQueue.main) .compactMap { $0 } .sink { [weak self] photoComponentEntity in @@ -210,30 +230,36 @@ final class PhotoPickerBottomSheetViewController: BottomSheetViewController { } .store(in: &self.cancellables) - viewModel.$selectedPhotos + self.viewModel.selectedPhotosPublisher .receive(on: DispatchQueue.main) .compactMap { $0 } - .sink { [weak self] _ in - self?.photoPickerCollectionView.reloadData() + .sink { [weak self] selectedPhotos in + self?.photoPickerCollectionView.reloadItems(at: selectedPhotos.map { IndexPath(item: $0, section: 0) }) } .store(in: &self.cancellables) - viewModel.selectedPhotoFramePublisher + self.viewModel.selectedPhotoFramePublisher .compactMap { $0 } .sink { [weak self] _ in self?.nextButton.isEnabled = true } - .store(in: &cancellables) + .store(in: &self.cancellables) - viewModel.$photoFetchResult - .receive(on: DispatchQueue.main) + self.viewModel.photoFetchResultWithChangeDetailsPublisher .compactMap { $0 } - .sink { [weak self] _ in - self?.photoPickerCollectionView.reloadData() + .receive(on: DispatchQueue.main) + .sink { [weak self] _, fetchResultChangeDetails in + guard let self = self else { return } + + if let changed = fetchResultChangeDetails?.changedIndexes, !changed.isEmpty { + self.photoPickerCollectionView.reloadItems(at: changed.map { IndexPath(item: $0, section:0) }) + } else { + self.photoPickerCollectionView.reloadData() + } } - .store(in: &cancellables) + .store(in: &self.cancellables) - viewModel.errorPublisher + self.viewModel.errorPublisher .compactMap { $0 } .sink { [weak self] error in let alert = UIAlertController.defaultAlert(title: "알림", message: error.localizedDescription) { _ in @@ -243,6 +269,12 @@ final class PhotoPickerBottomSheetViewController: BottomSheetViewController { self?.present(alert, animated: true, completion: nil) } .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: GlobalFontUseCase.Notifications.globalFontDidSet, object: nil) + .sink { [weak self] _ in + self?.configureFont() + } + .store(in: &self.cancellables) } // MARK: - Private Method @@ -258,32 +290,36 @@ final class PhotoPickerBottomSheetViewController: BottomSheetViewController { } } - private func checkPhotoAccessPermission(completionHandler: @escaping (Bool) -> Void) { - guard PHPhotoLibrary.authorizationStatus(for: .readWrite) != .authorized else { - return completionHandler(true) - } - - PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in - DispatchQueue.main.async { - completionHandler(status == .authorized) + private func requestAuthorization() { + let authorizationStatus = PHPhotoLibrary.authorizationStatus(for: .readWrite) + if authorizationStatus == .denied { + self.requestPermissionToAccessPhoto() + } else { + PHPhotoLibrary.requestAuthorization(for: .readWrite) { [weak self] status in + self?.viewModel.photoAccessPermissionDidChange(status) } } } - private func photoFrameDidSelect() { - self.checkPhotoAccessPermission { result in - guard result else { return } - self.viewModel?.fetchPhotoAssets() - self.setContentView(self.photoPickerCollectionView) - self.nextButton.configuration?.attributedTitle = AttributedString("완료", attributes: self.fontContainer) - self.nextButton.isEnabled = false - } + private func requestPermissionToAccessPhoto() { + let alert = UIAlertController.selectAlert( + title: "사진 접근 권한 요청", + message: "사진 접근 권한이 없습니다.\n'설정'을 눌러\n'사진' 접근을 허용해주세요.", + leftActionTitle: "취소", + rightActionTitle: "설정") { _ in + guard let url = URL(string: UIApplication.openSettingsURLString), + UIApplication.shared.canOpenURL(url) else { return } + + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } + + self.present(alert, animated: true, completion: nil) } } extension PhotoPickerBottomSheetViewController: CarouselViewDelegate { func selectedItemDidChange(_ index: Int) { - self.viewModel?.photoFrameDidSelect(index) + self.viewModel.photoFrameDidSelect(index) } } @@ -307,9 +343,9 @@ extension PhotoPickerBottomSheetViewController: UICollectionViewDataSource, UICo func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { if collectionView == self.photoPickerCollectionView { - return self.viewModel?.photoFetchResult?.count ?? 0 + return self.viewModel.photoFetchResultWithChangeDetails?.photoFetchResult.count ?? 0 } else { - return self.viewModel?.photoFrames.count ?? 0 + return self.viewModel.photoFrames.count } } @@ -322,13 +358,12 @@ extension PhotoPickerBottomSheetViewController: UICollectionViewDataSource, UICo for: indexPath ) - if let selectedPhotos = self.viewModel?.selectedPhotos, - let photoPickerCollectionViewCell = cell as? PhotoPickerCollectionViewCell, - let imageAsset = self.viewModel?.photoFetchResult?.object(at: indexPath.item) { + if let photoPickerCollectionViewCell = cell as? PhotoPickerCollectionViewCell, + let imageAsset = self.viewModel.photoFetchResultWithChangeDetails?.photoFetchResult.object(at: indexPath.item) { photoPickerCollectionViewCell.display(imageAsset) - if selectedPhotos.contains(indexPath.item), - let target = selectedPhotos.enumerated().first(where: { $0.element == indexPath.item }) { + if self.viewModel.selectedPhotos.contains(indexPath.item), + let target = self.viewModel.selectedPhotos.enumerated().first(where: { $0.element == indexPath.item }) { photoPickerCollectionViewCell.select(order: target.offset + 1) } } @@ -339,8 +374,8 @@ extension PhotoPickerBottomSheetViewController: UICollectionViewDataSource, UICo ) if let photoFrameCollectionViewCell = cell as? PhotoFrameCollectionViewCell, - let baseImage = self.viewModel?.photoFrames[indexPath.item].rawValue?.baseImage, - let displayName = self.viewModel?.photoFrames[indexPath.item].rawValue?.displayName { + let baseImage = self.viewModel.photoFrames[indexPath.item].rawValue?.baseImage, + let displayName = self.viewModel.photoFrames[indexPath.item].rawValue?.displayName { photoFrameCollectionViewCell.display(baseImage, displayName) } } @@ -350,17 +385,18 @@ extension PhotoPickerBottomSheetViewController: UICollectionViewDataSource, UICo func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { if collectionView == self.photoPickerCollectionView { - guard var selectedPhotos = self.viewModel?.selectedPhotos else { return } + var selectedPhotos = self.viewModel.selectedPhotos if selectedPhotos.contains(indexPath.item), let target = selectedPhotos.enumerated().first(where: { $0.element == indexPath.item }) { - self.viewModel?.photoDidSelected(selectedPhotos.filter({ $0 != target.element })) + self.viewModel.photoDidSelected(selectedPhotos.filter({ $0 != target.element })) + self.photoPickerCollectionView.reloadItems(at: [indexPath]) } else { selectedPhotos.append(indexPath.item) - self.viewModel?.photoDidSelected(selectedPhotos) + self.viewModel.photoDidSelected(selectedPhotos) } } else { - self.photoFrameDidSelect() + self.viewModel.photoFrameCellDidTap() } } } diff --git a/Doolda/Doolda/Presentation/ViewModels/PhotoPickerBottomSheetViewModel.swift b/Doolda/Doolda/EditPageScene/PhotoPickerScene/PhotoPickerBottomSheetViewModel.swift similarity index 50% rename from Doolda/Doolda/Presentation/ViewModels/PhotoPickerBottomSheetViewModel.swift rename to Doolda/Doolda/EditPageScene/PhotoPickerScene/PhotoPickerBottomSheetViewModel.swift index b83e6885..9b6aa156 100644 --- a/Doolda/Doolda/Presentation/ViewModels/PhotoPickerBottomSheetViewModel.swift +++ b/Doolda/Doolda/EditPageScene/PhotoPickerScene/PhotoPickerBottomSheetViewModel.swift @@ -11,37 +11,59 @@ import Foundation import Photos enum PhotoPickerBottomSheetViewModelError: LocalizedError { - case imageComposeError + case failedToComposeImages + case failedToLoadImages + case photoFrameNotSelected var errorDescription: String? { switch self { - case .imageComposeError: + case .failedToComposeImages: return "이미지 합성에 실패했습니다." + case .failedToLoadImages: + return "이미지 로드에 실패했습니다." + case .photoFrameNotSelected: + return "사진 프레임이 선택되지 않았습니다." } } } protocol PhotoPickerBottomSheetViewModelInput { - func fetchPhotoAssets() func photoFrameDidSelect(_ index: Int) func photoDidSelected(_ items: [Int]) + func photoFrameCellDidTap() + func nextButtonDidTap() func completeButtonDidTap() + func photoAccessPermissionDidChange(_ authorizationStatus: PHAuthorizationStatus) } protocol PhotoPickerBottomSheetViewModelOutput { - var selectedPhotoFramePublisher: Published.Publisher { get } - var isReadyToCompose: Published.Publisher { get } - var composedResultPublisher: Published.Publisher { get } - var errorPublisher: Published.Publisher { get } + var photoFrames: [PhotoFrameType] { get } + var selectedPhotos: [Int] { get } + var photoFetchResultWithChangeDetails: PhotoFetchResultWithChangeDetails? { get } + var selectedPhotosPublisher: AnyPublisher<[Int], Never> { get } + var photoFetchResultWithChangeDetailsPublisher: AnyPublisher { get } + var selectedPhotoFramePublisher: AnyPublisher { get } + var isReadyToSelectPhoto: AnyPublisher { get } + var isReadyToComposePhoto: AnyPublisher { get } + var isPhotoAccessiblePublisher: AnyPublisher { get } + var composedResultPublisher: AnyPublisher { get } + var errorPublisher: AnyPublisher { get } } typealias PhotoPickerBottomSheetViewModelProtocol = PhotoPickerBottomSheetViewModelInput & PhotoPickerBottomSheetViewModelOutput +typealias PhotoFetchResultWithChangeDetails = (photoFetchResult: PHFetchResult, fetchResultChangeDetails: PHFetchResultChangeDetails?) -class PhotoPickerBottomSheetViewModel: PhotoPickerBottomSheetViewModelProtocol { - var selectedPhotoFramePublisher: Published.Publisher { self.$selectedPhotoFrame } - var isReadyToCompose: Published.Publisher { self.$readyToComposeState } - var composedResultPublisher: Published.Publisher { self.$composedResult } - var errorPublisher: Published.Publisher { self.$error } +class PhotoPickerBottomSheetViewModel: NSObject, PhotoPickerBottomSheetViewModelProtocol { + var selectedPhotosPublisher: AnyPublisher<[Int], Never> { self.$selectedPhotos.eraseToAnyPublisher() } + var photoFetchResultWithChangeDetailsPublisher: AnyPublisher { + self.$photoFetchResultWithChangeDetails.eraseToAnyPublisher() + } + var selectedPhotoFramePublisher: AnyPublisher { self.$selectedPhotoFrame.eraseToAnyPublisher() } + var isReadyToSelectPhoto: AnyPublisher { self.$readyToSelectPhotoState.eraseToAnyPublisher() } + var isReadyToComposePhoto: AnyPublisher { self.$readyToComposeState.eraseToAnyPublisher() } + var isPhotoAccessiblePublisher: AnyPublisher { self.$photoAccessState.eraseToAnyPublisher() } + var composedResultPublisher: AnyPublisher { self.$composedResult.eraseToAnyPublisher() } + var errorPublisher: AnyPublisher { self.$error.eraseToAnyPublisher() } private(set) var photoFrames: [PhotoFrameType] private let imageUseCase: ImageUseCaseProtocol @@ -49,9 +71,11 @@ class PhotoPickerBottomSheetViewModel: PhotoPickerBottomSheetViewModelProtocol { private var cancellables = Set() - @Published private(set) var photoFetchResult: PHFetchResult? + @Published private(set) var photoFetchResultWithChangeDetails: PhotoFetchResultWithChangeDetails? @Published private(set) var selectedPhotos: [Int] = [] @Published private var selectedPhotoFrame: PhotoFrameType? + @Published private var photoAccessState: Bool? + @Published private var readyToSelectPhotoState: Bool = false @Published private var readyToComposeState: Bool = false @Published private var composedResult: PhotoComponentEntity? @Published private var error: Error? @@ -60,13 +84,17 @@ class PhotoPickerBottomSheetViewModel: PhotoPickerBottomSheetViewModelProtocol { self.photoFrames = PhotoFrameType.allCases self.imageUseCase = imageUseCase self.imageComposeUseCase = imageComposeUseCase + super.init() bind() } func fetchPhotoAssets() { let fetchOptions = PHFetchOptions() fetchOptions.sortDescriptors = [.init(key: "creationDate", ascending: false)] - self.photoFetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions) + fetchOptions.includeAllBurstAssets = true + fetchOptions.includeHiddenAssets = false + fetchOptions.includeAssetSourceTypes = [.typeUserLibrary] + self.photoFetchResultWithChangeDetails = (PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions), nil) } func photoFrameDidSelect(_ index: Int) { @@ -79,11 +107,19 @@ class PhotoPickerBottomSheetViewModel: PhotoPickerBottomSheetViewModelProtocol { self.selectedPhotos = items } + func photoFrameCellDidTap() { + selectPhotoFrame() + } + + func nextButtonDidTap() { + selectPhotoFrame() + } + func completeButtonDidTap() { guard self.readyToComposeState, let photoFrame = self.selectedPhotoFrame else { return } - let assets = self.selectedPhotos.compactMap { self.photoFetchResult?.object(at: $0) } + let assets = self.selectedPhotos.compactMap { self.photoFetchResultWithChangeDetails?.photoFetchResult.object(at: $0) } let convertImagePublishers = assets.map { self.convertAssetToCIImage(asset: $0) } @@ -91,7 +127,9 @@ class PhotoPickerBottomSheetViewModel: PhotoPickerBottomSheetViewModelProtocol { .collect() .map { $0.compactMap { $0 } } .flatMap { [weak self] images -> AnyPublisher in - guard let self = self else { return Fail(error: PhotoPickerBottomSheetViewModelError.imageComposeError).eraseToAnyPublisher() } + guard let self = self else { + return Fail(error: PhotoPickerBottomSheetViewModelError.failedToComposeImages).eraseToAnyPublisher() + } return self.imageComposeUseCase.compose(photoFrameType: photoFrame, images: images) } .sink { [weak self] completion in @@ -121,6 +159,12 @@ class PhotoPickerBottomSheetViewModel: PhotoPickerBottomSheetViewModelProtocol { .store(in: &self.cancellables) } + func photoAccessPermissionDidChange(_ authorizationStatus: PHAuthorizationStatus) { + guard authorizationStatus == .authorized || authorizationStatus == .limited else { return } + self.photoAccessState = true + selectPhotoFrame() + } + private func bind() { self.$selectedPhotos .sink { [weak self] photos in @@ -133,21 +177,50 @@ class PhotoPickerBottomSheetViewModel: PhotoPickerBottomSheetViewModelProtocol { .store(in: &self.cancellables) } - private func convertAssetToCIImage(asset: PHAsset) -> AnyPublisher { + private func selectPhotoFrame() { + guard self.selectedPhotoFrame != nil else { + return self.error = PhotoPickerBottomSheetViewModelError.photoFrameNotSelected + } + + let authorizationStatus = PHPhotoLibrary.authorizationStatus(for: .readWrite) + guard authorizationStatus == .authorized || authorizationStatus == .limited else { + return self.photoAccessState = false + } + + PHPhotoLibrary.shared().register(self) + + self.fetchPhotoAssets() + + self.readyToSelectPhotoState = true + } + + private func convertAssetToCIImage(asset: PHAsset) -> AnyPublisher { let imageRequestOptions = PHImageRequestOptions() + imageRequestOptions.isNetworkAccessAllowed = true imageRequestOptions.deliveryMode = .highQualityFormat - return Future { promise in + return Future { promise in PHImageManager.default().requestImage( for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .aspectFill, options: imageRequestOptions ) { image, _ in - guard let cgImage = image?.cgImage else { return promise(.success(nil)) } + guard let cgImage = image?.cgImage else { + return promise(.failure(PhotoPickerBottomSheetViewModelError.failedToLoadImages)) + } promise(.success(CIImage(cgImage: cgImage))) } } .eraseToAnyPublisher() } } + +extension PhotoPickerBottomSheetViewModel: PHPhotoLibraryChangeObserver { + func photoLibraryDidChange(_ changeInstance: PHChange) { + guard let photoFetchResult = self.photoFetchResultWithChangeDetails?.photoFetchResult, + let changes = changeInstance.changeDetails(for: photoFetchResult) else { return } + + self.photoFetchResultWithChangeDetails = (changes.fetchResultAfterChanges, changes) + } +} diff --git a/Doolda/Doolda/UI/Views/PhotoPickerCollectionViewCell.swift b/Doolda/Doolda/EditPageScene/PhotoPickerScene/PhotoPickerCollectionViewCell.swift similarity index 75% rename from Doolda/Doolda/UI/Views/PhotoPickerCollectionViewCell.swift rename to Doolda/Doolda/EditPageScene/PhotoPickerScene/PhotoPickerCollectionViewCell.swift index 2e934726..9eaf50cb 100644 --- a/Doolda/Doolda/UI/Views/PhotoPickerCollectionViewCell.swift +++ b/Doolda/Doolda/EditPageScene/PhotoPickerScene/PhotoPickerCollectionViewCell.swift @@ -37,6 +37,12 @@ class PhotoPickerCollectionViewCell: UICollectionViewCell { return label }() + private lazy var activityIndicator: UIActivityIndicatorView = { + let activityIndicator = UIActivityIndicatorView() + activityIndicator.hidesWhenStopped = true + return activityIndicator + }() + // MARK: - Private Properties private var requestImageId: PHImageRequestID? @@ -60,6 +66,15 @@ class PhotoPickerCollectionViewCell: UICollectionViewCell { } override func prepareForReuse() { + super.prepareForReuse() + + if let requestImageId = requestImageId { + PHImageManager.default().cancelImageRequest(requestImageId) + } + self.imageView.image = nil + + self.activityIndicator.isHidden = false + self.activityIndicator.startAnimating() self.deselect() } @@ -80,27 +95,35 @@ class PhotoPickerCollectionViewCell: UICollectionViewCell { make.top.equalToSuperview().offset(15) } + self.addSubview(self.activityIndicator) + self.activityIndicator.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + self.layoutIfNeeded() } // MARK: - Public Methods func display(_ asset: PHAsset) { - if let requestImageId = requestImageId { - PHImageManager.default().cancelImageRequest(requestImageId) - } - let imageRequestOptions = PHImageRequestOptions() imageRequestOptions.deliveryMode = .opportunistic + imageRequestOptions.isNetworkAccessAllowed = true self.requestImageId = PHImageManager.default().requestImage( for: asset, targetSize: self.bounds.size, contentMode: .aspectFill, options: imageRequestOptions - ) { image, _ in - guard let image = image else { return } - self.imageView.image = image + ) { [weak self] image, requestInformation in + guard let requestInformation = requestInformation, + let requestIdKey = requestInformation["PHImageResultRequestIDKey"] as? Int else { return } + let requestImageId = PHImageRequestID.init(requestIdKey) + + if self?.requestImageId == requestImageId { + self?.imageView.image = image + self?.activityIndicator.stopAnimating() + } } } diff --git a/Doolda/Doolda/UI/Views/PackedStickerCell.swift b/Doolda/Doolda/EditPageScene/StickerPickerScene/PackedStickerCollectionViewCell.swift similarity index 59% rename from Doolda/Doolda/UI/Views/PackedStickerCell.swift rename to Doolda/Doolda/EditPageScene/StickerPickerScene/PackedStickerCollectionViewCell.swift index 760c6de5..faa014cf 100644 --- a/Doolda/Doolda/UI/Views/PackedStickerCell.swift +++ b/Doolda/Doolda/EditPageScene/StickerPickerScene/PackedStickerCollectionViewCell.swift @@ -1,5 +1,5 @@ // -// PackedStickerCell.swift +// PackedStickerCollectionViewCell.swift // Doolda // // Created by Dozzing on 2021/11/16. @@ -9,13 +9,14 @@ import Combine import CoreMotion import UIKit +import Kingfisher import SnapKit -class PackedStickerCell: UICollectionViewCell { +class PackedStickerCollectionViewCell: UICollectionViewCell { // MARK: - Static Properties - static let identifier = "PackedStickerCell" + static let identifier = "PackedStickerCollectionViewCell" // MARK: - Subviews @@ -31,7 +32,7 @@ class PackedStickerCell: UICollectionViewCell { return coverView }() - private lazy var coverSealingSticker: UIImageView = { + private lazy var coverSticker: UIImageView = { let coverSticker = UIImageView() coverSticker.contentMode = .scaleAspectFit return coverSticker @@ -41,9 +42,8 @@ class PackedStickerCell: UICollectionViewCell { let slider = UISlider() slider.maximumValue = 100 slider.minimumValue = 0 - slider.maximumTrackTintColor = UIColor.dooldaMaximumTrackTintColor + slider.setMaximumTrackImage(UIImage.dottedLine, for: .normal) slider.minimumTrackTintColor = UIColor.dooldaMinimumTrackTintColor - slider.setThumbImage(UIImage.scissors, for: .normal) return slider }() @@ -56,18 +56,22 @@ class PackedStickerCell: UICollectionViewCell { private let gravity: UIGravityBehavior = UIGravityBehavior() - private let collider: UICollisionBehavior = { - let collider = UICollisionBehavior() - collider.translatesReferenceBoundsIntoBoundary = true - return collider - }() - private let itemBehavior: UIDynamicItemBehavior = { let behavior = UIDynamicItemBehavior() behavior.elasticity = 0.4 return behavior }() + private let maximumCollisionCount: Int = 5 + + private var colliders: [UICollisionBehavior] = { + var colliders = [UICollisionBehavior]() + let collider = UICollisionBehavior() + collider.translatesReferenceBoundsIntoBoundary = true + colliders.append(collider) + return colliders + }() + private lazy var animator: UIDynamicAnimator = UIDynamicAnimator(referenceView: self.stickerPackBody) private var cancellables = Set() @@ -88,7 +92,7 @@ class PackedStickerCell: UICollectionViewCell { // MARK: - Helpers private func configureUI() { - self.backgroundColor = UIColor.dooldaStickerPackBackground + self.backgroundColor = UIColor.white self.addSubview(self.stickerPackBody) self.stickerPackBody.snp.makeConstraints { make in @@ -103,6 +107,10 @@ class PackedStickerCell: UICollectionViewCell { make.height.equalTo(self.stickerPackBody).multipliedBy(0.2) } + self.slider.tintColor = .darkGray + self.slider.setThumbImage(UIImage.scissors, for: .normal) + self.slider.setThumbImage(UIImage.scissors, for: .highlighted) + self.stickerPackCover.addSubview(self.slider) self.slider.snp.makeConstraints { make in make.top.equalToSuperview().offset(8) @@ -110,8 +118,8 @@ class PackedStickerCell: UICollectionViewCell { make.trailing.equalToSuperview().offset(-5) } - self.stickerPackCover.addSubview(self.coverSealingSticker) - self.coverSealingSticker.snp.makeConstraints { make in + self.stickerPackCover.addSubview(self.coverSticker) + self.coverSticker.snp.makeConstraints { make in make.centerX.equalToSuperview() make.centerY.equalTo(self.stickerPackCover.snp.bottom) make.width.equalToSuperview().multipliedBy(0.25) @@ -124,16 +132,20 @@ class PackedStickerCell: UICollectionViewCell { .receive(on: DispatchQueue.main) .sink { [weak self] isAnimating in guard let gravity = self?.gravity, - let collider = self?.collider, + let colliders = self?.colliders, let itemBehavior = self?.itemBehavior else { return } if isAnimating { self?.animator.addBehavior(gravity) - self?.animator.addBehavior(collider) + for collider in colliders { + self?.animator.addBehavior(collider) + } self?.animator.addBehavior(itemBehavior) } else { self?.animator.removeBehavior(gravity) - self?.animator.removeBehavior(collider) + for collider in colliders { + self?.animator.removeBehavior(collider) + } self?.animator.removeBehavior(itemBehavior) } } @@ -143,36 +155,35 @@ class PackedStickerCell: UICollectionViewCell { // MARK: - Public Methods func configure(with stickerPack: StickerPackEntity) { - let stickers = stickerPack.stickersUrl var widthOffset: CGFloat = 10 var heightOffset: CGFloat = 10 + var maxHeight: CGFloat = 0 + let bodyWidth = self.frame.width - 16 - for url in stickers { - guard let stickerImage = try? UIImage(data: Data(contentsOf: url)) else { continue } - let stickerView = UIImageView(image: stickerImage) - let ratio = stickerImage.size.height / stickerImage.size.width - let width: CGFloat = max(self.frame.width * 0.2, 50) + for sticker in stickerPack.stickersName { + guard let stickerImage = UIImage(named: sticker) else { continue } + let stickerView = self.configureStickerView(with: stickerImage) + let stickerWidth = stickerView.frame.width + maxHeight = max(stickerView.frame.height, maxHeight) + + if widthOffset + stickerWidth >= bodyWidth { + widthOffset = 10 + heightOffset += maxHeight + 5 + maxHeight = 0 + } self.stickerPackBody.addSubview(stickerView) - stickerView.frame = CGRect(x: widthOffset, y: heightOffset, width: width , height: width * ratio) + stickerView.frame.origin = CGPoint(x: widthOffset, y: heightOffset) self.gravity.addItem(stickerView) - self.collider.addItem(stickerView) + self.addCollider(to: stickerView) self.itemBehavior.addItem(stickerView) - // FIXME: 스티커 최대 개수 제한하도록 임시 구현 - if widthOffset < 100 { - widthOffset += 30 - } else { - widthOffset = 10 - heightOffset += 50 - } - - if self.stickerPackBody.subviews.count >= 8 { break } + widthOffset += stickerWidth + 5 } - guard let coverImage = try? UIImage(data: Data(contentsOf: stickerPack.sealingImageUrl)) else { return } - self.coverSealingSticker.image = coverImage + let coverStickerImage = UIImage(named: stickerPack.coverStickerName) + self.coverSticker.image = coverStickerImage } func configureGravity(motion: CMDeviceMotion?, error: Error?) { @@ -183,7 +194,7 @@ class PackedStickerCell: UICollectionViewCell { let gravityX = CGFloat(gravity.x) let gravityY = CGFloat(-gravity.y) - self.gravity.gravityDirection = CGVector(dx: gravityX * 2.5, dy: gravityY * 2.5) + self.gravity.gravityDirection = CGVector(dx: gravityX * 3.5, dy: gravityY * 3.5) } func clear() { @@ -193,7 +204,9 @@ class PackedStickerCell: UICollectionViewCell { self.stickerPackBody.subviews.forEach { subview in subview.removeFromSuperview() self.gravity.removeItem(subview) - self.collider.removeItem(subview) + for collider in self.colliders { + collider.removeItem(subview) + } self.itemBehavior.removeItem(subview) } @@ -205,4 +218,38 @@ class PackedStickerCell: UICollectionViewCell { self.stickerPackCover.alpha = 0 } + // MARK: - Private Methods + + private func addCollider(to item: UIView) { + guard var collider = self.colliders.last else { return } + + if collider.items.count >= maximumCollisionCount { + let newCollider = UICollisionBehavior() + newCollider.translatesReferenceBoundsIntoBoundary = true + self.colliders.append(newCollider) + collider = newCollider + } + collider.addItem(item) + } + + private func configureStickerView(with stickerImage: UIImage) -> UIImageView { + let stickerView = UIImageView(image: stickerImage) + stickerView.contentMode = .scaleAspectFit + + let bodyWidth = self.frame.width - 16 + let width: CGFloat + let height: CGFloat + let imageRatio = stickerImage.size.width / stickerImage.size.height + + if stickerImage.size.width > stickerImage.size.height { + width = bodyWidth / CGFloat(self.maximumCollisionCount - 1) + height = width / imageRatio + } else { + height = bodyWidth / CGFloat(self.maximumCollisionCount - 1) + width = height * imageRatio + } + stickerView.frame.size = CGSize(width: width, height: height) + return stickerView + } + } diff --git a/Doolda/Doolda/UI/ViewControllers/StickerPickerViewController.swift b/Doolda/Doolda/EditPageScene/StickerPickerScene/StickerPickerBottomSheetViewController.swift similarity index 62% rename from Doolda/Doolda/UI/ViewControllers/StickerPickerViewController.swift rename to Doolda/Doolda/EditPageScene/StickerPickerScene/StickerPickerBottomSheetViewController.swift index 0e150f05..3330fa93 100644 --- a/Doolda/Doolda/UI/ViewControllers/StickerPickerViewController.swift +++ b/Doolda/Doolda/EditPageScene/StickerPickerScene/StickerPickerBottomSheetViewController.swift @@ -1,5 +1,5 @@ // -// StickerPickerViewController.swift +// StickerPickerBottomSheetViewController.swift // Doolda // // Created by Dozzing on 2021/11/15. @@ -11,11 +11,11 @@ import UIKit import SnapKit -protocol StickerPickerViewControllerDelegate: AnyObject { +protocol StickerPickerBottomSheetViewControllerDelegate: AnyObject { func stickerDidSelected(_ stickerComponentEntity: StickerComponentEntity) } -class StickerPickerViewController: BottomSheetViewController { +class StickerPickerBottomSheetViewController: BottomSheetViewController { // MARK: - Subviews @@ -57,18 +57,19 @@ class StickerPickerViewController: BottomSheetViewController { // MARK: - Private Properties - private var viewModel: StickerPickerViewModel! - private weak var delegate: StickerPickerViewControllerDelegate? + private var viewModel: StickerPickerBottomSheetViewModelProtocol! + private weak var delegate: StickerPickerBottomSheetViewControllerDelegate? + private var feedbackGenerator: UIImpactFeedbackGenerator? private var cancellables = [Int: AnyCancellable]() // MARK: - Initializers convenience init( - stickerPickerViewModel: StickerPickerViewModel, - delegate: StickerPickerViewControllerDelegate? + stickerPickerBottomSheetViewModel: StickerPickerBottomSheetViewModelProtocol, + delegate: StickerPickerBottomSheetViewControllerDelegate? ) { self.init(nibName: nil, bundle: nil) - self.viewModel = stickerPickerViewModel + self.viewModel = stickerPickerBottomSheetViewModel self.delegate = delegate } @@ -77,6 +78,8 @@ class StickerPickerViewController: BottomSheetViewController { override func viewDidLoad() { super.viewDidLoad() self.configureUI() + self.configureGenerator() + self.bindUI() } // MARK: - Helpers @@ -95,19 +98,38 @@ class StickerPickerViewController: BottomSheetViewController { self.body.addSubview(self.stickerPickerView) self.stickerPickerView.snp.makeConstraints { make in make.top.equalTo(topStack.snp.bottom).offset(10) - make.leading.trailing.equalToSuperview() - make.bottom.equalTo(self.view.safeAreaLayoutGuide.snp.bottom) + make.leading.trailing.equalTo(self.body) + make.bottom.equalTo(self.body.snp.bottom).offset(-13) } } - private func bindCellUI(_ cell: PackedStickerCell, at indexPath: IndexPath) { + private func configureGenerator() { + self.feedbackGenerator = UIImpactFeedbackGenerator(style: .light) + self.feedbackGenerator?.prepare() + } + + private func bindUI() { + let publihser = self.closeButton.publisher(for: .touchUpInside) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.dismiss(animated: true, completion: nil) + } + self.cancellables[-1] = publihser + } + + private func bindCellUI(_ cell: PackedStickerCollectionViewCell, at indexPath: IndexPath) { let publisher = cell.slider.publisher(for: .valueChanged) .receive(on: DispatchQueue.main) .sink { [weak self] _ in - if cell.slider.value >= cell.slider.maximumValue * 0.95 { + if Int(cell.slider.value) % 3 == 1 { + self?.feedbackGenerator?.impactOccurred() + } + + if cell.slider.value == cell.slider.maximumValue { guard let stickerPack = self?.viewModel.getStickerPackEntity(at: indexPath.section) else { return } stickerPack.isUnpacked = true + self?.feedbackGenerator?.impactOccurred() UIView.animate(withDuration: 1.0, animations: { cell.unpackCell() }) { [weak self] _ in UIView.animate(withDuration: 0.5, animations: { cell.alpha = 0 }) { [weak self] _ in self?.stickerPickerView.collectionView.collectionViewLayout.invalidateLayout() @@ -133,31 +155,41 @@ class StickerPickerViewController: BottomSheetViewController { let config = UICollectionViewCompositionalLayoutConfiguration() config.scrollDirection = .horizontal + layout.configuration = config return layout } private func createPackedStickerLayoutSection(in environment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { - let width = environment.container.contentSize.width * 0.45 - let height = width * 1.25 + let height = environment.container.contentSize.height * 0.75 + let width = height * 0.85 let widthInset = (environment.container.contentSize.width - width) / 2 let heightInset = (environment.container.contentSize.height - height) / 2 let item = NSCollectionLayoutItem( layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)) ) - item.contentInsets = .init(top: heightInset, leading: widthInset, bottom: heightInset, trailing: widthInset) + item.contentInsets = .init(top: heightInset/2, leading: widthInset, bottom: heightInset, trailing: widthInset) let group = NSCollectionLayoutGroup.vertical( layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)), subitems: [item] ) + let footerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(16)) + let footer = NSCollectionLayoutBoundarySupplementaryItem( + layoutSize: footerSize, + elementKind: UICollectionView.elementKindSectionFooter, + alignment: .bottom + ) + let section = NSCollectionLayoutSection(group: group) + section.boundarySupplementaryItems = [footer] return section } private func createUnPackedStickerLayoutSection(in environment: NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection { + let item = NSCollectionLayoutItem( layoutSize: .init(widthDimension: .fractionalWidth(0.25), heightDimension: .fractionalHeight(1)) ) @@ -167,20 +199,30 @@ class StickerPickerViewController: BottomSheetViewController { layoutSize: .init(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(0.25)), subitems: [item] ) - group.contentInsets = .init(top: 12, leading: 12, bottom: 12, trailing: 12) + group.contentInsets = .init(top: 6, leading: 8, bottom: 10, trailing: 8) + + let anchor = NSCollectionLayoutAnchor(edges: [.bottom], absoluteOffset: CGPoint(x: 0, y: 16)) + let footerSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .estimated(16)) + let footer = NSCollectionLayoutBoundarySupplementaryItem( + layoutSize: footerSize, + elementKind: UICollectionView.elementKindSectionFooter, + containerAnchor: anchor + ) let section = NSCollectionLayoutSection(group: group) + section.contentInsets = .init(top: 0, leading: 0, bottom: 16, trailing: 0) section.orthogonalScrollingBehavior = .continuous + section.boundarySupplementaryItems = [footer] return section } } -extension StickerPickerViewController: UICollectionViewDelegate { +extension StickerPickerBottomSheetViewController: UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { self.stickerPickerView.currentPack = indexPath.section - guard let cell = cell as? PackedStickerCell, + guard let cell = cell as? PackedStickerCollectionViewCell, let operationQueue = OperationQueue.current, let stickerPack = self.viewModel.getStickerPackEntity(at: indexPath.section) else { return } @@ -192,21 +234,21 @@ extension StickerPickerViewController: UICollectionViewDelegate { } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - guard let cell = collectionView.cellForItem(at: indexPath) as? UnpackedStickerCell, + guard let _ = collectionView.cellForItem(at: indexPath) as? UnpackedStickerCollectionViewCell, let stickerComponentEntity = self.viewModel.stickerDidSelect(at: indexPath) else { return } self.delegate?.stickerDidSelected(stickerComponentEntity) self.dismiss(animated: true, completion: nil) } } -extension StickerPickerViewController: UICollectionViewDataSource { +extension StickerPickerBottomSheetViewController: UICollectionViewDataSource { func numberOfSections(in collectionView: UICollectionView) -> Int { return self.viewModel.getStickerPacks().count } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { guard let stickerPack = self.viewModel.getStickerPackEntity(at: section) else { return 0 } - if stickerPack.isUnpacked { return stickerPack.stickersUrl.count } + if stickerPack.isUnpacked { return stickerPack.stickerCount } return 1 } @@ -216,13 +258,26 @@ extension StickerPickerViewController: UICollectionViewDataSource { } if !stickerPack.isUnpacked { - return collectionView.dequeueReusableCell(withReuseIdentifier: PackedStickerCell.identifier, for: indexPath) + return collectionView.dequeueReusableCell(withReuseIdentifier: PackedStickerCollectionViewCell.identifier, for: indexPath) } - guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: UnpackedStickerCell.identifier, for: indexPath) as? UnpackedStickerCell, - let stickerUrl = self.viewModel.getStickerUrl(at: indexPath) else { return UICollectionViewCell() } + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: UnpackedStickerCollectionViewCell.identifier, for: indexPath) as? UnpackedStickerCollectionViewCell, + let stickerName = self.viewModel.getStickerName(at: indexPath), + let stickerImage = UIImage(named: stickerName) else { return UICollectionViewCell() } - cell.configure(with: stickerUrl) + cell.configure(with: stickerImage) return cell } + + func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { + guard let footerView = collectionView.dequeueReusableSupplementaryView( + ofKind: UICollectionView.elementKindSectionFooter, + withReuseIdentifier: StickerPickerCollectionViewFooter.identifier, + for: indexPath + ) as? StickerPickerCollectionViewFooter, + let stickerPack = self.viewModel.getStickerPackEntity(at: indexPath.section) else { return UICollectionReusableView() } + + footerView.configureStickerPackTilte(with: stickerPack.displayName) + return footerView + } } diff --git a/Doolda/Doolda/Presentation/ViewModels/StickerPickerViewModel.swift b/Doolda/Doolda/EditPageScene/StickerPickerScene/StickerPickerBottomSheetViewModel.swift similarity index 73% rename from Doolda/Doolda/Presentation/ViewModels/StickerPickerViewModel.swift rename to Doolda/Doolda/EditPageScene/StickerPickerScene/StickerPickerBottomSheetViewModel.swift index 17ecda8a..55bd43a0 100644 --- a/Doolda/Doolda/Presentation/ViewModels/StickerPickerViewModel.swift +++ b/Doolda/Doolda/EditPageScene/StickerPickerScene/StickerPickerBottomSheetViewModel.swift @@ -8,14 +8,14 @@ import CoreGraphics import Foundation -protocol StickerPickerViewModelProtocol { +protocol StickerPickerBottomSheetViewModelProtocol { func getStickerPacks() -> [StickerPackType] func getStickerPackEntity(at index: Int) -> StickerPackEntity? - func getStickerUrl(at indexPath: IndexPath) -> URL? + func getStickerName(at indexPath: IndexPath) -> String? func stickerDidSelect(at indexPath: IndexPath) -> StickerComponentEntity? } -class StickerPickerViewModel: StickerPickerViewModelProtocol { +class StickerPickerBottomSheetViewModel: StickerPickerBottomSheetViewModelProtocol { private let stickerUseCase: StickerUseCase init(stickerUseCase: StickerUseCase) { @@ -30,8 +30,8 @@ class StickerPickerViewModel: StickerPickerViewModelProtocol { return stickerUseCase.getStickerPackEntity(at: index) } - func getStickerUrl(at indexPath: IndexPath) -> URL? { - return stickerUseCase.getStickerUrl(at: indexPath) + func getStickerName(at indexPath: IndexPath) -> String? { + return stickerUseCase.getStickerName(at: indexPath) } func stickerDidSelect(at indexPath: IndexPath) -> StickerComponentEntity? { diff --git a/Doolda/Doolda/EditPageScene/StickerPickerScene/StickerPickerCollectionViewFooter.swift b/Doolda/Doolda/EditPageScene/StickerPickerScene/StickerPickerCollectionViewFooter.swift new file mode 100644 index 00000000..a4c59950 --- /dev/null +++ b/Doolda/Doolda/EditPageScene/StickerPickerScene/StickerPickerCollectionViewFooter.swift @@ -0,0 +1,55 @@ +// +// StickerPickerCollectionViewFooter.swift +// Doolda +// +// Created by Dozzing on 2021/11/21. +// + +import UIKit + +import SnapKit + +class StickerPickerCollectionViewFooter: UICollectionReusableView { + + // MARK: - Static Properties + + static let identifier = "StickerPickerCollectionViewFooter" + + // MARK: - Subviews + + private lazy var title: UILabel = { + let label = UILabel() + label.textAlignment = .center + label.font = .systemFont(ofSize: 16) + label.textColor = .dooldaLabel + return label + }() + + // MARK: - Initializers + + override init(frame: CGRect) { + super.init(frame: frame) + self.configureUI() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + self.configureUI() + } + + // MARK: - Helpers + + private func configureUI() { + self.addSubview(self.title) + self.title.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + + // MARK: - Public Methods + + func configureStickerPackTilte(with stickerPackName: String) { + self.title.text = stickerPackName + } + +} diff --git a/Doolda/Doolda/UI/Views/StickerPickerView.swift b/Doolda/Doolda/EditPageScene/StickerPickerScene/StickerPickerView.swift similarity index 81% rename from Doolda/Doolda/UI/Views/StickerPickerView.swift rename to Doolda/Doolda/EditPageScene/StickerPickerScene/StickerPickerView.swift index 301a688e..fc09c113 100644 --- a/Doolda/Doolda/UI/Views/StickerPickerView.swift +++ b/Doolda/Doolda/EditPageScene/StickerPickerScene/StickerPickerView.swift @@ -20,8 +20,13 @@ class StickerPickerView: UIView { collectionView.isPagingEnabled = true collectionView.showsHorizontalScrollIndicator = false collectionView.backgroundColor = .clear - collectionView.register(PackedStickerCell.self, forCellWithReuseIdentifier: PackedStickerCell.identifier) - collectionView.register(UnpackedStickerCell.self, forCellWithReuseIdentifier: UnpackedStickerCell.identifier) + collectionView.register(PackedStickerCollectionViewCell.self, forCellWithReuseIdentifier: PackedStickerCollectionViewCell.identifier) + collectionView.register(UnpackedStickerCollectionViewCell.self, forCellWithReuseIdentifier: UnpackedStickerCollectionViewCell.identifier) + collectionView.register( + StickerPickerCollectionViewFooter.self, + forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, + withReuseIdentifier: StickerPickerCollectionViewFooter.identifier + ) return collectionView }() diff --git a/Doolda/Doolda/UI/Views/UnpackedStickerCell.swift b/Doolda/Doolda/EditPageScene/StickerPickerScene/UnpackedStickerCollectionViewCell.swift similarity index 72% rename from Doolda/Doolda/UI/Views/UnpackedStickerCell.swift rename to Doolda/Doolda/EditPageScene/StickerPickerScene/UnpackedStickerCollectionViewCell.swift index 654c2e0c..7a530876 100644 --- a/Doolda/Doolda/UI/Views/UnpackedStickerCell.swift +++ b/Doolda/Doolda/EditPageScene/StickerPickerScene/UnpackedStickerCollectionViewCell.swift @@ -1,5 +1,5 @@ // -// UnpackedStickerCell.swift +// UnpackedStickerCollectionViewCell.swift // Doolda // // Created by Dozzing on 2021/11/16. @@ -9,11 +9,11 @@ import UIKit import SnapKit -class UnpackedStickerCell: UICollectionViewCell { +class UnpackedStickerCollectionViewCell: UICollectionViewCell { // MARK: - Static Properties - static let identifier = "UnpackedStickerCell" + static let identifier = "UnpackedStickerCollectionViewCell" // MARK: - Subviews @@ -37,9 +37,8 @@ class UnpackedStickerCell: UICollectionViewCell { // MARK: - Helpers - func configure(with sticker: URL) { - guard let image = try? UIImage(data: Data(contentsOf: sticker)) else { return } - self.imageView.image = image + func configure(with sticker: UIImage) { + self.imageView.image = sticker } private func configureUI() { diff --git a/Doolda/Doolda/EditPageScene/TextEditScene/FontColorCollectionViewCell.swift b/Doolda/Doolda/EditPageScene/TextEditScene/FontColorCollectionViewCell.swift new file mode 100644 index 00000000..586d0f6b --- /dev/null +++ b/Doolda/Doolda/EditPageScene/TextEditScene/FontColorCollectionViewCell.swift @@ -0,0 +1,50 @@ +// +// FontColorCollectionViewCell.swift +// Doolda +// +// Created by 김민주 on 2021/11/22. +// + +import UIKit + +class FontColorCollectionViewCell: UICollectionViewCell { + + // MARK: - Static Properties + + static let identifier = "FontColorCollectionViewCell" + + // MARK: - Subviews + + private lazy var colorView: UIView = { + let view = UIView() + view.layer.borderWidth = 1 + view.layer.borderColor = UIColor.gray.cgColor + view.layer.cornerRadius = 15 + return view + }() + + // MARK: - Initializers + + override init(frame: CGRect) { + super.init(frame: frame) + self.configureUI() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + self.configureUI() + } + + // MARK: - Helpers + + func configure(with color: UIColor) { + self.colorView.backgroundColor = color + } + + private func configureUI() { + self.addSubview(self.colorView) + self.colorView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } +} diff --git a/Doolda/Doolda/EditPageScene/TextEditScene/FontColorPickerView.swift b/Doolda/Doolda/EditPageScene/TextEditScene/FontColorPickerView.swift new file mode 100644 index 00000000..9714727c --- /dev/null +++ b/Doolda/Doolda/EditPageScene/TextEditScene/FontColorPickerView.swift @@ -0,0 +1,54 @@ +// +// FontColorPickerView.swift +// Doolda +// +// Created by 김민주 on 2021/11/22. +// + +import UIKit + +import SnapKit + +class FontColorPickerView: UIView { + + // MARK: - Subviews + + lazy var collectionView: UICollectionView = { + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + + collectionView.isPagingEnabled = false + collectionView.showsHorizontalScrollIndicator = false + collectionView.backgroundColor = .clear + let collectionViewLayout = UICollectionViewFlowLayout() + collectionViewLayout.scrollDirection = .horizontal + collectionViewLayout.minimumLineSpacing = 15 + collectionView.collectionViewLayout = collectionViewLayout + collectionView.register(FontColorCollectionViewCell.self, forCellWithReuseIdentifier: FontColorCollectionViewCell.identifier) + + return collectionView + }() + + // MARK: - Initialiazers + + convenience init( + frame: CGRect, + collectionViewDelegate: UICollectionViewDelegate? = nil, + collectionViewDataSource: UICollectionViewDataSource? = nil + ) { + self.init(frame: frame) + self.collectionView.delegate = collectionViewDelegate + self.collectionView.dataSource = collectionViewDataSource + + self.configureUI() + } + + // MARK: - Helpers + + private func configureUI() { + self.addSubview(self.collectionView) + self.collectionView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } +} + diff --git a/Doolda/Doolda/EditPageScene/TextEditScene/FontSizeControlView.swift b/Doolda/Doolda/EditPageScene/TextEditScene/FontSizeControlView.swift new file mode 100644 index 00000000..cd9b7373 --- /dev/null +++ b/Doolda/Doolda/EditPageScene/TextEditScene/FontSizeControlView.swift @@ -0,0 +1,119 @@ +// +// FontSizeControlView.swift +// Doolda +// +// Created by 김민주 on 2021/11/23. +// + +import UIKit + +class FontSizeControl: UIControl { + private (set) var value: CGFloat = 1.0 + + private let renderer = FontSizeControlRenderer() + + private var minimumValue: CGFloat = 0.0 + private var maximumValue: CGFloat = 2.0 + private var previousLocation = CGPoint() + + override init(frame: CGRect) { + super.init(frame: frame) + self.configureLayer() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + self.configureLayer() + } + + private func configureLayer() { + self.renderer.updateBounds(self.bounds) + + self.layer.addSublayer(self.renderer.trackLayer) + self.layer.addSublayer(self.renderer.pointerLayer) + + } + + func setValue(_ newValue: CGFloat, animated: Bool = false) { + self.value = min(maximumValue, max(minimumValue, newValue)) + } + + override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + self.previousLocation = touch.location(in: self) + if self.renderer.trackLayer.frame.contains(self.previousLocation) { + self.frame.origin = CGPoint(x: 0, y: self.frame.origin.y) + return true + } else { + return false + } + } + + override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + let location = touch.location(in: self) + let deltaLocation = Double(location.y - self.previousLocation.y) + + CATransaction.begin() + CATransaction.setDisableActions(true) + let initalPointerX = self.renderer.pointerLayer.position.x + if deltaLocation < 0 { + self.renderer.pointerLayer.position = CGPoint( + x: initalPointerX, + y: self.previousLocation.y + deltaLocation < 0 ? 0 : self.previousLocation.y + deltaLocation + ) + } else if deltaLocation >= 0 { + self.renderer.pointerLayer.position = CGPoint( + x: initalPointerX, + y: self.previousLocation.y + deltaLocation > self.renderer.trackLayer.bounds.height + ? self.renderer.trackLayer.bounds.height : self.previousLocation.y + deltaLocation + ) + } + CATransaction.commit() + + let valueGap = self.maximumValue - self.minimumValue + let currentValue = valueGap - ( self.renderer.pointerLayer.position.y / self.renderer.trackLayer.bounds.height * valueGap ) + self.setValue(currentValue) + + return true + } + + private class FontSizeControlRenderer { + let trackLayer = CAShapeLayer() + let pointerLayer = CAShapeLayer() + + init() { + self.trackLayer.fillColor = UIColor.clear.cgColor + self.pointerLayer.fillColor = UIColor.clear.cgColor + } + + func updateBounds(_ bounds: CGRect) { + self.trackLayer.bounds = bounds + self.trackLayer.position = CGPoint(x: bounds.midX, y: bounds.midY) + self.updateTrackLayerPath() + + self.pointerLayer.bounds = bounds + self.pointerLayer.bounds.size = CGSize(width: bounds.width, height: bounds.width) + self.pointerLayer.position = CGPoint(x: bounds.midX, y: bounds.midY) + self.updatePointerLayerPath() + } + + private func updateTrackLayerPath() { + let bounds = self.trackLayer.bounds + let path = CGMutablePath() + path.move(to: CGPoint(x: bounds.minX, y: bounds.minY + bounds.width/8.0)) + path.addLine(to: CGPoint(x:bounds.width/2.0, y: bounds.height - bounds.width/8.0)) + path.addLine(to: CGPoint(x:bounds.width, y: bounds.minY + bounds.width/8.0)) + path.addLine(to: CGPoint(x: bounds.minX, y: bounds.minY + bounds.width/8.0)) + self.trackLayer.path = path + trackLayer.fillColor = UIColor.white.withAlphaComponent(0.5).cgColor + } + + private func updatePointerLayerPath() { + let radius: CGFloat = self.pointerLayer.bounds.width / 2.0 + self.pointerLayer.path = UIBezierPath( + roundedRect: CGRect(x: 0, y: 0, width: 2.0 * radius, height: 2.0 * radius), + cornerRadius: radius + ).cgPath + self.pointerLayer.fillColor = UIColor.white.cgColor + } + } +} diff --git a/Doolda/Doolda/EditPageScene/TextEditScene/TextEditViewController.swift b/Doolda/Doolda/EditPageScene/TextEditScene/TextEditViewController.swift new file mode 100644 index 00000000..9aee77d6 --- /dev/null +++ b/Doolda/Doolda/EditPageScene/TextEditScene/TextEditViewController.swift @@ -0,0 +1,329 @@ +// +// TextEditViewController.swift +// Doolda +// +// Created by 김민주 on 2021/11/17. +// + +import Combine +import UIKit + +import SnapKit + +protocol TextEditViewControllerDelegate: AnyObject { + func textInputDidEndAdd(_ textComponentEntity: TextComponentEntity) + func textInputDidEndEditing(_ textComponentEntity: TextComponentEntity) +} + +class TextEditViewController: UIViewController { + + // MARK: - Subviews + + private lazy var placeholderLabel: UILabel = { + var label = UILabel() + label.font = .systemFont(ofSize: 18) + label.text = "내용을 입력하세요" + label.textColor = .systemGray + return label + }() + + private lazy var inputTextView: UITextView = { + var textView = UITextView() + textView.font = .systemFont(ofSize: 18) + textView.autocapitalizationType = .words + textView.isScrollEnabled = true + textView.backgroundColor = .clear + textView.textAlignment = .center + textView.delegate = self + textView.autocapitalizationType = .none + textView.tintColor = .clear + return textView + }() + + private lazy var fontColorView: FontColorPickerView = { + let frame = CGRect(x: 0, y: 0, width: view.frame.width, height: 40) + let fontColorView = FontColorPickerView( + frame: frame, + collectionViewDelegate: self, + collectionViewDataSource: self + ) + return fontColorView + }() + + private lazy var fontSizeControl: FontSizeControl = { + let fontSizeControl = FontSizeControl(frame: CGRect(x: 0, y: .zero, width: 30, height: 300)) + return fontSizeControl + }() + + // MARK: - Private Properties + + private var widthRatioFromAbsolute: CGFloat? + private var heightRatioFromAbsolute: CGFloat? + + private var currentColorIndex: Int = 0 { + didSet { + guard oldValue != self.currentColorIndex else { return } + self.feedbackGenerator?.impactOccurred() + guard let currentColor = self.viewModel.getFontColor(at: self.currentColorIndex) else { return } + if self.inputTextView.textColor != .systemGray { + self.inputTextView.textColor = UIColor(cgColor: currentColor) + } + self.fontColorView.collectionView.reloadItems( + at: [ + IndexPath(item: self.currentColorIndex, section: 0), + IndexPath(item: oldValue, section: 0) + ] + ) + } + } + + private var cancellables: Set = [] + private var viewModel: TextEditViewModelProtocol! + private weak var delegate: TextEditViewControllerDelegate? + private var feedbackGenerator: UIImpactFeedbackGenerator? + private var initialFontColorPickerScrollDone: Bool = false + + override var inputAccessoryView: UIView? { + return self.fontColorView + } + + // MARK: - Initializers + + convenience init( + textEditViewModel: TextEditViewModelProtocol, + delegate: TextEditViewControllerDelegate?, + widthRatioFromAbsolute: CGFloat?, + heightRatioFromAbsolute: CGFloat? + ) { + self.init(nibName: nil, bundle: nil) + self.viewModel = textEditViewModel + self.delegate = delegate + self.widthRatioFromAbsolute = widthRatioFromAbsolute + self.heightRatioFromAbsolute = heightRatioFromAbsolute + self.modalPresentationStyle = .overFullScreen + } + + // MARK: - Lifecycle Methods + + override func viewDidLoad() { + super.viewDidLoad() + + self.configureUI() + self.configureGenerator() + self.bindUI() + } + + override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + guard !self.initialFontColorPickerScrollDone else { return } + self.initialFontColorPickerScrollDone = true + self.configureFontColorPicker() + } + + // MARK: - Helpers + + private func configureUI() { + self.view.backgroundColor = .black.withAlphaComponent(0.7) + + self.view.addSubview(self.placeholderLabel) + self.placeholderLabel.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.bottom.equalTo(self.view.snp.centerY).offset(-9) + } + + self.view.addSubview(self.inputTextView) + self.inputTextView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.bottom.equalTo(self.view.snp.centerY) + } + self.inputTextView.becomeFirstResponder() + + self.view.addSubview(self.fontSizeControl) + self.fontSizeControl.snp.makeConstraints { make in + make.height.equalTo(300) + make.width.equalTo(30) + make.bottom.equalTo(self.view.snp.centerY) + } + } + + private func configureGenerator() { + self.feedbackGenerator = UIImpactFeedbackGenerator(style: .light) + self.feedbackGenerator?.prepare() + } + + private func bindUI() { + self.view.publisher(for: UITapGestureRecognizer()) + .sink { [weak self] _ in + guard let self = self, + let textComponenetEntity = self.viewModel?.inputViewEditingDidEnd( + input: self.inputTextView.text, + contentSize: self.computeSizeToAbsolute(with: self.inputTextView.contentSize), + fontSize: 18 * self.fontSizeControl.value, + colorIndex: self.currentColorIndex + ) else { return } + if self.viewModel?.selectedTextComponent != nil { + self.delegate?.textInputDidEndEditing(textComponenetEntity) + } else { + self.delegate?.textInputDidEndAdd(textComponenetEntity) + } + self.dismiss(animated: false) + }.store(in: &self.cancellables) + + self.fontSizeControl.publisher(for: .allTouchEvents) + .sink { [weak self] control in + guard let self = self , + let fontsizeControl = control as? FontSizeControl else { return } + self.inputTextView.font = .systemFont(ofSize: 18 * fontsizeControl.value) + let maximumWidth: CGFloat = self.view.frame.width - 40 + let newSize = self.inputTextView.sizeThatFits(CGSize(width: maximumWidth, height: CGFloat.greatestFiniteMagnitude)) + + self.inputTextView.snp.updateConstraints { make in + make.width.equalTo(newSize.width) + make.height.equalTo(newSize.height) + } + }.store(in: &self.cancellables) + } + + // MARK: - Private Methods + + private func computeSizeToAbsolute(with size: CGSize) -> CGSize { + let computedWidth = size.width / ( self.widthRatioFromAbsolute ?? 0.0 ) + let computedHeight = size.height / ( self.heightRatioFromAbsolute ?? 0.0 ) + return CGSize(width: computedWidth, height: computedHeight) + } + + private func configureFontColorPicker() { + if let selectedTextComponent = self.viewModel?.selectedTextComponent, + let selecedColorIndex = FontColorType.allCases.firstIndex(of: selectedTextComponent.fontColor) { + self.currentColorIndex = selecedColorIndex + self.fontColorView.collectionView.scrollToItem( + at: IndexPath.init(row: selecedColorIndex, section: 0), + at: .centeredHorizontally, + animated: true + ) + } + } +} + +extension TextEditViewController: UITextViewDelegate { + func textViewDidBeginEditing(_ textView: UITextView) { + if let selectedTextComponent = self.viewModel?.selectedTextComponent { + self.placeholderLabel.isHidden = true + self.inputTextView.text = selectedTextComponent.text + } else { + self.placeholderLabel.isHidden = false + } + textView.sizeToFit() + textView.snp.makeConstraints { make in + make.width.equalTo(textView.contentSize.width) + make.height.equalTo(textView.contentSize.height) + } + } + + func textViewDidChange(_ textView: UITextView) { + if textView.text.isEmpty { + self.placeholderLabel.isHidden = false + self.inputTextView.tintColor = .clear + + } else { + self.placeholderLabel.isHidden = true + self.inputTextView.tintColor = .black + } + + let maximumWidth: CGFloat = self.view.frame.width - 40 + let newSize = textView.sizeThatFits(CGSize(width: maximumWidth, height: CGFloat.greatestFiniteMagnitude)) + + textView.snp.updateConstraints { make in + make.width.equalTo(newSize.width) + make.height.equalTo(newSize.height) + } + } +} + +extension TextEditViewController: UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + let cellWidth: CGFloat = 45 + self.currentColorIndex = indexPath.item + let offset = CGPoint(x: CGFloat(self.currentColorIndex) * cellWidth - collectionView.contentInset.left, y: 0) + collectionView.setContentOffset(offset, animated: true) + } + + func collectionView( + _ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + sizeForItemAt indexPath: IndexPath + ) -> CGSize { + return CGSize(width: 30, height: 30) + } + + func collectionView( + _ collectionView: UICollectionView, + layout collectionViewLayout: UICollectionViewLayout, + insetForSectionAt section: Int + ) -> UIEdgeInsets { + let inset = collectionView.frame.width / 2.0 - 15 + return UIEdgeInsets(top: 5, left: inset, bottom: 5, right: inset) + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if let collectionView = scrollView as? UICollectionView { + let cellCount = collectionView.numberOfItems(inSection: 0) + let cellWidth: CGFloat = 45 + let offset = scrollView.contentOffset + + let index = Int(round((offset.x + collectionView.contentInset.left) / cellWidth)) + + if index > self.currentColorIndex { + self.currentColorIndex = index < cellCount ? index : cellCount - 1 + } else if index < self.currentColorIndex { + self.currentColorIndex = index > 0 ? index : 0 + } + } + } + + func scrollViewWillEndDragging( + _ scrollView: UIScrollView, + withVelocity velocity: CGPoint, + targetContentOffset: UnsafeMutablePointer + ) { + if let collectionView = scrollView as? UICollectionView { + let cellCount = collectionView.numberOfItems(inSection: 0) + let cellWidth: CGFloat = 45 + var offset = targetContentOffset.pointee + + let index = Int(round((offset.x + collectionView.contentInset.left) / cellWidth)) + + if index > self.currentColorIndex { + self.currentColorIndex = index < cellCount ? index : cellCount - 1 + } else if index < self.currentColorIndex { + self.currentColorIndex = index > 0 ? index : 0 + } + + offset = CGPoint(x: CGFloat(self.currentColorIndex) * cellWidth - collectionView.contentInset.left, y: 0) + targetContentOffset.pointee = offset + } + } +} + +extension TextEditViewController: UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return self.viewModel.getFontColorCount() + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell( + withReuseIdentifier: FontColorCollectionViewCell.identifier, + for: indexPath + ) as? FontColorCollectionViewCell, + let cellColor = self.viewModel.getFontColor(at: indexPath.item) + else { return UICollectionViewCell() } + + cell.configure(with: UIColor(cgColor: cellColor)) + if indexPath.item == self.currentColorIndex { + cell.transform = .init(scaleX: 1.2, y: 1.2) + } else { + cell.transform = .identity + } + return cell + } +} diff --git a/Doolda/Doolda/EditPageScene/TextEditScene/TextEditViewModel.swift b/Doolda/Doolda/EditPageScene/TextEditScene/TextEditViewModel.swift new file mode 100644 index 00000000..d41fc80e --- /dev/null +++ b/Doolda/Doolda/EditPageScene/TextEditScene/TextEditViewModel.swift @@ -0,0 +1,50 @@ +// +// TextInputViewModel.swift +// Doolda +// +// Created by 김민주 on 2021/11/17. +// + +import CoreGraphics +import Foundation + +protocol TextEditViewModelProtocol { + var selectedTextComponent: TextComponentEntity? { get } + func getFontColorCount() -> Int + func getFontColor(at index: Int) -> CGColor? + func inputViewEditingDidEnd(input: String, contentSize: CGSize, fontSize:CGFloat, colorIndex: Int) -> TextComponentEntity? +} + +class TextEditViewModel: TextEditViewModelProtocol { + private let textUseCase: TextUseCaseProtocol + let selectedTextComponent: TextComponentEntity? + + init(textUseCase: TextUseCaseProtocol, selectedTextComponent: TextComponentEntity?) { + self.textUseCase = textUseCase + self.selectedTextComponent = selectedTextComponent + } + + func getFontColorCount() -> Int { + return FontColorType.allCases.count + } + + func getFontColor(at index: Int) -> CGColor? { + let fontColorType = FontColorType.allCases + return fontColorType[index].rawValue + } + + func inputViewEditingDidEnd(input: String, contentSize: CGSize, fontSize: CGFloat, colorIndex: Int) -> TextComponentEntity? { + let color = FontColorType.allCases[colorIndex] + if let selectedTextComponent = selectedTextComponent { + return self.textUseCase.changeTextComponent( + from: selectedTextComponent, + with: input, + contentSize: contentSize, + fontSize: fontSize, + color: color + ) + } else { + return self.textUseCase.getTextComponent(with: input, contentSize: contentSize, fontSize: fontSize, color: color) + } + } +} diff --git a/Doolda/Doolda/PageDetailScene/PageDetailViewController.swift b/Doolda/Doolda/PageDetailScene/PageDetailViewController.swift new file mode 100644 index 00000000..bab8191b --- /dev/null +++ b/Doolda/Doolda/PageDetailScene/PageDetailViewController.swift @@ -0,0 +1,198 @@ +// +// PageDetailViewController.swift +// Doolda +// +// Created by 김민주 on 2021/11/24. +// + +import Combine +import UIKit + +class PageDetailViewController: UIViewController { + + // MARK: - Subviews + + private lazy var diaryPageView: DiaryPageView = { + let view = DiaryPageView() + view.clipsToBounds = true + view.layer.cornerRadius = 4 + view.layer.borderColor = UIColor.black.cgColor + view.layer.borderWidth = 1 + view.delegate = self + return view + }() + + private lazy var shareButton: UIButton = { + var button = UIButton(frame: CGRect(x: .zero, y: .zero, width: 24, height: 24)) + button.setImage(.squareAndArrowUp, for: .normal) + button.setPreferredSymbolConfiguration(UIImage.SymbolConfiguration.init(pointSize: 21), forImageIn: .normal) + return button + }() + + private lazy var editPageButton: UIButton = { + var button = UIButton(frame: CGRect(x: .zero, y: .zero, width: 24, height: 24)) + button.setImage(.squareAndPencil, for: .normal) + button.setPreferredSymbolConfiguration(UIImage.SymbolConfiguration.init(pointSize: 21), forImageIn: .normal) + return button + }() + + private lazy var activityIndicator: UIActivityIndicatorView = { + let activityIndicator = UIActivityIndicatorView() + activityIndicator.style = .large + return activityIndicator + }() + + private lazy var hapticGenerator: UIImpactFeedbackGenerator = { + let generator = UIImpactFeedbackGenerator(style: .light) + return generator + }() + + // MARK: - Override Properties + + override var prefersStatusBarHidden: Bool { return true } + + // MARK: - Private Properties + + private var viewModel: PageDetailViewModelProtocol! + private var cancellables: Set = [] + + // MARK: - Initializers + + convenience init(viewModel: PageDetailViewModelProtocol) { + self.init(nibName: nil, bundle: nil) + self.viewModel = viewModel + } + + deinit { + print(#file, "DEINIT") + self.viewModel.deinitRequested() + } + + // MARK: - Lifecycle Methods + + override func viewDidLoad() { + super.viewDidLoad() + self.configureUI() + self.configureFont() + self.bindUI() + self.bindViewModel() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.viewModel.pageDetailViewWillAppear() + self.navigationController?.hidesBarsOnSwipe = false + } + + // MARK: - Helpers + + func configureUI() { + self.view.backgroundColor = .dooldaBackground + guard let navigationController = self.navigationController else { return } + navigationController.isNavigationBarHidden = false + navigationController.navigationBar.tintColor = .dooldaLabel + navigationController.navigationBar.topItem?.title = "" + + let date = self.viewModel.getDate() + self.title = DateFormatter.koreanFormatter.string(from: date) + + self.view.addSubview(self.diaryPageView) + + self.diaryPageView.snp.makeConstraints { make in + make.centerX.equalToSuperview() + make.top.equalTo(self.view.safeAreaLayoutGuide).offset(12) + make.width.equalTo(self.diaryPageView.snp.height).multipliedBy(17.0 / 30.0) + let screenHeight = UIScreen.main.bounds.size.height + if screenHeight > 750 { + make.height.equalTo(self.view.safeAreaLayoutGuide).offset(-80) + } else { + make.height.equalTo(self.view.safeAreaLayoutGuide).offset(-45) + } + } + + self.diaryPageView.addSubview(self.activityIndicator) + self.activityIndicator.snp.makeConstraints { make in + make.center.equalToSuperview() + } + + self.view.addSubview(self.shareButton) + self.shareButton.snp.makeConstraints { make in + make.top.equalTo(self.diaryPageView.snp.bottom).offset(5) + make.leading.equalTo(self.diaryPageView.snp.leading) + make.width.equalTo(30) + make.height.equalTo(self.shareButton.snp.width) + } + + self.view.addSubview(self.editPageButton) + self.editPageButton.snp.makeConstraints { make in + make.top.equalTo(self.diaryPageView.snp.bottom).offset(5) + make.trailing.equalTo(self.diaryPageView.snp.trailing) + make.width.equalTo(30) + make.height.equalTo(self.editPageButton.snp.width) + } + + self.editPageButton.isEnabled = self.viewModel.isPageEditable() + self.activityIndicator.startAnimating() + } + + private func bindUI() { + self.shareButton.publisher(for: .touchUpInside) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self = self else { return } + self.hapticGenerator.prepare() + self.hapticGenerator.impactOccurred() + self.savePageAndShare() + } + .store(in: &self.cancellables) + + self.editPageButton.publisher(for: .touchUpInside) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.hapticGenerator.prepare() + self?.hapticGenerator.impactOccurred() + self?.viewModel.editPageButtonDidTap() + } + .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: GlobalFontUseCase.Notifications.globalFontDidSet, object: nil) + .sink { [weak self] _ in + self?.configureFont() + } + .store(in: &self.cancellables) + } + + private func bindViewModel() { + self.viewModel.rawPageEntityPublisher + .compactMap { $0 } + .receive(on: DispatchQueue.main) + .sink { [weak self] rawPageEntity in + self?.diaryPageView.pageBackgroundColor = UIColor(cgColor: rawPageEntity.backgroundType.rawValue) + self?.diaryPageView.components = rawPageEntity.components + }.store(in: &self.cancellables) + } + + private func savePageAndShare() { + UIGraphicsBeginImageContext(self.diaryPageView.frame.size) + guard let context = UIGraphicsGetCurrentContext() else { return } + self.diaryPageView.layer.render(in: context) + if let pageImage = UIGraphicsGetImageFromCurrentImageContext() { + UIGraphicsEndImageContext() + var imagesToShare = [AnyObject]() + imagesToShare.append(pageImage) + let activityViewController = UIActivityViewController(activityItems: imagesToShare , applicationActivities: nil) + activityViewController.popoverPresentationController?.sourceView = self.view + present(activityViewController, animated: true, completion: nil) + } + } + + private func configureFont() { + self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)] + } +} + +extension PageDetailViewController: DiaryPageViewDelegate { + func diaryPageDrawDidFinish(_ diaryPageView: DiaryPageView) { + self.activityIndicator.stopAnimating() + } +} diff --git a/Doolda/Doolda/PageDetailScene/PageDetailViewCoordinator.swift b/Doolda/Doolda/PageDetailScene/PageDetailViewCoordinator.swift new file mode 100644 index 00000000..c2c83ac3 --- /dev/null +++ b/Doolda/Doolda/PageDetailScene/PageDetailViewCoordinator.swift @@ -0,0 +1,91 @@ +// +// PageDetailViewCoordinator.swift +// Doolda +// +// Created by Dozzing on 2021/11/24. +// + +import Combine +import UIKit + +final class PageDetailViewCoordinator: BaseCoordinator { + + // MARK: - Nested enum + + enum Notifications { + static let editPageRequested = Notification.Name("editPageRequested") + } + + enum Keys { + static let rawPageEntity = "rawPageEntity" + } + + // MARK: - Public Properties + private let user: User + private let pageEntity: PageEntity + + private var cancellables: Set = [] + + // MARK: - Initializers + + init(identifier: UUID, presenter: UINavigationController, user: User, pageEntity: PageEntity) { + self.user = user + self.pageEntity = pageEntity + super.init(identifier: identifier, presenter: presenter) + self.bind() + } + + // MARK: - Helpers + + private func bind() { + NotificationCenter.default.publisher(for: Notifications.editPageRequested, object: nil) + .compactMap { $0.userInfo?[Keys.rawPageEntity] as? RawPageEntity } + .receive(on: DispatchQueue.main) + .sink { [weak self] rawPageEntity in + self?.editPageRequested(with: rawPageEntity) + } + .store(in: &self.cancellables) + } + + // MARK: - Public Methods + + func start() { + let networkService = URLSessionNetworkService.shared + let coreDataPersistenceService = CoreDataPersistenceService.shared + let coreDataPageEntityPersistenceService = CoreDataPageEntityPersistenceService(coreDataPersistenceService: coreDataPersistenceService) + let fileManagerPersistenceService = FileManagerPersistenceService.shared + + let rawPageRepository = RawPageRepository( + networkService: networkService, + coreDataPageEntityPersistenceService: coreDataPageEntityPersistenceService, + fileManagerPersistenceService: fileManagerPersistenceService + ) + + let getRawPageUseCase = GetRawPageUseCase(rawPageRepository: rawPageRepository) + + let viewModel = PageDetaillViewModel( + sceneId: self.identifier, + user: self.user, + pageEntity: self.pageEntity, + getRawPageUseCase: getRawPageUseCase + ) + + let viewController = PageDetailViewController(viewModel: viewModel) + self.presenter.topViewController?.navigationController?.pushViewController(viewController, animated: true) + } + + // MARK: - Private Methods + + private func editPageRequested(with rawPageEntity: RawPageEntity) { + let identifier = UUID() + let coordinator = EditPageViewCoordinator( + identifier: identifier, + presenter: self.presenter, + user: self.user, + pageEntity: self.pageEntity, + rawPageEntity: rawPageEntity + ) + self.children[identifier] = coordinator + coordinator.start() + } +} diff --git a/Doolda/Doolda/PageDetailScene/PageDetailViewModel.swift b/Doolda/Doolda/PageDetailScene/PageDetailViewModel.swift new file mode 100644 index 00000000..bbad8557 --- /dev/null +++ b/Doolda/Doolda/PageDetailScene/PageDetailViewModel.swift @@ -0,0 +1,87 @@ +// +// PageDetailViewModel.swift +// Doolda +// +// Created by Dozzing on 2021/11/24. +// + +import Combine +import Foundation + +protocol PageDetailViewModelInput { + func pageDetailViewWillAppear() + func isPageEditable() -> Bool + func getDate() -> Date + func editPageButtonDidTap() + func deinitRequested() +} + +protocol PageDetailViewModelOuput { + var rawPageEntityPublisher: AnyPublisher { get } + var errorPublisher: AnyPublisher { get } +} + +typealias PageDetailViewModelProtocol = PageDetailViewModelInput & PageDetailViewModelOuput + +class PageDetaillViewModel: PageDetailViewModelProtocol { + var rawPageEntityPublisher: AnyPublisher { self.$rawPageEntity.eraseToAnyPublisher() } + var errorPublisher: AnyPublisher { self.$error.eraseToAnyPublisher() } + + @Published private var rawPageEntity: RawPageEntity? + @Published private var error: Error? + + private let sceneId: UUID + private let user: User + private let pageEntity: PageEntity + private let getRawPageUseCase: GetRawPageUseCaseProtocol + + init( + sceneId: UUID, + user: User, + pageEntity: PageEntity, + getRawPageUseCase: GetRawPageUseCaseProtocol + ) { + self.sceneId = sceneId + self.user = user + self.pageEntity = pageEntity + self.getRawPageUseCase = getRawPageUseCase + } + + private var cancellabels: Set = [] + + func pageDetailViewWillAppear() { + self.getRawPageUseCase.getRawPageEntity(metaData: self.pageEntity) + .sink { [weak self] completion in + guard case .failure(let error) = completion else { return } + self?.error = error + } receiveValue: { [weak self] rawPageEntity in + self?.rawPageEntity = rawPageEntity + } + .store(in: &self.cancellabels) + } + + func isPageEditable() -> Bool { + return self.pageEntity.author.id == self.user.id + } + + func getDate() -> Date { + return self.pageEntity.createdTime + } + + func editPageButtonDidTap() { + guard let rawPageEntity = self.rawPageEntity else { return } + NotificationCenter.default.post( + name: PageDetailViewCoordinator.Notifications.editPageRequested, + object: self, + userInfo: [PageDetailViewCoordinator.Keys.rawPageEntity: rawPageEntity] + ) + } + + func deinitRequested() { + NotificationCenter.default.post( + name: BaseCoordinator.Notifications.coordinatorRemoveFromParent, + object: nil, + userInfo: [BaseCoordinator.Keys.sceneId: self.sceneId] + ) + } +} diff --git a/Doolda/Doolda/UI/Views/CopyableLabel.swift b/Doolda/Doolda/PairingScene/CopyableLabel.swift similarity index 100% rename from Doolda/Doolda/UI/Views/CopyableLabel.swift rename to Doolda/Doolda/PairingScene/CopyableLabel.swift diff --git a/Doolda/Doolda/UI/ViewControllers/PairingViewController.swift b/Doolda/Doolda/PairingScene/PairingViewController.swift similarity index 77% rename from Doolda/Doolda/UI/ViewControllers/PairingViewController.swift rename to Doolda/Doolda/PairingScene/PairingViewController.swift index e644eae6..ce079fd3 100644 --- a/Doolda/Doolda/UI/ViewControllers/PairingViewController.swift +++ b/Doolda/Doolda/PairingScene/PairingViewController.swift @@ -39,7 +39,7 @@ class PairingViewController: UIViewController { private lazy var myIdTitleLabel: UILabel = { let label = UILabel() - label.textColor = .dooldaSubLabel + label.textColor = .dooldaSublabel label.text = "내 초대코드" return label }() @@ -54,49 +54,38 @@ class PairingViewController: UIViewController { private lazy var friendIdTitleLabel: UILabel = { let label = UILabel() - label.textColor = .dooldaSubLabel + label.textColor = .dooldaSublabel label.text = "상대방 초대코드를 전달 받으셨나요?" return label }() private lazy var friendIdTextField: UITextField = { let textField = UITextField() - textField.attributedPlaceholder = NSAttributedString( - string: "전달 받은 초대코드 입력", - attributes: [NSAttributedString.Key.foregroundColor: UIColor.dooldaPlaceholder as Any] - ) textField.textColor = .dooldaLabel textField.textAlignment = .center return textField }() private lazy var pairButton: UIButton = { - var configuration = UIButton.Configuration.filled() - configuration.cornerStyle = .capsule - var container = AttributeContainer() - configuration.attributedTitle = AttributedString("연결하기", attributes: container) - configuration.baseBackgroundColor = .dooldaHighlighted - configuration.baseForegroundColor = .dooldaLabel - let button = UIButton(configuration: configuration) + let button = DooldaButton() + button.setTitle("연결하기", for: .normal) + button.setTitleColor(.dooldaLabel, for: .normal) + button.backgroundColor = .dooldaHighlighted button.isEnabled = false return button }() private lazy var pairSkipButton: UIButton = { - var configuration = UIButton.Configuration.filled() - configuration.cornerStyle = .capsule - var container = AttributeContainer() - configuration.attributedTitle = AttributedString("혼자 쓰러가기", attributes: container) - configuration.baseBackgroundColor = .dooldaHighlighted - configuration.baseForegroundColor = .dooldaLabel - let button = UIButton(configuration: configuration) - button.isEnabled = true + let button = DooldaButton() + button.setTitle("혼자 쓰러가기", for: .normal) + button.setTitleColor(.dooldaLabel, for: .normal) + button.backgroundColor = .dooldaHighlighted return button }() private lazy var divider: UIView = { let view = UIView() - view.backgroundColor = .dooldaSubLabel + view.backgroundColor = .dooldaSublabel return view }() @@ -116,17 +105,15 @@ class PairingViewController: UIViewController { return stackView }() - private let transparentNavigationBarAppearance: UINavigationBarAppearance = { - let appearance = UINavigationBarAppearance() - appearance.backgroundColor = .clear - appearance.configureWithTransparentBackground() - return appearance + private lazy var hapticGenerator: UIImpactFeedbackGenerator = { + let generator = UIImpactFeedbackGenerator(style: .light) + return generator }() // MARK: - Private Properties private var cancellables: Set = [] - private var viewModel: PairingViewModel? + private var viewModel: PairingViewModel! // MARK: - Initializers @@ -135,35 +122,41 @@ class PairingViewController: UIViewController { self.viewModel = viewModel } +// deinit { +// print(#file, "DEINIT") +// self.viewModel.deinitRequested() +// } + // MARK: - Lifecycle Methods override func viewDidLoad() { super.viewDidLoad() self.configureUI() - self.bindUI() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - self.navigationController?.navigationBar.standardAppearance = transparentNavigationBarAppearance - self.navigationController?.navigationBar.scrollEdgeAppearance = transparentNavigationBarAppearance self.configureFont() + self.bindUI() } // MARK: - Helpers private func configureUI() { self.view.backgroundColor = .dooldaBackground - self.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: self.refreshButton) + self.navigationController?.navigationBar.isHidden = true self.view.addSubview(scrollView) self.scrollView.snp.makeConstraints { make in make.edges.equalToSuperview() } + self.view.addSubview(self.refreshButton) + self.refreshButton.snp.makeConstraints { make in + make.top.equalTo(self.view.safeAreaLayoutGuide).offset(16) + make.trailing.equalToSuperview().offset(-16) + } + self.scrollView.addSubview(contentView) self.contentView.snp.makeConstraints { make in - make.top.leading.trailing.equalToSuperview() + make.top.equalToSuperview().offset(44) + make.leading.trailing.equalToSuperview() make.centerX.equalToSuperview() make.bottom.equalToSuperview().priority(.low) make.centerY.equalToSuperview().priority(.low) @@ -212,23 +205,23 @@ class PairingViewController: UIViewController { } private func configureFont() { - self.logoLabel.font = UIFont(name: DoolDaFont.dovemayo.rawValue, size: 72) + self.logoLabel.font = UIFont(name: FontType.dovemayo.name, size: 72) self.instructionLabel.font = .systemFont(ofSize: 18) self.myIdTitleLabel.font = .systemFont(ofSize: 14) self.myIdLabel.font = .systemFont(ofSize: 16) self.friendIdTitleLabel.font = .systemFont(ofSize: 14) self.friendIdTextField.font = .systemFont(ofSize: 16) - self.pairButton.titleLabel?.font = .systemFont(ofSize: 16) - self.pairSkipButton.titleLabel?.font = .systemFont(ofSize: 16) + self.pairButton.titleLabel?.font = .systemFont(ofSize: 14) + self.pairSkipButton.titleLabel?.font = .systemFont(ofSize: 14) } - // MARK: - FIXME : should bind to viewModel - private func bindUI() { guard let viewModel = self.viewModel else { return } self.refreshButton.publisher(for: .touchUpInside) .receive(on: DispatchQueue.main) - .sink { _ in + .sink { [weak self] _ in + self?.hapticGenerator.prepare() + self?.hapticGenerator.impactOccurred() viewModel.refreshButtonDidTap() } .store(in: &self.cancellables) @@ -241,33 +234,37 @@ class PairingViewController: UIViewController { self.pairButton.publisher(for: .touchUpInside) .receive(on: DispatchQueue.main) - .sink { _ in + .sink { [weak self] _ in + self?.hapticGenerator.prepare() + self?.hapticGenerator.impactOccurred() viewModel.pairButtonDidTap() } .store(in: &self.cancellables) self.pairSkipButton.publisher(for: .touchUpInside) .receive(on: DispatchQueue.main) - .sink { _ in - self.viewModel?.pairSkipButtonDidTap() + .sink { [weak self] _ in + self?.hapticGenerator.prepare() + self?.hapticGenerator.impactOccurred() + self?.viewModel.pairSkipButtonDidTap() } .store(in: &self.cancellables) - self.viewModel?.myId + self.viewModel.myId .receive(on: DispatchQueue.main) .sink { [weak self] myId in self?.myIdLabel.text = myId } .store(in: &self.cancellables) - self.viewModel?.isFriendIdValid + self.viewModel.isFriendIdValid .receive(on: DispatchQueue.main) .sink { [weak self] isValid in self?.pairButton.isEnabled = isValid } .store(in: &self.cancellables) - self.viewModel?.errorPublisher + self.viewModel.errorPublisher .receive(on: DispatchQueue.main) .compactMap { $0 } .sink { [weak self] error in @@ -289,6 +286,20 @@ class PairingViewController: UIViewController { self?.updateScrollView(with: keyboardHeight) } .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: PushMessageEntity.Notifications.userPairedWithFriend, object: nil) + .sink { [weak self] _ in + self?.hapticGenerator.prepare() + self?.hapticGenerator.impactOccurred() + self?.viewModel.userPairedWithFriendNotificationDidReceived() + } + .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: GlobalFontUseCase.Notifications.globalFontDidSet, object: nil) + .sink { [weak self] _ in + self?.configureFont() + } + .store(in: &self.cancellables) } // MARK: - Private Methods @@ -309,5 +320,4 @@ class PairingViewController: UIViewController { ) self.present(alert, animated: true) } - } diff --git a/Doolda/Doolda/PairingScene/PairingViewCoordinator.swift b/Doolda/Doolda/PairingScene/PairingViewCoordinator.swift new file mode 100644 index 00000000..c26743a5 --- /dev/null +++ b/Doolda/Doolda/PairingScene/PairingViewCoordinator.swift @@ -0,0 +1,83 @@ +// +// ParingViewCoordinator.swift +// Doolda +// +// Created by Dozzing on 2021/11/02. +// + +import Combine +import UIKit + +class PairingViewCoordinator: BaseCoordinator { + + // MARK: - Nested enum + + enum Notifications { + static let userDidPaired = Notification.Name("userDidPaired") + } + + enum Keys { + static let user = "user" + } + + private let user: User + + private var cancellables: Set = [] + + init(identifier: UUID, presenter: UINavigationController, user: User) { + self.user = user + super.init(identifier: identifier, presenter: presenter) + self.bind() + } + + func start() { + let userDefaultsPersistenceService = UserDefaultsPersistenceService.shared + let urlSessionNetworkService = URLSessionNetworkService.shared + + let userRepository = UserRepository( + persistenceService: userDefaultsPersistenceService, + networkService: urlSessionNetworkService + ) + + let pairRepository = PairRepository(networkService: urlSessionNetworkService) + let fcmTokenRepository = FCMTokenRepository(urlSessionNetworkService: urlSessionNetworkService) + let firebaseMessageRepository = FirebaseMessageRepository(urlSessionNetworkService: urlSessionNetworkService) + + let pairUserUseCase = PairUserUseCase(userRepository: userRepository, pairRepository: pairRepository) + let refreshUserUseCase = RefreshUserUseCase(userRepository: userRepository) + let firebaseMessageUseCase = FirebaseMessageUseCase( + fcmTokenRepository: fcmTokenRepository, + firebaseMessageRepository: firebaseMessageRepository + ) + + let viewModel = PairingViewModel( + sceneId: self.identifier, + user: user, + pairUserUseCase: pairUserUseCase, + refreshUserUseCase: refreshUserUseCase, + firebaseMessageUseCase: firebaseMessageUseCase + ) + + DispatchQueue.main.async { + let viewController = PairingViewController(viewModel: viewModel) + self.presenter.setViewControllers([viewController], animated: false) + } + } + + private func bind() { + NotificationCenter.default.publisher(for: Notifications.userDidPaired, object: nil) + .receive(on: DispatchQueue.main) + .compactMap { $0.userInfo?[Keys.user] as? User } + .sink { [weak self] user in + self?.userDidPaired(user: user) + } + .store(in: &self.cancellables) + } + + private func userDidPaired(user: User) { + let identifier = UUID() + let diaryViewCoordinator = DiaryViewCoordinator(identifier: identifier, presenter: self.presenter, user: user) + self.children[identifier] = diaryViewCoordinator + diaryViewCoordinator.start() + } +} diff --git a/Doolda/Doolda/Presentation/ViewModels/PairingViewModel.swift b/Doolda/Doolda/PairingScene/PairingViewModel.swift similarity index 56% rename from Doolda/Doolda/Presentation/ViewModels/PairingViewModel.swift rename to Doolda/Doolda/PairingScene/PairingViewModel.swift index 17d29374..ce1de4c9 100644 --- a/Doolda/Doolda/Presentation/ViewModels/PairingViewModel.swift +++ b/Doolda/Doolda/PairingScene/PairingViewModel.swift @@ -13,14 +13,16 @@ protocol PairingViewModelInput { func pairButtonDidTap() func pairSkipButtonDidTap() func refreshButtonDidTap() + func userPairedWithFriendNotificationDidReceived() + func deinitRequested() } protocol PairingViewModelOutput { var myId: AnyPublisher { get } var isFriendIdValid: AnyPublisher { get } - var pairedUserPublisher: Published.Publisher { get } - var isPairedByRefreshPublisher: Published.Publisher { get } - var errorPublisher: Published.Publisher { get } + var pairedUserPublisher: AnyPublisher { get } + var isPairedByRefreshPublisher: AnyPublisher { get } + var errorPublisher: AnyPublisher { get } } typealias PairingViewModelProtocol = PairingViewModelInput & PairingViewModelOutput @@ -37,29 +39,33 @@ final class PairingViewModel: PairingViewModelProtocol { .compactMap { DDID.isValid(ddidString: $0) } .eraseToAnyPublisher() - var pairedUserPublisher: Published.Publisher { self.$pairedUser } - var isPairedByRefreshPublisher: Published.Publisher { self.$isPairedByRefresh } - var errorPublisher: Published.Publisher { self.$error } + var pairedUserPublisher: AnyPublisher { self.$pairedUser.eraseToAnyPublisher() } + var isPairedByRefreshPublisher: AnyPublisher { self.$isPairedByRefresh.eraseToAnyPublisher() } + var errorPublisher: AnyPublisher { self.$error.eraseToAnyPublisher() } + private let sceneId: UUID private let user: User - private let coordinator: PairingViewCoordinatorProtocol private let pairUserUseCase: PairUserUseCaseProtocol private let refreshUserUseCase: RefreshUserUseCaseProtocol + private let firebaseMessageUseCase: FirebaseMessageUseCaseProtocol + private var cancellables: Set = [] @Published private var pairedUser: User? @Published private var isPairedByRefresh: Bool = false @Published private var error: Error? init( + sceneId: UUID, user: User, - coordinator: PairingViewCoordinatorProtocol, pairUserUseCase: PairUserUseCaseProtocol, - refreshUserUseCase: RefreshUserUseCaseProtocol + refreshUserUseCase: RefreshUserUseCaseProtocol, + firebaseMessageUseCase: FirebaseMessageUseCaseProtocol ) { + self.sceneId = sceneId self.user = user - self.coordinator = coordinator self.pairUserUseCase = pairUserUseCase self.refreshUserUseCase = refreshUserUseCase + self.firebaseMessageUseCase = firebaseMessageUseCase bind() } @@ -73,7 +79,14 @@ final class PairingViewModel: PairingViewModelProtocol { self.pairUserUseCase.pairedUserPublisher .compactMap { $0 } .sink { [weak self] user in - self?.coordinator.userDidPaired(user: user) + if let friendId = user.friendId, friendId != user.id { + self?.firebaseMessageUseCase.sendMessage(to: friendId, message: PushMessageEntity.userPairedWithFriend) + } + NotificationCenter.default.post( + name: PairingViewCoordinator.Notifications.userDidPaired, + object: nil, + userInfo: [PairingViewCoordinator.Keys.user: user] + ) } .store(in: &self.cancellables) @@ -81,7 +94,11 @@ final class PairingViewModel: PairingViewModelProtocol { .compactMap { $0 } .sink { [weak self] user in if user.pairId != nil { - self?.coordinator.userDidPaired(user: user) + NotificationCenter.default.post( + name: PairingViewCoordinator.Notifications.userDidPaired, + object: nil, + userInfo: [PairingViewCoordinator.Keys.user: user] + ) } else { self?.isPairedByRefresh = false } @@ -101,4 +118,16 @@ final class PairingViewModel: PairingViewModelProtocol { func refreshButtonDidTap() { self.refreshUserUseCase.refresh(for: self.user) } + + func userPairedWithFriendNotificationDidReceived() { + self.refreshUserUseCase.refresh(for: self.user) + } + + func deinitRequested() { + NotificationCenter.default.post( + name: BaseCoordinator.Notifications.coordinatorRemoveFromParent, + object: nil, + userInfo: [BaseCoordinator.Keys.sceneId: self.sceneId] + ) + } } diff --git a/Doolda/Doolda/Presentation/Interfaces/.gitkeep b/Doolda/Doolda/Presentation/Interfaces/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Doolda/Doolda/Presentation/Interfaces/CoordinatorProtocol.swift b/Doolda/Doolda/Presentation/Interfaces/CoordinatorProtocol.swift deleted file mode 100644 index 865e3e61..00000000 --- a/Doolda/Doolda/Presentation/Interfaces/CoordinatorProtocol.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// CoordinatorProtocol.swift -// Doolda -// -// Created by Seunghun Yang on 2021/11/08. -// - -import UIKit - -protocol CoordinatorProtocol { - var presenter: UINavigationController { get } - - func start() -} diff --git a/Doolda/Doolda/Presentation/Interfaces/DiaryViewCoordinatorProtocol.swift b/Doolda/Doolda/Presentation/Interfaces/DiaryViewCoordinatorProtocol.swift deleted file mode 100644 index 52714bea..00000000 --- a/Doolda/Doolda/Presentation/Interfaces/DiaryViewCoordinatorProtocol.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// DiaryViewCoordinatorProtocol.swift -// Doolda -// -// Created by Seunghun Yang on 2021/11/08. -// - -import Foundation - -protocol DiaryViewCoordinatorProtocol: CoordinatorProtocol { - func editPageRequested() - func settingsPageRequested() - func filteringSheetRequested() -} diff --git a/Doolda/Doolda/Presentation/Interfaces/EditPageViewCoordinatorProtocol.swift b/Doolda/Doolda/Presentation/Interfaces/EditPageViewCoordinatorProtocol.swift deleted file mode 100644 index c97dd071..00000000 --- a/Doolda/Doolda/Presentation/Interfaces/EditPageViewCoordinatorProtocol.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// EditPageViewCoordinatorProtocol.swift -// Doolda -// -// Created by 김민주 on 2021/11/08. -// - -import Foundation - -protocol EditPageViewCoordinatorProtocol: CoordinatorProtocol { - func editingPageSaved() - func editingPageCanceled() - func addPhotoComponent() - func addTextComponent() - func addStickerComponent() - func changeBackgroundType() -} diff --git a/Doolda/Doolda/Presentation/Interfaces/PairingViewCoordinatorProtocol.swift b/Doolda/Doolda/Presentation/Interfaces/PairingViewCoordinatorProtocol.swift deleted file mode 100644 index 534baf43..00000000 --- a/Doolda/Doolda/Presentation/Interfaces/PairingViewCoordinatorProtocol.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// PairingViewCoordinatorProtocol.swift -// Doolda -// -// Created by Seunghun Yang on 2021/11/03. -// - -import Foundation - -protocol PairingViewCoordinatorProtocol: CoordinatorProtocol { - func userDidPaired(user: User) -} diff --git a/Doolda/Doolda/Presentation/Interfaces/SplashViewCoordinatorProtocol.swift b/Doolda/Doolda/Presentation/Interfaces/SplashViewCoordinatorProtocol.swift deleted file mode 100644 index 5bd0ead2..00000000 --- a/Doolda/Doolda/Presentation/Interfaces/SplashViewCoordinatorProtocol.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// SplashViewCoordinatorProtocol.swift -// Doolda -// -// Created by 정지승 on 2021/11/01. -// - -import Foundation - -protocol SplashViewCoordinatorProtocol: CoordinatorProtocol { - func userNotPaired(myId: DDID) - func userAlreadyPaired(user: User) -} diff --git a/Doolda/Doolda/Presentation/ViewModels/.gitkeep b/Doolda/Doolda/Presentation/ViewModels/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Doolda/Doolda/Presentation/ViewModels/DiaryViewModel.swift b/Doolda/Doolda/Presentation/ViewModels/DiaryViewModel.swift deleted file mode 100644 index 21e35444..00000000 --- a/Doolda/Doolda/Presentation/ViewModels/DiaryViewModel.swift +++ /dev/null @@ -1,156 +0,0 @@ -// -// DiaryViewModel.swift -// Doolda -// -// Created by Seunghun Yang on 2021/11/16. -// - -import Combine -import Foundation - -protocol DiaryViewModelInput { - func diaryViewWillAppear() - func filterButtonDidTap() - func displayModeToggleButtonDidTap() - func settingsButtonDidTap() - func addPageButtonDidTap() - func refreshButtonDidTap() - func filterDidApply(author: DiaryAuthorFilter, orderBy: DiaryOrderFilter) - func pageDidDisplay(jsonPath: String) -> AnyPublisher -} - -protocol DiaryViewModelOutput { - var errorPublisher: Published.Publisher { get } - var displayModePublisher: Published.Publisher { get } - var isMyTurnPublisher: Published.Publisher { get } - var filteredPageEntitiesPublisher: AnyPublisher<[PageEntity], Never> { get } - var isRefreshingPublisher: Published.Publisher { get } - var displayMode: DiaryDisplayMode { get } -} - -typealias DiaryViewModelProtocol = DiaryViewModelInput & DiaryViewModelOutput - -enum DiaryDisplayMode { - case carousel, list - - mutating func toggle() { - switch self { - case .carousel: self = .list - case .list: self = .carousel - } - } -} - -enum DiaryAuthorFilter { - case user, friend, both -} - -enum DiaryOrderFilter { - case ascending, descending -} - -enum DiaryViewModelError: LocalizedError { - case userNotPaired - - var errorDescription: String? { - switch self { - case .userNotPaired: - return "연결된 짝이 없습니다." - } - } -} - -class DiaryViewModel: DiaryViewModelProtocol { - var errorPublisher: Published.Publisher { self.$error } - var displayModePublisher: Published.Publisher { self.$displayMode } - var isMyTurnPublisher: Published.Publisher { self.$isMyTurn } - var isRefreshingPublisher: Published.Publisher { self.$isRefreshing } - - @Published var displayMode: DiaryDisplayMode = .carousel - - lazy var filteredPageEntitiesPublisher: AnyPublisher<[PageEntity], Never> = Publishers - .CombineLatest3(self.$pageEntities, self.$authorFilter, self.$orderFilter) - .map { $0.0 } - .eraseToAnyPublisher() - - @Published private var error: Error? - @Published private var isRefreshing: Bool = false - @Published private var isMyTurn: Bool = false - @Published private var filteredPageEntities: [PageEntity] = [] - @Published private var pageEntities: [PageEntity] = [] - @Published private var authorFilter: DiaryAuthorFilter = .user - @Published private var orderFilter: DiaryOrderFilter = .descending - - private var cancellables: Set = [] - - private let user: User - private let coordinator: DiaryViewCoordinatorProtocol - private let checkMyTurnUseCase: CheckMyTurnUseCaseProtocol - private let getPageUseCase: GetPageUseCaseProtocol - private let getRawPageUseCase: GetRawPageUseCaseProtocol - - init( - user: User, - coordinator: DiaryViewCoordinatorProtocol, - checkMyTurnUseCase: CheckMyTurnUseCaseProtocol, - getPageUseCase: GetPageUseCaseProtocol, - getRawPageUseCase: GetRawPageUseCaseProtocol - ) { - self.user = user - self.coordinator = coordinator - self.checkMyTurnUseCase = checkMyTurnUseCase - self.getPageUseCase = getPageUseCase - self.getRawPageUseCase = getRawPageUseCase - } - - func diaryViewWillAppear() { - self.fetchPages() - } - - func pageDidDisplay(jsonPath: String) -> AnyPublisher { - guard let pairId = self.user.pairId else { return Fail(error: DiaryViewModelError.userNotPaired).eraseToAnyPublisher() } - return self.getRawPageUseCase.getRawPageEntity(for: pairId, jsonPath: jsonPath) - } - - func displayModeToggleButtonDidTap() { - self.displayMode.toggle() - } - - func addPageButtonDidTap() { - self.coordinator.editPageRequested() - } - - func refreshButtonDidTap() { - self.fetchPages() - } - - func settingsButtonDidTap() { - self.coordinator.settingsPageRequested() - } - - func filterButtonDidTap() { - self.coordinator.filteringSheetRequested() - } - - func filterDidApply(author: DiaryAuthorFilter, orderBy: DiaryOrderFilter) { - self.authorFilter = author - self.orderFilter = orderBy - } - - private func fetchPages() { - guard let pairId = self.user.pairId else { return } - self.isRefreshing = true - - Publishers.Zip(self.checkMyTurnUseCase.checkTurn(for: self.user), self.getPageUseCase.getPages(for: pairId)) - .sink { [weak self] completion in - guard case .failure(let error) = completion else { return } - self?.error = error - self?.isRefreshing = false - } receiveValue: { [weak self] isMyTurn, pages in - self?.isMyTurn = isMyTurn - self?.pageEntities = pages - self?.isRefreshing = false - } - .store(in: &self.cancellables) - } -} diff --git a/Doolda/Doolda/Presentation/ViewModels/EditPageViewModel.swift b/Doolda/Doolda/Presentation/ViewModels/EditPageViewModel.swift deleted file mode 100644 index 74937a7c..00000000 --- a/Doolda/Doolda/Presentation/ViewModels/EditPageViewModel.swift +++ /dev/null @@ -1,145 +0,0 @@ -// -// EditPageViewModel.swift -// Doolda -// -// Created by 김민주 on 2021/11/08. -// - -import Combine -import CoreGraphics -import Foundation - -protocol EditPageViewModelInput { - func canvasDidTap(at point: CGPoint) - func componentDidDrag(at point: CGPoint) - func componentDidRotate(by angle: CGFloat) - func componentDidScale(by scale: CGFloat) - func componentBringFrontControlDidTap() - func componentSendBackControlDidTap() - func componentRemoveControlDidTap() - - func photoComponentAddButtonDidTap() - func textComponentAddButtonDidTap() - func stickerComponentAddButtonDidTap() - func backgroundTypeButtonDidTap() - - func componentEntityDidAdd(_ component: ComponentEntity) - func backgroundColorDidChange(_ backgroundColor: BackgroundType) - func saveEditingPageButtonDidTap() - func cancelEditingPageButtonDidTap() -} - -protocol EditPageViewModelOutput { - var selectedComponentPublisher: Published.Publisher { get } - var componentsPublisher: Published<[ComponentEntity]>.Publisher { get } - var backgroundPublisher: Published.Publisher { get } - var errorPublisher: Published.Publisher { get } -} - -typealias EditPageViewModelProtocol = EditPageViewModelInput & EditPageViewModelOutput - -final class EditPageViewModel: EditPageViewModelProtocol { - var selectedComponentPublisher: Published.Publisher { self.editPageUseCase.selectedComponentPublisher } - var componentsPublisher: Published<[ComponentEntity]>.Publisher { self.$components } - var backgroundPublisher: Published.Publisher { self.$background } - var errorPublisher: Published.Publisher { self.$error } - - private let user: User - private let coordinator: EditPageViewCoordinatorProtocol - private let editPageUseCase: EditPageUseCaseProtocol - private var cancellables: Set = [] - - @Published private var components: [ComponentEntity] = [] - @Published private var background: BackgroundType = .dooldaBackground - @Published private var error: Error? - - init( - user: User, - coordinator: EditPageViewCoordinatorProtocol, - editPageUseCase: EditPageUseCaseProtocol - ) { - self.user = user - self.coordinator = coordinator - self.editPageUseCase = editPageUseCase - bind() - } - - private func bind() { - self.editPageUseCase.rawPagePublisher - .sink { [weak self] rawPageEntity in - guard let self = self, - let rawPageEntity = rawPageEntity else { return } - self.components = rawPageEntity.components - self.background = rawPageEntity.backgroundType - }.store(in : &cancellables) - - self.editPageUseCase.resultPublisher - .dropFirst() - .sink { [weak self] _ in - self?.coordinator.editingPageSaved() - }.store(in: &self.cancellables) - - self.editPageUseCase.errorPublisher - .assign(to: &$error) - } - - func canvasDidTap(at point: CGPoint) { - self.editPageUseCase.selectComponent(at: point) - } - - func componentDidDrag(at point: CGPoint) { - self.editPageUseCase.moveComponent(to: point) - } - - func componentDidRotate(by angle: CGFloat) { - self.editPageUseCase.rotateComponent(by: angle) - } - - func componentDidScale(by scale: CGFloat) { - self.editPageUseCase.scaleComponent(by: scale) - } - - func componentBringFrontControlDidTap() { - self.editPageUseCase.bringComponentFront() - } - - func componentSendBackControlDidTap() { - self.editPageUseCase.sendComponentBack() - } - - func componentRemoveControlDidTap() { - self.editPageUseCase.removeComponent() - } - - func photoComponentAddButtonDidTap() { - self.coordinator.addPhotoComponent() - } - - func textComponentAddButtonDidTap() { - self.coordinator.addTextComponent() - } - - func stickerComponentAddButtonDidTap() { - self.coordinator.addStickerComponent() - } - - func backgroundTypeButtonDidTap() { - self.coordinator.changeBackgroundType() - } - - func componentEntityDidAdd(_ component: ComponentEntity) { - self.editPageUseCase.addComponent(component) - } - - func backgroundColorDidChange(_ backgroundColor: BackgroundType) { - self.editPageUseCase.changeBackgroundType(backgroundColor) - } - - func saveEditingPageButtonDidTap() { - self.editPageUseCase.savePage(author: self.user) - } - - func cancelEditingPageButtonDidTap() { - self.coordinator.editingPageCanceled() - } -} diff --git a/Doolda/Doolda/Presentation/ViewModels/SettingViewModel.swift b/Doolda/Doolda/Presentation/ViewModels/SettingViewModel.swift deleted file mode 100644 index 9551c280..00000000 --- a/Doolda/Doolda/Presentation/ViewModels/SettingViewModel.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// SettingViewModel.swift -// Doolda -// -// Created by 김민주 on 2021/11/17. -// - -import Foundation - -class SettingViewModel { - -} diff --git a/Doolda/Doolda/Presentation/ViewModels/TextInputViewModel.swift b/Doolda/Doolda/Presentation/ViewModels/TextInputViewModel.swift deleted file mode 100644 index d8884d57..00000000 --- a/Doolda/Doolda/Presentation/ViewModels/TextInputViewModel.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// TextInputViewModel.swift -// Doolda -// -// Created by 김민주 on 2021/11/17. -// - -import CoreGraphics -import Foundation - -protocol TextInputViewModelProtocol { - func inputViewEditingDidEnd(input: String, contentSize: CGSize, fontSize:CGFloat, color: FontColorType) -> TextComponentEntity -} - -class TextInputViewModel { - private let textUseCase: TextUseCaseProtocol - - init(textUseCase: TextUseCaseProtocol) { - self.textUseCase = textUseCase - } - - func inputViewEditingDidEnd(input: String, contentSize: CGSize, fontSize: CGFloat, color: FontColorType) -> TextComponentEntity { - return self.textUseCase.getTextComponent(with: input, contentSize: contentSize, fontSize: fontSize, color: color) - } -} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Color/dooldaBackground.colorset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/Color/dooldaBackground.colorset/Contents.json index be6be254..beb16bf8 100644 --- a/Doolda/Doolda/Resources/Assets.xcassets/Color/dooldaBackground.colorset/Contents.json +++ b/Doolda/Doolda/Resources/Assets.xcassets/Color/dooldaBackground.colorset/Contents.json @@ -41,9 +41,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0x28", - "green" : "0x2B", - "red" : "0x2D" + "blue" : "0x3F", + "green" : "0x46", + "red" : "0x4A" } }, "idiom" : "universal" diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Color/dooldaHighlighted.colorset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/Color/dooldaHighlighted.colorset/Contents.json index 87d7d8cf..ddb9aa53 100644 --- a/Doolda/Doolda/Resources/Assets.xcassets/Color/dooldaHighlighted.colorset/Contents.json +++ b/Doolda/Doolda/Resources/Assets.xcassets/Color/dooldaHighlighted.colorset/Contents.json @@ -41,9 +41,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0x59", - "green" : "0x69", - "red" : "0x73" + "blue" : "0x7B", + "green" : "0x8C", + "red" : "0x98" } }, "idiom" : "universal" diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Color/dooldaMinimumTrackTintColor.colorset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/Color/dooldaMinimumTrackTintColor.colorset/Contents.json index 138d43d6..e9b279de 100644 --- a/Doolda/Doolda/Resources/Assets.xcassets/Color/dooldaMinimumTrackTintColor.colorset/Contents.json +++ b/Doolda/Doolda/Resources/Assets.xcassets/Color/dooldaMinimumTrackTintColor.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.620", - "green" : "0.620", - "red" : "0.620" + "blue" : "0.714", + "green" : "0.714", + "red" : "0.714" } }, "idiom" : "universal" @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.620", - "green" : "0.620", - "red" : "0.620" + "blue" : "0.714", + "green" : "0.714", + "red" : "0.714" } }, "idiom" : "universal" diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Color/dooldaStickerPackBackground.colorset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/Color/dooldaWarning.colorset/Contents.json similarity index 71% rename from Doolda/Doolda/Resources/Assets.xcassets/Color/dooldaStickerPackBackground.colorset/Contents.json rename to Doolda/Doolda/Resources/Assets.xcassets/Color/dooldaWarning.colorset/Contents.json index 4a30ef2d..121f1989 100644 --- a/Doolda/Doolda/Resources/Assets.xcassets/Color/dooldaStickerPackBackground.colorset/Contents.json +++ b/Doolda/Doolda/Resources/Assets.xcassets/Color/dooldaWarning.colorset/Contents.json @@ -4,9 +4,9 @@ "color" : { "color-space" : "srgb", "components" : { - "alpha" : "0.900", - "blue" : "1.000", - "green" : "1.000", + "alpha" : "1.000", + "blue" : "0.000", + "green" : "0.000", "red" : "1.000" } }, @@ -22,10 +22,10 @@ "color" : { "color-space" : "srgb", "components" : { - "alpha" : "0.900", - "blue" : "1.000", - "green" : "1.000", - "red" : "1.000" + "alpha" : "1.000", + "blue" : "0x40", + "green" : "0x40", + "red" : "0xE1" } }, "idiom" : "universal" diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Images/dottedLine.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/Images/dottedLine.imageset/Contents.json new file mode 100644 index 00000000..238af4ef --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/Images/dottedLine.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "dottedLine.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "dottedLine-1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "dottedLine-2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Images/dottedLine.imageset/dottedLine-1.png b/Doolda/Doolda/Resources/Assets.xcassets/Images/dottedLine.imageset/dottedLine-1.png new file mode 100644 index 00000000..ed33a5cd Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/Images/dottedLine.imageset/dottedLine-1.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Images/dottedLine.imageset/dottedLine-2.png b/Doolda/Doolda/Resources/Assets.xcassets/Images/dottedLine.imageset/dottedLine-2.png new file mode 100644 index 00000000..ed33a5cd Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/Images/dottedLine.imageset/dottedLine-2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Images/dottedLine.imageset/dottedLine.png b/Doolda/Doolda/Resources/Assets.xcassets/Images/dottedLine.imageset/dottedLine.png new file mode 100644 index 00000000..ed33a5cd Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/Images/dottedLine.imageset/dottedLine.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehog.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehog.imageset/Contents.json index 19477088..82beadee 100644 --- a/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehog.imageset/Contents.json +++ b/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehog.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "hedgehog-1.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "hedgehog-2.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehog.imageset/hedgehog-1.png b/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehog.imageset/hedgehog-1.png new file mode 100644 index 00000000..f66c2e51 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehog.imageset/hedgehog-1.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehog.imageset/hedgehog-2.png b/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehog.imageset/hedgehog-2.png new file mode 100644 index 00000000..f66c2e51 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehog.imageset/hedgehog-2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogFrustrated.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogFrustrated.imageset/Contents.json new file mode 100644 index 00000000..1dec15f0 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogFrustrated.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "hedgehogFrustrated.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "hedgehogFrustrated-1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "hedgehogFrustrated-2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogFrustrated.imageset/hedgehogFrustrated-1.png b/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogFrustrated.imageset/hedgehogFrustrated-1.png new file mode 100644 index 00000000..06c41c8e Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogFrustrated.imageset/hedgehogFrustrated-1.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogFrustrated.imageset/hedgehogFrustrated-2.png b/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogFrustrated.imageset/hedgehogFrustrated-2.png new file mode 100644 index 00000000..06c41c8e Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogFrustrated.imageset/hedgehogFrustrated-2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogFrustrated.imageset/hedgehogFrustrated.png b/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogFrustrated.imageset/hedgehogFrustrated.png new file mode 100644 index 00000000..06c41c8e Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogFrustrated.imageset/hedgehogFrustrated.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogWriting.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogWriting.imageset/Contents.json index 2c9492e5..1c9e5666 100644 --- a/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogWriting.imageset/Contents.json +++ b/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogWriting.imageset/Contents.json @@ -6,10 +6,12 @@ "scale" : "1x" }, { + "filename" : "hedgehogWriting-1.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "hedgehogWriting-2.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogWriting.imageset/hedgehogWriting-1.png b/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogWriting.imageset/hedgehogWriting-1.png new file mode 100644 index 00000000..b4f3935e Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogWriting.imageset/hedgehogWriting-1.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogWriting.imageset/hedgehogWriting-2.png b/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogWriting.imageset/hedgehogWriting-2.png new file mode 100644 index 00000000..b4f3935e Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/Images/hedgehogWriting.imageset/hedgehogWriting-2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Images/scissors.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/Images/scissors.imageset/Contents.json index a999967d..9e642c9a 100644 --- a/Doolda/Doolda/Resources/Assets.xcassets/Images/scissors.imageset/Contents.json +++ b/Doolda/Doolda/Resources/Assets.xcassets/Images/scissors.imageset/Contents.json @@ -6,12 +6,12 @@ "scale" : "1x" }, { - "filename" : "scissors-1.png", + "filename" : "scissors@2.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "scissors-2.png", + "filename" : "scissors@3.png", "idiom" : "universal", "scale" : "3x" } diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Images/scissors.imageset/scissors-1.png b/Doolda/Doolda/Resources/Assets.xcassets/Images/scissors.imageset/scissors-1.png deleted file mode 100644 index 3d11c957..00000000 Binary files a/Doolda/Doolda/Resources/Assets.xcassets/Images/scissors.imageset/scissors-1.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Images/scissors.imageset/scissors-2.png b/Doolda/Doolda/Resources/Assets.xcassets/Images/scissors.imageset/scissors-2.png deleted file mode 100644 index 3d11c957..00000000 Binary files a/Doolda/Doolda/Resources/Assets.xcassets/Images/scissors.imageset/scissors-2.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Images/scissors.imageset/scissors.png b/Doolda/Doolda/Resources/Assets.xcassets/Images/scissors.imageset/scissors.png index 3d11c957..749fc556 100644 Binary files a/Doolda/Doolda/Resources/Assets.xcassets/Images/scissors.imageset/scissors.png and b/Doolda/Doolda/Resources/Assets.xcassets/Images/scissors.imageset/scissors.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Images/scissors.imageset/scissors@2.png b/Doolda/Doolda/Resources/Assets.xcassets/Images/scissors.imageset/scissors@2.png new file mode 100644 index 00000000..39c366ce Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/Images/scissors.imageset/scissors@2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/Images/scissors.imageset/scissors@3.png b/Doolda/Doolda/Resources/Assets.xcassets/Images/scissors.imageset/scissors@3.png new file mode 100644 index 00000000..21f94574 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/Images/scissors.imageset/scissors@3.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_0.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_0.imageset/Contents.json new file mode 100644 index 00000000..cd91aacc --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_0.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "boolbada_0.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_0.imageset/boolbada_0.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_0.imageset/boolbada_0.png new file mode 100644 index 00000000..156105d4 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_0.imageset/boolbada_0.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_1.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_1.imageset/Contents.json new file mode 100644 index 00000000..abe884d4 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "boolbada_1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/StickerPacks/Buddy/buddySticker_0.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_1.imageset/boolbada_1.png similarity index 100% rename from Doolda/Doolda/Resources/StickerPacks/Buddy/buddySticker_0.png rename to Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_1.imageset/boolbada_1.png diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_2.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_2.imageset/Contents.json new file mode 100644 index 00000000..75d5886a --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "boolbada_2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_2.imageset/boolbada_2.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_2.imageset/boolbada_2.png new file mode 100644 index 00000000..050a3668 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_2.imageset/boolbada_2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_3.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_3.imageset/Contents.json new file mode 100644 index 00000000..13553e9b --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "boolbada_3.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_3.imageset/boolbada_3.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_3.imageset/boolbada_3.png new file mode 100644 index 00000000..5da521fa Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_3.imageset/boolbada_3.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_4.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_4.imageset/Contents.json new file mode 100644 index 00000000..e307df05 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_4.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "boolbada_4.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_4.imageset/boolbada_4.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_4.imageset/boolbada_4.png new file mode 100644 index 00000000..9aa73b7f Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_4.imageset/boolbada_4.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_5.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_5.imageset/Contents.json new file mode 100644 index 00000000..8a1abb73 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_5.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "boolbada_5.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_5.imageset/boolbada_5.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_5.imageset/boolbada_5.png new file mode 100644 index 00000000..c6e5fb3e Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_5.imageset/boolbada_5.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_6.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_6.imageset/Contents.json new file mode 100644 index 00000000..ab2078db --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_6.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "boolbada_6.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_6.imageset/boolbada_6.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_6.imageset/boolbada_6.png new file mode 100644 index 00000000..4c5d5074 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_6.imageset/boolbada_6.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_7.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_7.imageset/Contents.json new file mode 100644 index 00000000..461fb7a7 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_7.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "boolbada_7.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_7.imageset/boolbada_7.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_7.imageset/boolbada_7.png new file mode 100644 index 00000000..d12f1178 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_7.imageset/boolbada_7.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_8.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_8.imageset/Contents.json new file mode 100644 index 00000000..83dfb27c --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_8.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "buddy_2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "buddy_2-1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "buddy_2-2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_8.imageset/buddy_2-1.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_8.imageset/buddy_2-1.png new file mode 100644 index 00000000..28120c94 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_8.imageset/buddy_2-1.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_8.imageset/buddy_2-2.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_8.imageset/buddy_2-2.png new file mode 100644 index 00000000..28120c94 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_8.imageset/buddy_2-2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_8.imageset/buddy_2.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_8.imageset/buddy_2.png new file mode 100644 index 00000000..28120c94 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_8.imageset/buddy_2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_9.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_9.imageset/Contents.json new file mode 100644 index 00000000..35515fba --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_9.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "buddy_3.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "buddy_3-1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "buddy_3-2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_9.imageset/buddy_3-1.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_9.imageset/buddy_3-1.png new file mode 100644 index 00000000..35be2118 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_9.imageset/buddy_3-1.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_9.imageset/buddy_3-2.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_9.imageset/buddy_3-2.png new file mode 100644 index 00000000..35be2118 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_9.imageset/buddy_3-2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_9.imageset/buddy_3.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_9.imageset/buddy_3.png new file mode 100644 index 00000000..35be2118 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_9.imageset/buddy_3.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_cover.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_cover.imageset/Contents.json new file mode 100644 index 00000000..d5192a71 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_cover.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "boolbada_cover.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_cover.imageset/boolbada_cover.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_cover.imageset/boolbada_cover.png new file mode 100644 index 00000000..029558b4 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Boolbada/boolbada_cover.imageset/boolbada_cover.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_0.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_0.imageset/Contents.json new file mode 100644 index 00000000..505936d8 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_0.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "withBuddy_0.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "withBuddy_0-1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "withBuddy_0-2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_0.imageset/withBuddy_0-1.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_0.imageset/withBuddy_0-1.png new file mode 100644 index 00000000..2af8e31f Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_0.imageset/withBuddy_0-1.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_0.imageset/withBuddy_0-2.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_0.imageset/withBuddy_0-2.png new file mode 100644 index 00000000..2af8e31f Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_0.imageset/withBuddy_0-2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_0.imageset/withBuddy_0.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_0.imageset/withBuddy_0.png new file mode 100644 index 00000000..2af8e31f Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_0.imageset/withBuddy_0.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_1.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_1.imageset/Contents.json new file mode 100644 index 00000000..8f0484c9 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_1.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Purple11.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Purple11-1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Purple11-2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_1.imageset/Purple11-1.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_1.imageset/Purple11-1.png new file mode 100644 index 00000000..62ddb42f Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_1.imageset/Purple11-1.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_1.imageset/Purple11-2.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_1.imageset/Purple11-2.png new file mode 100644 index 00000000..62ddb42f Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_1.imageset/Purple11-2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_1.imageset/Purple11.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_1.imageset/Purple11.png new file mode 100644 index 00000000..62ddb42f Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_1.imageset/Purple11.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_10.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_10.imageset/Contents.json new file mode 100644 index 00000000..ec6166b9 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_10.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Green9.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Green9-1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Green9-2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_10.imageset/Green9-1.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_10.imageset/Green9-1.png new file mode 100644 index 00000000..81d4d257 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_10.imageset/Green9-1.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_10.imageset/Green9-2.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_10.imageset/Green9-2.png new file mode 100644 index 00000000..81d4d257 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_10.imageset/Green9-2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_10.imageset/Green9.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_10.imageset/Green9.png new file mode 100644 index 00000000..81d4d257 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_10.imageset/Green9.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_11.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_11.imageset/Contents.json new file mode 100644 index 00000000..9cfba885 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_11.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Green10.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Green10-1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Green10-2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_11.imageset/Green10-1.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_11.imageset/Green10-1.png new file mode 100644 index 00000000..38aab7f8 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_11.imageset/Green10-1.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_11.imageset/Green10-2.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_11.imageset/Green10-2.png new file mode 100644 index 00000000..38aab7f8 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_11.imageset/Green10-2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_11.imageset/Green10.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_11.imageset/Green10.png new file mode 100644 index 00000000..38aab7f8 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_11.imageset/Green10.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_2.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_2.imageset/Contents.json new file mode 100644 index 00000000..bd8e5188 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_2.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Pink2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Pink2-1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Pink2-2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_2.imageset/Pink2-1.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_2.imageset/Pink2-1.png new file mode 100644 index 00000000..d7446552 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_2.imageset/Pink2-1.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_2.imageset/Pink2-2.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_2.imageset/Pink2-2.png new file mode 100644 index 00000000..d7446552 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_2.imageset/Pink2-2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_2.imageset/Pink2.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_2.imageset/Pink2.png new file mode 100644 index 00000000..d7446552 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_2.imageset/Pink2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_3.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_3.imageset/Contents.json new file mode 100644 index 00000000..681457a5 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_3.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Pink12.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Pink12-1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Pink12-2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_3.imageset/Pink12-1.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_3.imageset/Pink12-1.png new file mode 100644 index 00000000..e4f4b010 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_3.imageset/Pink12-1.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_3.imageset/Pink12-2.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_3.imageset/Pink12-2.png new file mode 100644 index 00000000..e4f4b010 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_3.imageset/Pink12-2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_3.imageset/Pink12.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_3.imageset/Pink12.png new file mode 100644 index 00000000..e4f4b010 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_3.imageset/Pink12.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_4.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_4.imageset/Contents.json new file mode 100644 index 00000000..5e699a68 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_4.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Yellow4.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Yellow4-1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Yellow4-2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_4.imageset/Yellow4-1.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_4.imageset/Yellow4-1.png new file mode 100644 index 00000000..be9d12e2 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_4.imageset/Yellow4-1.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_4.imageset/Yellow4-2.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_4.imageset/Yellow4-2.png new file mode 100644 index 00000000..be9d12e2 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_4.imageset/Yellow4-2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_4.imageset/Yellow4.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_4.imageset/Yellow4.png new file mode 100644 index 00000000..be9d12e2 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_4.imageset/Yellow4.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_5.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_5.imageset/Contents.json new file mode 100644 index 00000000..8d1d48fc --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_5.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Yellow14.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Yellow14-1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Yellow14-2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_5.imageset/Yellow14-1.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_5.imageset/Yellow14-1.png new file mode 100644 index 00000000..616ee395 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_5.imageset/Yellow14-1.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_5.imageset/Yellow14-2.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_5.imageset/Yellow14-2.png new file mode 100644 index 00000000..616ee395 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_5.imageset/Yellow14-2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_5.imageset/Yellow14.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_5.imageset/Yellow14.png new file mode 100644 index 00000000..616ee395 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_5.imageset/Yellow14.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_6.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_6.imageset/Contents.json new file mode 100644 index 00000000..c662e174 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_6.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "withBuddy_3.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "withBuddy_3-1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "withBuddy_3-2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_6.imageset/withBuddy_3-1.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_6.imageset/withBuddy_3-1.png new file mode 100644 index 00000000..58da291c Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_6.imageset/withBuddy_3-1.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_6.imageset/withBuddy_3-2.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_6.imageset/withBuddy_3-2.png new file mode 100644 index 00000000..58da291c Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_6.imageset/withBuddy_3-2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_6.imageset/withBuddy_3.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_6.imageset/withBuddy_3.png new file mode 100644 index 00000000..58da291c Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_6.imageset/withBuddy_3.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_7.imageset/Blue5-1.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_7.imageset/Blue5-1.png new file mode 100644 index 00000000..68d1f7d1 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_7.imageset/Blue5-1.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_7.imageset/Blue5-2.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_7.imageset/Blue5-2.png new file mode 100644 index 00000000..68d1f7d1 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_7.imageset/Blue5-2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_7.imageset/Blue5.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_7.imageset/Blue5.png new file mode 100644 index 00000000..68d1f7d1 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_7.imageset/Blue5.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_7.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_7.imageset/Contents.json new file mode 100644 index 00000000..562f4220 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_7.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Blue5.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Blue5-1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Blue5-2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_8.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_8.imageset/Contents.json new file mode 100644 index 00000000..4bd4ccde --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_8.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "withBuddy_4.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "withBuddy_4-1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "withBuddy_4-2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_8.imageset/withBuddy_4-1.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_8.imageset/withBuddy_4-1.png new file mode 100644 index 00000000..16a70cf0 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_8.imageset/withBuddy_4-1.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_8.imageset/withBuddy_4-2.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_8.imageset/withBuddy_4-2.png new file mode 100644 index 00000000..16a70cf0 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_8.imageset/withBuddy_4-2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_8.imageset/withBuddy_4.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_8.imageset/withBuddy_4.png new file mode 100644 index 00000000..16a70cf0 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_8.imageset/withBuddy_4.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_9.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_9.imageset/Contents.json new file mode 100644 index 00000000..03709785 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_9.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Red15.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Red15-1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Red15-2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_9.imageset/Red15-1.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_9.imageset/Red15-1.png new file mode 100644 index 00000000..aebba88a Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_9.imageset/Red15-1.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_9.imageset/Red15-2.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_9.imageset/Red15-2.png new file mode 100644 index 00000000..aebba88a Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_9.imageset/Red15-2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_9.imageset/Red15.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_9.imageset/Red15.png new file mode 100644 index 00000000..aebba88a Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_9.imageset/Red15.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_cover.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_cover.imageset/Contents.json new file mode 100644 index 00000000..126ed9c8 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_cover.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "withBuddy_cover.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_cover.imageset/withBuddy_cover.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_cover.imageset/withBuddy_cover.png new file mode 100644 index 00000000..20a63f7f Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Buddy/withBuddy_cover.imageset/withBuddy_cover.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_0.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_0.imageset/Contents.json new file mode 100644 index 00000000..3d0381ff --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_0.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "htmlCoder_0.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_0.imageset/htmlCoder_0.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_0.imageset/htmlCoder_0.png new file mode 100644 index 00000000..d1f8dc86 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_0.imageset/htmlCoder_0.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_1.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_1.imageset/Contents.json new file mode 100644 index 00000000..c05c4dd1 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_1.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "htmlCoder_1.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_1.imageset/htmlCoder_1.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_1.imageset/htmlCoder_1.png new file mode 100644 index 00000000..a60e7fd5 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_1.imageset/htmlCoder_1.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_10.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_10.imageset/Contents.json new file mode 100644 index 00000000..f0fa874f --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_10.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "htmlCoder_10.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_10.imageset/htmlCoder_10.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_10.imageset/htmlCoder_10.png new file mode 100644 index 00000000..22a07b47 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_10.imageset/htmlCoder_10.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_2.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_2.imageset/Contents.json new file mode 100644 index 00000000..4e085267 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "htmlCoder_2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_2.imageset/htmlCoder_2.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_2.imageset/htmlCoder_2.png new file mode 100644 index 00000000..250895f2 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_2.imageset/htmlCoder_2.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_3.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_3.imageset/Contents.json new file mode 100644 index 00000000..8886c5ad --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_3.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "htmlCoder_3.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_3.imageset/htmlCoder_3.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_3.imageset/htmlCoder_3.png new file mode 100644 index 00000000..04853132 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_3.imageset/htmlCoder_3.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_4.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_4.imageset/Contents.json new file mode 100644 index 00000000..60b302f4 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_4.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "htmlCoder_4.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_4.imageset/htmlCoder_4.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_4.imageset/htmlCoder_4.png new file mode 100644 index 00000000..dab3d4e4 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_4.imageset/htmlCoder_4.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_5.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_5.imageset/Contents.json new file mode 100644 index 00000000..e8b98b87 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_5.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "htmlCoder_5.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_5.imageset/htmlCoder_5.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_5.imageset/htmlCoder_5.png new file mode 100644 index 00000000..74e3dd44 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_5.imageset/htmlCoder_5.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_6.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_6.imageset/Contents.json new file mode 100644 index 00000000..a879dffa --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_6.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "htmlCoder_6.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_6.imageset/htmlCoder_6.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_6.imageset/htmlCoder_6.png new file mode 100644 index 00000000..892e351a Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_6.imageset/htmlCoder_6.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_7.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_7.imageset/Contents.json new file mode 100644 index 00000000..e1c6d88b --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_7.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "htmlCoder_7.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_7.imageset/htmlCoder_7.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_7.imageset/htmlCoder_7.png new file mode 100644 index 00000000..6f401a99 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_7.imageset/htmlCoder_7.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_8.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_8.imageset/Contents.json new file mode 100644 index 00000000..9329e016 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_8.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "htmlCoder_8.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_8.imageset/htmlCoder_8.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_8.imageset/htmlCoder_8.png new file mode 100644 index 00000000..2581ae3b Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_8.imageset/htmlCoder_8.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_9.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_9.imageset/Contents.json new file mode 100644 index 00000000..b7dad3a8 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_9.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "htmlCoder_9.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_9.imageset/htmlCoder_9.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_9.imageset/htmlCoder_9.png new file mode 100644 index 00000000..6ce677da Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_9.imageset/htmlCoder_9.png differ diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_cover.imageset/Contents.json b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_cover.imageset/Contents.json new file mode 100644 index 00000000..ac9c96f9 --- /dev/null +++ b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_cover.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "htmlCoder_cover.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_cover.imageset/htmlCoder_cover.png b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_cover.imageset/htmlCoder_cover.png new file mode 100644 index 00000000..78fe0617 Binary files /dev/null and b/Doolda/Doolda/Resources/Assets.xcassets/StickerPacks/HtmlCoder/htmlCoder_cover.imageset/htmlCoder_cover.png differ diff --git a/Doolda/Doolda/Resources/Config.xcconfig b/Doolda/Doolda/Resources/Config.xcconfig new file mode 100644 index 00000000..cbf95e1c --- /dev/null +++ b/Doolda/Doolda/Resources/Config.xcconfig @@ -0,0 +1,11 @@ +// +// Config.xcconfig +// Doolda +// +// Created by Seunghun Yang on 2021/11/22. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 + +FCM_SERVER_KEY = AAAABi_vS2s:APA91bGcfkGUsRD4r7ZgONyU987ZQKw5ifuzDIUdk35ohKKN1rROyK8zv_HG899-LZzIKePAtzYukrbYCj8rL_prOlGuqz7J07DJPaUsCTigaLe0yHClseGzaAuSiP-o6gyrmvQwSuNz diff --git a/Doolda/Doolda/Resources/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents b/Doolda/Doolda/Resources/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents index d08ba395..6c188b91 100644 --- a/Doolda/Doolda/Resources/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents +++ b/Doolda/Doolda/Resources/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents @@ -1,12 +1,14 @@ + + - + - + \ No newline at end of file diff --git a/Doolda/Doolda/Resources/Fonts/darae.ttf b/Doolda/Doolda/Resources/Fonts/darae.ttf new file mode 100644 index 00000000..83d303a8 Binary files /dev/null and b/Doolda/Doolda/Resources/Fonts/darae.ttf differ diff --git a/Doolda/Doolda/Resources/Fonts/dungGeunMo.otf b/Doolda/Doolda/Resources/Fonts/dungGeunMo.otf new file mode 100644 index 00000000..9112c3ea Binary files /dev/null and b/Doolda/Doolda/Resources/Fonts/dungGeunMo.otf differ diff --git a/Doolda/Doolda/Resources/Fonts/kotraHope.otf b/Doolda/Doolda/Resources/Fonts/kotraHope.otf new file mode 100644 index 00000000..00943487 Binary files /dev/null and b/Doolda/Doolda/Resources/Fonts/kotraHope.otf differ diff --git a/Doolda/Doolda/Resources/Fonts/kyoboHandwriting.otf b/Doolda/Doolda/Resources/Fonts/kyoboHandwriting.otf new file mode 100644 index 00000000..362fe59d Binary files /dev/null and b/Doolda/Doolda/Resources/Fonts/kyoboHandwriting.otf differ diff --git a/Doolda/Doolda/Resources/Fonts/uhBeeMysen.ttf b/Doolda/Doolda/Resources/Fonts/uhBeeMysen.ttf new file mode 100644 index 00000000..3dbef5d9 Binary files /dev/null and b/Doolda/Doolda/Resources/Fonts/uhBeeMysen.ttf differ diff --git a/Doolda/Doolda/Resources/GoogleService-Info.plist b/Doolda/Doolda/Resources/GoogleService-Info.plist new file mode 100644 index 00000000..eb793a33 --- /dev/null +++ b/Doolda/Doolda/Resources/GoogleService-Info.plist @@ -0,0 +1,34 @@ + + + + + CLIENT_ID + 26574015339-qpv7i4b654fmljeaplvc85lv3bh5aglm.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.26574015339-qpv7i4b654fmljeaplvc85lv3bh5aglm + API_KEY + AIzaSyDhAXUu_EQ_uijre31lbByLjHY-iS_WirI + GCM_SENDER_ID + 26574015339 + PLIST_VERSION + 1 + BUNDLE_ID + kr.codesquad.doolda + PROJECT_ID + doolda + STORAGE_BUCKET + doolda.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:26574015339:ios:9701b4704d0e6634071a2e + + \ No newline at end of file diff --git a/Doolda/Doolda/Resources/Info.plist b/Doolda/Doolda/Resources/Info.plist index f9d6e0f0..9852b634 100644 --- a/Doolda/Doolda/Resources/Info.plist +++ b/Doolda/Doolda/Resources/Info.plist @@ -2,11 +2,20 @@ - NSPhotoLibraryUsageDescription - + UISupportedInterfaceOrientations~iphone + + UIInterfaceOrientationPortrait + + FCM_SERVER_KEY + $(FCM_SERVER_KEY) UIAppFonts dovemayo.otf + darae.ttf + dungGeunMo.otf + kotraHope.otf + kyoboHandwriting.otf + uhBeeMysen.ttf UIApplicationSceneManifest @@ -25,5 +34,9 @@ + UIBackgroundModes + + remote-notification + diff --git a/Doolda/Doolda/Resources/PhotoFrames/LifeFourCuts.jpg b/Doolda/Doolda/Resources/PhotoFrames/lifeFourCuts.jpg similarity index 100% rename from Doolda/Doolda/Resources/PhotoFrames/LifeFourCuts.jpg rename to Doolda/Doolda/Resources/PhotoFrames/lifeFourCuts.jpg diff --git a/Doolda/Doolda/Resources/PhotoFrames/Normal.jpg b/Doolda/Doolda/Resources/PhotoFrames/normal.jpg similarity index 100% rename from Doolda/Doolda/Resources/PhotoFrames/Normal.jpg rename to Doolda/Doolda/Resources/PhotoFrames/normal.jpg diff --git a/Doolda/Doolda/Resources/PhotoFrames/Polaroid.jpg b/Doolda/Doolda/Resources/PhotoFrames/polaroid.jpg similarity index 100% rename from Doolda/Doolda/Resources/PhotoFrames/Polaroid.jpg rename to Doolda/Doolda/Resources/PhotoFrames/polaroid.jpg diff --git a/Doolda/Doolda/Resources/PhotoFrames/polaroid_long.jpg b/Doolda/Doolda/Resources/PhotoFrames/polaroid_long.jpg new file mode 100644 index 00000000..17b5e101 Binary files /dev/null and b/Doolda/Doolda/Resources/PhotoFrames/polaroid_long.jpg differ diff --git a/Doolda/Doolda/Resources/StickerPacks/Boolbada/boolbadaSticker_0.png b/Doolda/Doolda/Resources/StickerPacks/Boolbada/boolbadaSticker_0.png deleted file mode 100644 index 4c9d7572..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/Boolbada/boolbadaSticker_0.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/Boolbada/boolbadaSticker_1.png b/Doolda/Doolda/Resources/StickerPacks/Boolbada/boolbadaSticker_1.png deleted file mode 100644 index 845bd4cd..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/Boolbada/boolbadaSticker_1.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/Boolbada/boolbadaSticker_2.png b/Doolda/Doolda/Resources/StickerPacks/Boolbada/boolbadaSticker_2.png deleted file mode 100644 index 5993ef2a..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/Boolbada/boolbadaSticker_2.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/Boolbada/boolbadaSticker_3.png b/Doolda/Doolda/Resources/StickerPacks/Boolbada/boolbadaSticker_3.png deleted file mode 100644 index 26156ab1..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/Boolbada/boolbadaSticker_3.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/Boolbada/boolbadaSticker_4.png b/Doolda/Doolda/Resources/StickerPacks/Boolbada/boolbadaSticker_4.png deleted file mode 100644 index 951da42e..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/Boolbada/boolbadaSticker_4.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/Boolbada/boolbadaSticker_cover.png b/Doolda/Doolda/Resources/StickerPacks/Boolbada/boolbadaSticker_cover.png deleted file mode 100644 index 75c09c5b..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/Boolbada/boolbadaSticker_cover.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/Buddy/buddySticker_1.png b/Doolda/Doolda/Resources/StickerPacks/Buddy/buddySticker_1.png deleted file mode 100644 index d1ac40f0..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/Buddy/buddySticker_1.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/Buddy/buddySticker_2.png b/Doolda/Doolda/Resources/StickerPacks/Buddy/buddySticker_2.png deleted file mode 100644 index b10a4bca..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/Buddy/buddySticker_2.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/Buddy/buddySticker_3.png b/Doolda/Doolda/Resources/StickerPacks/Buddy/buddySticker_3.png deleted file mode 100644 index 29bda533..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/Buddy/buddySticker_3.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/Buddy/buddySticker_4.png b/Doolda/Doolda/Resources/StickerPacks/Buddy/buddySticker_4.png deleted file mode 100644 index 59c0cdae..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/Buddy/buddySticker_4.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/Buddy/buddySticker_cover.png b/Doolda/Doolda/Resources/StickerPacks/Buddy/buddySticker_cover.png deleted file mode 100644 index c59e2e07..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/Buddy/buddySticker_cover.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_0.png b/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_0.png deleted file mode 100644 index 8be1eef0..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_0.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_1.png b/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_1.png deleted file mode 100644 index 3bd74df1..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_1.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_10.png b/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_10.png deleted file mode 100644 index e9594063..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_10.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_2.png b/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_2.png deleted file mode 100644 index 3c7bcd0b..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_2.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_3.png b/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_3.png deleted file mode 100644 index 24d21727..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_3.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_4.png b/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_4.png deleted file mode 100644 index f20b9e91..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_4.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_5.png b/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_5.png deleted file mode 100644 index fcbc490b..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_5.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_6.png b/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_6.png deleted file mode 100644 index 3f08b818..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_6.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_7.png b/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_7.png deleted file mode 100644 index e8f38e74..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_7.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_8.png b/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_8.png deleted file mode 100644 index 5d616666..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_8.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_9.png b/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_9.png deleted file mode 100644 index b2b9dc63..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_9.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_cover.png b/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_cover.png deleted file mode 100644 index ec5794d8..00000000 Binary files a/Doolda/Doolda/Resources/StickerPacks/HtmlCoder/htmlCoder_cover.png and /dev/null differ diff --git a/Doolda/Doolda/Resources/ko.lproj/InfoPlist.strings b/Doolda/Doolda/Resources/ko.lproj/InfoPlist.strings new file mode 100644 index 00000000..3a81cf74 --- /dev/null +++ b/Doolda/Doolda/Resources/ko.lproj/InfoPlist.strings @@ -0,0 +1,9 @@ +/* + InfoPlist.strings + Doolda + + Created by Seunghun Yang on 2021/12/02. + +*/ + +"CFBundleDisplayName"="둘다"; diff --git a/Doolda/Doolda/Resources/ko.lproj/LaunchScreen.strings b/Doolda/Doolda/Resources/ko.lproj/LaunchScreen.strings new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/Doolda/Doolda/Resources/ko.lproj/LaunchScreen.strings @@ -0,0 +1 @@ + diff --git a/Doolda/Doolda/Resources/license.txt b/Doolda/Doolda/Resources/license.txt new file mode 100644 index 00000000..323938e5 --- /dev/null +++ b/Doolda/Doolda/Resources/license.txt @@ -0,0 +1,91 @@ +SnapKit + +Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +===== + +Kingfisher + +The MIT License (MIT) + +Copyright (c) 2019 Wei Wang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== + +SwiftLint + +The MIT License (MIT) + +Copyright (c) 2020 Realm Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +===== + +글꼴 라이센스 + +이 앱에선 둘기마요에서 제공한 [둘기마요체], 김다래에서 제공한 [다래 손글씨체], Kotra에서 제공한 [코트라 희망체], 어비에서 제공한 [어비 마이센체], Dalgona에서 제공한 [neo둥근모], 교보문고에서 제공한 [교보 손글씨 2019]가 사용되어 있습니다. + +[다래 손글씨체] +https://blog.naver.com/drstyle/30158806258 + +[neo둥근모] +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 + +[교보 손글씨 2019] +Copyright KYOBO LIFE INSURANCE CO., LTD. All Rights Reserved. diff --git a/Doolda/Doolda/Service/CoreData/CoreDataPageEntity+CoreDataProperties.swift b/Doolda/Doolda/Service/CoreData/CoreDataPageEntity+CoreDataProperties.swift index 2c691f90..617843d1 100644 --- a/Doolda/Doolda/Service/CoreData/CoreDataPageEntity+CoreDataProperties.swift +++ b/Doolda/Doolda/Service/CoreData/CoreDataPageEntity+CoreDataProperties.swift @@ -17,22 +17,32 @@ extension CoreDataPageEntity { @NSManaged public var id: String? @NSManaged public var pairId: String? @NSManaged public var jsonPath: String? - @NSManaged public var timeStamp: Date? + @NSManaged public var createdTime: Date? + @NSManaged public var updatedTime: Date? + @NSManaged public var isUpToDate: Bool func toPageEntity() -> PageEntity? { guard let id = DDID(from: id ?? ""), let pairId = DDID(from: pairId ?? ""), - let timeStamp = timeStamp, + let createdTime = createdTime, + let updatedTime = updatedTime, let jsonPath = jsonPath else { return nil } - return PageEntity(author: User(id: id, pairId: pairId), timeStamp: timeStamp, jsonPath: jsonPath) + return PageEntity( + author: User(id: id, pairId: pairId), + createdTime: createdTime, + updatedTime: updatedTime, + jsonPath: jsonPath + ) } func update(_ pageEntity: PageEntity) { self.id = pageEntity.author.id.ddidString self.pairId = pageEntity.author.pairId?.ddidString self.jsonPath = pageEntity.jsonPath - self.timeStamp = pageEntity.timeStamp + self.createdTime = pageEntity.createdTime + self.isUpToDate = self.updatedTime == pageEntity.updatedTime + self.updatedTime = pageEntity.updatedTime } } diff --git a/Doolda/Doolda/Service/CoreData/CoreDataPageEntityPersistenceService.swift b/Doolda/Doolda/Service/CoreData/CoreDataPageEntityPersistenceService.swift index e4875d52..bd8e614f 100644 --- a/Doolda/Doolda/Service/CoreData/CoreDataPageEntityPersistenceService.swift +++ b/Doolda/Doolda/Service/CoreData/CoreDataPageEntityPersistenceService.swift @@ -20,13 +20,40 @@ enum CoreDataPageEntityPersistenceServiceError: LocalizedError { } } -class CoreDataPageEntityPersistenceService: CoreDataPageEntityPersistenceServiceProtocol { +final class CoreDataPageEntityPersistenceService: CoreDataPageEntityPersistenceServiceProtocol { private let coreDataPersistenceService: CoreDataPersistenceServiceProtocol init(coreDataPersistenceService: CoreDataPersistenceServiceProtocol) { self.coreDataPersistenceService = coreDataPersistenceService } + func isPageEntityUpToDate(_ pageEntity: PageEntity) -> AnyPublisher { + guard let context = coreDataPersistenceService.backgroundContext else { + return Fail(error: CoreDataPageEntityPersistenceServiceError.failedToInitializeCoreDataContainer).eraseToAnyPublisher() + } + + return Future { promise in + context.perform { + do { + let fetchRequest = CoreDataPageEntity.fetchRequest() + fetchRequest.predicate = NSPredicate( + format: "%K == %@", + #keyPath(CoreDataPageEntity.createdTime), + pageEntity.createdTime as NSDate + ) + + guard let fetchResult = try context.fetch(fetchRequest).first else { + return promise(.failure(RawPageRepositoryError.failedToFetchRawPage)) + } + promise(.success(fetchResult.isUpToDate)) + } catch { + promise(.failure(error)) + } + } + } + .eraseToAnyPublisher() + } + func fetchPageEntities() -> AnyPublisher<[PageEntity], Error> { guard let context = coreDataPersistenceService.backgroundContext else { return Fail(error: CoreDataPageEntityPersistenceServiceError.failedToInitializeCoreDataContainer).eraseToAnyPublisher() @@ -36,7 +63,7 @@ class CoreDataPageEntityPersistenceService: CoreDataPageEntityPersistenceService context.perform { do { let fetchRequest = CoreDataPageEntity.fetchRequest() - fetchRequest.sortDescriptors = [NSSortDescriptor(key: "timeStamp", ascending: false)] + fetchRequest.sortDescriptors = [NSSortDescriptor(key: "createdTime", ascending: false)] let fetchResult = try context.fetch(fetchRequest) let pageEntities = fetchResult.compactMap { $0.toPageEntity() } @@ -49,19 +76,35 @@ class CoreDataPageEntityPersistenceService: CoreDataPageEntityPersistenceService .eraseToAnyPublisher() } - func savePageEntity(_ pageEntity: PageEntity) -> AnyPublisher { - guard let context = coreDataPersistenceService.backgroundContext else { + func savePageEntity(_ pageEntity: PageEntity) -> AnyPublisher { + guard let context = coreDataPersistenceService.backgroundContext, + let coreDataPageEntity = NSEntityDescription.entity(forEntityName: CoreDataPageEntity.coreDataPageEntityName, in: context) else { return Fail(error: CoreDataPageEntityPersistenceServiceError.failedToInitializeCoreDataContainer).eraseToAnyPublisher() } return Future { promise in context.perform { - let entity = CoreDataPageEntity(context: context) - entity.update(pageEntity) - do { - try context.save() - promise(.success(())) + let fetchRequest = CoreDataPageEntity.fetchRequest() + fetchRequest.predicate = NSPredicate( + format: "%K == %@", + #keyPath(CoreDataPageEntity.createdTime), + pageEntity.createdTime as NSDate + ) + let fetchResult = try context.fetch(fetchRequest) + + if let cachedCoreDataPage = fetchResult.first, + cachedCoreDataPage.updatedTime != pageEntity.updatedTime { + cachedCoreDataPage.update(pageEntity) + } else if fetchResult.isEmpty, + let coreDataPage = NSManagedObject(entity: coreDataPageEntity, insertInto: context) as? CoreDataPageEntity { + coreDataPage.update(pageEntity) + } + + if context.hasChanges { + try context.save() + } + promise(.success(pageEntity)) } catch { promise(.failure(error)) } diff --git a/Doolda/Doolda/Service/CoreData/CoreDataPersistenceService.swift b/Doolda/Doolda/Service/CoreData/CoreDataPersistenceService.swift index 6e4ed1ee..1dc125df 100644 --- a/Doolda/Doolda/Service/CoreData/CoreDataPersistenceService.swift +++ b/Doolda/Doolda/Service/CoreData/CoreDataPersistenceService.swift @@ -26,29 +26,30 @@ enum CoreDataPersistenceServiceError: LocalizedError { final class CoreDataPersistenceService: CoreDataPersistenceServiceProtocol { static let coreDataModelName = "CoreDataModel" - private var isPersistentStoreLoaded = false - - private let persistentContainer: NSPersistentContainer - - init() { - self.persistentContainer = NSPersistentContainer(name: Self.coreDataModelName) - self.persistentContainer.loadPersistentStores { _, error in - guard error == nil else { return } - self.isPersistentStoreLoaded = true - } - } + static let shared = CoreDataPersistenceService() - var context: NSManagedObjectContext? { + lazy var context: NSManagedObjectContext? = { guard self.isPersistentStoreLoaded else { return nil } let context = self.persistentContainer.viewContext context.automaticallyMergesChangesFromParent = true return context - } + }() - var backgroundContext: NSManagedObjectContext? { + lazy var backgroundContext: NSManagedObjectContext? = { guard self.isPersistentStoreLoaded else { return nil } let context = self.persistentContainer.newBackgroundContext() context.automaticallyMergesChangesFromParent = true return context + }() + + private var isPersistentStoreLoaded = false + private var persistentContainer: NSPersistentContainer + + private init() { + self.persistentContainer = NSPersistentContainer(name: Self.coreDataModelName) + self.persistentContainer.loadPersistentStores { [weak self] _, error in + guard error == nil else { return } + self?.isPersistentStoreLoaded = true + } } } diff --git a/Doolda/Doolda/Service/Networks/URLSessionNetworkService.swift b/Doolda/Doolda/Service/Networks/URLSessionNetworkService.swift index 78aec008..4d062c48 100644 --- a/Doolda/Doolda/Service/Networks/URLSessionNetworkService.swift +++ b/Doolda/Doolda/Service/Networks/URLSessionNetworkService.swift @@ -9,7 +9,7 @@ import Combine import Foundation import Accelerate -class URLSessionNetworkService: URLSessionNetworkServiceProtocol { +final class URLSessionNetworkService: URLSessionNetworkServiceProtocol { enum Errors: LocalizedError { case invalidUrl @@ -21,15 +21,14 @@ class URLSessionNetworkService: URLSessionNetworkServiceProtocol { } } - private let session: URLSession - private let decoder: JSONDecoder + static let shared: URLSessionNetworkService = URLSessionNetworkService() + + private let session: URLSession = .shared + private let decoder: JSONDecoder = JSONDecoder() // MARK: - Initializers - init(session: URLSession = .shared, decoder: JSONDecoder = JSONDecoder()) { - self.session = session - self.decoder = decoder - } + private init() {} func request(_ urlRequest: URLRequestBuilder) -> AnyPublisher { guard let urlRequest = urlRequest.urlRequest else { return Fail(error: Errors.invalidUrl).eraseToAnyPublisher() } diff --git a/Doolda/Doolda/Service/Persistences/FileManagerPersistenceService.swift b/Doolda/Doolda/Service/Persistences/FileManagerPersistenceService.swift index f2bf8602..3771ad8f 100644 --- a/Doolda/Doolda/Service/Persistences/FileManagerPersistenceService.swift +++ b/Doolda/Doolda/Service/Persistences/FileManagerPersistenceService.swift @@ -8,6 +8,20 @@ import Combine import Foundation +enum FileDocuments { + typealias RawValue = URL? + + case temporary + case cache + + var rawValue: RawValue { + switch self { + case .temporary: return FileManager.default.temporaryDirectory + case .cache: return FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first + } + } +} + enum FileManagerPersistenceServiceError: LocalizedError { case failedToSaveFile case failedToFetchFile @@ -22,7 +36,12 @@ enum FileManagerPersistenceServiceError: LocalizedError { } } -class FileManagerPersistenceService: FileManagerPersistenceServiceProtocol { +final class FileManagerPersistenceService: FileManagerPersistenceServiceProtocol { + + static let shared: FileManagerPersistenceService = FileManagerPersistenceService() + + private init() {} + func save(data: Data, at documents: FileDocuments, fileName: String) -> AnyPublisher { guard let fileUrl = documents.rawValue?.appendingPathComponent(fileName) else { return Fail(error: FileManagerPersistenceServiceError.failedToSaveFile).eraseToAnyPublisher() diff --git a/Doolda/Doolda/Service/Persistences/UserDefaultsPersistenceService.swift b/Doolda/Doolda/Service/Persistences/UserDefaultsPersistenceService.swift index 7e7d01e3..db1e40b4 100644 --- a/Doolda/Doolda/Service/Persistences/UserDefaultsPersistenceService.swift +++ b/Doolda/Doolda/Service/Persistences/UserDefaultsPersistenceService.swift @@ -7,7 +7,12 @@ import Foundation -class UserDefaultsPersistenceService: UserDefaultsPersistenceServiceProtocol { +final class UserDefaultsPersistenceService: UserDefaultsPersistenceServiceProtocol { + + static let shared: UserDefaultsPersistenceService = UserDefaultsPersistenceService() + + private init() {} + func set(key: String, value: Any?) { UserDefaults.standard.set(value, forKey: key) } diff --git a/Doolda/Doolda/SettingsScene/FontPickerViewController.swift b/Doolda/Doolda/SettingsScene/FontPickerViewController.swift new file mode 100644 index 00000000..a6b94c34 --- /dev/null +++ b/Doolda/Doolda/SettingsScene/FontPickerViewController.swift @@ -0,0 +1,150 @@ +// +// FontPickerViewController.swift +// Doolda +// +// Created by Dozzing on 2021/11/23. +// + +import Combine +import UIKit + +import SnapKit + +protocol FontPickerViewControllerDelegate: AnyObject { + func fontDidSelect(_ font: FontType) +} + +class FontPickerViewController: BottomSheetViewController { + + // MARK: - Subviews + + private lazy var bottomSheetTitle: UILabel = { + let label = UILabel() + label.textColor = .dooldaLabel + label.text = "폰트 설정" + label.textAlignment = .center + return label + }() + + private lazy var fontPicker: UIPickerView = { + let fontPicker = UIPickerView() + fontPicker.backgroundColor = .clear + return fontPicker + }() + + private lazy var applyButton: UIButton = { + let button = DooldaButton() + button.setTitleColor(.dooldaLabel, for: .normal) + button.backgroundColor = .dooldaHighlighted + button.setTitle("적용", for: .normal) + return button + }() + + // MARK: - Private Properties + + private var cancellables: Set = [] + private weak var delegate: FontPickerViewControllerDelegate? + + // MARK: - Initializers + + convenience init(delegate: FontPickerViewControllerDelegate?) { + self.init(nibName: nil, bundle: nil) + self.delegate = delegate + } + + // MARK: - LifeCycle Methods + + override func viewDidLoad() { + super.viewDidLoad() + self.configureUI() + self.configureFont() + self.bindUI() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + } + + // MARK: - Helpers + + private func configureUI() { + self.fontPicker.delegate = self + self.fontPicker.dataSource = self + + self.body.backgroundColor = .dooldaBackground + + self.body.addSubview(self.bottomSheetTitle) + self.bottomSheetTitle.snp.makeConstraints { make in + make.top.equalTo(self.body).offset(16) + make.leading.equalTo(self.body).offset(16) + make.trailing.equalTo(self.body).offset(-16) + make.height.equalTo(20) + } + + self.body.addSubview(self.fontPicker) + self.fontPicker.snp.makeConstraints { make in + make.leading.trailing.equalTo(self.bottomSheetTitle) + make.top.equalTo(self.bottomSheetTitle.snp.bottom).offset(8) + make.height.equalTo(162) + } + + self.body.addSubview(self.applyButton) + self.applyButton.snp.makeConstraints { make in + make.leading.trailing.equalTo(self.bottomSheetTitle) + make.top.equalTo(self.fontPicker.snp.bottom).offset(8) + make.height.equalTo(44) + } + + self.detent = .custom(292) + } + + private func configureFont() { + self.bottomSheetTitle.font = .systemFont(ofSize: 16) + } + + private func bindUI() { + self.applyButton.publisher(for: .touchUpInside) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let index = self?.fontPicker.selectedRow(inComponent: 0), + let font = FontType.allCases[exist: index] else { return } + self?.delegate?.fontDidSelect(font) + self?.dismiss(animated: true, completion: nil) + } + .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: GlobalFontUseCase.Notifications.globalFontDidSet, object: nil) + .sink { [weak self] _ in + self?.configureFont() + } + .store(in: &self.cancellables) + } + +} + +extension FontPickerViewController: UIPickerViewDelegate { + func pickerView(_ pickerView: UIPickerView, rowHeightForComponent component: Int) -> CGFloat { + return 38 + } + + func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { + guard let fontName = FontType.allCases[exist: row]?.name, + let text = FontType.allCases[exist: row]?.displayName else { return UIView() } + + let pickerLabel = view as? UILabel ?? UILabel() + pickerLabel.text = text + pickerLabel.font = UIFont(name: fontName, size: 28) + pickerLabel.textAlignment = .center + return pickerLabel + } +} + +extension FontPickerViewController: UIPickerViewDataSource { + func numberOfComponents(in pickerView: UIPickerView) -> Int { + return 1 + } + + func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + return FontType.allCases.count + } +} diff --git a/Doolda/Doolda/SettingsScene/InformationViewController.swift b/Doolda/Doolda/SettingsScene/InformationViewController.swift new file mode 100644 index 00000000..714a192e --- /dev/null +++ b/Doolda/Doolda/SettingsScene/InformationViewController.swift @@ -0,0 +1,97 @@ +// +// InformationViewController.swift +// Doolda +// +// Created by Dozzing on 2021/11/22. +// + +import Combine +import UIKit + +import SnapKit + +class InformationViewController: UIViewController { + + // MARK: - Subviews + + private var textView: UITextView = { + let textView = UITextView() + textView.font = .systemFont(ofSize: 16) + textView.textColor = .dooldaLabel + textView.backgroundColor = .clear + textView.isScrollEnabled = true + textView.isEditable = false + return textView + }() + + private var imageView: UIImageView = { + let imageView = UIImageView() + return imageView + }() + + // MARK: - Public Properties + + @Published var titleText: String? + @Published var contentText: String? + @Published var image: UIImage? + + // MARK: - Private Properties + + private var cancellables: Set = [] + + // MARK: - Initializers + + convenience init() { + self.init(nibName: nil, bundle: nil) + self.bindUI() + } + + // MARK: - LifeCycle Methods + + override func viewDidLoad() { + super.viewDidLoad() + self.configureUI() + } + + // MARK: - Helpers + + private func configureUI() { + self.view.backgroundColor = .dooldaBackground + + self.view.addSubview(self.textView) + self.textView.snp.makeConstraints { make in + make.top.leading.equalTo(self.view.safeAreaLayoutGuide).offset(16) + make.bottom.trailing.equalTo(self.view.safeAreaLayoutGuide).offset(-16) + } + + self.view.addSubview(self.imageView) + self.imageView.snp.makeConstraints { make in + make.top.leading.equalTo(self.view.safeAreaLayoutGuide).offset(16) + make.bottom.trailing.equalTo(self.view.safeAreaLayoutGuide).offset(-16) + } + } + + private func bindUI() { + self.$titleText + .receive(on: DispatchQueue.main) + .sink { [weak self] title in + self?.navigationItem.title = title + } + .store(in: &self.cancellables) + + self.$contentText + .receive(on: DispatchQueue.main) + .sink { [weak self] contentText in + self?.textView.text = contentText + } + .store(in: &self.cancellables) + + self.$image + .receive(on: DispatchQueue.main) + .sink { [weak self] image in + self?.imageView.image = image + } + .store(in: &self.cancellables) + } + +} diff --git a/Doolda/Doolda/SettingsScene/SettingsTableViewCell.swift b/Doolda/Doolda/SettingsScene/SettingsTableViewCell.swift new file mode 100644 index 00000000..144b500a --- /dev/null +++ b/Doolda/Doolda/SettingsScene/SettingsTableViewCell.swift @@ -0,0 +1,164 @@ +// +// SettingsTableViewCell.swift +// Doolda +// +// Created by Dozzing on 2021/11/22. +// + +import Combine +import UIKit + +import SnapKit + +class SettingsTableViewCell: UITableViewCell { + + enum Style { + case detail, disclosure, switchControl + } + + // MARK: - Static Properties + + static let identifier = "SettingsTableViewCell" + + // MARK: - Subviews + + private lazy var titleLabel: UILabel = { + let label = UILabel() + label.textColor = .dooldaLabel + label.text = "title" + return label + }() + + private lazy var detailLabel: UILabel = { + let label = UILabel() + label.textColor = .dooldaLabel + return label + }() + + lazy var switchControl: UISwitch = { + let switchControl = UISwitch() + switchControl.onTintColor = .dooldaHighlighted + return switchControl + }() + + private lazy var stackView: UIStackView = { + let stackView = UIStackView( + arrangedSubviews: [ + self.titleLabel, + self.detailLabel, + self.switchControl + ] + ) + stackView.axis = .horizontal + return stackView + }() + + private lazy var separator: CALayer = { + let separator = CALayer() + separator.backgroundColor = UIColor.dooldaLabel?.withAlphaComponent(0.2).cgColor + return separator + }() + + // MARK: - Public Properties + + @Published var title: String? + @Published var detailText: String? + @Published var font: UIFont? + + // MARK: - Private Properties + + private var style: Style? + private var cancellables: Set = [] + + // MARK: - Initializers + + convenience init(style: Style) { + self.init() + self.style = style + self.configureUI() + self.configureFont() + self.bindUI() + } + + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + } + + // MARK: - LifeCycle Methods + + override func setNeedsLayout() { + super.setNeedsLayout() + separator.frame = CGRect(x: 16, y: self.frame.height - 1, width: self.frame.width-32, height: 1) + } + + // MARK: - Helpers + + private func configureUI() { + self.backgroundColor = .clear + + self.contentView.addSubview(self.stackView) + self.stackView.snp.makeConstraints { make in + make.top.equalToSuperview().offset(8) + make.bottom.equalToSuperview().offset(-8) + make.leading.equalToSuperview().offset(16) + make.trailing.equalToSuperview().offset(-16) + } + + self.layer.addSublayer(self.separator) + + switch self.style { + case .disclosure: + self.accessoryType = .disclosureIndicator + self.detailLabel.isHidden = true + self.switchControl.isHidden = true + case .detail: + self.detailLabel.isHidden = false + self.switchControl.isHidden = true + case .switchControl: + self.detailLabel.isHidden = true + self.switchControl.isHidden = false + default: return + } + } + + private func configureFont() { + let font = self.font ?? .systemFont(ofSize: 16) + + self.titleLabel.font = font + self.detailLabel.font = font + } + + private func bindUI() { + self.$title + .receive(on: DispatchQueue.main) + .sink { [weak self] title in + self?.titleLabel.text = title + } + .store(in: &self.cancellables) + + self.$detailText + .receive(on: DispatchQueue.main) + .sink { [weak self] detailText in + self?.detailLabel.text = detailText + self?.detailLabel.snp.updateConstraints { make in + make.width.equalTo(self?.detailLabel.intrinsicContentSize.width ?? 0) + } + } + .store(in: &self.cancellables) + + self.$font + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.configureFont() + self?.detailLabel.snp.updateConstraints { make in + make.width.equalTo(self?.detailLabel.intrinsicContentSize.width ?? 0) + } + } + .store(in: &self.cancellables) + } + +} diff --git a/Doolda/Doolda/SettingsScene/SettingsTableViewHeader.swift b/Doolda/Doolda/SettingsScene/SettingsTableViewHeader.swift new file mode 100644 index 00000000..27bd0c13 --- /dev/null +++ b/Doolda/Doolda/SettingsScene/SettingsTableViewHeader.swift @@ -0,0 +1,85 @@ +// +// SettingsTableViewHeader.swift +// Doolda +// +// Created by Dozzing on 2021/11/22. +// + +import Combine +import UIKit + +import SnapKit + +class SettingsTableViewHeader: UITableViewHeaderFooterView { + + // MARK: - Static Properties + + static let identifier = "SettingsTableViewHeader" + + // MARK: - Subviews + + private lazy var titleLabel: UILabel = { + let title = UILabel() + title.textColor = .dooldaSublabel + return title + }() + + // MARK: - Public Properties + + @Published var title: String? + @Published var font: UIFont? + + // MARK: - Private Properties + + private var cancellables: Set = [] + + // MARK: - Initializers + + override init(reuseIdentifier: String?) { + super.init(reuseIdentifier: reuseIdentifier) + self.configureUI() + self.configureFont() + self.bindUI() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + self.configureUI() + } + + // MARK: - Helpers + + private func configureUI() { + self.backgroundView?.backgroundColor = .red + self.addSubview(titleLabel) + self.titleLabel.snp.makeConstraints { make in + make.top.equalToSuperview().offset(8) + make.bottom.equalToSuperview().offset(-8) + make.leading.equalToSuperview().offset(16) + make.width.equalToSuperview() + } + } + + private func configureFont() { + var font = self.font + if font == nil { font = .systemFont(ofSize: 16) } + self.titleLabel.font = font + } + + private func bindUI() { + self.$title + .receive(on: DispatchQueue.main) + .sink { [weak self] title in + self?.titleLabel.text = title + } + .store(in: &self.cancellables) + + self.$font + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.configureFont() + } + .store(in: &self.cancellables) + } + +} diff --git a/Doolda/Doolda/SettingsScene/SettingsViewController.swift b/Doolda/Doolda/SettingsScene/SettingsViewController.swift new file mode 100644 index 00000000..ac2ac98a --- /dev/null +++ b/Doolda/Doolda/SettingsScene/SettingsViewController.swift @@ -0,0 +1,259 @@ +// +// SettingsViewController.swift +// Doolda +// +// Created by Dozzing on 2021/11/21. +// + +import Combine +import UIKit + +import SnapKit + +class SettingsViewController: UIViewController { + + struct SettingsSection { + let title: String + var settingsOptions: [SettingsOptions] + } + + struct SettingsOptions { + let cell: SettingsTableViewCell + let handler: (() -> Void)? + } + + // MARK: - Subviews + + private lazy var tableView: UITableView = { + let tableView = UITableView() + tableView.register(SettingsTableViewCell.self, forCellReuseIdentifier: SettingsTableViewCell.identifier) + tableView.register(SettingsTableViewHeader.self, forHeaderFooterViewReuseIdentifier: SettingsTableViewHeader.identifier) + tableView.backgroundColor = .clear + tableView.separatorStyle = .none + tableView.isScrollEnabled = false + tableView.delegate = self + tableView.dataSource = self + return tableView + }() + + private lazy var disconnectButton: UIButton = { + let button = UIButton() + button.setTitle("친구 끊기", for: .normal) + button.setTitleColor(.dooldaWarning, for: .normal) + return button + }() + + private lazy var settingsSections: [SettingsSection] = { + let alertCell = SettingsTableViewCell(style: .switchControl) + alertCell.title = "앱 실행 중 알림" + let alertOption = SettingsOptions(cell: alertCell, handler: nil) + + let fontCell = SettingsTableViewCell(style: .detail) + fontCell.title = "폰트 설정" + let fontOption = SettingsOptions(cell: fontCell, handler: self.viewModel.fontTypeDidTap) + + let versionCell = SettingsTableViewCell(style: .detail) + versionCell.title = "앱 현재 버전" + versionCell.detailText = DooldaInfoType.appVersion.rawValue + let appVersionOption = SettingsOptions(cell: versionCell, handler: nil) + + let openSourceCell = SettingsTableViewCell(style: .disclosure) + openSourceCell.title = "Open Source License" + let openSourceOption = SettingsOptions(cell: openSourceCell, handler: self.viewModel.openSourceLicenseDidTap) + + let privacyCell = SettingsTableViewCell(style: .disclosure) + privacyCell.title = "개인 정보 처리 방침" + let privacyOption = SettingsOptions(cell: privacyCell, handler: self.viewModel.privacyPolicyDidTap) + + let contributorCell = SettingsTableViewCell(style: .disclosure) + contributorCell.title = "만든 사람들" + let contributorsOption = SettingsOptions(cell: contributorCell, handler: self.viewModel.contributorDidTap) + + let appSection = SettingsSection(title: "앱 설정", settingsOptions: [alertOption, fontOption]) + let serviceSection = SettingsSection( + title: "서비스 정보", + settingsOptions: [appVersionOption, openSourceOption, privacyOption, contributorsOption] + ) + + let sections: [SettingsSection] = [appSection, serviceSection] + return sections + }() + + // MARK: - Override Properties + + override var prefersStatusBarHidden: Bool { return true } + + // MARK: - Private Properties + + private var viewModel: SettingsViewModelProtocol! + private var cancellables: Set = [] + + // MARK: - Initializers + + convenience init(viewModel: SettingsViewModelProtocol) { + self.init(nibName: nil, bundle: nil) + self.viewModel = viewModel + } + + deinit { + print(#file, "DEINIT") + self.viewModel.deinitRequested() + } + + // MARK: - LifeCycle Methods + + override func viewDidLoad() { + super.viewDidLoad() + self.configureUI() + self.configureFont() + self.bindUI() + self.viewModel.settingsViewDidLoad() + } + + // MARK: - Helpers + + private func configureUI() { + self.view.backgroundColor = .dooldaBackground + self.title = "설정" + + self.navigationController?.isNavigationBarHidden = false + self.navigationController?.navigationBar.tintColor = .dooldaLabel + self.navigationController?.navigationBar.topItem?.title = "" + self.navigationItem.backButtonTitle = "" + + self.view.addSubview(self.tableView) + self.tableView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + } + + private func configureFont() { + self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 16)] + self.disconnectButton.titleLabel?.font = .systemFont(ofSize: 16) + + self.settingsSections.enumerated().forEach { index, section in + guard let header = self.tableView.headerView(forSection: index) as? SettingsTableViewHeader else { return } + header.font = .systemFont(ofSize: 17) + + section.settingsOptions.forEach { options in + options.cell.font = .systemFont(ofSize: 16) + } + } + } + + private func bindUI() { + self.viewModel.pushNotificationStatePublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] isPushNotificationOn in + guard let isPushNotificationOn = isPushNotificationOn, + let section = self?.settingsSections[exist: 0], + let alertCell = section.settingsOptions[exist: 0]?.cell else { return } + alertCell.switchControl.isOn = isPushNotificationOn + } + .store(in: &self.cancellables) + + self.viewModel.selectedFontPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] selectedFont in + guard let section = self?.settingsSections[exist: 0], + let fontCell = section.settingsOptions[exist: 1]?.cell else { return } + fontCell.detailText = selectedFont?.displayName + } + .store(in: &self.cancellables) + + self.viewModel.errorPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] error in + guard let error = error as? LocalizedError else { return } + let alert = UIAlertController.defaultAlert(title: "오류", message: error.localizedDescription) { _ in } + self?.present(alert, animated: true, completion: nil) + } + .store(in: &self.cancellables) + + self.disconnectButton.publisher(for: .touchUpInside) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self = self else { return } + let alert = UIAlertController.selectAlert( + title: "친구 끊기", + message: "정말 친구와 연결을 끊으시겠습니까?\n모든 데이터가 지워집니다.", + leftActionTitle: "취소", + rightActionTitle: "확인" ) { _ in + self.viewModel.unpairButtonDidTap() + } + self.present(alert, animated: true, completion: nil) + } + .store(in: &self.cancellables) + + guard let section = self.settingsSections[exist: 0], + let alertCell = section.settingsOptions[exist: 0]?.cell else { return } + alertCell.switchControl.publisher(for: .valueChanged) + .sink { [weak self] _ in + self?.viewModel.pushNotificationDidToggle(alertCell.switchControl.isOn) + } + .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: GlobalFontUseCase.Notifications.globalFontDidSet, object: nil) + .sink { [weak self] _ in + self?.configureFont() + } + .store(in: &self.cancellables) + } +} + +extension SettingsViewController: UITableViewDelegate { + func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + guard let section = self.settingsSections[exist: indexPath.section], + let handler = section.settingsOptions[exist: indexPath.row]?.handler else { return } + handler() + } +} + +extension SettingsViewController: UITableViewDataSource { + func numberOfSections(in tableView: UITableView) -> Int { + return self.settingsSections.count + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return self.settingsSections[exist: section]?.settingsOptions.count ?? 0 + } + + func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { + return 40 + } + + func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { + if section == self.settingsSections.count - 1 { return 65 } + return 0 + } + + func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { + guard let header = tableView.dequeueReusableHeaderFooterView( + withIdentifier: SettingsTableViewHeader.identifier + ) as? SettingsTableViewHeader else { return nil } + + header.title = self.settingsSections[section].title + return header + } + + func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? { + if section == self.settingsSections.count - 1 { + return self.disconnectButton + } + return nil + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + guard let section = self.settingsSections[exist: indexPath.section], + let cell = section.settingsOptions[exist: indexPath.row]?.cell else { return UITableViewCell() } + + cell.selectionStyle = .none + return cell + } +} + +extension SettingsViewController: FontPickerViewControllerDelegate { + func fontDidSelect(_ font: FontType) { + self.viewModel.fontTypeDidChanged(font.name) + } +} diff --git a/Doolda/Doolda/SettingsScene/SettingsViewCoordinator.swift b/Doolda/Doolda/SettingsScene/SettingsViewCoordinator.swift new file mode 100644 index 00000000..1f12a5c8 --- /dev/null +++ b/Doolda/Doolda/SettingsScene/SettingsViewCoordinator.swift @@ -0,0 +1,117 @@ +// +// SettingsViewCoordinator.swift +// Doolda +// +// Created by Dozzing on 2021/11/21. +// + +import Combine +import UIKit + +class SettingsViewCoordinator: BaseCoordinator { + + // MARK: - Nested enum + + enum Notifications { + static let fontPickerSheetRequested = Notification.Name("fontPickerSheetRequested") + static let informationViewRequested = Notification.Name("informationViewRequested") + } + + enum Keys { + static let infoType = "infoType" + } + + + // MARK: - Private Properties + + private let user: User + + private var cancellables: Set = [] + + // MARK: - Initializers + + init(identifier: UUID, presenter: UINavigationController, user: User) { + self.user = user + super.init(identifier: identifier, presenter: presenter) + self.bind() + } + + // MARK: - Helpers + + private func bind() { + NotificationCenter.default.publisher(for: Notifications.fontPickerSheetRequested, object: nil) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.fontPickerSheetRequested() + } + .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: Notifications.informationViewRequested, object: nil) + .receive(on: DispatchQueue.main) + .compactMap { $0.userInfo?[Keys.infoType] as? DooldaInfoType } + .sink { [weak self] infoType in + self?.informationViewRequested(for: infoType) + } + .store(in: &self.cancellables) + } + + // MARK: - Public Methods + + func start() { + DispatchQueue.main.async { + let userDefaultsPersistenceService = UserDefaultsPersistenceService.shared + let urlSessionNetworkService = URLSessionNetworkService.shared + + let globalFontRepository = GlobalFontRepository(persistenceService: userDefaultsPersistenceService) + let pushNotificationStateRepository = PushNotificationStateRepository(persistenceService: userDefaultsPersistenceService) + let userRepository = UserRepository( + persistenceService: userDefaultsPersistenceService, + networkService: urlSessionNetworkService + ) + let pairRepository = PairRepository(networkService: urlSessionNetworkService) + let fcmTokenRepository = FCMTokenRepository(urlSessionNetworkService: urlSessionNetworkService) + let firebaseMessageRepository = FirebaseMessageRepository(urlSessionNetworkService: urlSessionNetworkService) + + let globalFontUseCase = GlobalFontUseCase(globalFontRepository: globalFontRepository) + let pushNotificationStateUseCase = PushNotificationStateUseCase(pushNotificationStateRepository: pushNotificationStateRepository) + let unpairUserUseCase = UnpairUserUseCase(userRepository: userRepository, pairRepository: pairRepository) + let firebaseMessageUseCase = FirebaseMessageUseCase( + fcmTokenRepository: fcmTokenRepository, + firebaseMessageRepository: firebaseMessageRepository + ) + + let viewModel = SettingsViewModel( + sceneId: self.identifier, + user: self.user, + globalFontUseCase: globalFontUseCase, + unpairUserUseCase: unpairUserUseCase, + pushNotificationStateUseCase: pushNotificationStateUseCase, + firebaseMessageUseCase: firebaseMessageUseCase + ) + let viewController = SettingsViewController(viewModel: viewModel) + self.presenter.pushViewController(viewController, animated: true) + } + } + + // MARK: - Private Methods + + private func fontPickerSheetRequested() { + guard let settingsViewController = self.presenter.topViewController as? SettingsViewController else { return } + let fontPickerSheet = FontPickerViewController(delegate: settingsViewController) + settingsViewController.present(fontPickerSheet, animated: false, completion: nil) + } + + private func informationViewRequested(for option: DooldaInfoType) { + let viewController = InformationViewController() + viewController.titleText = option.title + + if option == .contributor { + // FIXME: 만든 사람들 이미지를 넣어야함!! + // viewController.image = UIImage.contributorImage + } else { + viewController.contentText = option.content + } + self.presenter.topViewController?.navigationController?.pushViewController(viewController, animated: true) + } + +} diff --git a/Doolda/Doolda/SettingsScene/SettingsViewModel.swift b/Doolda/Doolda/SettingsScene/SettingsViewModel.swift new file mode 100644 index 00000000..d4794790 --- /dev/null +++ b/Doolda/Doolda/SettingsScene/SettingsViewModel.swift @@ -0,0 +1,136 @@ +// +// SettingsViewModel.swift +// Doolda +// +// Created by Dozzing on 2021/11/22. +// + +import Combine +import Foundation + +protocol SettingsViewModelInput { + func settingsViewDidLoad() + func fontTypeDidTap() + func fontTypeDidChanged(_ fontName: String) + func pushNotificationDidToggle(_ isOn: Bool) + func openSourceLicenseDidTap() + func privacyPolicyDidTap() + func contributorDidTap() + func unpairButtonDidTap() + func deinitRequested() +} + +protocol SettingsViewModelOutput { + var errorPublisher: AnyPublisher { get } + var pushNotificationStatePublisher: AnyPublisher { get } + var selectedFontPublisher: AnyPublisher { get } +} + +typealias SettingsViewModelProtocol = SettingsViewModelInput & SettingsViewModelOutput + +final class SettingsViewModel: SettingsViewModelProtocol { + var errorPublisher: AnyPublisher { self.$error.eraseToAnyPublisher() } + var pushNotificationStatePublisher: AnyPublisher { self.$isPushNotificationOn.eraseToAnyPublisher() } + var selectedFontPublisher: AnyPublisher { self.$selectedFont.eraseToAnyPublisher() } + + private let sceneId: UUID + private let user: User + private let globalFontUseCase: GlobalFontUseCaseProtocol + private let unpairUserUseCase: UnpairUserUseCaseProtocol + private let pushNotificationStateUseCase: PushNotificationStateUseCaseProtocol + private let firebaseMessageUseCase: FirebaseMessageUseCaseProtocol + + private var cancellables: Set = [] + @Published private var error: Error? + @Published private var isPushNotificationOn: Bool? + @Published private var selectedFont: FontType? + + init( + sceneId: UUID, + user: User, + globalFontUseCase: GlobalFontUseCaseProtocol, + unpairUserUseCase: UnpairUserUseCaseProtocol, + pushNotificationStateUseCase: PushNotificationStateUseCaseProtocol, + firebaseMessageUseCase: FirebaseMessageUseCaseProtocol + ) { + self.sceneId = sceneId + self.user = user + self.globalFontUseCase = globalFontUseCase + self.unpairUserUseCase = unpairUserUseCase + self.pushNotificationStateUseCase = pushNotificationStateUseCase + self.firebaseMessageUseCase = firebaseMessageUseCase + } + + func settingsViewDidLoad() { + self.isPushNotificationOn = self.pushNotificationStateUseCase.getPushNotificationState() + self.selectedFont = self.globalFontUseCase.getGlobalFont() + } + + func fontTypeDidTap() { + NotificationCenter.default.post( + name: SettingsViewCoordinator.Notifications.fontPickerSheetRequested, + object: nil + ) + } + + func fontTypeDidChanged(_ fontName: String) { + self.globalFontUseCase.setGlobalFont(with: fontName) + self.globalFontUseCase.saveGlobalFont(as: fontName) + self.selectedFont = FontType(fontName: fontName) + } + + func pushNotificationDidToggle(_ isOn: Bool) { + self.pushNotificationStateUseCase.setPushNotificationState(as: isOn) + } + + func openSourceLicenseDidTap() { + NotificationCenter.default.post( + name: SettingsViewCoordinator.Notifications.informationViewRequested, + object: nil, + userInfo: [SettingsViewCoordinator.Keys.infoType: DooldaInfoType.openSourceLicense] + ) + } + + func privacyPolicyDidTap() { + NotificationCenter.default.post( + name: SettingsViewCoordinator.Notifications.informationViewRequested, + object: nil, + userInfo: [SettingsViewCoordinator.Keys.infoType: DooldaInfoType.privacyPolicy] + ) + } + + func contributorDidTap() { + NotificationCenter.default.post( + name: SettingsViewCoordinator.Notifications.informationViewRequested, + object: nil, + userInfo: [SettingsViewCoordinator.Keys.infoType: DooldaInfoType.contributor] + ) + } + + func unpairButtonDidTap() { + self.unpairUserUseCase.unpair(user: self.user) + .sink { [weak self] completion in + guard case .failure(let error) = completion else { return } + self?.error = error + } receiveValue: { [weak self] _ in + if let friendId = self?.user.friendId, + friendId != self?.user.id { + self?.firebaseMessageUseCase.sendMessage(to: friendId, message: PushMessageEntity.userDisconnected) + } + + NotificationCenter.default.post( + name: AppCoordinator.Notifications.appRestartSignal, + object: nil + ) + } + .store(in: &self.cancellables) + } + + func deinitRequested() { + NotificationCenter.default.post( + name: BaseCoordinator.Notifications.coordinatorRemoveFromParent, + object: nil, + userInfo: [BaseCoordinator.Keys.sceneId: self.sceneId] + ) + } +} diff --git a/Doolda/Doolda/UI/ViewControllers/SplashViewController.swift b/Doolda/Doolda/SplashScene/SplashViewController.swift similarity index 84% rename from Doolda/Doolda/UI/ViewControllers/SplashViewController.swift rename to Doolda/Doolda/SplashScene/SplashViewController.swift index 2749554d..c6ff5b14 100644 --- a/Doolda/Doolda/UI/ViewControllers/SplashViewController.swift +++ b/Doolda/Doolda/SplashScene/SplashViewController.swift @@ -39,7 +39,7 @@ final class SplashViewController: UIViewController { // MARK: - Private Properties - private var viewModel: SplashViewModel? + private var viewModel: SplashViewModel! private var cancellables: Set = [] // MARK: - Initializers @@ -49,11 +49,17 @@ final class SplashViewController: UIViewController { self.viewModel = viewModel } +// deinit { +// print(#file, "DEINIT") +// self.viewModel.deinitRequested() +// } + // MARK: - Lifecycle Methods override func viewDidLoad() { super.viewDidLoad() self.configureUI() + self.configureFont() self.bindUI() self.viewModel?.applyGlobalFont() self.viewModel?.prepareUserInfo() @@ -61,7 +67,6 @@ final class SplashViewController: UIViewController { override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) - self.configureFont() } // MARK: - Helpers @@ -93,8 +98,8 @@ final class SplashViewController: UIViewController { } private func configureFont() { - self.titleLabel.font = UIFont(name: DoolDaFont.dovemayo.rawValue, size: 72) - self.subtitleLabel.font = UIFont(name: DoolDaFont.dovemayo.rawValue, size: 18) + self.titleLabel.font = UIFont(name: FontType.dovemayo.name, size: 72) + self.subtitleLabel.font = UIFont(name: FontType.dovemayo.name, size: 18) } private func bindUI() { @@ -105,6 +110,12 @@ final class SplashViewController: UIViewController { self?.presentNetworkAlert() } .store(in: &cancellables) + + NotificationCenter.default.publisher(for: GlobalFontUseCase.Notifications.globalFontDidSet, object: nil) + .sink { [weak self] _ in + self?.configureFont() + } + .store(in: &self.cancellables) } // MARK: - Private Methods diff --git a/Doolda/Doolda/SplashScene/SplashViewCoordinator.swift b/Doolda/Doolda/SplashScene/SplashViewCoordinator.swift new file mode 100644 index 00000000..e2f3aae9 --- /dev/null +++ b/Doolda/Doolda/SplashScene/SplashViewCoordinator.swift @@ -0,0 +1,93 @@ +// +// SplashViewCoordinator.swift +// Doolda +// +// Created by 정지승 on 2021/11/01. +// + +import Combine +import UIKit + +final class SplashViewCoordinator: BaseCoordinator { + + //MARK: - Nested enum + + enum Notifications { + static let userNotPaired = Notification.Name("userNotPaired") + static let userAlreadyPaired = Notification.Name("userAlreadyPaired") + } + + enum Keys { + static let user = "user" + static let myId = "myId" + } + + private var cancellables: Set = [] + + override init(identifier: UUID, presenter: UINavigationController) { + super.init(identifier: identifier, presenter: presenter) + self.bind() + } + + func start() { + let userDefaultsPersistenceService = UserDefaultsPersistenceService.shared + let urlSessionNetworkService = URLSessionNetworkService.shared + + let userRespository = UserRepository( + persistenceService: userDefaultsPersistenceService, + networkService: urlSessionNetworkService + ) + let globalFontRepository = GlobalFontRepository( + persistenceService: userDefaultsPersistenceService + ) + + let getMyIdUseCase = GetMyIdUseCase(userRepository: userRespository) + let getUserUseCase = GetUserUseCase(userRepository: userRespository) + let registerUserUseCase = RegisterUserUseCase(userRepository: userRespository) + let globalFontUseCase = GlobalFontUseCase(globalFontRepository: globalFontRepository) + + let viewModel = SplashViewModel( + sceneId: self.identifier, + getMyIdUseCase: getMyIdUseCase, + getUserUseCase: getUserUseCase, + registerUserUseCase: registerUserUseCase, + globalFontUseCase: globalFontUseCase + ) + + let viewController = SplashViewController(viewModel: viewModel) + self.presenter.pushViewController(viewController, animated: false) + } + + private func bind() { + NotificationCenter.default.publisher(for: Notifications.userNotPaired, object: nil) + .receive(on: DispatchQueue.main) + .compactMap { $0.userInfo?[Keys.myId] as? DDID } + .sink { [weak self] myId in + self?.userNotPaired(myId: myId) + } + .store(in: &self.cancellables) + + NotificationCenter.default.publisher(for: Notifications.userAlreadyPaired, object: nil) + .receive(on: DispatchQueue.main) + .compactMap { $0.userInfo?[Keys.user] as? User } + .sink { [weak self] user in + self?.userAlreadyPaired(user: user) + } + .store(in: &self.cancellables) + } + + private func userNotPaired(myId: DDID) { + let user = User(id: myId) + let identifier = UUID() + let paringViewCoordinator = PairingViewCoordinator(identifier: identifier, presenter: self.presenter, user: user) + self.children[identifier] = paringViewCoordinator + paringViewCoordinator.start() + } + + private func userAlreadyPaired(user: User) { + let identifier = UUID() + let diaryViewCoordinator = DiaryViewCoordinator(identifier: identifier, presenter: self.presenter, user: user) + self.children[identifier] = diaryViewCoordinator + diaryViewCoordinator.start() + } +} diff --git a/Doolda/Doolda/Presentation/ViewModels/SplashViewModel.swift b/Doolda/Doolda/SplashScene/SplashViewModel.swift similarity index 69% rename from Doolda/Doolda/Presentation/ViewModels/SplashViewModel.swift rename to Doolda/Doolda/SplashScene/SplashViewModel.swift index 6fd45367..f06275a9 100644 --- a/Doolda/Doolda/Presentation/ViewModels/SplashViewModel.swift +++ b/Doolda/Doolda/SplashScene/SplashViewModel.swift @@ -11,7 +11,7 @@ import Foundation final class SplashViewModel { @Published var error: Error? - private let coordinator: SplashViewCoordinatorProtocol + private let sceneId: UUID private let getMyIdUseCase: GetMyIdUseCaseProtocol private let getUserUseCase: GetUserUseCaseProtocol private let registerUserUseCase: RegisterUserUseCaseProtocol @@ -21,13 +21,13 @@ final class SplashViewModel { @Published private var user: User? init( - coordinator: SplashViewCoordinatorProtocol, + sceneId: UUID, getMyIdUseCase: GetMyIdUseCaseProtocol, getUserUseCase: GetUserUseCaseProtocol, registerUserUseCase: RegisterUserUseCaseProtocol, globalFontUseCase: GlobalFontUseCaseProtocol ) { - self.coordinator = coordinator + self.sceneId = sceneId self.getMyIdUseCase = getMyIdUseCase self.getUserUseCase = getUserUseCase self.registerUserUseCase = registerUserUseCase @@ -35,8 +35,8 @@ final class SplashViewModel { } func applyGlobalFont() { - guard let globalFontName = self.globalFontUseCase.getGlobalFont() else { return } - self.globalFontUseCase.setGlobalFont(with: globalFontName) + guard let globalFont = self.globalFontUseCase.getGlobalFont() else { return } + self.globalFontUseCase.setGlobalFont(with: globalFont.name) } func prepareUserInfo() { @@ -49,8 +49,18 @@ final class SplashViewModel { .compactMap { $0 } .sink(receiveValue: { [weak self] user in if user.pairId?.ddidString.isEmpty == false { - self?.coordinator.userAlreadyPaired(user: user) - } else { self?.coordinator.userNotPaired(myId: user.id) } + NotificationCenter.default.post( + name: SplashViewCoordinator.Notifications.userAlreadyPaired, + object: self, + userInfo: [SplashViewCoordinator.Keys.user: user] + ) + } else { + NotificationCenter.default.post( + name: SplashViewCoordinator.Notifications.userNotPaired, + object: self, + userInfo: [SplashViewCoordinator.Keys.myId: user.id] + ) + } }) .store(in: &self.cancellables) @@ -85,4 +95,12 @@ final class SplashViewModel { } .store(in: &self.cancellables) } + + func deinitRequested() { + NotificationCenter.default.post( + name: BaseCoordinator.Notifications.coordinatorRemoveFromParent, + object: nil, + userInfo: [BaseCoordinator.Keys.sceneId: self.sceneId] + ) + } } diff --git a/Doolda/Doolda/Support/Constants/API.swift b/Doolda/Doolda/Support/Constants/API.swift index ed611dfd..c4c7db0f 100644 --- a/Doolda/Doolda/Support/Constants/API.swift +++ b/Doolda/Doolda/Support/Constants/API.swift @@ -10,17 +10,24 @@ import Foundation enum FirebaseAPIs: URLRequestBuilder { case getUserDocuement(String) case createUserDocument(String) - case patchUserDocuement(String, String) + case patchUserDocument(String, String, String) case getPairDocument(String) case createPairDocument(String, String) case patchPairDocument(String, String) + case deletePairDocument(String) - case createPageDocument(String, Date, String, String) case getPageDocuments(String, Date?) + case createPageDocument(String, Date, Date, String, String) + case patchPageDocument(String, Date, Date, String, String) + + case getFCMTokenDocument(String) + case patchFCMTokenDocument(String, String) case uploadDataFile(String, String, Data) case downloadDataFile(String, String) + + case sendFirebaseMessage(String, String, String, [String: Any]) } extension FirebaseAPIs { @@ -28,6 +35,8 @@ extension FirebaseAPIs { switch self { case .uploadDataFile(let pairId, let fileName, _), .downloadDataFile(let pairId, let fileName): return URL(string: "https://firebasestorage.googleapis.com/v0/b/doolda.appspot.com/o/\(pairId)%2F\(fileName)") + case .sendFirebaseMessage: + return URL(string: "https://fcm.googleapis.com/fcm/send") default: return URL(string: "https://firestore.googleapis.com/v1/projects/doolda/databases/(default)/") } @@ -37,11 +46,11 @@ extension FirebaseAPIs { extension FirebaseAPIs { var path: String? { switch self { - case .getUserDocuement(let userId), .patchUserDocuement(let userId, _): + case .getUserDocuement(let userId), .patchUserDocument(let userId, _, _): return "documents/user/\(userId)" case .createUserDocument: return "documents/user" - case .getPairDocument(let pairId), .patchPairDocument(let pairId, _): + case .getPairDocument(let pairId), .patchPairDocument(let pairId, _), .deletePairDocument(let pairId): return "documents/pair/\(pairId)" case .createPairDocument: return "documents/pair" @@ -49,6 +58,10 @@ extension FirebaseAPIs { return "documents:runQuery" case .createPageDocument: return "documents/page" + case .patchPageDocument(_, _, _, let jsonPath, let pairId): + return "documents/page/\(pairId + jsonPath)" + case .getFCMTokenDocument(let userId), .patchFCMTokenDocument(let userId, _): + return "documents/fcmToken/\(userId)" default: return nil } } @@ -57,13 +70,19 @@ extension FirebaseAPIs { extension FirebaseAPIs { var parameters: [String : String]? { switch self { - case .getUserDocuement, .getPairDocument, .getPageDocuments: + case .getUserDocuement, + .getPairDocument, + .getPageDocuments, + .sendFirebaseMessage, + .getFCMTokenDocument, + .patchFCMTokenDocument, + .deletePairDocument: return nil case .createUserDocument(let id), .createPairDocument(let id, _): return ["documentId": id] - case .createPageDocument(_, _, let jsonPath, let pairId): + case .createPageDocument(_, _, _, let jsonPath, let pairId): return ["documentId": pairId + jsonPath] - case .patchUserDocuement, .patchPairDocument: + case .patchUserDocument, .patchPairDocument, .patchPageDocument: return ["currentDocument.exists": "true"] case .uploadDataFile, .downloadDataFile: return ["alt": "media"] @@ -74,12 +93,14 @@ extension FirebaseAPIs { extension FirebaseAPIs { var method: HttpMethod { switch self { - case .getUserDocuement, .getPairDocument, .downloadDataFile: + case .getUserDocuement, .getPairDocument, .getFCMTokenDocument, .downloadDataFile: return .get - case .createUserDocument, .createPairDocument, .createPageDocument, .uploadDataFile, .getPageDocuments: + case .createUserDocument, .createPairDocument, .createPageDocument, .uploadDataFile, .getPageDocuments, .sendFirebaseMessage: return .post - case .patchUserDocuement, .patchPairDocument: + case .patchUserDocument, .patchPairDocument, .patchPageDocument, .patchFCMTokenDocument: return .patch + case .deletePairDocument: + return .delete } } } @@ -89,6 +110,8 @@ extension FirebaseAPIs { switch self { case .uploadDataFile: return ["Content-Type": "application/octet-stream"] + case .sendFirebaseMessage: + return ["Content-Type": "application/json", "Authorization": "key=\(Secrets.fcmServerKey ?? "")"] default : return ["Content-Type": "application/json", "Accept": "application/json"] } @@ -98,7 +121,7 @@ extension FirebaseAPIs { extension FirebaseAPIs { var body: [String: Any]? { switch self { - case .getUserDocuement, .getPairDocument, .uploadDataFile, .downloadDataFile: + case .getUserDocuement, .getPairDocument, .getFCMTokenDocument, .uploadDataFile, .downloadDataFile, .deletePairDocument: return nil case .getPageDocuments(let pairId, let date): var filters = [[String: Any]]() @@ -143,12 +166,12 @@ extension FirebaseAPIs { ] ] case .createUserDocument(let userId): - let userDocument = UserDocument(userId: userId, pairId: "") + let userDocument = UserDocument(userId: userId, pairId: "", friendId: "") return [ "fields": userDocument.fields ] - case .patchUserDocuement(let userId, let pairId): - let userDocument = UserDocument(userId: userId, pairId: pairId) + case .patchUserDocument(let userId, let pairId, let friendId): + let userDocument = UserDocument(userId: userId, pairId: pairId, friendId: friendId) return [ "fields": userDocument.fields ] @@ -157,11 +180,34 @@ extension FirebaseAPIs { return [ "fields": pairDocument.fields ] - case .createPageDocument(let authorId, let createdTime, let jsonPath, let pairId): - let pageDocument = PageDocument(author: authorId, createdTime: createdTime, jsonPath: jsonPath, pairId: pairId) + case .createPageDocument(let authorId, let createdTime, let updatedTime, let jsonPath, let pairId), + .patchPageDocument(let authorId, let createdTime, let updatedTime, let jsonPath, let pairId): + let pageDocument = PageDocument( + author: authorId, + createdTime: createdTime, + updatedTime: updatedTime, + jsonPath: jsonPath, + pairId: pairId + ) return [ "fields": pageDocument.fields ] + case .sendFirebaseMessage(let receiverToken, let title, let body, let data): + return [ + "to": receiverToken, + "notification": [ + "title": title, + "body": body, + "mutable_content": true, + "sound": "Tri-tone" + ], + "data": data + ] + case .patchFCMTokenDocument(_, let token): + let tokenDocument = FCMTokenDocument(token: token) + return [ + "fields": tokenDocument.fields + ] } } diff --git a/Doolda/Doolda/Support/Constants/DoolDaFont.swift b/Doolda/Doolda/Support/Constants/DoolDaFont.swift deleted file mode 100644 index d0daf5ae..00000000 --- a/Doolda/Doolda/Support/Constants/DoolDaFont.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// DoolDaFont.swift -// Doolda -// -// Created by 김민주 on 2021/11/17. -// - -import Foundation - -enum DoolDaFont: String { - case dovemayo = "Dovemayo" -} diff --git a/Doolda/Doolda/Support/Constants/FileDocuments.swift b/Doolda/Doolda/Support/Constants/FileDocuments.swift deleted file mode 100644 index 67102a75..00000000 --- a/Doolda/Doolda/Support/Constants/FileDocuments.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// FileDocuments.swift -// Doolda -// -// Created by Dozzing on 2021/11/10. -// - -import Foundation - -enum FileDocuments { - typealias RawValue = URL? - - case temporary - case cache - - var rawValue: RawValue { - switch self { - case .temporary: return FileManager.default.temporaryDirectory - case .cache: return FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first - } - } -} diff --git a/Doolda/Doolda/Support/Constants/Secrets.swift b/Doolda/Doolda/Support/Constants/Secrets.swift new file mode 100644 index 00000000..b41b7580 --- /dev/null +++ b/Doolda/Doolda/Support/Constants/Secrets.swift @@ -0,0 +1,12 @@ +// +// Secrets.swift +// Doolda +// +// Created by Seunghun Yang on 2021/11/22. +// + +import Foundation + +enum Secrets { + static let fcmServerKey = Bundle.main.infoDictionary?["FCM_SERVER_KEY"] as? String +} diff --git a/Doolda/Doolda/Support/Extensions/Collection+Extensions.swift b/Doolda/Doolda/Support/Extensions/Collection+Extensions.swift new file mode 100644 index 00000000..1b913289 --- /dev/null +++ b/Doolda/Doolda/Support/Extensions/Collection+Extensions.swift @@ -0,0 +1,14 @@ +// +// Collection+Extensions.swift +// Doolda +// +// Created by Seunghun Yang on 2021/11/22. +// + +import Foundation + +extension Collection where Indices.Iterator.Element == Index { + subscript (exist index: Index) -> Iterator.Element? { + return indices.contains(index) ? self[index] : nil + } +} diff --git a/Doolda/Doolda/Support/Extensions/DateFormatter+Extensions.swift b/Doolda/Doolda/Support/Extensions/DateFormatter+Extensions.swift index 6479af68..be322e38 100644 --- a/Doolda/Doolda/Support/Extensions/DateFormatter+Extensions.swift +++ b/Doolda/Doolda/Support/Extensions/DateFormatter+Extensions.swift @@ -31,4 +31,11 @@ extension DateFormatter { formatter.dateFormat = "dd" return formatter }() + + static let koreanFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "ko_kr") + formatter.dateFormat = "yyyy.M.dd.eeee" + return formatter + }() } diff --git a/Doolda/Doolda/Support/Extensions/UIColor+Extensions.swift b/Doolda/Doolda/Support/Extensions/UIColor+Extensions.swift index 288a6fc9..824ba882 100644 --- a/Doolda/Doolda/Support/Extensions/UIColor+Extensions.swift +++ b/Doolda/Doolda/Support/Extensions/UIColor+Extensions.swift @@ -10,7 +10,7 @@ import UIKit extension UIColor { static let dooldaLabel = UIColor(named: "dooldaLabel") - static let dooldaSubLabel = UIColor(named: "dooldaSubLabel") + static let dooldaSublabel = UIColor(named: "dooldaSublabel") static let dooldaBackground = UIColor(named: "dooldaBackground") static let dooldaPlaceholder = UIColor(named: "dooldaPlaceholder") static let dooldaHighlighted = UIColor(named: "dooldaHighlighted") @@ -18,7 +18,7 @@ extension UIColor { static let dooldaActivityIndicatorBackground = UIColor(named: "dooldaActivityIndicatorBackground") static let dooldaStickerPackBody = UIColor(named: "dooldaStickerPackBody") static let dooldaStickerPackCover = UIColor(named: "dooldaStickerPackCover") - static let dooldaStickerPackBackground = UIColor(named: "dooldaStickerPackBackground") static let dooldaMaximumTrackTintColor = UIColor(named: "dooldaMaximumTrackTintColor") static let dooldaMinimumTrackTintColor = UIColor(named: "dooldaMinimumTrackTintColor") + static let dooldaWarning = UIColor(named: "dooldaWarning") } diff --git a/Doolda/Doolda/Support/Extensions/UIFont+Extensions.swift b/Doolda/Doolda/Support/Extensions/UIFont+Extensions.swift index 54286234..b5ef866c 100644 --- a/Doolda/Doolda/Support/Extensions/UIFont+Extensions.swift +++ b/Doolda/Doolda/Support/Extensions/UIFont+Extensions.swift @@ -9,7 +9,7 @@ import UIKit extension UIFont { static var isOverrided: Bool = false - static var globalFontFamily: String = DoolDaFont.dovemayo.rawValue + static var globalFontFamily: String = FontType.dovemayo.name class func overrideInitialize() { guard self == UIFont.self , !self.isOverrided else { return } @@ -46,7 +46,7 @@ extension UIFont { private class func myDefaultFont(ofSize fontSize: CGFloat, withTraits traits: UIFontDescriptor.SymbolicTraits = []) -> UIFont { guard let descriptor = UIFontDescriptor(name: self.globalFontFamily, size: fontSize).withSymbolicTraits(traits) else { - return UIFont.systemFont(ofSize: fontSize) + return UIFont(descriptor: UIFontDescriptor(name: self.globalFontFamily, size: fontSize), size: fontSize) } return UIFont(descriptor: descriptor, size: fontSize) } diff --git a/Doolda/Doolda/Support/Extensions/UIImage+Extensions.swift b/Doolda/Doolda/Support/Extensions/UIImage+Extensions.swift index 3ac2dd8d..54fa6ed7 100644 --- a/Doolda/Doolda/Support/Extensions/UIImage+Extensions.swift +++ b/Doolda/Doolda/Support/Extensions/UIImage+Extensions.swift @@ -28,4 +28,8 @@ extension UIImage { static let plus = UIImage(systemName: "plus") static let hedgehog = UIImage(named: "hedgehog") static let hedgehogWriting = UIImage(named: "hedgehogWriting") + static let hedgehogFrustrated = UIImage(named: "hedgehogFrustrated") + static let squareAndArrowUp = UIImage(systemName: "square.and.arrow.up") + static let squareAndPencil = UIImage(systemName: "square.and.pencil") + static let dottedLine = UIImage(named: "dottedLine") } diff --git a/Doolda/Doolda/UI/Coordinators/.gitkeep b/Doolda/Doolda/UI/Coordinators/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Doolda/Doolda/UI/Coordinators/AppCoordinator.swift b/Doolda/Doolda/UI/Coordinators/AppCoordinator.swift deleted file mode 100644 index ecf752b7..00000000 --- a/Doolda/Doolda/UI/Coordinators/AppCoordinator.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// AppCoordinator.swift -// Doolda -// -// Created by 정지승 on 2021/11/01. -// - -import UIKit - -final class AppCoordinator: CoordinatorProtocol { - var presenter: UINavigationController - - init(presenter: UINavigationController) { - self.presenter = presenter - } - - func start() { - let splashViewCoordinator = SplashViewCoordinator(presenter: self.presenter) - splashViewCoordinator.start() - } -} diff --git a/Doolda/Doolda/UI/Coordinators/DiaryViewCoordinator.swift b/Doolda/Doolda/UI/Coordinators/DiaryViewCoordinator.swift deleted file mode 100644 index f96712c2..00000000 --- a/Doolda/Doolda/UI/Coordinators/DiaryViewCoordinator.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// DiaryViewCoordinator.swift -// Doolda -// -// Created by Dozzing on 2021/11/02. -// - -import UIKit - -class DiaryViewCoordinator: DiaryViewCoordinatorProtocol { - - var presenter: UINavigationController - private let user: User - - init(presenter: UINavigationController, user: User) { - self.presenter = presenter - self.user = user - } - - func start() { - let urlSessionNetworkService = URLSessionNetworkService() - let coreDataPersistenceService = CoreDataPersistenceService() - let coreDataPageEntityPersistenceService = CoreDataPageEntityPersistenceService(coreDataPersistenceService: coreDataPersistenceService) - let fileManagerPersistenceService = FileManagerPersistenceService() - - let pairRepository = PairRepository(networkService: urlSessionNetworkService) - let pageRepository = PageRepository( - urlSessionNetworkService: urlSessionNetworkService, - pageEntityPersistenceService: coreDataPageEntityPersistenceService - ) - - let rawPageRepository = RawPageRepository( - networkService: urlSessionNetworkService, - fileManagerPersistenceService: fileManagerPersistenceService - ) - - let checkMyTurnUseCase = CheckMyTurnUseCase(pairRepository: pairRepository) - let getPageUseCase = GetPageUseCase(pageRepository: pageRepository) - let getRawPageUseCase = GetRawPageUseCase(rawPageRepository: rawPageRepository) - - let viewModel = DiaryViewModel( - user: self.user, - coordinator: self, - checkMyTurnUseCase: checkMyTurnUseCase, - getPageUseCase: getPageUseCase, - getRawPageUseCase: getRawPageUseCase - ) - - DispatchQueue.main.async { - let viewController = DiaryViewController(viewModel: viewModel) - self.presenter.setViewControllers([viewController], animated: false) - } - } - - func editPageRequested() { - let coordinator = EditPageViewCoordinator(presenter: self.presenter, user: self.user) - coordinator.start() - } - - func settingsPageRequested() { - } - - func filteringSheetRequested() { - } - -} diff --git a/Doolda/Doolda/UI/Coordinators/EditPageViewCoordinator.swift b/Doolda/Doolda/UI/Coordinators/EditPageViewCoordinator.swift deleted file mode 100644 index 8befa8ab..00000000 --- a/Doolda/Doolda/UI/Coordinators/EditPageViewCoordinator.swift +++ /dev/null @@ -1,121 +0,0 @@ -// -// EditPageViewCoordinator.swift -// Doolda -// -// Created by 김민주 on 2021/11/09. -// - -import Combine -import UIKit - -class EditPageViewCoordinator: EditPageViewCoordinatorProtocol { - var presenter: UINavigationController - private let user: User - - init(presenter: UINavigationController, user: User) { - self.presenter = presenter - self.user = user - } - - func start() { - DispatchQueue.main.async { - let fileManagerPersistenceService = FileManagerPersistenceService() - let urlSessionNetworkService = URLSessionNetworkService() - let coreDataPersistenceService = CoreDataPersistenceService() - let coreDataPageEntityPersistenceService = CoreDataPageEntityPersistenceService( - coreDataPersistenceService: coreDataPersistenceService - ) - - let pairRepository = PairRepository(networkService: urlSessionNetworkService) - let imageRepository = ImageRepository( - fileManagerService: fileManagerPersistenceService, - networkService: urlSessionNetworkService - ) - let pageRepository = PageRepository( - urlSessionNetworkService: urlSessionNetworkService, - pageEntityPersistenceService: coreDataPageEntityPersistenceService - ) - let rawPageRepository = RawPageRepository( - networkService: urlSessionNetworkService, - fileManagerPersistenceService: fileManagerPersistenceService - ) - - let imageUseCase = ImageUseCase(imageRepository: imageRepository) - let editPageUseCase = EditPageUseCase( - user: self.user, - imageUseCase: imageUseCase, - pageRepository: pageRepository, - rawPageRepository: rawPageRepository, - pairRepository: pairRepository - ) - - let editPageViewModel = EditPageViewModel(user: self.user, coordinator: self, editPageUseCase: editPageUseCase) - - let viewController = EditPageViewController(viewModel: editPageViewModel) - self.presenter.pushViewController(viewController, animated: true) - } - } - - func editingPageSaved() { - DispatchQueue.main.async { - self.presenter.popViewController(animated: true) - } - } - - func editingPageCanceled() { - DispatchQueue.main.async { - self.presenter.popViewController(animated: true) - } - } - - func addPhotoComponent() { - let fileManagerPersistenceService = FileManagerPersistenceService() - let urlSessionNetworkService = URLSessionNetworkService() - - let imageRepository = ImageRepository(fileManagerService: fileManagerPersistenceService, networkService: urlSessionNetworkService) - let imageUseCase = ImageUseCase(imageRepository: imageRepository) - let imageComposeUseCaes = ImageComposeUseCase() - - let photoPickerBottomSheetViewModel = PhotoPickerBottomSheetViewModel( - imageUseCase: imageUseCase, - imageComposeUseCase: imageComposeUseCaes - ) - - let delegatedViewController = self.presenter.topViewController as? EditPageViewController - let viewController = PhotoPickerBottomSheetViewController( - photoPickerViewModel: photoPickerBottomSheetViewModel, - delegate: delegatedViewController - ) - - self.presenter.topViewController?.present(viewController, animated: false, completion: nil) - } - - func addTextComponent() { - let delegatedViewController = self.presenter.topViewController as? EditPageViewController - - let textInputViewModel = TextInputViewModel(textUseCase: TextUseCase()) - let viewController = TextInputViewController( - textInputViewModel: textInputViewModel, - delegate: delegatedViewController, - widthRatioFromAbsolute: delegatedViewController?.widthRatioFromAbsolute, - heightRatioFromAbsolute: delegatedViewController?.heightRatioFromAbsolute - ) - - self.presenter.topViewController?.present(viewController, animated: false, completion: nil) - } - - func addStickerComponent() { - let stickerUseCase = StickerUseCase() - let stickerPickerViewModel = StickerPickerViewModel(stickerUseCase: stickerUseCase) - let delegatedViewController = self.presenter.topViewController as? EditPageViewController - let viewController = StickerPickerViewController(stickerPickerViewModel: stickerPickerViewModel, delegate: delegatedViewController) - self.presenter.topViewController?.present(viewController, animated: false, completion: nil) - } - - func changeBackgroundType() { - let delegatedViewController = self.presenter.topViewController as? EditPageViewController - let viewController = BackgroundTypePickerViewController(delegate: delegatedViewController) - - delegatedViewController?.present(viewController, animated: false, completion: nil) - } -} diff --git a/Doolda/Doolda/UI/Coordinators/ParingViewCoordinator.swift b/Doolda/Doolda/UI/Coordinators/ParingViewCoordinator.swift deleted file mode 100644 index a5a3002b..00000000 --- a/Doolda/Doolda/UI/Coordinators/ParingViewCoordinator.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// ParingViewCoordinator.swift -// Doolda -// -// Created by Dozzing on 2021/11/02. -// - -import UIKit - -class PairingViewCoordinator: PairingViewCoordinatorProtocol { - var presenter: UINavigationController - private let user: User - - init(presenter: UINavigationController, user: User) { - self.presenter = presenter - self.user = user - } - - func start() { - let userDefaultsPersistenceService = UserDefaultsPersistenceService() - let urlSessionNetworkService = URLSessionNetworkService() - - let userRepository = UserRepository( - persistenceService: userDefaultsPersistenceService, - networkService: urlSessionNetworkService - ) - - let pairRepository = PairRepository(networkService: urlSessionNetworkService) - - let pairUserUseCase = PairUserUseCase(userRepository: userRepository, pairRepository: pairRepository) - let refreshUserUseCase = RefreshUserUseCase(userRepository: userRepository) - - let viewModel = PairingViewModel( - user: user, - coordinator: self, - pairUserUseCase: pairUserUseCase, - refreshUserUseCase: refreshUserUseCase - ) - - DispatchQueue.main.async { - let viewController = PairingViewController(viewModel: viewModel) - self.presenter.setViewControllers([viewController], animated: false) - } - } - - func userDidPaired(user: User) { - let diaryViewCoordinator = DiaryViewCoordinator(presenter: self.presenter, user: user) - diaryViewCoordinator.start() - } -} diff --git a/Doolda/Doolda/UI/Coordinators/SplashViewCoordinator.swift b/Doolda/Doolda/UI/Coordinators/SplashViewCoordinator.swift deleted file mode 100644 index 751a5557..00000000 --- a/Doolda/Doolda/UI/Coordinators/SplashViewCoordinator.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// SplashViewCoordinator.swift -// Doolda -// -// Created by 정지승 on 2021/11/01. -// - -import UIKit - -class SplashViewCoordinator: SplashViewCoordinatorProtocol { - var presenter: UINavigationController - - init(presenter: UINavigationController) { - self.presenter = presenter - } - - func start() { - let userDefaultsPersistenceService = UserDefaultsPersistenceService() - let urlSessionNetworkService = URLSessionNetworkService() - - let userRespository = UserRepository( - persistenceService: userDefaultsPersistenceService, - networkService: urlSessionNetworkService - ) - let globalFontRepository = GlobalFontRepository( - persistenceService: userDefaultsPersistenceService - ) - - let getMyIdUseCase = GetMyIdUseCase(userRepository: userRespository) - let getUserUseCase = GetUserUseCase(userRepository: userRespository) - let registerUserUseCase = RegisterUserUseCase(userRepository: userRespository) - let globalFontUseCase = GlobalFontUseCase(globalFontRepository: globalFontRepository) - - let viewModel = SplashViewModel( - coordinator: self, - getMyIdUseCase: getMyIdUseCase, - getUserUseCase: getUserUseCase, - registerUserUseCase: registerUserUseCase, - globalFontUseCase: globalFontUseCase - ) - - let viewController = SplashViewController(viewModel: viewModel) - self.presenter.pushViewController(viewController, animated: false) - } - - func userNotPaired(myId: DDID) { - let user = User(id: myId) - let paringViewCoordinator = PairingViewCoordinator(presenter: self.presenter, user: user) - paringViewCoordinator.start() - } - - func userAlreadyPaired(user: User) { - // FIXME : should change to diaryViewController - let diaryViewCoordinator = DiaryViewCoordinator(presenter: self.presenter, user: user) - diaryViewCoordinator.start() -// let editPageViewCoordinator = EditPageViewCoordinator(presenter: self.presenter, user: user) -// editPageViewCoordinator.start() - } -} diff --git a/Doolda/Doolda/UI/ViewControllers/TextInputViewController.swift b/Doolda/Doolda/UI/ViewControllers/TextInputViewController.swift deleted file mode 100644 index 7d46d39c..00000000 --- a/Doolda/Doolda/UI/ViewControllers/TextInputViewController.swift +++ /dev/null @@ -1,131 +0,0 @@ -// -// TextInputViewController.swift -// Doolda -// -// Created by 김민주 on 2021/11/17. -// - -import Combine -import UIKit - -import SnapKit - -protocol TextInputViewControllerDelegate: AnyObject { - func textInputDidEndEditing(_ textComponentEntity: TextComponentEntity) -} - -class TextInputViewController: UIViewController { - - // MARK: - Subviews - - private lazy var inputTextView: UITextView = { - var textView = UITextView() - textView.font = .systemFont(ofSize: 16) - textView.autocapitalizationType = .words - textView.isScrollEnabled = true - textView.backgroundColor = .clear - textView.textAlignment = .center - textView.delegate = self - return textView - }() - - // MARK: - Private Properties - - private var widthRatioFromAbsolute: CGFloat? - private var heightRatioFromAbsolute: CGFloat? - - private var cancellables: Set = [] - private var viewModel: TextInputViewModel? - private weak var delegate: TextInputViewControllerDelegate? - - // MARK: - Initializers - - convenience init( - textInputViewModel: TextInputViewModel, - delegate: TextInputViewControllerDelegate?, - widthRatioFromAbsolute: CGFloat?, - heightRatioFromAbsolute: CGFloat? - ) { - self.init(nibName: nil, bundle: nil) - self.viewModel = textInputViewModel - self.delegate = delegate - self.widthRatioFromAbsolute = widthRatioFromAbsolute - self.heightRatioFromAbsolute = heightRatioFromAbsolute - self.modalPresentationStyle = .overFullScreen - } - - // MARK: - Lifecycle Methods - - override func viewDidLoad() { - super.viewDidLoad() - - self.configureUI() - self.bindUI() - } - - private func bindUI() { - self.view.publisher(for: UITapGestureRecognizer()) - .sink { [weak self] _ in - guard let self = self, - let textComponenetEntity = self.viewModel?.inputViewEditingDidEnd( - input: self.inputTextView.text, - contentSize: self.computeSizeToAbsolute(with: self.inputTextView.contentSize), - fontSize: 16, - color: .black - ) else { return } - self.delegate?.textInputDidEndEditing(textComponenetEntity) - self.dismiss(animated: false) - }.store(in: &self.cancellables) - } - - // MARK: - Helpers - - private func configureUI() { - self.view.backgroundColor = .black.withAlphaComponent(0.3) - - self.view.addSubview(self.inputTextView) - self.inputTextView.snp.makeConstraints { make in - make.centerX.equalToSuperview() - make.bottom.equalTo(self.view.snp.centerY) - } - self.inputTextView.becomeFirstResponder() - } - - // MARK: - Private Methods - - private func computeSizeToAbsolute(with size: CGSize) -> CGSize { - let computedWidth = size.width / ( self.widthRatioFromAbsolute ?? 0.0 ) - let computedHeight = size.height / ( self.heightRatioFromAbsolute ?? 0.0 ) - return CGSize(width: computedWidth, height: computedHeight) - } -} - -extension TextInputViewController: UITextViewDelegate { - func textViewDidBeginEditing(_ textView: UITextView) { - textView.text = "내용을 입력하세요" - textView.textColor = .darkGray - textView.sizeToFit() - textView.snp.makeConstraints { make in - make.width.equalTo(textView.contentSize.width) - make.height.equalTo(textView.contentSize.height) - } - } - - func textViewDidChange(_ textView: UITextView) { - if textView.textColor == .darkGray, - let input = textView.text.last { - textView.textColor = .black - textView.text = String(input) - } else if textView.textColor == .black, textView.text.isEmpty { - textView.textColor = .darkGray - textView.text = "내용을 입력하세요" - } - let maximumWidth: CGFloat = self.view.frame.width - 40 - let newSize = textView.sizeThatFits(CGSize(width: maximumWidth, height: CGFloat.greatestFiniteMagnitude)) - - textView.snp.updateConstraints { make in - make.width.equalTo(newSize.width) - make.height.equalTo(newSize.height) - } - } -} diff --git a/Doolda/Doolda/UI/Views/.gitkeep b/Doolda/Doolda/UI/Views/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Doolda/Doolda/UI/Views/DiaryPageView.swift b/Doolda/Doolda/UI/Views/DiaryPageView.swift deleted file mode 100644 index baf55e8f..00000000 --- a/Doolda/Doolda/UI/Views/DiaryPageView.swift +++ /dev/null @@ -1,228 +0,0 @@ -// -// DiaryPageView.swift -// Doolda -// -// Created by Seunghun Yang on 2021/11/15. -// - -import Combine -import UIKit - -import SnapKit - -class DiaryPageViewCell: UICollectionViewCell { - - // MARK: - Static Properties - - static let cellIdentifier: String = "DiaryPageViewCell" - - // MARK: - Subviews - - private lazy var pageView: UIView = UIView() - private lazy var layeredView: UIView = UIView() - private lazy var dayLabel: UILabel = { - let label = UILabel() - label.font = .systemFont(ofSize: 100) - label.adjustsFontSizeToFitWidth = true - label.textColor = .black - return label - }() - - private lazy var monthLabel: UILabel = { - let label = UILabel() - label.font = .systemFont(ofSize: 100) - label.adjustsFontSizeToFitWidth = true - label.textColor = .black - return label - }() - - private lazy var dayLabelUnderBar: UIView = { - let view = UIView() - view.backgroundColor = .black - return view - }() - - private lazy var activityIndicator: UIActivityIndicatorView = { - let activityIndicator = UIActivityIndicatorView() - activityIndicator.style = .large - return activityIndicator - }() - - // MARK: - Public Properties - - var timestamp: Date? { - didSet { - guard let timestamp = timestamp else { return } - self.dayLabel.text = DateFormatter.dayFormatter.string(from: timestamp) - self.monthLabel.text = DateFormatter.monthNameFormatter.string(from: timestamp).uppercased() - } - } - - // MARK: - Private Properties - - private var cancellables: Set = [] - private var widthRatioFromAbsolute: CGFloat { - return self.frame.size.width / 1700.0 - } - - private var heightRatioFromAbsolute: CGFloat { - return self.frame.size.height / 3000.0 - } - - private var rawPageEntity: RawPageEntity? { - didSet { self.drawPage() } - } - - // MARK: - Initializers - - override init(frame: CGRect) { - super.init(frame: frame) - self.configureUI() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - self.configureUI() - } - - // MARK: - Lifecycle Methods - - override func layoutSubviews() { - self.drawPage() - } - - // MARK: - Helpers - - private func configureUI() { - self.clipsToBounds = true - self.layer.borderWidth = 1 - self.layer.cornerRadius = 4 - self.layer.borderColor = UIColor.black.cgColor - - self.addSubview(self.pageView) - self.pageView.snp.makeConstraints { make in - make.edges.equalToSuperview() - } - - self.addSubview(self.layeredView) - self.layeredView.snp.makeConstraints { make in - make.edges.equalToSuperview() - } - - self.layeredView.addSubview(self.activityIndicator) - self.activityIndicator.snp.makeConstraints { make in - make.center.equalToSuperview() - } - - self.layeredView.addSubview(self.dayLabel) - self.dayLabel.snp.makeConstraints { make in - make.top.equalToSuperview().offset(16) - make.leading.equalToSuperview().offset(16) - make.width.equalTo(self.snp.width).dividedBy(7.0) - make.height.equalTo(self.dayLabel.snp.width) - } - - self.layeredView.addSubview(self.dayLabelUnderBar) - self.dayLabelUnderBar.snp.makeConstraints { make in - make.height.equalTo(1) - make.width.equalTo(self.dayLabel.snp.width) - make.top.equalTo(self.dayLabel.snp.bottom) - make.centerX.equalTo(self.dayLabel.snp.centerX) - } - - self.layeredView.addSubview(self.monthLabel) - self.monthLabel.snp.makeConstraints { make in - make.centerY.equalTo(self.dayLabel.snp.centerY) - make.leading.equalTo(self.dayLabel.snp.trailing).offset(5) - make.width.equalTo(self.snp.width).dividedBy(7.0) - make.height.equalTo(self.monthLabel.snp.width) - } - } - - private func computePointFromAbsolute(at point: CGPoint) -> CGPoint { - let computedX = point.x * self.widthRatioFromAbsolute - let computedY = point.y * self.heightRatioFromAbsolute - return CGPoint(x: computedX, y: computedY) - } - - private func computeSizeFromAbsolute(with size: CGSize) -> CGSize { - let computedWidth = size.width * self.widthRatioFromAbsolute - let computedHeight = size.height * self.widthRatioFromAbsolute - return CGSize(width: computedWidth, height: computedHeight) - } - - private func drawPage() { - guard let rawPage = self.rawPageEntity else { return } - self.pageView.subviews.forEach { $0.removeFromSuperview() } - - self.backgroundColor = UIColor(cgColor: rawPage.backgroundType.rawValue) - for componentEntity in rawPage.components { - let computedCGRect = CGRect( - origin: self.computePointFromAbsolute(at: componentEntity.origin), - size: self.computeSizeFromAbsolute(with: componentEntity.frame.size) - ) - - switch componentEntity { - case let photoComponentEtitiy as PhotoComponentEntity: - let photoComponentView = UIImageView(frame: computedCGRect) - photoComponentView.kf.setImage(with: photoComponentEtitiy.imageUrl) - self.pageView.addSubview(photoComponentView) - let transform = CGAffineTransform.identity - .rotated(by: componentEntity.angle) - .scaledBy(x: componentEntity.scale, y: componentEntity.scale) - photoComponentView.transform = transform - photoComponentView.layer.shadowColor = UIColor.lightGray.cgColor - photoComponentView.layer.shadowOpacity = 0.3 - photoComponentView.layer.shadowRadius = 10 - photoComponentView.layer.shadowOffset = CGSize(width: -5, height: -5) - case let stickerComponentEntity as StickerComponentEntity: - let stickerComponentView = UIImageView(frame: computedCGRect) - // FIXME: - Hotfix로 기존 코드 주석 처리 - //stickerComponentView.kf.setImage(with: stickerComponentEntity.stickerUrl) - stickerComponentView.kf.setImage(with: self.getStickerUrl(with: stickerComponentEntity.stickerUrl)) - self.pageView.addSubview(stickerComponentView) - let transform = CGAffineTransform.identity - .rotated(by: componentEntity.angle) - .scaledBy(x: componentEntity.scale, y: componentEntity.scale) - stickerComponentView.transform = transform - case let textComponentEntity as TextComponentEntity: - let textComponentView = UITextView(frame: computedCGRect) - textComponentView.backgroundColor = .clear - textComponentView.text = textComponentEntity.text - textComponentView.font = .systemFont(ofSize: textComponentEntity.fontSize) - textComponentView.textColor = UIColor(cgColor: textComponentEntity.fontColor.rawValue) - textComponentView.isScrollEnabled = false - textComponentView.textAlignment = .center - self.pageView.addSubview(textComponentView) - let transform = CGAffineTransform.identity - .rotated(by: componentEntity.angle) - .scaledBy(x: componentEntity.scale, y: componentEntity.scale) - textComponentView.transform = transform - default: - break - } - } - self.activityIndicator.stopAnimating() - self.monthLabel.sizeToFit() - } - - func displayRawPage(with rawPageEntityPublisher: AnyPublisher) { - self.activityIndicator.startAnimating() - self.cancellables = [] - rawPageEntityPublisher - .receive(on: DispatchQueue.main) - .sink(receiveCompletion: { _ in - return - }, receiveValue: { [weak self] rawPageEntity in - self?.rawPageEntity = rawPageEntity - }) - .store(in: &self.cancellables) - } - - // FIXME: - Hotfix에서 임시 구현한 함수 - private func getStickerUrl(with url: URL) -> URL? { - guard var stickerName = url.absoluteString.split(separator: "/").last else { return nil } - let name = String(stickerName.replacingOccurrences(of: ".png", with: "")) - return Bundle.main.url(forResource: name, withExtension: "png") - } -} diff --git a/Doolda/FirebaseNetworkTest/FirebaseNetworkTest.swift b/Doolda/FirebaseNetworkTest/FirebaseNetworkTest.swift deleted file mode 100644 index b991c20a..00000000 --- a/Doolda/FirebaseNetworkTest/FirebaseNetworkTest.swift +++ /dev/null @@ -1,149 +0,0 @@ -// -// FirebaseNetworkTest.swift -// FirebaseNetworkTest -// -// Created by 김민주 on 2021/11/02. -// - -import Combine -import XCTest - -import FirebaseCore -import Firebase - -class FirebaseNetworkTest: XCTestCase { - - private var networkService: FirebaseNetworkServiceProtocol? - - override func setUpWithError() throws { - self.networkService = FirebaseNetworkService() - } - - override func tearDownWithError() throws { - self.networkService = nil - } - - func testGetDummyPairId() throws { - guard let networkService = networkService else { - XCTFail() - return - } - - let expectation = XCTestExpectation() - let publisher = networkService.getDocument(path: "some_uuid_for_user", in: "user") - - let subscriber = publisher - .sink { completion in - switch completion { - case .finished: - expectation.fulfill() - break - case .failure(let error): - XCTFail(error.localizedDescription) - } - } receiveValue: { firebaseDocument in - guard let user = UserDocument(data: firebaseDocument.data) else { - XCTFail("초기화 에러") - return - } - XCTAssertEqual("some_uuid_for_pair", user.pairId) - } - - wait(for: [expectation], timeout: 10) - subscriber.cancel() - } - - func testGetDummyRecentlyEditedUser() throws { - guard let networkService = networkService else { - XCTFail() - return - } - - let expectation = XCTestExpectation() - let publisher = networkService.getDocument(path: "some_uuid_for_pair", in: "pair") - - let subscriber = publisher - .sink { completion in - switch completion { - case .finished: - expectation.fulfill() - break - case .failure(let error): - XCTFail(error.localizedDescription) - } - } receiveValue: { firebaseDocument in - guard let pair = PairDocument(data: firebaseDocument.data) else { - XCTFail("초기화 에러") - return - } - XCTAssertEqual("some_uuid_for_user", pair.recentlyEditedUser) - } - - wait(for: [expectation], timeout: 10) - subscriber.cancel() - } - - func testSetUserDocument() throws { - guard let networkService = networkService else { - XCTFail() - return - } - - let expectation = XCTestExpectation() - let publisher = networkService.setDocument( - path: UUID().uuidString, - in:"user", - with: ["pairId": ""]) - - let subscriber = publisher - .sink { completion in - switch completion { - case .finished: - expectation.fulfill() - break - case .failure(let error): - XCTFail(error.localizedDescription) - } - } receiveValue: { result in - XCTAssertTrue(result, "결과 값이 true가 아닙니다.") - } - - wait(for: [expectation], timeout: 10) - subscriber.cancel() - } - - func testSetPageDocument() throws { - guard let networkService = networkService else { - XCTFail() - return - } - - let expectation = XCTestExpectation() - let publisher = networkService.setDocument( - path: nil, - in: "page", - with: [ - "author": "", - "createdTime": Date(), - "jsonPath": "", - "pairId":"", - ] - ) - - let subscriber = publisher - .sink { completion in - switch completion { - case .finished: - expectation.fulfill() - break - case .failure(let error): - XCTFail(error.localizedDescription) - } - } receiveValue: { result in - XCTAssertTrue(result, "결과 값이 true가 아닙니다.") - } - - wait(for: [expectation], timeout: 10) - subscriber.cancel() - } -} diff --git a/Doolda/GeneratePairIdUseCaseTests/GeneratePairIdUseCaseTests.swift b/Doolda/GeneratePairIdUseCaseTests/GeneratePairIdUseCaseTests.swift deleted file mode 100644 index 3813551f..00000000 --- a/Doolda/GeneratePairIdUseCaseTests/GeneratePairIdUseCaseTests.swift +++ /dev/null @@ -1,102 +0,0 @@ -// -// GeneratePairIdUseCaseTests.swift -// GeneratePairIdUseCaseTests -// -// Created by 정지승 on 2021/11/03. -// - -import Combine -import XCTest - -class GeneratePairIdUseCaseTests: XCTestCase { - private var generatePairIdUseCase: GeneratePairIdUseCase! = nil - - class DummyUserRepository: UserRepositoryProtocol { - enum Errors: Error { - case notImplemented - case failedToPair - } - - static let testSuccessId1 = "2f48f241-9d64-4d16-bf56-70b9d4e0e791" - static let testSuccessId2 = "2f48f241-9d64-4d16-bf56-70b9d4e0e721" - static let testFailureId = "2f48f241-9d64-4d16-bf56-70b9d4e0e711" - - func fetchMyId() -> AnyPublisher { - return Fail(error: Errors.notImplemented).eraseToAnyPublisher() - } - - func fetchPairId(for id: String) -> AnyPublisher { - return Fail(error: Errors.notImplemented).eraseToAnyPublisher() - } - - func saveMyId(_ id: String) -> AnyPublisher { - return Fail(error: Errors.notImplemented).eraseToAnyPublisher() - } - - func savePairId(myId: String, friendId: String, pairId: String) -> AnyPublisher { - let isValid = myId == DummyUserRepository.testSuccessId1 && friendId == DummyUserRepository.testSuccessId2 - return isValid ? Just(myId).setFailureType(to: Error.self).eraseToAnyPublisher() : Fail(error: Errors.failedToPair).eraseToAnyPublisher() - } - - func checkUserIdIsExist(_ id: String) -> AnyPublisher { - return Just(id == DummyUserRepository.testSuccessId1 || id == DummyUserRepository.testSuccessId2).setFailureType(to: Error.self).eraseToAnyPublisher() - } - } - - override func setUpWithError() throws { - self.generatePairIdUseCase = GeneratePairIdUseCase(userRepository: DummyUserRepository()) - } - - override func tearDownWithError() throws { - self.generatePairIdUseCase = nil - } - - func testGeneratePairId_Success() { - self.generatePairIdUseCase.generatePairId( - myId: DummyUserRepository.testSuccessId1, - friendId: DummyUserRepository.testSuccessId2 - ) - - let testExpectation = expectation(description: "") - - _ = self.generatePairIdUseCase.pairedIdPublisher.sink { result in - XCTAssertNil(result) - testExpectation.fulfill() - } - - waitForExpectations(timeout: 5, handler: nil) - } - - func testGeneratePairId_Failure_SameUserId() { - self.generatePairIdUseCase.generatePairId( - myId: DummyUserRepository.testSuccessId1, - friendId: DummyUserRepository.testSuccessId1 - ) - - let testExpectation = expectation(description: "") - - _ = self.generatePairIdUseCase.errorPublisher.sink { error in - XCTAssertNotNil(error) - testExpectation.fulfill() - } - - waitForExpectations(timeout: 5, handler: nil) - } - - func testGeneratePairId_Failure_NotExistUserId() { - self.generatePairIdUseCase.generatePairId( - myId: DummyUserRepository.testSuccessId1, - friendId: DummyUserRepository.testFailureId - ) - - let testExpectation = expectation(description: "") - - _ = self.generatePairIdUseCase.errorPublisher.sink { error in - XCTAssertNotNil(error) - testExpectation.fulfill() - } - - waitForExpectations(timeout: 5, handler: nil) - } - -} diff --git a/Doolda/GetMyIdUseCaseTest/GetMyIdUseCaseTests.swift b/Doolda/GetMyIdUseCaseTest/GetMyIdUseCaseTests.swift deleted file mode 100644 index 5dfa5ea3..00000000 --- a/Doolda/GetMyIdUseCaseTest/GetMyIdUseCaseTests.swift +++ /dev/null @@ -1,134 +0,0 @@ -// -// GetMyIdUseCaseTests.swift -// DooldaTests -// -// Created by 김민주 on 2021/11/02. -// - -import Combine -import XCTest - -class GetMyIdUseCaseTests: XCTestCase { - private var dummyRepository: UserRepositoryProtocol? - private var getMyIdUseCase: GetMyIdUseCase? - private var cancellables: Set = [] - - override func tearDownWithError() throws { - self.getMyIdUseCase = nil - self.dummyRepository = nil - self.cancellables = [] - } - - func testGetMyIdSuceess() throws { - class DummyRepository: UserRepositoryProtocol { - enum Errors: Error { - case notImplemented - } - - func fetchMyId() -> AnyPublisher { - return Just("00000000-0000-0000-0000-000000000001").setFailureType(to: Error.self).eraseToAnyPublisher() - } - - func fetchPairId(for id: String) -> AnyPublisher { - return Fail(error: Errors.notImplemented).eraseToAnyPublisher() - } - - func saveMyId(_ id: String) -> AnyPublisher { - return Fail(error: Errors.notImplemented).eraseToAnyPublisher() - } - - func savePairId(myId: String, friendId: String, pairId: String) -> AnyPublisher { - return Fail(error: Errors.notImplemented).eraseToAnyPublisher() - } - - func checkUserIdIsExist(_ id: String) -> AnyPublisher { - return Fail(error: Errors.notImplemented).eraseToAnyPublisher() - } - } - - let expectation = self.expectation(description: "testGetMyIdSuceess") - var error: Error? - var result: String? - - self.dummyRepository = DummyRepository() - if let repository = self.dummyRepository { - self.getMyIdUseCase = GetMyIdUseCase(userRepository: repository) - } - - guard let usecase = self.getMyIdUseCase else { return XCTFail() } - - usecase.getMyId() - .sink { completion in - guard case .failure(let encounteredError) = completion else { return } - error = encounteredError - expectation.fulfill() - } receiveValue: { id in - result = id - expectation.fulfill() - } - .store(in: &cancellables) - - waitForExpectations(timeout: 5) - - XCTAssertNil(error) - XCTAssertEqual("00000000-0000-0000-0000-000000000001", result, "전달되는 id 값이 예상 id값과 다릅니다.") - } - - func testGetMyIdError() throws { - enum DummyError: Error { - case dummyError - } - - class DummyRepository: UserRepositoryProtocol { - enum Errors: Error { - case notImplemented - } - - func fetchMyId() -> AnyPublisher { - return Fail(error: DummyError.dummyError).eraseToAnyPublisher() - } - - func fetchPairId(for id: String) -> AnyPublisher { - return Fail(error: Errors.notImplemented).eraseToAnyPublisher() - } - - func saveMyId(_ id: String) -> AnyPublisher { - return Fail(error: Errors.notImplemented).eraseToAnyPublisher() - } - - func savePairId(myId: String, friendId: String, pairId: String) -> AnyPublisher { - return Fail(error: Errors.notImplemented).eraseToAnyPublisher() - } - - func checkUserIdIsExist(_ id: String) -> AnyPublisher { - return Fail(error: Errors.notImplemented).eraseToAnyPublisher() - } - } - - let expectation = self.expectation(description: "testGetMyIdError") - var error: DummyError? - var result: String? - - self.dummyRepository = DummyRepository() - if let repository = self.dummyRepository { - self.getMyIdUseCase = GetMyIdUseCase(userRepository: repository) - } - - guard let usecase = self.getMyIdUseCase else { return XCTFail() } - - usecase.getMyId() - .sink { completion in - guard case .failure(let encounteredError) = completion else { return } - error = encounteredError as? DummyError - expectation.fulfill() - } receiveValue: { id in - result = id - } - .store(in: &cancellables) - - waitForExpectations(timeout: 5) - - XCTAssertEqual(error, DummyError.dummyError) - XCTAssertNil(result) - } -} diff --git a/Doolda/GetPairIdUseCaseTest/GetPairIdUseCaseTest.swift b/Doolda/GetPairIdUseCaseTest/GetPairIdUseCaseTest.swift deleted file mode 100644 index c35e7b01..00000000 --- a/Doolda/GetPairIdUseCaseTest/GetPairIdUseCaseTest.swift +++ /dev/null @@ -1,131 +0,0 @@ -// -// GetPairIdUseCaseTest.swift -// GetPairIdUseCaseTest -// -// Created by Seunghun Yang on 2021/11/03. -// - -import Combine -import XCTest - -class GetPairIdUseCaseTest: XCTestCase { - private var getPairIdUseCase: GetPairIdUseCase? - private var cancellables: Set = [] - - class DummyUserRepository: UserRepositoryProtocol { - static let notExistingUser = "00000000-0000-0000-0000-000000000000" - static let userWithoutPair = "00000000-0000-0000-0000-000000000001" - static let userWithPair = "00000000-0000-0000-0000-000000000002" - static let pairId = "00000000-0000-0000-0000-000000000003" - - enum Errors: LocalizedError { - case userDoesNotExists - case unknownError - case notImplemented - } - - func fetchMyId() -> AnyPublisher { - return Fail(error: Errors.notImplemented).eraseToAnyPublisher() - } - - func fetchPairId(for id: String) -> AnyPublisher { - switch id { - case DummyUserRepository.notExistingUser: - return Fail(error: Errors.userDoesNotExists).eraseToAnyPublisher() - case DummyUserRepository.userWithPair: - return Just(DummyUserRepository.pairId).setFailureType(to: Error.self).eraseToAnyPublisher() - case DummyUserRepository.userWithoutPair: - return Just("").setFailureType(to: Error.self).eraseToAnyPublisher() - default: - return Fail(error: Errors.unknownError).eraseToAnyPublisher() - } - } - - func saveMyId(_ id: String) -> AnyPublisher { - return Fail(error: Errors.notImplemented).eraseToAnyPublisher() - } - - func savePairId(myId: String, friendId: String, pairId: String) -> AnyPublisher { - return Fail(error: Errors.notImplemented).eraseToAnyPublisher() - } - - func checkUserIdIsExist(_ id: String) -> AnyPublisher { - return Fail(error: Errors.notImplemented).eraseToAnyPublisher() - } - } - - override func setUpWithError() throws { - self.getPairIdUseCase = GetPairIdUseCase(userRepository: DummyUserRepository()) - } - - override func tearDownWithError() throws { - self.getPairIdUseCase = nil - self.cancellables = [] - } - - func testGetPairIdSuccessForUserWithPair() { - let expectation = self.expectation(description: "testGetPairIdSuccessForUserWithPair") - var error: Error? - var result: String? - - self.getPairIdUseCase?.getPairId(for: DummyUserRepository.userWithPair) - .sink(receiveCompletion: { completion in - guard case .failure(let encounteredError) = completion else { return } - error = encounteredError - expectation.fulfill() - }, receiveValue: { pairId in - result = pairId - expectation.fulfill() - }) - .store(in: &cancellables) - - waitForExpectations(timeout: 5) - - XCTAssertNil(error) - XCTAssertEqual(result, DummyUserRepository.pairId) - } - - func testGetPairIdSuccessForUserWithoutPair() { - let expectation = self.expectation(description: "testGetPairIdSuccessForUserWithoutPair") - var error: Error? - var result: String? - - self.getPairIdUseCase?.getPairId(for: DummyUserRepository.userWithoutPair) - .sink(receiveCompletion: { completion in - guard case .failure(let encounteredError) = completion else { return } - error = encounteredError - expectation.fulfill() - }, receiveValue: { pairId in - result = pairId - expectation.fulfill() - }) - .store(in: &cancellables) - - waitForExpectations(timeout: 5) - - XCTAssertNil(error) - XCTAssertEqual(result, "") - } - - func testGetPairIdSuccessForNotExistingUser() { - let expectation = self.expectation(description: "testGetPairIdSuccessForNotExistingUser") - var error: DummyUserRepository.Errors? - var result: String? - - self.getPairIdUseCase?.getPairId(for: DummyUserRepository.notExistingUser) - .sink(receiveCompletion: { completion in - guard case .failure(let encounteredError) = completion else { return XCTFail() } - error = encounteredError as? DummyUserRepository.Errors - expectation.fulfill() - }, receiveValue: { pairId in - result = pairId - expectation.fulfill() - }) - .store(in: &cancellables) - - waitForExpectations(timeout: 5) - - XCTAssertEqual(error, DummyUserRepository.Errors.userDoesNotExists) - XCTAssertNil(result) - } -} diff --git a/Doolda/ImageComposeUseCaseTest/ImageComposeUseCaseTest.swift b/Doolda/ImageComposeUseCaseTest/ImageComposeUseCaseTest.swift deleted file mode 100644 index ef736da7..00000000 --- a/Doolda/ImageComposeUseCaseTest/ImageComposeUseCaseTest.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// ImageComposeUseCaseTest.swift -// ImageComposeUseCaseTest -// -// Created by Dozzing on 2021/11/09. -// - -import XCTest - -class ImageComposeUseCaseTest: XCTestCase { - var imageComposeUseCase: ImageComposeUseCase? - - override func setUpWithError() throws { - self.imageComposeUseCase = ImageComposeUseCase() - } - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } - -} diff --git a/Doolda/ImageUseCaseTest/ImageUseCaseTest.swift b/Doolda/ImageUseCaseTest/ImageUseCaseTest.swift new file mode 100644 index 00000000..2f830ed6 --- /dev/null +++ b/Doolda/ImageUseCaseTest/ImageUseCaseTest.swift @@ -0,0 +1,188 @@ +// +// ImageUseCaseTest.swift +// ImageUseCaseTest +// +// Created by Dozzing on 2021/11/30. +// + +import Combine +import CoreImage +import XCTest + +class ImageUseCaseTest: XCTestCase { + private var cancellables: Set = [] + + override func tearDownWithError() throws { + self.cancellables.removeAll() + } + + func testSaveLocalSuccess() { + guard let dummyUrl = URL(string: "http://naver.com"), + let dummyImageUrl = URL(string: "https://user-images.githubusercontent.com/61934702/132952591-74350741-dac8-4295-9f72-33e9f382cb46.png"), + let dummyImage: CIImage = CIImage(contentsOf: dummyImageUrl) else { + XCTFail("Fail to initailize") + return + } + + let mockImageRepository = DummyImageRepository(dummyUrl: dummyUrl) + let imageUseCase = ImageUseCase(imageRepository: mockImageRepository) + + let expectation = self.expectation(description: "testImageUseCase") + + var resultUrl: URL? + + imageUseCase.saveLocal(image: dummyImage) + .sink { completion in + guard case .failure(let error) = completion else { return } + XCTFail("\(error.localizedDescription)") + expectation.fulfill() + } receiveValue: { url in + resultUrl = url + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 5) + + XCTAssertEqual(dummyUrl, resultUrl) + } + + func testSaveLocalFailureDueToRepository() { + guard let dummyUrl = URL(string: "http://naver.com"), + let dummyImageUrl = URL(string: "https://user-images.githubusercontent.com/61934702/132952591-74350741-dac8-4295-9f72-33e9f382cb46.png"), + let dummyImage: CIImage = CIImage(contentsOf: dummyImageUrl) else { + XCTFail("Fail to initailize") + return + } + + let mockImageRepository = DummyImageRepository(isSuccessMode: false, dummyUrl: dummyUrl) + let imageUseCase = ImageUseCase(imageRepository: mockImageRepository) + + let expectation = self.expectation(description: "testImageUseCase") + + imageUseCase.saveLocal(image: dummyImage) + .sink { completion in + guard case .failure(let error) = completion else { return } + XCTAssertNotNil(error) + expectation.fulfill() + } receiveValue: { _ in + XCTFail() + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 5) + } + + func testSaveLocalFailureDueToNilImageData() { + guard let dummyUrl = URL(string: "http://naver.com") else { + XCTFail("Fail to initailize") + return + } + + let mockImageRepository = DummyImageRepository(dummyUrl: dummyUrl) + let imageUseCase = ImageUseCase(imageRepository: mockImageRepository) + let dummyImage: CIImage = CIImage(color: .blue) + + let expectation = self.expectation(description: "testImageUseCase") + + imageUseCase.saveLocal(image: dummyImage) + .sink { completion in + guard case .failure(let error) = completion else { return } + XCTAssertEqual(error.localizedDescription, ImageUseCaseError.nilImageData.localizedDescription) + expectation.fulfill() + } receiveValue: { _ in + XCTFail() + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 5) + } + + func testSaveRemoteSuccess() { + guard let dummyUrl = URL(string: "http://naver.com"), + let dummyImageUrl = URL(string: "https://user-images.githubusercontent.com/61934702/132952591-74350741-dac8-4295-9f72-33e9f382cb46.png") else { + XCTFail("Fail to initailize") + return + } + + let mockImageRepository = DummyImageRepository(dummyUrl: dummyUrl) + let imageUseCase = ImageUseCase(imageRepository: mockImageRepository) + let dummyUser = User(id: DDID(), pairId: DDID(), friendId: DDID()) + + let expectation = self.expectation(description: "testImageUseCase") + + var resultUrl: URL? + + imageUseCase.saveRemote(for: dummyUser, localUrl: dummyImageUrl) + .sink { completion in + guard case .failure(let error) = completion else { return } + XCTFail("\(error.localizedDescription)") + expectation.fulfill() + } receiveValue: { url in + resultUrl = url + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 5) + + XCTAssertEqual(dummyUrl, resultUrl) + } + + func testSaveRemoteFailureDueToWrongImageUrl() { + guard let dummyUrl = URL(string: "http://naver.com"), + let dummyImageUrl = URL(string: "file://naver.com") else { + XCTFail("Fail to initailize") + return + } + + let mockImageRepository = DummyImageRepository(dummyUrl: dummyUrl) + let imageUseCase = ImageUseCase(imageRepository: mockImageRepository) + let dummyUser = User(id: DDID(), pairId: DDID(), friendId: DDID()) + + let expectation = self.expectation(description: "testImageUseCase") + + imageUseCase.saveRemote(for: dummyUser, localUrl: dummyImageUrl) + .sink { completion in + guard case .failure(let error) = completion else { return } + XCTAssertEqual(error.localizedDescription, ImageUseCaseError.failToLoadImageFromUrl.localizedDescription) + expectation.fulfill() + } receiveValue: { _ in + XCTFail() + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 5) + } + + func testSaveRemoteFailureDueToRepository() { + guard let dummyUrl = URL(string: "http://naver.com"), + let dummyImageUrl = URL(string: "http://naver.com") else { + XCTFail("Fail to initailize") + return + } + + let mockImageRepository = DummyImageRepository(isSuccessMode: false, dummyUrl: dummyUrl) + let imageUseCase = ImageUseCase(imageRepository: mockImageRepository) + let dummyUser = User(id: DDID(), pairId: DDID(), friendId: DDID()) + + let expectation = self.expectation(description: "testImageUseCase") + + imageUseCase.saveRemote(for: dummyUser, localUrl: dummyImageUrl) + .sink { completion in + guard case .failure(let error) = completion else { return } + XCTAssertNotNil(error) + expectation.fulfill() + } receiveValue: { _ in + XCTFail() + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 5) + } + +} diff --git a/Doolda/RefreshPairIdUseCaseTest/RefreshPairIdUseCaseTest.swift b/Doolda/RefreshPairIdUseCaseTest/RefreshPairIdUseCaseTest.swift deleted file mode 100644 index 4121a996..00000000 --- a/Doolda/RefreshPairIdUseCaseTest/RefreshPairIdUseCaseTest.swift +++ /dev/null @@ -1,135 +0,0 @@ -// -// RefreshPairIdUseCaseTest.swift -// RefreshPairIdUseCaseTest -// -// Created by 정지승 on 2021/11/04. -// - -import Combine -import XCTest - -final class RefreshPairIdUseCaseTest: XCTestCase { - final class DummyUserRepository: UserRepositoryProtocol { - enum TestCase { - case success - case failureNotExist - case failurePairIdIsEmpty - } - - enum TestError: Error { - case notImplemented - case notExistId - } - - private let testCase: TestCase - - init(testCase: TestCase) { - self.testCase = testCase - } - - func fetchMyId() -> AnyPublisher { - Just("").setFailureType(to: Error.self).eraseToAnyPublisher() - } - - func fetchPairId(for id: String) -> AnyPublisher { - switch testCase { - case .success: - return Just(UUID().uuidString).setFailureType(to: Error.self).eraseToAnyPublisher() - case .failureNotExist: - return Fail(error: TestError.notExistId).eraseToAnyPublisher() - case .failurePairIdIsEmpty: - return Just("").setFailureType(to: Error.self).eraseToAnyPublisher() - } - } - - func saveMyId(_ id: String) -> AnyPublisher { - Fail(error: TestError.notImplemented).eraseToAnyPublisher() - } - - func savePairId(myId: String, friendId: String, pairId: String) -> AnyPublisher { - Fail(error: TestError.notImplemented).eraseToAnyPublisher() - } - - func checkUserIdIsExist(_ id: String) -> AnyPublisher { - Fail(error: TestError.notImplemented).eraseToAnyPublisher() - } - } - - private var cancellables: Set = [] - - override func tearDownWithError() throws { - self.cancellables.forEach({ $0.cancel() }) - } - - func testRefreshPairId_success() { - let refreshPairIdUseCase = RefreshPairIdUseCase(userRepository: DummyUserRepository(testCase: .success)) - let expectation = expectation(description: "testRefreshPairId_success") - - refreshPairIdUseCase.pairIdPublisher - .dropFirst() - .sink { pairId in - XCTAssertNotNil(pairId) - expectation.fulfill() - } - .store(in: &self.cancellables) - - refreshPairIdUseCase.errorPublisher - .dropFirst() - .sink { error in - XCTAssertNil(error) - expectation.fulfill() - } - .store(in: &self.cancellables) - - refreshPairIdUseCase.refreshPairId(for: UUID().uuidString) - waitForExpectations(timeout: 5) - } - - func testRefreshPairId_failure_notExist() { - let refreshPairIdUseCase = RefreshPairIdUseCase(userRepository: DummyUserRepository(testCase: .failureNotExist)) - let expectation = expectation(description: "testRefreshPairId_failure_notExist") - - refreshPairIdUseCase.pairIdPublisher - .dropFirst() - .sink { pairId in - XCTAssertNil(pairId) - expectation.fulfill() - } - .store(in: &self.cancellables) - - refreshPairIdUseCase.errorPublisher - .dropFirst() - .sink { error in - XCTAssertNotNil(error) - expectation.fulfill() - } - .store(in: &self.cancellables) - - refreshPairIdUseCase.refreshPairId(for: UUID().uuidString) - waitForExpectations(timeout: 5) - } - - func testRefreshPairId_failure_isEmpty() { - let refreshPairIdUseCase = RefreshPairIdUseCase(userRepository: DummyUserRepository(testCase: .failurePairIdIsEmpty)) - let expectation = expectation(description: "testRefreshPairId_failure_isEmpty") - - refreshPairIdUseCase.pairIdPublisher - .dropFirst() - .sink { pairId in - XCTAssertEqual("", pairId) - expectation.fulfill() - } - .store(in: &self.cancellables) - - refreshPairIdUseCase.errorPublisher - .dropFirst() - .sink { error in - XCTAssertNotNil(error) - expectation.fulfill() - } - .store(in: &self.cancellables) - - refreshPairIdUseCase.refreshPairId(for: UUID().uuidString) - waitForExpectations(timeout: 5) - } -} diff --git a/Doolda/SplashViewModelTest/SplashViewModelTest.swift b/Doolda/SplashViewModelTest/SplashViewModelTest.swift deleted file mode 100644 index 070920a6..00000000 --- a/Doolda/SplashViewModelTest/SplashViewModelTest.swift +++ /dev/null @@ -1,174 +0,0 @@ -// -// SplashViewModelTest.swift -// SplashViewModelTest -// -// Created by Dozzing on 2021/11/04. -// - -import Combine -import XCTest - -class SplashViewModelTest: XCTestCase { - - private var splashViewModel: SplashViewModel? - private var dummyMyId: String? - private var dummyPairId: String? - - enum DummyError: Error { - case dummyError - } - - class DummyCoordinator: SplashViewCoordinatorDelegate { - enum Result { - case notPaired - case alreadyPaired - } - - var result: Result? - var myId: String? - var pairId: String? - - func userNotPaired(myId: String) { - self.result = .notPaired - self.myId = myId - } - - func userAlreadyPaired(myId: String, pairId: String) { - self.result = .alreadyPaired - self.myId = myId - self.pairId = pairId - } - } - - class DummyGetMyIdUseCase: GetMyIdUseCaseProtocol { - private var mockMyId: String? - - init(mockMyId: String?) { - self.mockMyId = mockMyId - } - - func getMyId() -> AnyPublisher { - guard let myId = self.mockMyId else { - return Result.Publisher(DummyError.dummyError).eraseToAnyPublisher() - } - return Result.Publisher(myId).eraseToAnyPublisher() - } - } - - class DummyGetPairIdUseCase: GetPairIdUseCaseProtocol { - private var mockPairId: String? - - init(mockPariId: String?) { - self.mockPairId = mockPariId - } - - func getPairId(for id: String) -> AnyPublisher { - guard let pairId = self.mockPairId else { - return Result.Publisher(DummyError.dummyError).eraseToAnyPublisher() - } - return Result.Publisher(pairId).eraseToAnyPublisher() - } - } - - class DummyGenerateMyIdUseCase: GenerateMyIdUseCaseProtocol { - var savedIdPublisher: Published.Publisher { self.$savedId } - var errorPublisher: Published.Publisher { self.$error } - - @Published private var savedId: String? - @Published private var error: Error? - - private var mockMyId: String? - - init(mockMyId: String?) { - self.mockMyId = mockMyId - } - - func generate() { - guard let myId = self.mockMyId else { - self.error = DummyError.dummyError - return - } - self.savedId = myId - } - } - - override func setUpWithError() throws { - self.splashViewModel = nil - self.dummyMyId = "00000000-0000-0000-0000-000000000001" - self.dummyPairId = "00000000-0000-0000-0000-000000000002" - } - - func testGetMyIdFail_GenerateMyIdFail() throws { - let mockCoordinatorDelegate = DummyCoordinator() - let mockGetMyIdUseCase = DummyGetMyIdUseCase(mockMyId: nil) - let mockGetPairIdUseCase = DummyGetPairIdUseCase(mockPariId: nil) - let mockGenerateMyIdUseCase = DummyGenerateMyIdUseCase(mockMyId: nil) - self.splashViewModel = SplashViewModel( - coordinatorDelegate: mockCoordinatorDelegate, - getMyIdUseCase: mockGetMyIdUseCase, - getPairIdUseCase: mockGetPairIdUseCase, - generateMyIdUseCase: mockGenerateMyIdUseCase - ) - - self.splashViewModel?.prepareUserInfo() - XCTAssertNotNil(self.splashViewModel?.error, "Incorrect error") - XCTAssertNil(mockCoordinatorDelegate.result, "Incorrect coordinator result") - XCTAssertNil(mockCoordinatorDelegate.myId, "Incorrect myId") - XCTAssertNil(mockCoordinatorDelegate.pairId, "Incorrect pairId") - } - - func testGetMyIdFail_GenerateMyIdSuccess() throws { - let mockCoordinatorDelegate = DummyCoordinator() - let mockGetMyIdUseCase = DummyGetMyIdUseCase(mockMyId: nil) - let mockGetPairIdUseCase = DummyGetPairIdUseCase(mockPariId: nil) - let mockGenerateMyIdUseCase = DummyGenerateMyIdUseCase(mockMyId: self.dummyMyId) - self.splashViewModel = SplashViewModel( - coordinatorDelegate: mockCoordinatorDelegate, - getMyIdUseCase: mockGetMyIdUseCase, - getPairIdUseCase: mockGetPairIdUseCase, - generateMyIdUseCase: mockGenerateMyIdUseCase - ) - - self.splashViewModel?.prepareUserInfo() - XCTAssertEqual(mockCoordinatorDelegate.result, .notPaired, "Incorrect coordinator result") - XCTAssertEqual(mockCoordinatorDelegate.myId, self.dummyMyId, "Incorrect myId") - XCTAssertNil(mockCoordinatorDelegate.pairId, "Incorrect pairId") - } - - func testGetMyIdSuccess_GetPairIdFail() throws { - let mockCoordinatorDelegate = DummyCoordinator() - let mockGetMyIdUseCase = DummyGetMyIdUseCase(mockMyId: self.dummyMyId) - let mockGetPairIdUseCase = DummyGetPairIdUseCase(mockPariId: nil) - let mockGenerateMyIdUseCase = DummyGenerateMyIdUseCase(mockMyId: self.dummyMyId) - self.splashViewModel = SplashViewModel( - coordinatorDelegate: mockCoordinatorDelegate, - getMyIdUseCase: mockGetMyIdUseCase, - getPairIdUseCase: mockGetPairIdUseCase, - generateMyIdUseCase: mockGenerateMyIdUseCase - ) - - self.splashViewModel?.prepareUserInfo() - XCTAssertEqual(mockCoordinatorDelegate.result, .notPaired, "Incorrect coordinator result") - XCTAssertEqual(mockCoordinatorDelegate.myId, self.dummyMyId, "Incorrect myId") - XCTAssertNil(mockCoordinatorDelegate.pairId, "Incorrect pairId") - } - - func testGetMyIdSuccess_GetPairIdSuccess() throws { - let mockCoordinatorDelegate = DummyCoordinator() - let mockGetMyIdUseCase = DummyGetMyIdUseCase(mockMyId: self.dummyMyId) - let mockGetPairIdUseCase = DummyGetPairIdUseCase(mockPariId: self.dummyPairId) - let mockGenerateMyIdUseCase = DummyGenerateMyIdUseCase(mockMyId: self.dummyMyId) - self.splashViewModel = SplashViewModel( - coordinatorDelegate: mockCoordinatorDelegate, - getMyIdUseCase: mockGetMyIdUseCase, - getPairIdUseCase: mockGetPairIdUseCase, - generateMyIdUseCase: mockGenerateMyIdUseCase - ) - - self.splashViewModel?.prepareUserInfo() - XCTAssertEqual(mockCoordinatorDelegate.result, .alreadyPaired, "Incorrect coordinator result") - XCTAssertEqual(mockCoordinatorDelegate.myId, self.dummyMyId, "Incorrect myId") - XCTAssertEqual(mockCoordinatorDelegate.pairId, self.dummyPairId, "Incorrect pairId") - } - -} diff --git a/Doolda/Tests/CheckMyTurnUseCaseTest/CheckMyTurnUseCaseTest.swift b/Doolda/Tests/CheckMyTurnUseCaseTest/CheckMyTurnUseCaseTest.swift new file mode 100644 index 00000000..ac440c0f --- /dev/null +++ b/Doolda/Tests/CheckMyTurnUseCaseTest/CheckMyTurnUseCaseTest.swift @@ -0,0 +1,67 @@ +// +// CheckMyTurnUseCaseTest.swift +// CheckMyTurnUseCaseTest +// +// Created by 정지승 on 2021/11/30. +// + +import Combine +import XCTest + +class CheckMyTurnUseCaseTest: XCTestCase { + private var cancellables: Set = [] + + override func tearDownWithError() throws { + self.cancellables = [] + } + + func testCheckMyTurnSuccess() { + let pairRepository = DummyPairRepository(isSuccessMode: true) + pairRepository.isCheckTurnMode = true + let checkMyTurnUseCase = CheckMyTurnUseCase(pairRepository: pairRepository) + let user = User(id: DDID(), pairId: nil, friendId: nil) + + let expectation = expectation(description: #function) + var errorResult: Error? + + checkMyTurnUseCase.checkTurn(for: user) + .sink { completion in + guard case .failure(let error) = completion else { return } + errorResult = error + expectation.fulfill() + } receiveValue: { isMyTurn in + XCTAssertFalse(isMyTurn) + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 10) + + XCTAssertNil(errorResult) + } + + func testCheckMyTurnFailure() { + let pairRepository = DummyPairRepository(isSuccessMode: false) + pairRepository.isCheckTurnMode = true + let checkMyTurnUseCase = CheckMyTurnUseCase(pairRepository: pairRepository) + let user = User(id: DDID(), pairId: nil, friendId: nil) + + let expectation = expectation(description: #function) + var errorResult: Error? + + checkMyTurnUseCase.checkTurn(for: user) + .sink { completion in + guard case .failure(let error) = completion else { return } + errorResult = error + expectation.fulfill() + } receiveValue: { isMyTurn in + XCTAssertFalse(isMyTurn) + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 10) + + XCTAssertNotNil(errorResult) + } +} diff --git a/Doolda/Tests/Dummies/DummyError.swift b/Doolda/Tests/Dummies/DummyError.swift new file mode 100644 index 00000000..87a64169 --- /dev/null +++ b/Doolda/Tests/Dummies/DummyError.swift @@ -0,0 +1,13 @@ +// +// DummyError.swift +// EditPageUseCaseTest +// +// Created by 김민주 on 2021/11/30. +// + +import Foundation + +enum DummyError: Error { + case notImplemented + case failed +} diff --git a/Doolda/Tests/Dummies/DummyFCMTokenRepository.swift b/Doolda/Tests/Dummies/DummyFCMTokenRepository.swift new file mode 100644 index 00000000..83a4e4a6 --- /dev/null +++ b/Doolda/Tests/Dummies/DummyFCMTokenRepository.swift @@ -0,0 +1,33 @@ +// +// DummyFCMTokenRepositoryProtocol.swift +// ImageUseCaseTest +// +// Created by Seunghun Yang on 2021/11/30. +// + +import Combine +import Foundation + +class DummyFCMTokenRepository: FCMTokenRepositoryProtocol { + var isSuccessMode: Bool = true + + init(isSuccessMode: Bool) { + self.isSuccessMode = isSuccessMode + } + + func saveToken(for userId: DDID, with token: String) -> AnyPublisher { + if self.isSuccessMode { + return Just(token).setFailureType(to: Error.self).eraseToAnyPublisher() + } else { + return Fail(error: DummyError.failed).eraseToAnyPublisher() + } + } + + func fetchToken(for userId: DDID) -> AnyPublisher { + if self.isSuccessMode { + return Just("DUMMYTOKEN").setFailureType(to: Error.self).eraseToAnyPublisher() + } else { + return Fail(error: DummyError.failed).eraseToAnyPublisher() + } + } +} diff --git a/Doolda/Tests/Dummies/DummyFirebaseMessageRepository.swift b/Doolda/Tests/Dummies/DummyFirebaseMessageRepository.swift new file mode 100644 index 00000000..77aa5914 --- /dev/null +++ b/Doolda/Tests/Dummies/DummyFirebaseMessageRepository.swift @@ -0,0 +1,25 @@ +// +// DummyFirebaseMessageRepository.swift +// ImageUseCaseTest +// +// Created by Seunghun Yang on 2021/11/30. +// + +import Combine +import Foundation + +class DummyFirebaseMessageRepository: FirebaseMessageRepositoryProtocol { + var isSuccessMode: Bool = true + + init(isSuccessMode: Bool) { + self.isSuccessMode = isSuccessMode + } + + func sendMessage(to token: String, title: String, body: String, data: [String : String]) -> AnyPublisher<[String : Any], Error> { + if self.isSuccessMode { + return Just([:]).setFailureType(to: Error.self).eraseToAnyPublisher() + } else { + return Fail(error: DummyError.failed).eraseToAnyPublisher() + } + } +} diff --git a/Doolda/Tests/Dummies/DummyGlobalFontRepository.swift b/Doolda/Tests/Dummies/DummyGlobalFontRepository.swift new file mode 100644 index 00000000..610fff82 --- /dev/null +++ b/Doolda/Tests/Dummies/DummyGlobalFontRepository.swift @@ -0,0 +1,24 @@ +// +// DummyGlobalFontRepository.swift +// GlobalFontUseCaseTest +// +// Created by 김민주 on 2021/11/30. +// + +import Foundation + +class DummyGlobalFontRepository: GlobalFontRepositoryProtocol { + var dummyGlobalFontName: String? + + init(dummyGlobalFontName: String? = nil) { + self.dummyGlobalFontName = dummyGlobalFontName + } + + func saveGlobalFont(as fontName: String) { + self.dummyGlobalFontName = fontName + } + + func getGlobalFont() -> String? { + return self.dummyGlobalFontName + } +} diff --git a/Doolda/Tests/Dummies/DummyImageRepository.swift b/Doolda/Tests/Dummies/DummyImageRepository.swift new file mode 100644 index 00000000..00011c86 --- /dev/null +++ b/Doolda/Tests/Dummies/DummyImageRepository.swift @@ -0,0 +1,35 @@ +// +// DummyImageRepository.swift +// EditPageUseCaseTest +// +// Created by Dozzing on 2021/11/30. +// + +import Combine +import Foundation + +class DummyImageRepository: ImageRepositoryProtocol { + var isSuccessMode: Bool = true + private let dummyUrl: URL + + init(isSuccessMode: Bool = true, dummyUrl: URL) { + self.isSuccessMode = isSuccessMode + self.dummyUrl = dummyUrl + } + + func saveLocal(imageData: Data, fileName: String) -> AnyPublisher { + if isSuccessMode { + return Just(dummyUrl).setFailureType(to: Error.self).eraseToAnyPublisher() + } else { + return Fail(error: DummyError.failed).eraseToAnyPublisher() + } + } + + func saveRemote(user: User, imageData: Data, fileName: String) -> AnyPublisher { + if isSuccessMode { + return Just(dummyUrl).setFailureType(to: Error.self).eraseToAnyPublisher() + } else { + return Fail(error: DummyError.failed).eraseToAnyPublisher() + } + } +} diff --git a/Doolda/Tests/Dummies/DummyImageUseCase.swift b/Doolda/Tests/Dummies/DummyImageUseCase.swift new file mode 100644 index 00000000..94021d91 --- /dev/null +++ b/Doolda/Tests/Dummies/DummyImageUseCase.swift @@ -0,0 +1,36 @@ +// +// DummyImageUseCase.swift +// EditPageUseCaseTest +// +// Created by 김민주 on 2021/11/30. +// + +import Combine +import CoreImage +import Foundation + +class DummyImageUseCase: ImageUseCaseProtocol { + var isSuccessMode: Bool = true + + init(isSuccessMode: Bool) { + self.isSuccessMode = isSuccessMode + } + + func saveLocal(image: CIImage) -> AnyPublisher { + if isSuccessMode { + return Just(URL(string: "https://naver.com")!).setFailureType(to: Error.self).eraseToAnyPublisher() + } else { + return Fail(error: DummyError.failed).eraseToAnyPublisher() + } + } + + func saveRemote(for user: User, localUrl: URL) -> AnyPublisher { + if isSuccessMode { + return Just(URL(string: "https://youtube.com")!).setFailureType(to: Error.self) + .delay(for: .seconds(1), tolerance: nil, scheduler: RunLoop.main, options: nil) + .eraseToAnyPublisher() + } else { + return Fail(error: DummyError.failed).eraseToAnyPublisher() + } + } +} diff --git a/Doolda/Tests/Dummies/DummyPageRepository.swift b/Doolda/Tests/Dummies/DummyPageRepository.swift new file mode 100644 index 00000000..798452d5 --- /dev/null +++ b/Doolda/Tests/Dummies/DummyPageRepository.swift @@ -0,0 +1,39 @@ +// +// DummyPageRepository.swift +// EditPageUseCaseTest +// +// Created by 김민주 on 2021/11/30. +// + +import Combine +import Foundation + +class DummyPageRepository: PageRepositoryProtocol { + var isSuccessMode: Bool = true + + init(isSuccessMode: Bool) { + self.isSuccessMode = isSuccessMode + } + + func updatePage(_ page: PageEntity) -> AnyPublisher { + return Fail(error: DummyError.notImplemented).eraseToAnyPublisher() + } + + func savePage(_ page: PageEntity) -> AnyPublisher { + if isSuccessMode { + return Just(page) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } else { + return Fail(error: DummyError.failed).eraseToAnyPublisher() + } + } + + func fetchPages(for pair: DDID) -> AnyPublisher<[PageEntity], Error> { + if isSuccessMode { + return Just([]).setFailureType(to: Error.self).eraseToAnyPublisher() + } else { + return Fail(error: DummyError.failed).eraseToAnyPublisher() + } + } +} diff --git a/Doolda/Tests/Dummies/DummyPairRepository.swift b/Doolda/Tests/Dummies/DummyPairRepository.swift new file mode 100644 index 00000000..b66e47ca --- /dev/null +++ b/Doolda/Tests/Dummies/DummyPairRepository.swift @@ -0,0 +1,42 @@ +// +// DummyPairRepository.swift +// EditPageUseCaseTest +// +// Created by 김민주 on 2021/11/30. +// + +import Combine +import Foundation + +class DummyPairRepository: PairRepositoryProtocol { + func deletePair(with user: User) -> AnyPublisher { + return Fail(error: DummyError.notImplemented).eraseToAnyPublisher() + } + + var isSuccessMode: Bool = true + var isCheckTurnMode: Bool = false + + init(isSuccessMode: Bool = true) { + self.isSuccessMode = isSuccessMode + } + + func setPairId(with user: User) -> AnyPublisher { + if isSuccessMode { + return Just(DDID()).setFailureType(to: Error.self).eraseToAnyPublisher() + } else { + return Fail(error: DummyError.notImplemented).eraseToAnyPublisher() + } + } + + func setRecentlyEditedUser(with user: User) -> AnyPublisher { + return self.isSuccessMode ? Just(DDID()).setFailureType(to: Error.self).eraseToAnyPublisher() : Fail(error: DummyError.notImplemented).eraseToAnyPublisher() + } + + func fetchRecentlyEditedUser(with user: User) -> AnyPublisher { + if self.isSuccessMode && self.isCheckTurnMode { + return Just(user.id).setFailureType(to: Error.self).eraseToAnyPublisher() + } else { + return Fail(error: DummyError.failed).eraseToAnyPublisher() + } + } +} diff --git a/Doolda/Tests/Dummies/DummyPushNotificationStateRepository.swift b/Doolda/Tests/Dummies/DummyPushNotificationStateRepository.swift new file mode 100644 index 00000000..ef8e7fe6 --- /dev/null +++ b/Doolda/Tests/Dummies/DummyPushNotificationStateRepository.swift @@ -0,0 +1,24 @@ +// +// DummyPushNotificationStateRepository.swift +// PushNotificationStateUseCaseTest +// +// Created by 김민주 on 2021/11/30. +// + +import Foundation + +class DummyPushNotificationStateRepository: PushNotificationStateRepositoryProtocol { + var dummyNotificationState: Bool? + + init(dummyNotificationState: Bool? = nil) { + self.dummyNotificationState = dummyNotificationState + } + + func save( _ state: Bool) { + self.dummyNotificationState = state + } + + func fetch() -> Bool? { + return dummyNotificationState + } +} diff --git a/Doolda/Tests/Dummies/DummyRawPageRepository.swift b/Doolda/Tests/Dummies/DummyRawPageRepository.swift new file mode 100644 index 00000000..fad67d96 --- /dev/null +++ b/Doolda/Tests/Dummies/DummyRawPageRepository.swift @@ -0,0 +1,35 @@ +// +// DummyRawPageRepository.swift +// EditPageUseCaseTest +// +// Created by 김민주 on 2021/11/30. +// + +import Combine +import Foundation + +class DummyRawPageRepository: RawPageRepositoryProtocol { + var isSuccessMode: Bool = true + + init(isSuccessMode: Bool) { + self.isSuccessMode = isSuccessMode + } + + func save(rawPage: RawPageEntity, at folder: String, with name: String) -> AnyPublisher { + if isSuccessMode { + return Just(rawPage) + .setFailureType(to: Error.self) + .eraseToAnyPublisher() + } else { + return Fail(error: DummyError.failed).eraseToAnyPublisher() + } + } + + func fetch(metaData: PageEntity) -> AnyPublisher { + if isSuccessMode { + return Just(RawPageEntity()).setFailureType(to: Error.self).eraseToAnyPublisher() + } else { + return Fail(error: DummyError.failed).eraseToAnyPublisher() + } + } +} diff --git a/Doolda/Tests/Dummies/DummyUserRepository.swift b/Doolda/Tests/Dummies/DummyUserRepository.swift new file mode 100644 index 00000000..6f19d15c --- /dev/null +++ b/Doolda/Tests/Dummies/DummyUserRepository.swift @@ -0,0 +1,73 @@ +// +// DummyUserRepository.swift +// +// EditPageUseCaseTest +// Created by Seunghun Yang on 2021/11/30. +// +import Combine + +import Foundation + +class DummyUserRepository: UserRepositoryProtocol { + var isSuccessMode: Bool = true + var dummyMyId: DDID + + static let firstUserId = DDID() + static let secondUserId = DDID() + static let thirdUserId = DDID() + static let fourthUserId = DDID() + static let fifthUserId = DDID() + static let sixthUserId = DDID() + static let fourthAndFifthUserPairId = DDID() + static let secondAndSixthUserPairId = DDID() + + private var userTable: [DDID: User] = [ + DummyUserRepository.firstUserId: User(id: DummyUserRepository.firstUserId, pairId: nil, friendId: nil), + DummyUserRepository.secondUserId: User(id: DummyUserRepository.secondUserId, pairId: nil, friendId: nil), + DummyUserRepository.fourthUserId: User(id: DummyUserRepository.fourthUserId, pairId: DummyUserRepository.fourthAndFifthUserPairId, friendId: DummyUserRepository.fifthUserId), + DummyUserRepository.fifthUserId: User(id: DummyUserRepository.fifthUserId, pairId: DummyUserRepository.fourthAndFifthUserPairId, friendId: DummyUserRepository.fourthUserId), + DummyUserRepository.sixthUserId: User(id: DummyUserRepository.sixthUserId, pairId: DummyUserRepository.secondAndSixthUserPairId, friendId: DummyUserRepository.secondUserId) + ] + + init(dummyMyId: DDID, isSuccessMode: Bool = true) { + self.dummyMyId = dummyMyId + self.isSuccessMode = isSuccessMode + } + + func setMyId(_ id: DDID) -> AnyPublisher { + return Just(dummyMyId).eraseToAnyPublisher() + } + + func getMyId() -> AnyPublisher { + return self.isSuccessMode ? Just(dummyMyId).eraseToAnyPublisher() : Just(nil).eraseToAnyPublisher() + } + + func setUser(_ user: User) -> AnyPublisher { + if isSuccessMode { + self.userTable[user.id] = user + return Just(user).setFailureType(to: Error.self).eraseToAnyPublisher() + } else { + return Fail(error: DummyError.failed).eraseToAnyPublisher() + } + } + + func resetUser(_ user: User) -> AnyPublisher { + return Fail(error: DummyError.notImplemented).eraseToAnyPublisher() + } + + func fetchUser(_ id: DDID) -> AnyPublisher { + if self.isSuccessMode { + return Just(self.userTable[id]).setFailureType(to: Error.self).eraseToAnyPublisher() + } else { + return Fail(error: DummyError.failed).eraseToAnyPublisher() + } + } + + func fetchUser(_ user: User) -> AnyPublisher { + if self.isSuccessMode { + return Just(self.userTable[user.id]).setFailureType(to: Error.self).eraseToAnyPublisher() + } else { + return Fail(error: DummyError.failed).eraseToAnyPublisher() + } + } +} diff --git a/Doolda/EditPageUseCaseTest/EditPageUseCaseTest.swift b/Doolda/Tests/EditPageUseCaseTest/EditPageUseCaseTest.swift similarity index 91% rename from Doolda/EditPageUseCaseTest/EditPageUseCaseTest.swift rename to Doolda/Tests/EditPageUseCaseTest/EditPageUseCaseTest.swift index 0205cee3..dc16bcb7 100644 --- a/Doolda/EditPageUseCaseTest/EditPageUseCaseTest.swift +++ b/Doolda/Tests/EditPageUseCaseTest/EditPageUseCaseTest.swift @@ -15,83 +15,13 @@ class EditPageUseCaseTest: XCTestCase { self.cancellables = [] } - enum TestError: Error { - case notImplemented - case failed - } - - class DummyImageUseCase: ImageUseCaseProtocol { - var isSuccessMode: Bool = true - - init(isSuccessMode: Bool) { - self.isSuccessMode = isSuccessMode - } - - func saveLocal(image: CIImage) -> AnyPublisher { - return Just(URL(string: "https://naver.com")!).eraseToAnyPublisher() - } - - func saveRemote(for user: User, localUrl: URL) -> AnyPublisher { - if isSuccessMode { - return Just(URL(string: "https://youtube.com")!).setFailureType(to: Error.self) - .delay(for: .seconds(1), tolerance: nil, scheduler: RunLoop.main, options: nil) - .eraseToAnyPublisher() - } else { - return Fail(error: TestError.failed).eraseToAnyPublisher() - } - } - } - - class DummyPageRepository: PageRepositoryProtocol { - var isSuccessMode: Bool = true - - init(isSuccessMode: Bool) { - self.isSuccessMode = isSuccessMode - } - - func savePage(_ page: PageEntity) -> AnyPublisher { - if isSuccessMode { - return Just(page) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } else { - return Fail(error: TestError.failed).eraseToAnyPublisher() - } - } - - func fetchPages(for pair: DDID) -> AnyPublisher<[PageEntity], Error> { - return Fail(error: TestError.notImplemented).eraseToAnyPublisher() - } - } - - class DummyRawPageRepository: RawPageRepositoryProtocol { - var isSuccessMode: Bool = true - - init(isSuccessMode: Bool) { - self.isSuccessMode = isSuccessMode - } - - - func saveRawPage(_ rawPage: RawPageEntity) -> AnyPublisher { - if isSuccessMode { - return Just(rawPage) - .setFailureType(to: Error.self) - .eraseToAnyPublisher() - } else { - return Fail(error: TestError.failed).eraseToAnyPublisher() - } - } - - func fetchRawPage(for path: String) -> AnyPublisher { - return Fail(error: TestError.notImplemented).eraseToAnyPublisher() - } - } - func testSavingSuccess() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testGetMyIdSuceess") @@ -101,10 +31,10 @@ class EditPageUseCaseTest: XCTestCase { editPageUseCase.addComponent(PhotoComponentEntity(frame: .zero, scale: .zero, angle: 0, aspectRatio: 0, imageUrl: URL(string: "https://naver.com")!)) editPageUseCase.addComponent(PhotoComponentEntity(frame: .zero, scale: .zero, angle: 0, aspectRatio: 0, imageUrl: URL(string: "https://naver.com")!)) editPageUseCase.addComponent(PhotoComponentEntity(frame: .zero, scale: .zero, angle: 0, aspectRatio: 0, imageUrl: URL(string: "https://naver.com")!)) - editPageUseCase.savePage(author: User(id: DDID(), pairId: DDID())) + editPageUseCase.savePage(author: User(id: DDID(), pairId: DDID()), metaData: nil) var error: Error? - var result: Void? + var result: Bool? editPageUseCase.errorPublisher .compactMap { $0 } @@ -130,22 +60,24 @@ class EditPageUseCaseTest: XCTestCase { func testSavingFailureDueToImageUseCase() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: false), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testSavingFailureDueToImageUseCase") - editPageUseCase.addComponent(PhotoComponentEntity(frame: .zero, scale: .zero, angle: 0, aspectRatio: 0, imageUrl: URL(string: "https://naver.com")!)) - editPageUseCase.addComponent(PhotoComponentEntity(frame: .zero, scale: .zero, angle: 0, aspectRatio: 0, imageUrl: URL(string: "https://naver.com")!)) - editPageUseCase.addComponent(PhotoComponentEntity(frame: .zero, scale: .zero, angle: 0, aspectRatio: 0, imageUrl: URL(string: "https://naver.com")!)) - editPageUseCase.addComponent(PhotoComponentEntity(frame: .zero, scale: .zero, angle: 0, aspectRatio: 0, imageUrl: URL(string: "https://naver.com")!)) - editPageUseCase.addComponent(PhotoComponentEntity(frame: .zero, scale: .zero, angle: 0, aspectRatio: 0, imageUrl: URL(string: "https://naver.com")!)) - editPageUseCase.savePage(author: User(id: DDID(), pairId: DDID())) + editPageUseCase.addComponent(PhotoComponentEntity(frame: .zero, scale: .zero, angle: 0, aspectRatio: 0, imageUrl: URL(string: "file://naver.com")!)) + editPageUseCase.addComponent(PhotoComponentEntity(frame: .zero, scale: .zero, angle: 0, aspectRatio: 0, imageUrl: URL(string: "file://naver.com")!)) + editPageUseCase.addComponent(PhotoComponentEntity(frame: .zero, scale: .zero, angle: 0, aspectRatio: 0, imageUrl: URL(string: "file://naver.com")!)) + editPageUseCase.addComponent(PhotoComponentEntity(frame: .zero, scale: .zero, angle: 0, aspectRatio: 0, imageUrl: URL(string: "file://naver.com")!)) + editPageUseCase.addComponent(PhotoComponentEntity(frame: .zero, scale: .zero, angle: 0, aspectRatio: 0, imageUrl: URL(string: "file://naver.com")!)) + editPageUseCase.savePage(author: User(id: DDID(), pairId: DDID()), metaData: nil) var error: Error? - var result: Void? + var result: Bool? editPageUseCase.errorPublisher .compactMap { $0 } @@ -159,6 +91,7 @@ class EditPageUseCaseTest: XCTestCase { .compactMap { $0 } .sink { encounteredResult in result = encounteredResult + expectation.fulfill() } .store(in: &self.cancellables) @@ -170,9 +103,11 @@ class EditPageUseCaseTest: XCTestCase { func testSavingFailureDueToPageRepository() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: false), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testSavingFailureDueToPageRepository") @@ -182,10 +117,10 @@ class EditPageUseCaseTest: XCTestCase { editPageUseCase.addComponent(PhotoComponentEntity(frame: .zero, scale: .zero, angle: 0, aspectRatio: 0, imageUrl: URL(string: "https://naver.com")!)) editPageUseCase.addComponent(PhotoComponentEntity(frame: .zero, scale: .zero, angle: 0, aspectRatio: 0, imageUrl: URL(string: "https://naver.com")!)) editPageUseCase.addComponent(PhotoComponentEntity(frame: .zero, scale: .zero, angle: 0, aspectRatio: 0, imageUrl: URL(string: "https://naver.com")!)) - editPageUseCase.savePage(author: User(id: DDID(), pairId: DDID())) + editPageUseCase.savePage(author: User(id: DDID(), pairId: DDID()), metaData: nil) var error: Error? - var result: Void? + var result: Bool? editPageUseCase.errorPublisher .compactMap { $0 } @@ -211,9 +146,11 @@ class EditPageUseCaseTest: XCTestCase { func testSavingFailureDueToRawPageRepository() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: false) + rawPageRepository: DummyRawPageRepository(isSuccessMode: false), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testSavingFailureDueToRawPageRepository") @@ -223,10 +160,10 @@ class EditPageUseCaseTest: XCTestCase { editPageUseCase.addComponent(PhotoComponentEntity(frame: .zero, scale: .zero, angle: 0, aspectRatio: 0, imageUrl: URL(string: "https://naver.com")!)) editPageUseCase.addComponent(PhotoComponentEntity(frame: .zero, scale: .zero, angle: 0, aspectRatio: 0, imageUrl: URL(string: "https://naver.com")!)) editPageUseCase.addComponent(PhotoComponentEntity(frame: .zero, scale: .zero, angle: 0, aspectRatio: 0, imageUrl: URL(string: "https://naver.com")!)) - editPageUseCase.savePage(author: User(id: DDID(), pairId: DDID())) + editPageUseCase.savePage(author: User(id: DDID(), pairId: DDID()), metaData: nil) var error: Error? - var result: Void? + var result: Bool? editPageUseCase.errorPublisher .compactMap { $0 } @@ -252,9 +189,11 @@ class EditPageUseCaseTest: XCTestCase { func testSelectComponentWithOriginSuccess() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testSelectComponentWithOriginSuccess") @@ -280,9 +219,11 @@ class EditPageUseCaseTest: XCTestCase { func testSelectComponentWithRightTopSuccess() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testSelectComponentWithRightTopSuccess") @@ -308,9 +249,11 @@ class EditPageUseCaseTest: XCTestCase { func testSelectComponentWithRightTopFailure() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testSelectComponentWithRightTopFailure") @@ -336,9 +279,11 @@ class EditPageUseCaseTest: XCTestCase { func testSelectComponentWithLeftBottomSuccess() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testSelectComponentWithLeftBottomSuccess") @@ -364,9 +309,11 @@ class EditPageUseCaseTest: XCTestCase { func testSelectComponentWithLeftBottomFailure() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testSelectComponentWithLeftBottomFailure") @@ -392,9 +339,11 @@ class EditPageUseCaseTest: XCTestCase { func testSelectComponentWithRightBotomSuccess() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testSelectComponentWithRightBotomSuccess") @@ -420,9 +369,11 @@ class EditPageUseCaseTest: XCTestCase { func testSelectComponentWithRightBotomFailure() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testSelectComponentWithRightBotomFailure") @@ -448,9 +399,11 @@ class EditPageUseCaseTest: XCTestCase { func testSelectScaledComponentWithRightBotomSuccess() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testSelectScaledComponentWithRightBotomSuccess") @@ -476,9 +429,11 @@ class EditPageUseCaseTest: XCTestCase { func testSelectScaledComponentWithRightBotomFailure() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testSelectScaledComponentWithRightBotomFailure") @@ -504,9 +459,11 @@ class EditPageUseCaseTest: XCTestCase { func testSelectRotatedComponentWithRightBotomSuccess() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testSelectRotatedComponentWithRightBotomSuccess") @@ -532,9 +489,11 @@ class EditPageUseCaseTest: XCTestCase { func testSelectRotatedComponentWithRightBotomFailure() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testSelectRotatedComponentWithRightBotomFailure") @@ -560,9 +519,11 @@ class EditPageUseCaseTest: XCTestCase { func testSelectOneAmongComponentsSuccess() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testSelectOneAmongComponentsSuccess") @@ -590,9 +551,11 @@ class EditPageUseCaseTest: XCTestCase { func testSelectOneAmongComponentsFailure() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testSelectOneAmongComponentsFailure") @@ -620,9 +583,11 @@ class EditPageUseCaseTest: XCTestCase { func testMoveSelectedComponentSuccess() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testMoveSelectedComponentSuccess") @@ -650,9 +615,11 @@ class EditPageUseCaseTest: XCTestCase { func testMoveSelectedComponentFailure() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testMoveSelectedComponentFailure") @@ -680,9 +647,11 @@ class EditPageUseCaseTest: XCTestCase { func testScaleSelectedComponentSuccess() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testScaleSelectedComponentSuccess") @@ -710,9 +679,11 @@ class EditPageUseCaseTest: XCTestCase { func testScaleSelectedComponentFailure() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testScaleSelectedComponentFailure") @@ -740,9 +711,11 @@ class EditPageUseCaseTest: XCTestCase { func testRotateSelectedComponentSuccess() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testRotateSelectedComponentSuccess") @@ -770,9 +743,11 @@ class EditPageUseCaseTest: XCTestCase { func testRotateSelectedComponentFailure() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testRotateSelectedComponentFailure") @@ -800,9 +775,11 @@ class EditPageUseCaseTest: XCTestCase { func testSendComponentBack() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let a = ComponentEntity(frame: CGRect(x: 0, y: 0, width: 10, height: 10), scale: 1, angle: 0, aspectRatio: 1) @@ -844,9 +821,11 @@ class EditPageUseCaseTest: XCTestCase { func testBringComponentFront() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let a = ComponentEntity(frame: CGRect(x: 0, y: 0, width: 10, height: 10), scale: 1, angle: 0, aspectRatio: 1) @@ -888,9 +867,11 @@ class EditPageUseCaseTest: XCTestCase { func testRemoveComponentSingle() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testRemoveComponentSingle") @@ -920,9 +901,11 @@ class EditPageUseCaseTest: XCTestCase { func testRemoveComponentDouble() { let editPageUseCase = EditPageUseCase( + user: User(id: DDID(), pairId: nil), imageUseCase: DummyImageUseCase(isSuccessMode: true), pageRepository: DummyPageRepository(isSuccessMode: true), - rawPageRepository: DummyRawPageRepository(isSuccessMode: true) + rawPageRepository: DummyRawPageRepository(isSuccessMode: true), + pairRepository: DummyPairRepository() ) let expectation = self.expectation(description: "testRemoveComponentDouble") diff --git a/Doolda/Tests/FCMTokenUseCaseTest/FCMTokenUseCaseTest.swift b/Doolda/Tests/FCMTokenUseCaseTest/FCMTokenUseCaseTest.swift new file mode 100644 index 00000000..7abab619 --- /dev/null +++ b/Doolda/Tests/FCMTokenUseCaseTest/FCMTokenUseCaseTest.swift @@ -0,0 +1,119 @@ +// +// FCMTokenUseCaseTest.swift +// FCMTokenUseCaseTest +// +// Created by Seunghun Yang on 2021/11/30. +// + +import Combine +import XCTest + +class FCMTokenUseCaseTest: XCTestCase { + private var cancellables: Set = [] + + override func tearDown() { + self.cancellables = [] + } + + func testSetTokenSuccess() { + let fcmTokenRepository = DummyFCMTokenRepository(isSuccessMode: true) + let fcmTokenUseCase = FCMTokenUseCase(fcmTokenRepository: fcmTokenRepository) + + let expectation = self.expectation(description: "testSetTokenSuccess") + var error: Error? + var result: String? + + let dummyToken = "DUMMYTOKEN" + fcmTokenUseCase.setToken(for: DDID(), with: dummyToken) + .sink { completion in + guard case .failure(let encounteredError) = completion else { return } + error = encounteredError + expectation.fulfill() + } receiveValue: { token in + result = token + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 3) + + XCTAssertNil(error) + XCTAssertEqual(dummyToken, result) + } + + func testSetTokenFailure() { + let fcmTokenRepository = DummyFCMTokenRepository(isSuccessMode: false) + let fcmTokenUseCase = FCMTokenUseCase(fcmTokenRepository: fcmTokenRepository) + + let expectation = self.expectation(description: "testSetTokenFailure") + var error: Error? + var result: String? + + let dummyToken = "DUMMYTOKEN" + fcmTokenUseCase.setToken(for: DDID(), with: dummyToken) + .sink { completion in + guard case .failure(let encounteredError) = completion else { return } + error = encounteredError + expectation.fulfill() + } receiveValue: { token in + result = token + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 3) + + XCTAssertNotNil(error) + XCTAssertNotEqual(dummyToken, result) + } + + func testGetTokenSuccess() { + let fcmTokenRepository = DummyFCMTokenRepository(isSuccessMode: true) + let fcmTokenUseCase = FCMTokenUseCase(fcmTokenRepository: fcmTokenRepository) + + let expectation = self.expectation(description: "testGetTokenSuccess") + var error: Error? + var result: String? + + fcmTokenUseCase.getToken(for: DDID()) + .sink { completion in + guard case .failure(let encounteredError) = completion else { return } + error = encounteredError + expectation.fulfill() + } receiveValue: { token in + result = token + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 3) + + XCTAssertNil(error) + XCTAssertNotNil(result) + } + + func testGetTokenFailure() { + let fcmTokenRepository = DummyFCMTokenRepository(isSuccessMode: false) + let fcmTokenUseCase = FCMTokenUseCase(fcmTokenRepository: fcmTokenRepository) + + let expectation = self.expectation(description: "testGetTokenFailure") + var error: Error? + var result: String? + + fcmTokenUseCase.getToken(for: DDID()) + .sink { completion in + guard case .failure(let encounteredError) = completion else { return } + error = encounteredError + expectation.fulfill() + } receiveValue: { token in + result = token + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 3) + + XCTAssertNotNil(error) + XCTAssertNil(result) + } +} diff --git a/Doolda/Tests/FirebaseMessageUseCaseTest/FirebaseMessageUseCaseTest.swift b/Doolda/Tests/FirebaseMessageUseCaseTest/FirebaseMessageUseCaseTest.swift new file mode 100644 index 00000000..5a003a7c --- /dev/null +++ b/Doolda/Tests/FirebaseMessageUseCaseTest/FirebaseMessageUseCaseTest.swift @@ -0,0 +1,113 @@ +// +// FirebaseMessageUseCaseTest.swift +// FirebaseMessageUseCaseTest +// +// Created by Seunghun Yang on 2021/11/30. +// + +import Combine +import XCTest + +class FirebaseMessageUseCaseTest: XCTestCase { + private var cancellables: Set = [] + + override func tearDown() { + self.cancellables = [] + } + + func testSendMessageSuccess() { + let fcmTokenRepository = DummyFCMTokenRepository(isSuccessMode: true) + let firebaseMessageRepository = DummyFirebaseMessageRepository(isSuccessMode: true) + let fireBaseMessageUseCase = FirebaseMessageUseCase( + fcmTokenRepository: fcmTokenRepository, + firebaseMessageRepository: firebaseMessageRepository + ) + + let expectation = self.expectation(description: #function) + var error: Error? + + fireBaseMessageUseCase.sendMessage(to: DDID(), message: PushMessageEntity.userPairedWithFriend) + + fireBaseMessageUseCase.errorPublisher + .sink { + error = $0 + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 3) + XCTAssertNil(error) + } + + func testSendMessageFailureDueToTokenRepository() { + let fcmTokenRepository = DummyFCMTokenRepository(isSuccessMode: false) + let firebaseMessageRepository = DummyFirebaseMessageRepository(isSuccessMode: true) + let fireBaseMessageUseCase = FirebaseMessageUseCase( + fcmTokenRepository: fcmTokenRepository, + firebaseMessageRepository: firebaseMessageRepository + ) + + let expectation = self.expectation(description: #function) + var error: Error? + + fireBaseMessageUseCase.sendMessage(to: DDID(), message: PushMessageEntity.userPairedWithFriend) + + fireBaseMessageUseCase.errorPublisher + .sink { + error = $0 + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 3) + XCTAssertNotNil(error) + } + + func testSendMessageFailureDueToMessageRepository() { + let fcmTokenRepository = DummyFCMTokenRepository(isSuccessMode: true) + let firebaseMessageRepository = DummyFirebaseMessageRepository(isSuccessMode: false) + let fireBaseMessageUseCase = FirebaseMessageUseCase( + fcmTokenRepository: fcmTokenRepository, + firebaseMessageRepository: firebaseMessageRepository + ) + + let expectation = self.expectation(description: #function) + var error: Error? + + fireBaseMessageUseCase.sendMessage(to: DDID(), message: PushMessageEntity.userPairedWithFriend) + + fireBaseMessageUseCase.errorPublisher + .sink { + error = $0 + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 3) + XCTAssertNotNil(error) + } + + func testSendMessageFailureDueToTokenAndMessageRepository() { + let fcmTokenRepository = DummyFCMTokenRepository(isSuccessMode: false) + let firebaseMessageRepository = DummyFirebaseMessageRepository(isSuccessMode: false) + let fireBaseMessageUseCase = FirebaseMessageUseCase( + fcmTokenRepository: fcmTokenRepository, + firebaseMessageRepository: firebaseMessageRepository + ) + + let expectation = self.expectation(description: #function) + var error: Error? + + fireBaseMessageUseCase.sendMessage(to: DDID(), message: PushMessageEntity.userPairedWithFriend) + + fireBaseMessageUseCase.errorPublisher + .sink { + error = $0 + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 3) + XCTAssertNotNil(error) + } +} diff --git a/Doolda/Tests/GetMyIdUseCaseTest/GetMyIdUseCaseTest.swift b/Doolda/Tests/GetMyIdUseCaseTest/GetMyIdUseCaseTest.swift new file mode 100644 index 00000000..dff092e1 --- /dev/null +++ b/Doolda/Tests/GetMyIdUseCaseTest/GetMyIdUseCaseTest.swift @@ -0,0 +1,67 @@ +// +// GetMyIdUseCaseTest.swift +// GetMyIdUseCaseTest +// +// Created by 김민주 on 2021/11/30. +// + +import Combine +import XCTest + +class GetMyIdUseCaseTest: XCTestCase { + private var cancellables: Set = [] + + override func tearDown(){ + self.cancellables = [] + } + + func testGetMyIdSuccess() { + guard let targetDDID = DDID(from: UUID.init().uuidString) else { + XCTFail() + return + } + + let getMyIdUseCase = GetMyIdUseCase( + userRepository: DummyUserRepository(dummyMyId: targetDDID, isSuccessMode: true) + ) + + let expectation = self.expectation(description: "testGetMyIdSuccess") + + var result: DDID? + + getMyIdUseCase.getMyId() + .sink { ddid in + result = ddid + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 5) + XCTAssertEqual(result, targetDDID) + } + + func testGetMyIdFailure() { + guard let targetDDID = DDID(from: UUID.init().uuidString) else { + XCTFail() + return + } + + let getMyIdUseCase = GetMyIdUseCase( + userRepository: DummyUserRepository(dummyMyId: targetDDID, isSuccessMode: false) + ) + + let expectation = self.expectation(description: "testGetMyIdFailure") + + var result: DDID? + + getMyIdUseCase.getMyId() + .sink { ddid in + result = ddid + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 5) + XCTAssertNil(result) + } +} diff --git a/Doolda/Tests/GetPageUseCaseTest/GetPageUseCaseTest.swift b/Doolda/Tests/GetPageUseCaseTest/GetPageUseCaseTest.swift new file mode 100644 index 00000000..f3fd6f50 --- /dev/null +++ b/Doolda/Tests/GetPageUseCaseTest/GetPageUseCaseTest.swift @@ -0,0 +1,59 @@ +// +// GetPageUseCaseTest.swift +// GetPageUseCaseTest +// +// Created by 정지승 on 2021/11/30. +// + +import Combine +import XCTest + +class GetPageUseCaseTest: XCTestCase { + private var cancellables: Set = [] + + override func tearDownWithError() throws { + self.cancellables.removeAll() + } + + func testGetPagesSuccess() { + let getPageUseCase = GetPageUseCase(pageRepository: DummyPageRepository(isSuccessMode: true)) + let expectation = expectation(description: #function) + + var errorResult: Error? + + getPageUseCase.getPages(for: DDID()) + .sink { completion in + guard case .failure(let error) = completion else { return } + errorResult = error + expectation.fulfill() + } receiveValue: { _ in + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 10) + + XCTAssertNil(errorResult) + } + + func testGetPagesFailure() { + let getPageUseCase = GetPageUseCase(pageRepository: DummyPageRepository(isSuccessMode: false)) + let expectation = expectation(description: #function) + + var errorResult: Error? + + getPageUseCase.getPages(for: DDID()) + .sink { completion in + guard case .failure(let error) = completion else { return } + errorResult = error + expectation.fulfill() + } receiveValue: { _ in + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 10) + + XCTAssertNotNil(errorResult) + } +} diff --git a/Doolda/Tests/GetRawPageUseCaseTest/GetRawPageUseCaseTest.swift b/Doolda/Tests/GetRawPageUseCaseTest/GetRawPageUseCaseTest.swift new file mode 100644 index 00000000..59ef6880 --- /dev/null +++ b/Doolda/Tests/GetRawPageUseCaseTest/GetRawPageUseCaseTest.swift @@ -0,0 +1,71 @@ +// +// GetRawPageUseCaseTest.swift +// GetRawPageUseCaseTest +// +// Created by 정지승 on 2021/11/30. +// + +import Combine +import XCTest + +class GetRawPageUseCaseTest: XCTestCase { + private var cancellables: Set = [] + + override func tearDownWithError() throws { + self.cancellables = [] + } + + func testGetRawPageEntitySuccess() { + let getRawPageUseCase = GetRawPageUseCase(rawPageRepository: DummyRawPageRepository(isSuccessMode: true)) + let pageEntity = PageEntity( + author: User(id: DDID(), pairId: DDID(), friendId: DDID()), + createdTime: Date(), + updatedTime: Date(), + jsonPath: "2001101011" + ) + + let expectation = expectation(description: #function) + var errorResult: Error? + + getRawPageUseCase.getRawPageEntity(metaData: pageEntity) + .sink { completion in + guard case .failure(let error) = completion else { return } + errorResult = error + expectation.fulfill() + } receiveValue: { _ in + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 10) + + XCTAssertNil(errorResult) + } + + func testGetRawPageEntityFailure() { + let getRawPageUseCase = GetRawPageUseCase(rawPageRepository: DummyRawPageRepository(isSuccessMode: false)) + let pageEntity = PageEntity( + author: User(id: DDID(), pairId: DDID(), friendId: DDID()), + createdTime: Date(), + updatedTime: Date(), + jsonPath: "2001101011" + ) + + let expectation = expectation(description: #function) + var errorResult: Error? + + getRawPageUseCase.getRawPageEntity(metaData: pageEntity) + .sink { completion in + guard case .failure(let error) = completion else { return } + errorResult = error + expectation.fulfill() + } receiveValue: { _ in + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 10) + + XCTAssertNotNil(errorResult) + } +} diff --git a/Doolda/Tests/GetUserUseCaseTest/GetUserUseCaseTest.swift b/Doolda/Tests/GetUserUseCaseTest/GetUserUseCaseTest.swift new file mode 100644 index 00000000..27a08f59 --- /dev/null +++ b/Doolda/Tests/GetUserUseCaseTest/GetUserUseCaseTest.swift @@ -0,0 +1,71 @@ +// +// GetUserUseCaseTest.swift +// GetUserUseCaseTest +// +// Created by Seunghun Yang on 2021/11/30. +// + +import Combine +import XCTest + +class GetUserUseCaseTest: XCTestCase { + private var cancellables: Set = [] + + override func tearDown() { + self.cancellables = [] + } + + func testGetUserSuccess() { + let userRepository = DummyUserRepository(dummyMyId: DDID(), isSuccessMode: true) + let getUserUseCase = GetUserUseCase(userRepository: userRepository) + + let expectation = self.expectation(description: "testGetUserSuccess") + + var error: Error? + var result: User? + + getUserUseCase.getUser(for: DummyUserRepository.firstUserId) + .compactMap { $0 } + .sink { completion in + guard case .failure(let encounteredError) = completion else { return } + error = encounteredError + expectation.fulfill() + } receiveValue: { user in + result = user + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 1) + + XCTAssertNil(error) + XCTAssertEqual(result, User(id: DummyUserRepository.firstUserId, pairId: nil, friendId: nil)) + } + + func testGetUserFailure() { + let userRepository = DummyUserRepository(dummyMyId: DDID(), isSuccessMode: false) + let getUserUseCase = GetUserUseCase(userRepository: userRepository) + + let expectation = self.expectation(description: "testGetUserFailure") + + var error: Error? + var result: User? + + getUserUseCase.getUser(for: DummyUserRepository.thirdUserId) + .compactMap { $0 } + .sink { completion in + guard case .failure(let encounteredError) = completion else { return } + error = encounteredError + expectation.fulfill() + } receiveValue: { user in + result = user + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 1) + + XCTAssertNotNil(error) + XCTAssertNotEqual(result, User(id: DummyUserRepository.thirdUserId, pairId: nil, friendId: nil)) + } +} diff --git a/Doolda/Tests/GlobalFontUseCaseTest/GlobalFontUseCaseTest.swift b/Doolda/Tests/GlobalFontUseCaseTest/GlobalFontUseCaseTest.swift new file mode 100644 index 00000000..6fd55e46 --- /dev/null +++ b/Doolda/Tests/GlobalFontUseCaseTest/GlobalFontUseCaseTest.swift @@ -0,0 +1,70 @@ +// +// GlobalFontUseCaseTest.swift +// GlobalFontUseCaseTest +// +// Created by 김민주 on 2021/11/30. +// + +import Combine +import XCTest + +class GlobalFontUseCaseTest: XCTestCase { + private var cancellables: Set = [] + + override func tearDown(){ + self.cancellables = [] + } + + func testSetGlobalFontSuccess() { + let globalFontUseCase = GlobalFontUseCase( + globalFontRepository: DummyGlobalFontRepository() + ) + + let targetFontName = "testFontName" + let expectation = self.expectation(description: #function) + + NotificationCenter.default.publisher(for: GlobalFontUseCase.Notifications.globalFontDidSet, object: nil) + .sink { _ in + expectation.fulfill() + } + .store(in: &self.cancellables) + + globalFontUseCase.setGlobalFont(with: targetFontName) + + waitForExpectations(timeout: 5) + XCTAssertEqual(targetFontName, UIFont.globalFontFamily) + } + + func testSaveGlobalFontSuccess() { + let dummyGlobalFontRepository = DummyGlobalFontRepository() + let globalFontUseCase = GlobalFontUseCase(globalFontRepository: dummyGlobalFontRepository) + + let targetFontName = "testFontName" + + globalFontUseCase.saveGlobalFont(as: targetFontName) + + XCTAssertEqual(targetFontName, dummyGlobalFontRepository.dummyGlobalFontName) + } + + func testGetGlobalFontSuccess() { + let targetFontName = "dungGeunMo" + let dummyGlobalFontRepository = DummyGlobalFontRepository(dummyGlobalFontName: targetFontName) + let globalFontUseCase = GlobalFontUseCase(globalFontRepository: dummyGlobalFontRepository) + + let globalFontType = globalFontUseCase.getGlobalFont() + + XCTAssertNotNil(globalFontType) + XCTAssertEqual(globalFontType?.name, targetFontName) + } + + func testGetDefaultGlobalFontDueToWrongFontName() { + let targetFontName = "dummy" + let dummyGlobalFontRepository = DummyGlobalFontRepository(dummyGlobalFontName: targetFontName) + let globalFontUseCase = GlobalFontUseCase(globalFontRepository: dummyGlobalFontRepository) + + let globalFontType = globalFontUseCase.getGlobalFont() + + XCTAssertNotNil(globalFontType) + XCTAssertEqual(globalFontType?.name, "dovemayo") + } +} diff --git a/Doolda/Tests/PairUserUseCaseTest/PairUserUseCaseTest.swift b/Doolda/Tests/PairUserUseCaseTest/PairUserUseCaseTest.swift new file mode 100644 index 00000000..c89b07ff --- /dev/null +++ b/Doolda/Tests/PairUserUseCaseTest/PairUserUseCaseTest.swift @@ -0,0 +1,277 @@ +// +// PairUserUseCaseTest.swift +// PairUserUseCaseTest +// +// Created by 정지승 on 2021/11/30. +// + +import Combine +import XCTest + +class PairUserUseCaseTest: XCTestCase { + private var cancellables: Set = [] + + override func tearDownWithError() throws { + self.cancellables = [] + } + + func testPairWithMyselfSuccess() { + let user = User(id: DDID(), pairId: nil, friendId: nil) + + let pairUserUseCase = PairUserUseCase( + userRepository: DummyUserRepository(dummyMyId: user.id, isSuccessMode: true), + pairRepository: DummyPairRepository(isSuccessMode: true) + ) + + let expectation = expectation(description: #function) + expectation.expectedFulfillmentCount = 2 + + pairUserUseCase.pair(user: user) + pairUserUseCase.pairedUserPublisher + .sink { user in + if let user = user, + let pairId = user.pairId, + let friendId = user.friendId { + XCTAssertEqual(user.id, friendId) + XCTAssertEqual(user.id, pairId) + } else { + XCTFail() + } + expectation.fulfill() + } + .store(in: &self.cancellables) + + var errorResult: Error? + pairUserUseCase.errorPublisher + .sink { error in + errorResult = error + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 10) + + XCTAssertNil(errorResult) + } + + func testPairWithMyselfFailure() { + let user = User(id: DDID(), pairId: nil, friendId: nil) + + let pairUserUseCase = PairUserUseCase( + userRepository: DummyUserRepository(dummyMyId: user.id, isSuccessMode: false), + pairRepository: DummyPairRepository(isSuccessMode: false) + ) + + let expectation = expectation(description: #function) + expectation.expectedFulfillmentCount = 2 + + pairUserUseCase.pair(user: user) + pairUserUseCase.pairedUserPublisher + .sink { user in + XCTAssertNil(user?.pairId) + expectation.fulfill() + } + .store(in: &self.cancellables) + + var errorResult: Error? + pairUserUseCase.errorPublisher + .sink { error in + errorResult = error + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 10) + + XCTAssertNotNil(errorResult) + } + + func testPairWithFriendSuccess() { + let user = User(id: DummyUserRepository.firstUserId, pairId: nil, friendId: nil) + + let pairUserUseCase = PairUserUseCase( + userRepository: DummyUserRepository(dummyMyId: user.id, isSuccessMode: true), + pairRepository: DummyPairRepository(isSuccessMode: true) + ) + + let expectation = expectation(description: #function) + expectation.expectedFulfillmentCount = 2 + + let friendId = DummyUserRepository.secondUserId + pairUserUseCase.pair(user: user, friendId: friendId) + pairUserUseCase.pairedUserPublisher + .sink { user in + if let user = user, + let friendIdResult = user.friendId { + XCTAssertNotNil(user.pairId) + XCTAssertEqual(friendId, friendIdResult) + } else { + XCTFail() + } + + expectation.fulfill() + } + .store(in: &self.cancellables) + + var errorResult: Error? + pairUserUseCase.errorPublisher + .sink { error in + errorResult = error + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 10) + + XCTAssertNil(errorResult) + } + + func testPairWithFriendWhereFriendAlreadyPairedWithAnotherUserFailure() { + let user = User(id: DummyUserRepository.firstUserId, pairId: nil, friendId: nil) + + let pairUserUseCase = PairUserUseCase( + userRepository: DummyUserRepository(dummyMyId: user.id, isSuccessMode: true), + pairRepository: DummyPairRepository(isSuccessMode: true) + ) + + let expectation = expectation(description: #function) + + let friendId = DummyUserRepository.fourthUserId + pairUserUseCase.pair(user: user, friendId: friendId) + + var errorResult: Error? + pairUserUseCase.errorPublisher + .sink { error in + errorResult = error + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 10) + + XCTAssertNotNil(errorResult) + } + + func testPairWithFriendWhereUserAlreadyPairedWithAnotherUserFailure() { + let user = User(id: DummyUserRepository.fourthUserId, pairId: nil, friendId: nil) + + let pairUserUseCase = PairUserUseCase( + userRepository: DummyUserRepository(dummyMyId: user.id, isSuccessMode: true), + pairRepository: DummyPairRepository(isSuccessMode: true) + ) + + let expectation = expectation(description: #function) + + let friendId = DummyUserRepository.firstUserId + pairUserUseCase.pair(user: user, friendId: friendId) + + var errorResult: Error? + pairUserUseCase.errorPublisher + .sink { error in + errorResult = error + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 10) + + XCTAssertNotNil(errorResult) + } + + func testPairWithFriendWhereUserAlreadyPairedWithFriendSuccess() { + let user = User(id: DummyUserRepository.fourthUserId, pairId: nil, friendId: nil) + + let pairUserUseCase = PairUserUseCase( + userRepository: DummyUserRepository(dummyMyId: user.id, isSuccessMode: true), + pairRepository: DummyPairRepository(isSuccessMode: true) + ) + + let expectation = expectation(description: #function) + expectation.expectedFulfillmentCount = 2 + + let friendId = DummyUserRepository.fifthUserId + pairUserUseCase.pair(user: user, friendId: friendId) + pairUserUseCase.pairedUserPublisher + .sink { user in + if let user = user { + XCTAssertNotNil(user.pairId) + } else { + XCTFail() + } + + expectation.fulfill() + } + .store(in: &self.cancellables) + + var errorResult: Error? + pairUserUseCase.errorPublisher + .sink { error in + errorResult = error + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 10) + + XCTAssertNil(errorResult) + } + + func testPairWithFriendWhereUserAndFriendHaveDifferentPairIdFailure() { + let user = User(id: DummyUserRepository.fourthUserId, pairId: nil, friendId: nil) + + let pairUserUseCase = PairUserUseCase( + userRepository: DummyUserRepository(dummyMyId: user.id, isSuccessMode: true), + pairRepository: DummyPairRepository(isSuccessMode: true) + ) + + let expectation = expectation(description: #function) + + let friendId = DummyUserRepository.sixthUserId + pairUserUseCase.pair(user: user, friendId: friendId) + + var errorResult: Error? + pairUserUseCase.errorPublisher + .sink { error in + errorResult = error + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 10) + + XCTAssertNotNil(errorResult) + } + + func testPairWithFriendFailure() { + let user = User(id: DummyUserRepository.firstUserId, pairId: nil, friendId: nil) + + let pairUserUseCase = PairUserUseCase( + userRepository: DummyUserRepository(dummyMyId: user.id, isSuccessMode: false), + pairRepository: DummyPairRepository(isSuccessMode: false) + ) + + let expectation = expectation(description: #function) + expectation.expectedFulfillmentCount = 2 + + let friendId = DummyUserRepository.secondUserId + pairUserUseCase.pair(user: user, friendId: friendId) + pairUserUseCase.pairedUserPublisher + .sink { user in + XCTAssertNil(user?.pairId) + expectation.fulfill() + } + .store(in: &self.cancellables) + + var errorResult: Error? + pairUserUseCase.errorPublisher + .sink { error in + errorResult = error + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 10) + + XCTAssertNotNil(errorResult) + } +} diff --git a/Doolda/Tests/PushNotificationStateUseCaseTest/PushNotificationStateUseCaseTest.swift b/Doolda/Tests/PushNotificationStateUseCaseTest/PushNotificationStateUseCaseTest.swift new file mode 100644 index 00000000..5f176381 --- /dev/null +++ b/Doolda/Tests/PushNotificationStateUseCaseTest/PushNotificationStateUseCaseTest.swift @@ -0,0 +1,51 @@ +// +// PushNotificationStateUseCaseTest.swift +// PushNotificationStateUseCaseTest +// +// Created by 김민주 on 2021/11/30. +// + +import Combine +import XCTest + +class PushNotificationStateUseCaseTest: XCTestCase { + + func testSetPushNotificationStateAsTrue() { + let targetState = true + let dummyPushNotificationStateRepository = DummyPushNotificationStateRepository() + let pushNotificationStateUseCase = PushNotificationStateUseCase( + pushNotificationStateRepository: dummyPushNotificationStateRepository + ) + + pushNotificationStateUseCase.setPushNotificationState(as: targetState) + + XCTAssertEqual(targetState, dummyPushNotificationStateRepository.dummyNotificationState) + } + + func testSetPushNotificationStateAsFalse() { + let targetState = false + let dummyPushNotificationStateRepository = DummyPushNotificationStateRepository() + let pushNotificationStateUseCase = PushNotificationStateUseCase( + pushNotificationStateRepository: dummyPushNotificationStateRepository + ) + + pushNotificationStateUseCase.setPushNotificationState(as: targetState) + + XCTAssertEqual(targetState, dummyPushNotificationStateRepository.dummyNotificationState) + } + + func testFetchPushNotificationStateSuccess() { + let targetState = true + let dummyPushNotificationStateRepository = DummyPushNotificationStateRepository(dummyNotificationState: targetState) + let pushNotificationStateUseCase = PushNotificationStateUseCase( + pushNotificationStateRepository: dummyPushNotificationStateRepository + ) + + guard let result = pushNotificationStateUseCase.getPushNotificationState() else { + XCTFail() + return + } + + XCTAssertEqual(targetState, result) + } +} diff --git a/Doolda/Tests/RefreshUserUseCaseTest/RefreshUserUseCaseTest.swift b/Doolda/Tests/RefreshUserUseCaseTest/RefreshUserUseCaseTest.swift new file mode 100644 index 00000000..f0f82543 --- /dev/null +++ b/Doolda/Tests/RefreshUserUseCaseTest/RefreshUserUseCaseTest.swift @@ -0,0 +1,77 @@ +// +// RefreshUserUseCaseTest.swift +// RefreshUserUseCaseTest +// +// Created by 정지승 on 2021/11/30. +// + +import Combine +import XCTest + +class RefreshUserUseCaseTest: XCTestCase { + private var cancellables: Set = [] + + override func tearDownWithError() throws { + self.cancellables = [] + } + + func testRefreshUserSuccess() { + let refreshUserUseCase = RefreshUserUseCase( + userRepository: DummyUserRepository( + dummyMyId: DummyUserRepository.fourthUserId, + isSuccessMode: true + ) + ) + + let expectation = expectation(description: #function) + + let user = User(id: DummyUserRepository.fourthUserId, pairId: nil, friendId: nil) + refreshUserUseCase.refresh(for: user) + refreshUserUseCase.refreshedUserPublisher + .sink { user in + XCTAssertNotNil(user) + XCTAssertNotNil(user?.friendId) + XCTAssertNotNil(user?.pairId) + expectation.fulfill() + } + .store(in: &self.cancellables) + + refreshUserUseCase.errorPublisher + .compactMap { $0 } + .sink { error in + XCTFail() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 10) + } + + func testRefreshUserFailure() { + let refreshUserUseCase = RefreshUserUseCase( + userRepository: DummyUserRepository( + dummyMyId: DummyUserRepository.fourthUserId, + isSuccessMode: false + ) + ) + + let expectation = expectation(description: #function) + + let user = User(id: DummyUserRepository.fourthUserId, pairId: nil, friendId: nil) + refreshUserUseCase.refresh(for: user) + refreshUserUseCase.refreshedUserPublisher + .sink { user in + XCTAssertNil(user) + } + .store(in: &self.cancellables) + + refreshUserUseCase.errorPublisher + .compactMap { $0 } + .sink { error in + XCTAssertNotNil(error) + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 10) + } +} diff --git a/Doolda/Tests/RegisterUserUseCaseTest/RegisterUserUseCaseTest.swift b/Doolda/Tests/RegisterUserUseCaseTest/RegisterUserUseCaseTest.swift new file mode 100644 index 00000000..99b06a7e --- /dev/null +++ b/Doolda/Tests/RegisterUserUseCaseTest/RegisterUserUseCaseTest.swift @@ -0,0 +1,65 @@ +// +// RegisterUserUseCaseTest.swift +// RegisterUserUseCaseTest +// +// Created by Seunghun Yang on 2021/11/30. +// + +import Combine +import XCTest + +class RegisterUserUseCaseTest: XCTestCase { + private var cancellables: Set = [] + + override func tearDown() { + self.cancellables = [] + } + + func testRegisterSuccess() { + let userRepository = DummyUserRepository(dummyMyId: DDID(), isSuccessMode: true) + let registerUserUseCase = RegisterUserUseCase(userRepository: userRepository) + + registerUserUseCase.register() + + let expectation = self.expectation(description: "testRegisterSuccess") + var error: Error? + var result: User? + + Publishers.Zip(registerUserUseCase.errorPublisher, registerUserUseCase.registeredUserPublisher) + .sink { encounteredError, user in + error = encounteredError + result = user + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 3) + + XCTAssertNil(error) + XCTAssertNotNil(result) + } + + func testRegisterFailure() { + let userRepository = DummyUserRepository(dummyMyId: DDID(), isSuccessMode: false) + let registerUserUseCase = RegisterUserUseCase(userRepository: userRepository) + + registerUserUseCase.register() + + let expectation = self.expectation(description: "testRegisterFailure") + var error: Error? + var result: User? + + Publishers.Zip(registerUserUseCase.errorPublisher, registerUserUseCase.registeredUserPublisher) + .sink { encounteredError, user in + error = encounteredError + result = user + expectation.fulfill() + } + .store(in: &self.cancellables) + + waitForExpectations(timeout: 3) + + XCTAssertNotNil(error) + XCTAssertNil(result) + } +} diff --git a/Doolda/Tests/TextUseCaseTest/TextUseCaseTest.swift b/Doolda/Tests/TextUseCaseTest/TextUseCaseTest.swift new file mode 100644 index 00000000..1e8e7178 --- /dev/null +++ b/Doolda/Tests/TextUseCaseTest/TextUseCaseTest.swift @@ -0,0 +1,76 @@ +// +// TextUseCaseTest.swift +// TextUseCaseTest +// +// Created by 김민주 on 2021/11/30. +// + +import XCTest + +class TextUseCaseTest: XCTestCase { + + func testGetTextComponent() { + let textUseCase = TextUseCase() + let targetText: String = "dummy" + let targetSize: CGSize = .zero + let targetOrigin = CGPoint(x: 850 - targetSize.width/2, y: 1500 - targetSize.height/2) + let targetFontSize: CGFloat = .zero + let targetColor: FontColorType = .black + let targetTextComponent = TextComponentEntity( + frame: CGRect(origin: targetOrigin, size: targetSize), + scale: 1.0, + angle: 0, + aspectRatio: 1, + text: targetText, + fontSize: targetFontSize, + fontColor: targetColor + ) + + let result = textUseCase.getTextComponent( + with: targetText, + contentSize: targetSize, + fontSize: targetFontSize, + color: targetColor + ) + + XCTAssertEqual(targetText, result.text) + XCTAssertEqual(targetSize, result.frame.size) + XCTAssertEqual(targetOrigin, result.frame.origin) + XCTAssertEqual(targetFontSize, result.fontSize) + XCTAssertEqual(targetColor, result.fontColor) + + XCTAssertNotEqual(targetTextComponent, result) + } + + func testChangeTextComponent() { + let textUseCase = TextUseCase() + let targetTextComponent = TextComponentEntity( + frame: CGRect(origin: .zero, size: .zero), + scale: 1.0, + angle: 0, + aspectRatio: 1, + text: "dummy", + fontSize: .zero, + fontColor: .black + ) + + let targetText: String = "changedummy" + let targetSize: CGSize = CGSize(width: 100, height: 100) + let targetFontSize: CGFloat = 16 + let targetColor: FontColorType = .purple + + let result = textUseCase.changeTextComponent( + from: targetTextComponent, + with: targetText, + contentSize: targetSize, + fontSize: targetFontSize, + color: targetColor + ) + + XCTAssertEqual(targetTextComponent, result) + XCTAssertEqual(targetText, result.text) + XCTAssertEqual(targetSize, result.frame.size) + XCTAssertEqual(targetFontSize, result.fontSize) + XCTAssertEqual(targetColor, result.fontColor) + } +} diff --git a/Doolda/URLSessionNetworkServiceTest/URLSessionNetworkServiceTest.swift b/Doolda/Tests/URLSessionNetworkServiceTest/URLSessionNetworkServiceTest.swift similarity index 64% rename from Doolda/URLSessionNetworkServiceTest/URLSessionNetworkServiceTest.swift rename to Doolda/Tests/URLSessionNetworkServiceTest/URLSessionNetworkServiceTest.swift index ca39900b..4c90682e 100644 --- a/Doolda/URLSessionNetworkServiceTest/URLSessionNetworkServiceTest.swift +++ b/Doolda/Tests/URLSessionNetworkServiceTest/URLSessionNetworkServiceTest.swift @@ -56,5 +56,33 @@ class URLSessionNetworkServiceTest: XCTestCase { wait(for: [expectation], timeout: 30) } + + func testPatchPageDocumentSuccess() throws { + guard let networkService = self.networkService, + let ddid = DDID(from: "00ACAA37-F0BA-49FB-9280-B42D0F68AD1B") else { + XCTFail() + return + } + + let expectation = XCTestExpectation() + let currentDate = Date() + let jsonPath = "211123230907" + + let urlRequest = FirebaseAPIs.patchPageDocument(ddid.ddidString, currentDate, currentDate, jsonPath, ddid.ddidString) + let publisher: AnyPublisher<[String: Any], Error> = networkService.request(urlRequest) + publisher + .compactMap { $0 } + .sink { completion in + guard case .failure(let error) = completion else { return } + XCTFail("\(error.localizedDescription)") + expectation.fulfill() + } receiveValue: { result in + expectation.fulfill() + } + .store(in: &cancellableSet) + + wait(for: [expectation], timeout: 30) + } + } diff --git a/Doolda/UserDefaultsPersistenceServiceTest/UserDefaultsPersistenceServiceTest.swift b/Doolda/UserDefaultsPersistenceServiceTest/UserDefaultsPersistenceServiceTest.swift deleted file mode 100644 index 5683c915..00000000 --- a/Doolda/UserDefaultsPersistenceServiceTest/UserDefaultsPersistenceServiceTest.swift +++ /dev/null @@ -1,47 +0,0 @@ -// -// UserDefaultsPersistenceServiceTest.swift -// UserDefaultsPersistenceServiceTest -// -// Created by 김민주 on 2021/11/03. -// - -import XCTest - -class UserDefaultsPersistenceServiceTest: XCTestCase { - - private var persistenceService: UserDefaultsPersistenceServiceProtocol? - - override func setUpWithError() throws { - self.persistenceService = UserDefaultsPersistenceService() - } - - override func tearDownWithError() throws { - self.persistenceService = nil - } - - func testSetValue() throws { - guard let persistenceService = self.persistenceService else { - XCTFail() - return - } - persistenceService.set(key: "userId", value: "TestId") - guard let userId: String = persistenceService.get(key: "userId") else { - XCTFail() - return - } - XCTAssertEqual(userId, "TestId") - } - - func testRemoveValue() throws { - guard let persistenceService = self.persistenceService else { - XCTFail() - return - } - persistenceService.remove(key: "userId") - guard let userId: String? = persistenceService.get(key: "userId") else { - XCTFail() - return - } - XCTAssertNil(userId, "값이 지워지지 않았습니다.") - } -} diff --git a/Doolda/UserRepositoryTests/UserRepositoryTests.swift b/Doolda/UserRepositoryTests/UserRepositoryTests.swift deleted file mode 100644 index 7efb7011..00000000 --- a/Doolda/UserRepositoryTests/UserRepositoryTests.swift +++ /dev/null @@ -1,138 +0,0 @@ -// -// UserRepositoryTests.swift -// UserRepositoryTests -// -// Created by 김민주 on 2021/11/04. -// - -import Combine -import XCTest - -class UserRepositoryTest: XCTestCase { - - private let dummyUserId = "00000000-0000-0000-0000-000000000000" - private let dummyfriendId = "00000000-0000-0000-0000-000000000001" - private let dummyPairId = "00000000-0000-0000-0000-000000000002" - private let dummyNotExistId = "00000000-0000-0000-0000-000000000005" - - private var userRepository: UserRepositoryProtocol? - private var cancellables: Set = [] - - override func setUpWithError() throws { - let userRepository = UserRepository(persistenceService: UserDefaultsPersistenceService(), - networkService: FirebaseNetworkService()) - self.userRepository = userRepository - } - - override func tearDownWithError() throws { - self.userRepository = nil - self.cancellables.removeAll() - let persistenceService = UserDefaultsPersistenceService() - persistenceService.remove(key: UserDefaults.Keys.userId) - } - - func testFetchMyIdSuccess() throws { - let persistenceService = UserDefaultsPersistenceService() - persistenceService.set(key: UserDefaults.Keys.userId, value: self.dummyUserId) - - guard let userRepository = userRepository else { - XCTFail() - return - } - userRepository.fetchMyId() - .sink(receiveCompletion: { completion in - guard case .failure(let error) = completion else { return } - XCTFail(error.localizedDescription) - }, receiveValue: { myId in - XCTAssertEqual(myId, self.dummyUserId, "불러온 내 id값이 예상값과 일치하지 않습니다.") - }).store(in: &self.cancellables) - } - - func testFetchMyIdFail() throws { - guard let userRepository = userRepository else { - XCTFail() - return - } - userRepository.fetchMyId() - .sink(receiveCompletion: { completion in - guard case .failure(let error) = completion else { return } - print(error) - }, receiveValue: { myId in - XCTFail() - }).store(in: &self.cancellables) - } - - func testsaveMyId() throws { - let expectation = XCTestExpectation() - - guard let userRepository = userRepository else { - XCTFail() - return - } - userRepository.saveMyId(self.dummyUserId).sink { completion in - guard case .failure(let error) = completion else { return } - XCTFail(error.localizedDescription) - } receiveValue: { myId in - XCTAssertEqual(myId, self.dummyUserId, "불러온 내 id값이 예상값과 일치하지 않습니다." ) - expectation.fulfill() - }.store(in: &self.cancellables) - wait(for: [expectation], timeout: 10) - } - - func testSavePairId() throws { - let expectation = XCTestExpectation() - - guard let userRepository = userRepository else { - XCTFail() - return - } - userRepository.savePairId(myId: self.dummyUserId, - friendId: self.dummyfriendId, - pairId: self.dummyPairId) - .sink { completion in - guard case .failure(let error) = completion else { return } - XCTFail(error.localizedDescription) - } receiveValue: { pairId in - XCTAssertEqual(pairId, self.dummyPairId, "불러온 pair id값이 예상값과 일치하지 않습니다." ) - expectation.fulfill() - }.store(in: &self.cancellables) - wait(for: [expectation], timeout: 10) - } - - func testCheckUserIdIsExistTrue() throws { - let expectation = XCTestExpectation() - - guard let userRepository = userRepository else { - XCTFail() - return - } - userRepository.checkUserIdIsExist(self.dummyUserId) - .sink { completion in - guard case .failure(let error) = completion else { return } - XCTFail(error.localizedDescription) - } receiveValue: { result in - XCTAssertTrue(result, "userId가 없습니다.") - expectation.fulfill() - }.store(in: &self.cancellables) - wait(for: [expectation], timeout: 10) - } - - func testCheckUserIdIsExistFalse() throws { - let expectation = XCTestExpectation() - - guard let userRepository = userRepository else { - XCTFail() - return - } - userRepository.checkUserIdIsExist(self.dummyNotExistId) - .sink { completion in - guard case .failure(let error) = completion else { return } - XCTFail(error.localizedDescription) - } receiveValue: { result in - XCTAssertFalse(result, "userId가 있습니다..") - expectation.fulfill() - }.store(in: &self.cancellables) - - wait(for: [expectation], timeout: 10) - } -} diff --git a/README.md b/README.md index 6cdbed17..77afd646 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@
- +![Untitled](https://user-images.githubusercontent.com/64180706/143469296-7cbfe17d-d517-4bc0-9df5-5d56babf4341.gif)
@@ -14,35 +14,136 @@
-## 프로젝트 소개 +## 프로젝트 소개 🦔 -친구와 함께 둘만의 다이어리를 꾸미고 공유해 보세요!! +친구와 단 둘이서 공유하고 싶은 추억이 있나요? + +둘다에서 친구와 함께한 추억을 기록 해 보세요 + +페이지가 하나씩 채워질 수록 친구와의 관계도 깊어질 거에요 + +
+ +## 배포 링크 🔗 +
+둘다를 직접 실행할 수 있는 배포링크입니다. +
+ +[둘다 배포링크](http://yabbyark.iptime.org)
-## Bool 바다 소개🔥 +## Bool 바다 소개 🔥 | S007 김민주 | S017 박세원 | S027 양승훈 | S053 정지승 | | :--------: | :--------: | :--------: | :--------: | | [@minglely](https://github.com/minglely) | [@ehWld](https://github.com/ehWld) | [@yabby1997](https://github.com/yabby1997) | [@JungJiSeung](https://github.com/JungJiSeung) | -| | | | | +| | | | |
-## Features✨ +## 동작 화면 📱 +
+둘다의 실제 동작화면 입니다.🦔 +
-![](https://i.imgur.com/3vulccW.png) +| ![](https://github.com/boostcampwm-2021/iOS07-DoolDa/blob/dev/Screenshots/%EB%82%B4%EA%B0%80%20%EC%97%B0%EA%B2%B0%ED%95%98%EB%8A%94%20%EA%B2%BD%EC%9A%B0.gif) | ![](https://github.com/boostcampwm-2021/iOS07-DoolDa/blob/dev/Screenshots/%EC%82%AC%EC%A7%84%20%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0.gif) | ![](https://github.com/boostcampwm-2021/iOS07-DoolDa/blob/dev/Screenshots/%ED%85%8D%EC%8A%A4%ED%8A%B8%20%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0.gif) | ![](https://github.com/boostcampwm-2021/iOS07-DoolDa/blob/dev/Screenshots/%EC%8A%A4%ED%8B%B0%EC%BB%A4%20%EC%B6%94%EA%B0%80%ED%95%98%EA%B8%B0.gif) | +| :-:| :-: | :-: | :-: | +| `페어링 화면` | `이미지 넣기` | `텍스트 넣기` | `스티커 넣기` | +| ![](https://github.com/boostcampwm-2021/iOS07-DoolDa/blob/dev/Screenshots/%EB%91%90%EA%B0%80%EC%A7%80%20%EB%B3%B4%EA%B8%B0%20%EB%B0%A9%EC%8B%9D.gif) | ![](https://github.com/boostcampwm-2021/iOS07-DoolDa/blob/dev/Screenshots/%ED%95%84%ED%84%B0%EB%A7%81%20%EC%98%B5%EC%85%98.gif) | ![](https://github.com/boostcampwm-2021/iOS07-DoolDa/blob/dev/Screenshots/%ED%8E%98%EC%9D%B4%EC%A7%80%20%EC%88%98%EC%A0%95%ED%95%98%EA%B8%B0.gif) | ![](https://github.com/boostcampwm-2021/iOS07-DoolDa/blob/dev/Screenshots/%EA%B8%B0%EB%B3%B8%20%ED%8F%B0%ED%8A%B8%20%EC%84%A0%ED%83%9D.gif) | +| `페이지 보기` | `페이지 필터` | `페이지 수정` | `폰트 변경` | -![](https://i.imgur.com/S6wKw7s.png) +## 주요 기능 ✨ + +### 🦔 친구와 연결하기 + +- 당신의 코드로 친구를 초대해 보세요 +- 초대코드를 입력한 사람이 먼저 페이지를 작성할 수 있어요 +- 새로고침 버튼을 눌러서 친구와 연결되었는지 확인할 수 있어요 + + +### 🦔 페이지 꾸미기 +- 하루의 내용을 글과 사진, 스티커로 기록해보세요 +- 원하는 속지의 색깔을 골라 다이어리의 분위기를 바꿔보세요 + + +### 🦔 다이어리 보기 + +- 좌측 상단의 버튼을 누르면 두가지 레이아웃 모드를 토글할 수 있어요 +- 필터 버튼으로 원하는 페이지를 골라보세요 + + +### 🦔 페이지 수정하기 & 공유하기 + +- 각 페이지를 수정하거나 공유할 수 있어요 +- 오른쪽 하단 편집 버튼으로 페이지를 수정할 수 있어요. +- 왼쪽 하단 공유 버튼으로 페이지를 이미지로 저장하거나 다른사람과 공유할 수 있어요 + + +### 🦔 푸시알림 보내기 + +- 다이어리 작성을 완료하면 상대방에게 푸시알림이 전달돼요 +- 새로고침을 통해 상대방에게 다이어리 작성을 요청할 수 있어요 + + +### 🦔 폰트 변경하기 + +- 제공되는 다양한 폰트들 중 원하는 폰트를 선택할 수 있어요 +- 선택된 폰트로 앱 전반의 기본 폰트가 변경되며, 앱이 종료되어도 기본 폰트 선택이 유지돼요. -
## Architecture🏛 : MVVM-C +
+둘다에서 사용하고 있는 프로젝트 구조 입니다. +
-![](https://i.imgur.com/IIyOSdx.png) +스크린샷 2021-12-03 오전 12 45 04 -## Framework +## 기술적 도전 💪 +
+프로젝트를 진행하면서 저희가 시도한 기술적 도전을 소개합니다. +
-
+### 💵 다이어리 캐싱 with CoreData + +- 다이어리 페이지 캐싱을 위해 데이터베이스를 직접 관리하지 않아도 객체 인스턴스를 통해 CRUD작업을 할 수 있는 `CoreData`를 사용하였습니다. +- 다이어리 페이지는 디스크에 캐싱하고 이에 대한 메타 데이터를 `CoreData`에 캐싱합니다. 다이어리 페이지 데이터를 사용할 때 `CoreData` 와 서버에 기록된 마지막 편집 시간을 비교하고, 이를 통해 서버로부터 다시 다이어리 페이지 데이터를 가져와야 하는지를 판단합니다. 이는 불필요한 통신을 줄여 통신 비용을 감소시키고, 더 나은 사용자 경험을 제공합니다. + +--- + +### 🚜 Combine + +- 파이프라인을 통해 계층간 데이터 전달과 비동기 이벤트 처리를 가능하도록 하는 `Combine` framework를 사용하였습니다. 애플에서 직접 제공하는 First Party Framework인 만큼 가장 신뢰할 수 있으며, `NotificationCenter` 와 `Timer` 등 기존의 Swift 비동기 코드와도 통합하기 유리하다는 장점에 따라 이를 선택하였습니다. +- `UIControl`과 `UIGestureRecognizer`의 이벤트를 위한 커스텀 Publisher와 Subscription 정의해 유저로부터 발생하는 비동기 이벤트에 대해서도 `Combine` 을 활용할 수 있도록 했습니다. 이를 통해 프로젝트 전반의 비동기 이벤트 처리에 있어 통일성과 코드 가독성을 향상시킬 수 있었습니다. + +--- + +### 🏛 Clean Architecture & MVVM-C + +- 비즈니스 로직과 변경이 자주 발생하는 외부의 레이어를 명확하게 분리하여 결합도를 낮추고  `ViewController` 간의 의존성을 낮추기 위해서 Clean Architecture 기반의 MVVM-C 패턴을 선택했습니다. +- `ViewModel`이 `Coordinator` 의 인터페이스를 통해 Scene을 전환시킬 수 있도록 하고, 각 Scene에 대한 의존성을 `Coordinator` 가 직접 주입하도록 했습니다. 이를 통해 `ViewController` 가 유저 입출력만을 담당하여 단일 책임 원칙을 지킬 수 있도록 했습니다. 이를 통해 각 객체 간의 결합도를 낮추고, 독자적으로 테스트가 가능한 구조로 설계하여 유지보수성을 높일 수 있었습니다. + +--- + +### 🌚 다크모드 지원 + +- iOS 13 버전 이후로 다크 모드 지원하도록 HIG에서 권장하고 있기 때문에, 둘다에서도 완벽한 다크모드를 지원하고자 했습니다. + +--- + +### 🎢 CGAffineTransform + +- 모든 다이어리에 표시될 객체의 각도와 크기 조절을 가능하도록 해, 다이어리를 꾸미는 사용자의 자유도를 높이고자 했습니다. 이를 위해 사용자의 제스처를 인식하여 CGRect, scale, angle 값을 객체에 저장하고, 이를 실제 View에 보여주기 위한 변환 과정에서 `CGAffineTransform`을 활용했습니다. + +--- + +### 🎨 DynamicAnimator + +- 시각적인 효과를 더해주기 위해 `UIDynamicAnimator` 를 활용했습니다 + +- 중력 효과를 주기 위해서 `UIGravityBehavior` 를 사용했습니다. +- 실제 기기가 받는 중력으로 애니메이션을 구현하기 위해 `CoreMotion`을 함께 사용했습니다. +- 스티커간 충돌 효과를 주기 위해 `UICollisionBehavior` 를 사용했습니다. +- 스티커에 탄성 효과를 주기 위해 `UIDynamicItemBehavior`를 활용했습니다. diff --git "a/Screenshots/\352\270\260\353\263\270 \355\217\260\355\212\270 \354\204\240\355\203\235.gif" "b/Screenshots/\352\270\260\353\263\270 \355\217\260\355\212\270 \354\204\240\355\203\235.gif" new file mode 100644 index 00000000..c2318b1c Binary files /dev/null and "b/Screenshots/\352\270\260\353\263\270 \355\217\260\355\212\270 \354\204\240\355\203\235.gif" differ diff --git "a/Screenshots/\353\202\264\352\260\200 \354\227\260\352\262\260\355\225\230\353\212\224 \352\262\275\354\232\260.gif" "b/Screenshots/\353\202\264\352\260\200 \354\227\260\352\262\260\355\225\230\353\212\224 \352\262\275\354\232\260.gif" new file mode 100644 index 00000000..57137ff2 Binary files /dev/null and "b/Screenshots/\353\202\264\352\260\200 \354\227\260\352\262\260\355\225\230\353\212\224 \352\262\275\354\232\260.gif" differ diff --git "a/Screenshots/\353\213\244\354\226\221\355\225\234 \354\212\244\355\213\260\354\273\244\355\214\251.gif" "b/Screenshots/\353\213\244\354\226\221\355\225\234 \354\212\244\355\213\260\354\273\244\355\214\251.gif" new file mode 100644 index 00000000..1a9cc3a4 Binary files /dev/null and "b/Screenshots/\353\213\244\354\226\221\355\225\234 \354\212\244\355\213\260\354\273\244\355\214\251.gif" differ diff --git "a/Screenshots/\353\221\220\352\260\200\354\247\200 \353\263\264\352\270\260 \353\260\251\354\213\235.gif" "b/Screenshots/\353\221\220\352\260\200\354\247\200 \353\263\264\352\270\260 \353\260\251\354\213\235.gif" new file mode 100644 index 00000000..3de29582 Binary files /dev/null and "b/Screenshots/\353\221\220\352\260\200\354\247\200 \353\263\264\352\270\260 \353\260\251\354\213\235.gif" differ diff --git "a/Screenshots/\354\202\254\354\247\204 \354\230\256\352\270\260\352\270\260, \355\231\225\353\214\200\355\225\230\352\270\260.gif" "b/Screenshots/\354\202\254\354\247\204 \354\230\256\352\270\260\352\270\260, \355\231\225\353\214\200\355\225\230\352\270\260.gif" new file mode 100644 index 00000000..15d42e8b Binary files /dev/null and "b/Screenshots/\354\202\254\354\247\204 \354\230\256\352\270\260\352\270\260, \355\231\225\353\214\200\355\225\230\352\270\260.gif" differ diff --git "a/Screenshots/\354\202\254\354\247\204 \354\266\224\352\260\200\355\225\230\352\270\260.gif" "b/Screenshots/\354\202\254\354\247\204 \354\266\224\352\260\200\355\225\230\352\270\260.gif" new file mode 100644 index 00000000..f1dc335a Binary files /dev/null and "b/Screenshots/\354\202\254\354\247\204 \354\266\224\352\260\200\355\225\230\352\270\260.gif" differ diff --git "a/Screenshots/\354\203\201\353\214\200\353\260\251\354\235\264 \353\213\244\354\235\264\354\226\264\353\246\254\353\245\274 \354\236\221\354\204\261.gif" "b/Screenshots/\354\203\201\353\214\200\353\260\251\354\235\264 \353\213\244\354\235\264\354\226\264\353\246\254\353\245\274 \354\236\221\354\204\261.gif" new file mode 100644 index 00000000..b6240973 Binary files /dev/null and "b/Screenshots/\354\203\201\353\214\200\353\260\251\354\235\264 \353\213\244\354\235\264\354\226\264\353\246\254\353\245\274 \354\236\221\354\204\261.gif" differ diff --git "a/Screenshots/\354\203\201\353\214\200\353\260\251\354\235\264 \354\203\210\353\241\234\352\263\240\354\271\250\354\235\204 \354\213\234\353\217\204.gif" "b/Screenshots/\354\203\201\353\214\200\353\260\251\354\235\264 \354\203\210\353\241\234\352\263\240\354\271\250\354\235\204 \354\213\234\353\217\204.gif" new file mode 100644 index 00000000..d32a6581 Binary files /dev/null and "b/Screenshots/\354\203\201\353\214\200\353\260\251\354\235\264 \354\203\210\353\241\234\352\263\240\354\271\250\354\235\204 \354\213\234\353\217\204.gif" differ diff --git "a/Screenshots/\354\203\201\353\214\200\353\260\251\354\235\264 \354\227\260\352\262\260\355\225\230\353\212\224 \352\262\275\354\232\260.gif" "b/Screenshots/\354\203\201\353\214\200\353\260\251\354\235\264 \354\227\260\352\262\260\355\225\230\353\212\224 \352\262\275\354\232\260.gif" new file mode 100644 index 00000000..611cebd6 Binary files /dev/null and "b/Screenshots/\354\203\201\353\214\200\353\260\251\354\235\264 \354\227\260\352\262\260\355\225\230\353\212\224 \352\262\275\354\232\260.gif" differ diff --git "a/Screenshots/\354\203\211\354\203\201, \355\201\254\352\270\260 \353\263\200\352\262\275\355\225\230\352\270\260.gif" "b/Screenshots/\354\203\211\354\203\201, \355\201\254\352\270\260 \353\263\200\352\262\275\355\225\230\352\270\260.gif" new file mode 100644 index 00000000..29cdd996 Binary files /dev/null and "b/Screenshots/\354\203\211\354\203\201, \355\201\254\352\270\260 \353\263\200\352\262\275\355\225\230\352\270\260.gif" differ diff --git "a/Screenshots/\354\212\244\355\213\260\354\273\244 \354\266\224\352\260\200\355\225\230\352\270\260.gif" "b/Screenshots/\354\212\244\355\213\260\354\273\244 \354\266\224\352\260\200\355\225\230\352\270\260.gif" new file mode 100644 index 00000000..dac95956 Binary files /dev/null and "b/Screenshots/\354\212\244\355\213\260\354\273\244 \354\266\224\352\260\200\355\225\230\352\270\260.gif" differ diff --git "a/Screenshots/\355\205\215\354\212\244\355\212\270 \354\266\224\352\260\200\355\225\230\352\270\260.gif" "b/Screenshots/\355\205\215\354\212\244\355\212\270 \354\266\224\352\260\200\355\225\230\352\270\260.gif" new file mode 100644 index 00000000..2c499912 Binary files /dev/null and "b/Screenshots/\355\205\215\354\212\244\355\212\270 \354\266\224\352\260\200\355\225\230\352\270\260.gif" differ diff --git "a/Screenshots/\355\216\230\354\235\264\354\247\200 \352\263\265\354\234\240 \352\270\260\353\212\245 \353\260\217 \354\240\200\354\236\245.gif" "b/Screenshots/\355\216\230\354\235\264\354\247\200 \352\263\265\354\234\240 \352\270\260\353\212\245 \353\260\217 \354\240\200\354\236\245.gif" new file mode 100644 index 00000000..14ce62ac Binary files /dev/null and "b/Screenshots/\355\216\230\354\235\264\354\247\200 \352\263\265\354\234\240 \352\270\260\353\212\245 \353\260\217 \354\240\200\354\236\245.gif" differ diff --git "a/Screenshots/\355\216\230\354\235\264\354\247\200 \354\210\230\354\240\225\355\225\230\352\270\260.gif" "b/Screenshots/\355\216\230\354\235\264\354\247\200 \354\210\230\354\240\225\355\225\230\352\270\260.gif" new file mode 100644 index 00000000..f65aebcf Binary files /dev/null and "b/Screenshots/\355\216\230\354\235\264\354\247\200 \354\210\230\354\240\225\355\225\230\352\270\260.gif" differ diff --git "a/Screenshots/\355\225\204\355\204\260\353\247\201 \354\230\265\354\205\230.gif" "b/Screenshots/\355\225\204\355\204\260\353\247\201 \354\230\265\354\205\230.gif" new file mode 100644 index 00000000..5790afb8 Binary files /dev/null and "b/Screenshots/\355\225\204\355\204\260\353\247\201 \354\230\265\354\205\230.gif" differ