diff --git a/Kabinett.xcodeproj/project.pbxproj b/Kabinett.xcodeproj/project.pbxproj index 7ec69fb1..ce76a1dc 100644 --- a/Kabinett.xcodeproj/project.pbxproj +++ b/Kabinett.xcodeproj/project.pbxproj @@ -84,21 +84,10 @@ 57ED9CE52C73760800A4312C /* goormSansOTF4.otf in Resources */ = {isa = PBXBuildFile; fileRef = 57ED9CE42C73760800A4312C /* goormSansOTF4.otf */; }; 57ED9CE72C73780100A4312C /* Pecita.otf in Resources */ = {isa = PBXBuildFile; fileRef = 57ED9CE62C73780100A4312C /* Pecita.otf */; }; 57ED9CE92C74262500A4312C /* SourceHanSerifK-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 57ED9CE82C74262400A4312C /* SourceHanSerifK-Regular.otf */; }; - 7F23AD6C2C7432B8007E1F28 /* LetterCompletionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F23AD6B2C7432B8007E1F28 /* LetterCompletionView.swift */; }; + 57F7A5AB2D954061002E1209 /* ContentWriteCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57F7A5AA2D954061002E1209 /* ContentWriteCell.swift */; }; 7F397C4E2C7C0ECE00388645 /* CustomTabViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F397C4D2C7C0ECE00388645 /* CustomTabViewModel.swift */; }; - 7F397C552C7DF20C00388645 /* ImportDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F397C542C7DF20C00388645 /* ImportDialog.swift */; }; 7F6CE9C02C6B2FEA0074568E /* CustomTabView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F6CE9BF2C6B2FE90074568E /* CustomTabView.swift */; }; - 7F6CE9C22C6B33DD0074568E /* OptionOverlay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F6CE9C12C6B33DD0074568E /* OptionOverlay.swift */; }; - 7F6CE9C42C6B50050074568E /* ImagePickerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F6CE9C32C6B50050074568E /* ImagePickerViewModel.swift */; }; - 7F6CE9E22C6E25500074568E /* CameraViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F6CE9E12C6E25500074568E /* CameraViewModel.swift */; }; - 7F6CE9E62C6E28400074568E /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F6CE9E52C6E28400074568E /* CameraView.swift */; }; - 7F78684C2C78B41A0083D204 /* ImagePickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F78684B2C78B41A0083D204 /* ImagePickerView.swift */; }; - 7F7868562C7B14220083D204 /* HorizontalPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F7868552C7B14220083D204 /* HorizontalPadding.swift */; }; 7F9890822C7EF5C30035CB0D /* CustomTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F9890812C7EF5C30035CB0D /* CustomTabBar.swift */; }; - 7FCAE2B12C73080000228FA7 /* ImagePreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FCAE2B02C73080000228FA7 /* ImagePreview.swift */; }; - 7FCAE2B82C730E1700228FA7 /* OverlappingImagesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FCAE2B72C730E1700228FA7 /* OverlappingImagesView.swift */; }; - 7FCAE2BA2C73102900228FA7 /* ImageDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FCAE2B92C73102900228FA7 /* ImageDetailView.swift */; }; - 7FCAE2BC2C73157C00228FA7 /* LetterWritingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FCAE2BB2C73157C00228FA7 /* LetterWritingView.swift */; }; 8314013F2C69C34500F601FB /* Letter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8314013E2C69C34500F601FB /* Letter.swift */; }; 832C72672C71CF7B0071E8D0 /* SignUpUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832C72662C71CF7B0071E8D0 /* SignUpUseCase.swift */; }; 8356843D2CE0A43600120EC8 /* ServiceKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 835684352CE0A43600120EC8 /* ServiceKeys.swift */; }; @@ -127,7 +116,6 @@ 83F0D6872C7072DB001B8733 /* FirestoreWriterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83F0D6862C7072DB001B8733 /* FirestoreWriterManager.swift */; }; AF738DC42D50B3900081FB96 /* Extension+NotificationName.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF738DC32D50B3900081FB96 /* Extension+NotificationName.swift */; }; AF738DC62D50B3C00081FB96 /* ToastViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF738DC52D50B3C00081FB96 /* ToastViewModel.swift */; }; - AF9B18F82C894B5900F3E446 /* DefaultImportLetterUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF9B18F72C894B5900F3E446 /* DefaultImportLetterUseCase.swift */; }; AF9B18FA2C894B7100F3E446 /* DefaultLetterBoxUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF9B18F92C894B7100F3E446 /* DefaultLetterBoxUseCase.swift */; }; AFA58F222C6A004C00A7C569 /* WriteLetterUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA58F212C6A004C00A7C569 /* WriteLetterUseCase.swift */; }; AFA58F242C6A02BF00A7C569 /* ImportLetterUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA58F232C6A02BF00A7C569 /* ImportLetterUseCase.swift */; }; @@ -137,7 +125,6 @@ AFCFDFC32C7C3F2A00BEFFDF /* DefaultProfileUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83949C892C71BC0F0080D72C /* DefaultProfileUseCase.swift */; }; AFCFDFC42C7C3F2A00BEFFDF /* FirestoreLetterBoxManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA58F252C6AE33D00A7C569 /* FirestoreLetterBoxManager.swift */; }; AFDE7D2E2C75797A0019F2DE /* FirestorageLetterManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFDE7D2D2C75797A0019F2DE /* FirestorageLetterManager.swift */; }; - AFE197A02D118277003597B9 /* OptionGuideView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFE1979F2D118277003597B9 /* OptionGuideView.swift */; }; E2E0DCD12CAED20800596DF7 /* LoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2E0DCD02CAED20800596DF7 /* LoadingView.swift */; }; /* End PBXBuildFile section */ @@ -235,21 +222,10 @@ 57ED9CE42C73760800A4312C /* goormSansOTF4.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = goormSansOTF4.otf; sourceTree = ""; }; 57ED9CE62C73780100A4312C /* Pecita.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Pecita.otf; sourceTree = ""; }; 57ED9CE82C74262400A4312C /* SourceHanSerifK-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SourceHanSerifK-Regular.otf"; sourceTree = ""; }; - 7F23AD6B2C7432B8007E1F28 /* LetterCompletionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterCompletionView.swift; sourceTree = ""; }; + 57F7A5AA2D954061002E1209 /* ContentWriteCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentWriteCell.swift; sourceTree = ""; }; 7F397C4D2C7C0ECE00388645 /* CustomTabViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTabViewModel.swift; sourceTree = ""; }; - 7F397C542C7DF20C00388645 /* ImportDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportDialog.swift; sourceTree = ""; }; 7F6CE9BF2C6B2FE90074568E /* CustomTabView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTabView.swift; sourceTree = ""; }; - 7F6CE9C12C6B33DD0074568E /* OptionOverlay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionOverlay.swift; sourceTree = ""; }; - 7F6CE9C32C6B50050074568E /* ImagePickerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerViewModel.swift; sourceTree = ""; }; - 7F6CE9E12C6E25500074568E /* CameraViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraViewModel.swift; sourceTree = ""; }; - 7F6CE9E52C6E28400074568E /* CameraView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = ""; }; - 7F78684B2C78B41A0083D204 /* ImagePickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePickerView.swift; sourceTree = ""; }; - 7F7868552C7B14220083D204 /* HorizontalPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HorizontalPadding.swift; sourceTree = ""; }; 7F9890812C7EF5C30035CB0D /* CustomTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTabBar.swift; sourceTree = ""; }; - 7FCAE2B02C73080000228FA7 /* ImagePreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImagePreview.swift; sourceTree = ""; }; - 7FCAE2B72C730E1700228FA7 /* OverlappingImagesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverlappingImagesView.swift; sourceTree = ""; }; - 7FCAE2B92C73102900228FA7 /* ImageDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageDetailView.swift; sourceTree = ""; }; - 7FCAE2BB2C73157C00228FA7 /* LetterWritingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LetterWritingView.swift; sourceTree = ""; }; 8314013E2C69C34500F601FB /* Letter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Letter.swift; sourceTree = ""; }; 832C72662C71CF7B0071E8D0 /* SignUpUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignUpUseCase.swift; sourceTree = ""; }; 835684352CE0A43600120EC8 /* ServiceKeys.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceKeys.swift; sourceTree = ""; }; @@ -278,7 +254,6 @@ 83F0D6862C7072DB001B8733 /* FirestoreWriterManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirestoreWriterManager.swift; sourceTree = ""; }; AF738DC32D50B3900081FB96 /* Extension+NotificationName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Extension+NotificationName.swift"; sourceTree = ""; }; AF738DC52D50B3C00081FB96 /* ToastViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastViewModel.swift; sourceTree = ""; }; - AF9B18F72C894B5900F3E446 /* DefaultImportLetterUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultImportLetterUseCase.swift; sourceTree = ""; }; AF9B18F92C894B7100F3E446 /* DefaultLetterBoxUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultLetterBoxUseCase.swift; sourceTree = ""; }; AFA58F172C69DB1300A7C569 /* Writer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Writer.swift; sourceTree = ""; }; AFA58F212C6A004C00A7C569 /* WriteLetterUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteLetterUseCase.swift; sourceTree = ""; }; @@ -288,7 +263,6 @@ AFA75B262D013F8900DA418F /* FirestoreLetterWriteManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirestoreLetterWriteManager.swift; sourceTree = ""; }; AFB88B582C89410600E79F90 /* DefaultWriteLetterUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultWriteLetterUseCase.swift; sourceTree = ""; }; AFDE7D2D2C75797A0019F2DE /* FirestorageLetterManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FirestorageLetterManager.swift; sourceTree = ""; }; - AFE1979F2D118277003597B9 /* OptionGuideView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionGuideView.swift; sourceTree = ""; }; E2E0DCD02CAED20800596DF7 /* LoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingView.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -399,6 +373,7 @@ 579E35FA2D2BB0B300F92A87 /* StationeryCell.swift */, 579E35FC2D2BB0FA00F92A87 /* EnvelopeCell.swift */, 579E35FE2D2BB11400F92A87 /* StampCell.swift */, + 57F7A5AA2D954061002E1209 /* ContentWriteCell.swift */, ); path = Cells; sourceTree = ""; @@ -418,39 +393,15 @@ path = Font; sourceTree = ""; }; - 7F397C4F2C7C381E00388645 /* Camera */ = { - isa = PBXGroup; - children = ( - 7F6CE9E52C6E28400074568E /* CameraView.swift */, - ); - path = Camera; - sourceTree = ""; - }; 7F397C502C7C382A00388645 /* CustomTabView */ = { isa = PBXGroup; children = ( 7F6CE9BF2C6B2FE90074568E /* CustomTabView.swift */, 7F9890812C7EF5C30035CB0D /* CustomTabBar.swift */, - 7F397C542C7DF20C00388645 /* ImportDialog.swift */, - 7F6CE9C12C6B33DD0074568E /* OptionOverlay.swift */, - AFE1979F2D118277003597B9 /* OptionGuideView.swift */, ); path = CustomTabView; sourceTree = ""; }; - 7F397C512C7C386500388645 /* PhotoImporting */ = { - isa = PBXGroup; - children = ( - 7FCAE2B02C73080000228FA7 /* ImagePreview.swift */, - 7FCAE2B92C73102900228FA7 /* ImageDetailView.swift */, - 7FCAE2B72C730E1700228FA7 /* OverlappingImagesView.swift */, - 7FCAE2BB2C73157C00228FA7 /* LetterWritingView.swift */, - 7F78684B2C78B41A0083D204 /* ImagePickerView.swift */, - 7F23AD6B2C7432B8007E1F28 /* LetterCompletionView.swift */, - ); - path = PhotoImporting; - sourceTree = ""; - }; 830CE3CD2CA2E35B00740627 /* Services */ = { isa = PBXGroup; children = ( @@ -468,7 +419,6 @@ isa = PBXGroup; children = ( AFB88B582C89410600E79F90 /* DefaultWriteLetterUseCase.swift */, - AF9B18F72C894B5900F3E446 /* DefaultImportLetterUseCase.swift */, AF9B18F92C894B7100F3E446 /* DefaultLetterBoxUseCase.swift */, 83949C892C71BC0F0080D72C /* DefaultProfileUseCase.swift */, 83D9C8E42C830C7600EF2684 /* DefaultSignUpUseCase.swift */, @@ -558,16 +508,6 @@ path = UseCase; sourceTree = ""; }; - 8366B6F12C65ECDB0021FAE0 /* ImportLetter */ = { - isa = PBXGroup; - children = ( - 7F397C512C7C386500388645 /* PhotoImporting */, - 7F397C4F2C7C381E00388645 /* Camera */, - 7F7868552C7B14220083D204 /* HorizontalPadding.swift */, - ); - path = ImportLetter; - sourceTree = ""; - }; 8366B6F22C65ECE10021FAE0 /* LetterBox */ = { isa = PBXGroup; children = ( @@ -620,15 +560,6 @@ path = SignUp; sourceTree = ""; }; - 8366B6F62C65ECFF0021FAE0 /* ImportLetter */ = { - isa = PBXGroup; - children = ( - 7F6CE9C32C6B50050074568E /* ImagePickerViewModel.swift */, - 7F6CE9E12C6E25500074568E /* CameraViewModel.swift */, - ); - path = ImportLetter; - sourceTree = ""; - }; 8366B6F72C65ED080021FAE0 /* LetterBox */ = { isa = PBXGroup; children = ( @@ -721,7 +652,6 @@ 8366B6F42C65ECEC0021FAE0 /* Profile */, 8366B6F32C65ECE60021FAE0 /* WriteLetter */, 8366B6F22C65ECE10021FAE0 /* LetterBox */, - 8366B6F12C65ECDB0021FAE0 /* ImportLetter */, ); path = View; sourceTree = ""; @@ -733,7 +663,6 @@ 8366B6F92C65ED150021FAE0 /* Profile */, 8366B6F82C65ED0F0021FAE0 /* WriteLetter */, 8366B6F72C65ED080021FAE0 /* LetterBox */, - 8366B6F62C65ECFF0021FAE0 /* ImportLetter */, ); path = ViewModel; sourceTree = ""; @@ -1015,17 +944,11 @@ 577157032C75D7C300E21162 /* StationerySelectionViewModel.swift in Sources */, AF9B18FA2C894B7100F3E446 /* DefaultLetterBoxUseCase.swift in Sources */, 57ED9CD52C70E4A400A4312C /* OpenSourceLicenseModalView.swift in Sources */, - 7F397C552C7DF20C00388645 /* ImportDialog.swift in Sources */, - 7F7868562C7B14220083D204 /* HorizontalPadding.swift in Sources */, 8356843F2CE0A43600120EC8 /* DIContainer.swift in Sources */, 538150542C8AF04B007B1E5A /* Extension+LetterType.swift in Sources */, - AFE197A02D118277003597B9 /* OptionGuideView.swift in Sources */, - AF9B18F82C894B5900F3E446 /* DefaultImportLetterUseCase.swift in Sources */, 5358E0732CE16E630089C59F /* SearchBarViewModel.swift in Sources */, 83CA92AB2C8160CB00DFB68B /* Publisher+.swift in Sources */, - 7F6CE9C42C6B50050074568E /* ImagePickerViewModel.swift in Sources */, 534C67B72C7FF85700F0C175 /* LetterContentView.swift in Sources */, - 7FCAE2BA2C73102900228FA7 /* ImageDetailView.swift in Sources */, 04DEC0E72C6C6C7300D289EA /* ProfileView.swift in Sources */, 573EE1F62D2BA50300978283 /* MiniTabBarView.swift in Sources */, 53A482DA2C6C6F2D00F00A9A /* LetterBoxCell.swift in Sources */, @@ -1046,7 +969,6 @@ 5358E07B2CE1F0200089C59F /* ZoomableScrollView.swift in Sources */, 835684422CE0A43600120EC8 /* Module.swift in Sources */, 83A826CD2C6F2D23006FB09B /* ProfileUseCase.swift in Sources */, - 7F6CE9E62C6E28400074568E /* CameraView.swift in Sources */, 57966BA02C7EC452008D650B /* SelectionTabView.swift in Sources */, 04DEC0E32C6B2E1C00D289EA /* SignUpKabinettNumberSelectView.swift in Sources */, 04DEC0E12C6AFA7500D289EA /* SignUpNameInputView.swift in Sources */, @@ -1063,9 +985,7 @@ 530912CC2C7045F200964130 /* ToastView.swift in Sources */, 5771570B2C77071400E21162 /* ContentWriteViewModel.swift in Sources */, 53A99EA92C81914B00896AAC /* CalendarViewModel.swift in Sources */, - 7FCAE2B12C73080000228FA7 /* ImagePreview.swift in Sources */, 83CA92AE2C8181DB00DFB68B /* FirestorageWriterManager.swift in Sources */, - 7FCAE2B82C730E1700228FA7 /* OverlappingImagesView.swift in Sources */, AFA58F302C6C4B2A00A7C569 /* LetterBoxUseCase.swift in Sources */, AFA58F222C6A004C00A7C569 /* WriteLetterUseCase.swift in Sources */, 8314013F2C69C34500F601FB /* Letter.swift in Sources */, @@ -1077,19 +997,15 @@ 53BA8D212C69E4B60084D5CC /* LetterEnvelopeCell.swift in Sources */, AFDE7D2E2C75797A0019F2DE /* FirestorageLetterManager.swift in Sources */, 57966B9E2C7DB267008D650B /* Extension+TextField.swift in Sources */, - 7F78684C2C78B41A0083D204 /* ImagePickerView.swift in Sources */, - 7F23AD6C2C7432B8007E1F28 /* LetterCompletionView.swift in Sources */, AFB88B592C89410600E79F90 /* DefaultWriteLetterUseCase.swift in Sources */, 53FC6B8A2C90221600E7D9A8 /* LetterHelper.swift in Sources */, 57ED94FA2C84AFAC00A6F187 /* LetterWriteModel.swift in Sources */, 839CE0F92C644BEF003635F3 /* ContentView.swift in Sources */, 57966BA42C7FF739008D650B /* PreviewLetterView.swift in Sources */, 04DEC0DD2C6A3AD600D289EA /* SignUpView.swift in Sources */, - 7F6CE9E22C6E25500074568E /* CameraViewModel.swift in Sources */, 573EE1F82D2BA55B00978283 /* CustomTextEditor.swift in Sources */, - 7FCAE2BC2C73157C00228FA7 /* LetterWritingView.swift in Sources */, - 7F6CE9C22C6B33DD0074568E /* OptionOverlay.swift in Sources */, 57966B9C2C7D8DAF008D650B /* EnvelopeStampSelectionViewModel.swift in Sources */, + 57F7A5AB2D954061002E1209 /* ContentWriteCell.swift in Sources */, 7F6CE9C02C6B2FEA0074568E /* CustomTabView.swift in Sources */, 53FC6B822C901F7700E7D9A8 /* Extension+Collection.swift in Sources */, 53DC1A2F2C7F0FC000575ACC /* LetterViewModel.swift in Sources */, diff --git a/Kabinett/Application/DIContainer/Keys/UseCaseKeys.swift b/Kabinett/Application/DIContainer/Keys/UseCaseKeys.swift index 80ed2830..0fa15df0 100644 --- a/Kabinett/Application/DIContainer/Keys/UseCaseKeys.swift +++ b/Kabinett/Application/DIContainer/Keys/UseCaseKeys.swift @@ -24,7 +24,3 @@ struct WriteLetterUseCaseKey: InjectionKey { struct LetterBoxUseCaseKey: InjectionKey { typealias Value = LetterBoxUseCase } - -struct ImportLetterUseCaseKey: InjectionKey { - typealias Value = ImportLetterUseCase -} diff --git a/Kabinett/Application/KabinettApp.swift b/Kabinett/Application/KabinettApp.swift index 72c8a2d4..66ee94b9 100644 --- a/Kabinett/Application/KabinettApp.swift +++ b/Kabinett/Application/KabinettApp.swift @@ -152,14 +152,6 @@ struct KabinettApp: App { authManager: authManager ) } - Module(ImportLetterUseCaseKey.self) { - DefaultImportLetterUseCase( - authManager: authManager, - writerManager: firestoreWriterManager, - letterManager: firestoreLetterWriteManager, - letterStorageManager: firestorageLetterManager - ) - } } } } diff --git a/Kabinett/Data/DefaultUseCases/DefaultImportLetterUseCase.swift b/Kabinett/Data/DefaultUseCases/DefaultImportLetterUseCase.swift deleted file mode 100644 index 8c0c06ca..00000000 --- a/Kabinett/Data/DefaultUseCases/DefaultImportLetterUseCase.swift +++ /dev/null @@ -1,117 +0,0 @@ -// -// DefaultImportLetterUseCase.swift -// Kabinett -// -// Created by JIHYE SEOK on 9/5/24. -// - -import Foundation -import Combine -import os - -final class DefaultImportLetterUseCase { - private let logger: Logger - private let authManager: AuthManager - private let writerManager: FirestoreWriterManager - private let letterManager: FirestoreLetterWriteManager - private let letterStorageManager: FirestorageLetterManager - - init( - authManager: AuthManager, - writerManager: FirestoreWriterManager, - letterManager: FirestoreLetterWriteManager, - letterStorageManager: FirestorageLetterManager - ) { - self.logger = Logger( - subsystem: "co.kr.codegrove.Kabinett", - category: "DefaultImportLetterUseCase" - ) - self.authManager = authManager - self.writerManager = writerManager - self.letterManager = letterManager - self.letterStorageManager = letterStorageManager - } -} - -extension DefaultImportLetterUseCase: ImportLetterUseCase { - func saveLetter(postScript: String?, - envelope: String, - stamp: String, - fromUserId: String?, - fromUserName: String, - fromUserKabinettNumber: Int?, - toUserId: String?, - toUserName: String, - toUserKabinettNumber: Int?, - photoContents: [Data], - date: Date, - isRead: Bool - ) async -> Result { - do { - let photoContentStringUrl = try await letterStorageManager.convertPhotoToUrl(photoContents: photoContents) - - let letter = Letter( - id: nil, - fontString: nil, - postScript: postScript ?? "", - envelopeImageUrlString: envelope, - stampImageUrlString: stamp, - fromUserId: fromUserId ?? "", - fromUserName: fromUserName, - fromUserKabinettNumber: fromUserKabinettNumber ?? 0, - toUserId: toUserId ?? "", - toUserName: toUserName, - toUserKabinettNumber: toUserKabinettNumber ?? 0, - content: [], - photoContents: photoContentStringUrl, - date: date, - stationeryImageUrlString: nil, - isRead: isRead) - - return await letterManager.saveLetterToFireStore(letter: letter, fromUserId: fromUserId, toUserId: toUserId) - } catch { - logger.error("Failed to save Photo Letter: \(error.localizedDescription)") - return .failure(error) - } - } - - // TODO: - Refactor this codes - func findWriter(by query: String) async -> [Writer] { - do { - async let resultByName = letterManager.findDocuments( - by: Query(key: .name, value: query), - as: Writer.self - ) - async let resultByNumber = letterManager.findDocuments( - by: Query(key: .kabinettNumber, value: query), - as: Writer.self - ) - - return try await resultByName + resultByNumber - } catch { - logger.error("Find writer error: \(error.localizedDescription)") - return [] - } - } - - func getCurrentWriter() -> AnyPublisher { - authManager - .getCurrentUser() - .compactMap { $0 } - .asyncMap { [weak self] user in - await self?.writerManager.getWriterDocument(with: user.uid) - } - .compactMap { $0 } - .eraseToAnyPublisher() - } - - // 편지봉투 로딩 - func loadEnvelopes() async -> Result<[String], any Error> { - await letterStorageManager.loadStorage(path: "Envelopes") - } - - // 우표 로딩 - func loadStamps() async -> Result<[String], any Error> { - await letterStorageManager.loadStorage(path: "Stamps") - } -} diff --git a/Kabinett/Domain/UseCase/ImportLetterUseCase/ImportLetterUseCase.swift b/Kabinett/Domain/UseCase/ImportLetterUseCase/ImportLetterUseCase.swift index 6b41a1f6..8b137891 100644 --- a/Kabinett/Domain/UseCase/ImportLetterUseCase/ImportLetterUseCase.swift +++ b/Kabinett/Domain/UseCase/ImportLetterUseCase/ImportLetterUseCase.swift @@ -1,30 +1 @@ -// -// ImportLetterUseCase.swift -// Kabinett -// -// Created by JIHYE SEOK on 8/12/24. -// -import Foundation -import Combine - -protocol ImportLetterUseCase { - func saveLetter(postScript: String?, - envelope: String, - stamp: String, - fromUserId: String?, - fromUserName: String, - fromUserKabinettNumber: Int?, - toUserId: String?, - toUserName: String, - toUserKabinettNumber: Int?, - photoContents: [Data], - date: Date, - isRead: Bool - ) async -> Result - func findWriter(by query: String) async -> [Writer] - func getCurrentWriter() -> AnyPublisher - - func loadEnvelopes() async -> Result<[String], any Error> - func loadStamps() async -> Result<[String], any Error> -} diff --git a/Kabinett/Presentation/Commons/CustomTabView/CustomTabBar.swift b/Kabinett/Presentation/Commons/CustomTabView/CustomTabBar.swift index 8e63715a..037d32b4 100644 --- a/Kabinett/Presentation/Commons/CustomTabView/CustomTabBar.swift +++ b/Kabinett/Presentation/Commons/CustomTabView/CustomTabBar.swift @@ -43,7 +43,7 @@ struct CustomTabBar: View { }) { Image(uiImage: image) .renderingMode(.template) - .foregroundStyle(viewModel.selectedTab == tag ? Color.primary600 : Color.primary300) + .foregroundStyle(viewModel.selectedTab == tag ? Color.primary900 : Color.primary300) } } diff --git a/Kabinett/Presentation/Commons/CustomTabView/CustomTabView.swift b/Kabinett/Presentation/Commons/CustomTabView/CustomTabView.swift index 0e398ab9..be3216ba 100644 --- a/Kabinett/Presentation/Commons/CustomTabView/CustomTabView.swift +++ b/Kabinett/Presentation/Commons/CustomTabView/CustomTabView.swift @@ -12,19 +12,16 @@ struct CustomTabView: View { @StateObject private var customTabViewModel = CustomTabViewModel() @StateObject private var profileViewModel: ProfileViewModel @StateObject private var envelopeStampSelectionViewModel: EnvelopeStampSelectionViewModel - @StateObject private var imagePickerViewModel: ImagePickerViewModel - @State private var letterWriteViewModel = LetterWriteModel() + @State private var letterWriteModel = LetterWriteModel() init() { @Injected(LetterBoxUseCaseKey.self) var letterBoxUseCase: LetterBoxUseCase @Injected(ProfileUseCaseKey.self) var profileUseCase: ProfileUseCase @Injected(WriteLetterUseCaseKey.self) var writeLetterUseCase: WriteLetterUseCase - @Injected(ImportLetterUseCaseKey.self) var importLetterUseCase: ImportLetterUseCase self._customTabViewModel = StateObject(wrappedValue: CustomTabViewModel()) self._profileViewModel = StateObject(wrappedValue: ProfileViewModel(profileUseCase: profileUseCase)) self._envelopeStampSelectionViewModel = StateObject(wrappedValue: EnvelopeStampSelectionViewModel(useCase: writeLetterUseCase)) - self._imagePickerViewModel = StateObject(wrappedValue: ImagePickerViewModel(componentsUseCase: importLetterUseCase)) } var body: some View { @@ -54,43 +51,14 @@ struct CustomTabView: View { .onChange(of: customTabViewModel.selectedTab) { oldValue, newValue in if newValue == 1 { withAnimation { - customTabViewModel.showOptions = true + customTabViewModel.showWriteView = true } customTabViewModel.selectedTab = oldValue } } - .overlay( - Group { - if customTabViewModel.showOptions { - OptionOverlay( - customTabViewModel: customTabViewModel, - imageViewModel: imagePickerViewModel - ) - } - } - ) - .overlay( - ImportDialog( - viewModel: customTabViewModel, - envelopeStampSelectionViewModel: envelopeStampSelectionViewModel - ) - ) - .overlay( - ImagePickerView( - imageViewModel: imagePickerViewModel, - customViewModel: customTabViewModel, - envelopeStampSelectionViewModel: envelopeStampSelectionViewModel - ) - ) - .fullScreenCover(isPresented: $customTabViewModel.showCamera) { - CameraView(imagePickerViewModel: imagePickerViewModel) - } - .sheet(isPresented: $customTabViewModel.showWriteLetterView) { - ContentWriteView( - letterContent: $letterWriteViewModel, - imageViewModel: imagePickerViewModel, - customTabViewModel: customTabViewModel - ) + .sheet(isPresented: $customTabViewModel.showWriteView) { + UserSelectionView(letter: $letterWriteModel.writeLetter, customViewModel: customTabViewModel) + .presentationDetents([.height(300), .large]) } } } diff --git a/Kabinett/Presentation/Commons/CustomTabView/ImportDialog.swift b/Kabinett/Presentation/Commons/CustomTabView/ImportDialog.swift deleted file mode 100644 index bc9fc415..00000000 --- a/Kabinett/Presentation/Commons/CustomTabView/ImportDialog.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// ImportDialog.swift -// Kabinett -// -// Created by 김정우 on 8/27/24. -// - -import SwiftUI - -struct ImportDialog: View { - @ObservedObject var viewModel: CustomTabViewModel - @ObservedObject var envelopeStampSelectionViewModel: EnvelopeStampSelectionViewModel - - var body: some View { - EmptyView() - .confirmationDialog("편지를 보관할 방법을 선택하세요.", isPresented: $viewModel.showImportDialog, titleVisibility: .visible) { - Button("촬영하기") { - viewModel.hideOptions() - viewModel.showCamera = true - } - Button("앨범에서 가져오기") { - viewModel.hideOptions() - viewModel.showPhotoLibrary = true - } - Button("취소", role: .cancel) { - viewModel.showImportDialog = false - viewModel.showOptions = true - } - } - } -} diff --git a/Kabinett/Presentation/Commons/CustomTabView/OptionGuideView.swift b/Kabinett/Presentation/Commons/CustomTabView/OptionGuideView.swift deleted file mode 100644 index dafa5b8f..00000000 --- a/Kabinett/Presentation/Commons/CustomTabView/OptionGuideView.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// OptionGuideView.swift -// Kabinett -// -// Created by JIHYE SEOK on 12/17/24. -// - -import SwiftUI - -struct OptionGuideView: View { - var body: some View { - HStack(spacing: 0) { - Image("LetterImportGuide") - .resizable() - .scaledToFit() - .frame(width: UIScreen.main.bounds.width/2 - 8) - - Image("LetterWriteGuide") - .resizable() - .scaledToFit() - .frame(width: UIScreen.main.bounds.width/2 - 8) - } - } -} diff --git a/Kabinett/Presentation/Commons/CustomTabView/OptionOverlay.swift b/Kabinett/Presentation/Commons/CustomTabView/OptionOverlay.swift deleted file mode 100644 index c36e9f8e..00000000 --- a/Kabinett/Presentation/Commons/CustomTabView/OptionOverlay.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// OptionOverlay.swift -// Kabinett -// -// Created by 김정우 on 8/13/24. -// - -import SwiftUI - -struct OptionOverlay: View { - @AppStorage("isFirstWrite") private var isFirstWrite: Bool = true - @ObservedObject var customTabViewModel: CustomTabViewModel - @State private var letterContent = LetterWriteModel() - @State private var isWritingLetter = false - @ObservedObject var imageViewModel: ImagePickerViewModel - - var body: some View { - NavigationStack { - ZStack { - Color.black.opacity(0.8) - .edgesIgnoringSafeArea(.all) - .onTapGesture { - withAnimation { - customTabViewModel.hideOptions() - } - } - - VStack(spacing: 0) { - Spacer() - - if isFirstWrite { - OptionGuideView() - } - - HStack(spacing: 2) { - Button(action: { - customTabViewModel.showImportDialogAndHideOptions() - customTabViewModel.hideOptions() - }) { - Text("편지 보관하기") - .font(.system(size: 16)) - .fontWeight(.semibold) - .frame(maxWidth: .infinity) - .frame(height: 24) - .padding() - .background(Color.primary100) - .foregroundColor(.contentPrimary) - } - - NavigationLink("편지 쓰기") { - StationerySelectionView( - letterContent: $letterContent, - customViewModel: customTabViewModel, - imageViewModel: imageViewModel - ) - } - .font(.system(size: 16)) - .fontWeight(.semibold) - .frame(maxWidth: .infinity) - .frame(height: 24) - .padding() - .background(Color.primary100) - .foregroundColor(.contentPrimary) - } - .cornerRadius(10) - .padding(.horizontal) - .padding(.bottom, customTabViewModel.calculateOptionOverlayBottomPadding()) - } - .background(Color.clear) - } - .background(ClearBackground()) - .onDisappear { - if isFirstWrite { - isFirstWrite = false - } - } - } - } -} - - -// MARK: - 배경색 제거를 위한 코드 -struct ClearBackground: UIViewRepresentable { - public func makeUIView(context: Context) -> UIView { - let view = ClearBackgroundView() - DispatchQueue.main.async { - view.superview?.superview?.backgroundColor = .clear - } - return view - } - public func updateUIView(_ uiView: UIView, context: Context) {} -} - -class ClearBackgroundView: UIView { - open override func layoutSubviews() { - guard let parentView = superview?.superview else { - return - } - parentView.backgroundColor = .clear - } -} diff --git a/Kabinett/Presentation/Commons/CustomTabViewModel/CustomTabViewModel.swift b/Kabinett/Presentation/Commons/CustomTabViewModel/CustomTabViewModel.swift index 1dbcd282..73dc5107 100644 --- a/Kabinett/Presentation/Commons/CustomTabViewModel/CustomTabViewModel.swift +++ b/Kabinett/Presentation/Commons/CustomTabViewModel/CustomTabViewModel.swift @@ -9,22 +9,18 @@ import SwiftUI final class CustomTabViewModel: ObservableObject { @Published var selectedTab: Int = 0 - @Published var showOptions: Bool = false + @Published var showWriteView: Bool = false @Published var showImportDialog: Bool = false + @Published var showCamera: Bool = false @Published var showPhotoLibrary: Bool = false @Published var showImagePreview: Bool = false - @Published var showWriteLetterView: Bool = false + @Published var letterBoxNavigationPath = NavigationPath() @Published var profileNavigationPath = NavigationPath() - @Published var isLetterWrite: Bool = false - @Published var previousTab: Int? static let profileTabTappedNotification = Notification.Name("profileTabTappedNotification") - private var lastTabSelectionTime: Date? - private let doubleTapInterval: TimeInterval = 0.2 - // MARK: TabView SystemImage Size let envelopeImage: UIImage let plusImage: UIImage @@ -32,7 +28,7 @@ final class CustomTabViewModel: ObservableObject { init() { self.envelopeImage = UIImage(systemName: "envelope")!.applyingSymbolConfiguration(.init(pointSize: 21, weight: .medium))! - self.plusImage = UIImage(systemName: "plus")!.applyingSymbolConfiguration(.init(pointSize: 24, weight: .medium))! + self.plusImage = UIImage(systemName: "pencil.line")!.applyingSymbolConfiguration(.init(pointSize: 24, weight: .medium))! self.profileImage = UIImage(systemName: "circle.fill")!.applyingSymbolConfiguration(.init(pointSize: 21, weight: .medium))! } @@ -59,7 +55,7 @@ final class CustomTabViewModel: ObservableObject { } } else if tab == 1 { withAnimation(.easeInOut(duration: 0.3)) { - showOptions = true + showWriteView = true } } else { selectedTab = tab @@ -77,30 +73,9 @@ final class CustomTabViewModel: ObservableObject { } } - func navigateToLetterBox() { - selectedTab = 0 - showOptions = false - showImportDialog = false - showPhotoLibrary = false - showCamera = false - showImagePreview = false - showWriteLetterView = false - } - - // MARK: OptionOverlay sheet 관련 Method - func hideOptions() { - showOptions = false - } - - func showImportDialogAndHideOptions() { - showOptions = false - showImportDialog = true - isLetterWrite = false - } - - func showWriteLetterViewAndHideOptions() { - showOptions = false - showWriteLetterView = true + // MARK: OptionOverlay sheet 관련 Method + func hideWriteView() { + showWriteView = false } // MARK: ImagePicker sheet 관련 Method diff --git a/Kabinett/Presentation/View/ImportLetter/.gitkeep b/Kabinett/Presentation/View/ImportLetter/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Kabinett/Presentation/View/ImportLetter/Camera/CameraView.swift b/Kabinett/Presentation/View/ImportLetter/Camera/CameraView.swift deleted file mode 100644 index dc7a92f3..00000000 --- a/Kabinett/Presentation/View/ImportLetter/Camera/CameraView.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// CameraView.swift -// Kabinett -// -// Created by 김정우 on 8/15/24. -// - -import SwiftUI - -struct CameraView: View { - @StateObject private var viewModel = CameraViewModel() - @ObservedObject var imagePickerViewModel: ImagePickerViewModel - @Environment(\.dismiss) private var dismiss - - var body: some View { - CameraViewRepresentable(viewModel: viewModel) - .edgesIgnoringSafeArea(.all) - .onChange(of: viewModel.capturedImage) { _, newImage in - if let image = newImage, - let imageData = image.jpegData(compressionQuality: 0.5) { - imagePickerViewModel.photoContents.append(imageData) - dismiss() - } - } - } -} - -// MARK: - Camera View Representable -private struct CameraViewRepresentable: UIViewControllerRepresentable { - @ObservedObject var viewModel: CameraViewModel - - func makeUIViewController(context: Context) -> UIImagePickerController { - let picker = UIImagePickerController() - picker.sourceType = .camera - picker.delegate = context.coordinator - return picker - } - - func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {} - - func makeCoordinator() -> Coordinator { - Coordinator(self) - } - - // MARK: Coordinator (UIImagePickerController Coordinator) - class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate { - let parent: CameraViewRepresentable - - init(_ parent: CameraViewRepresentable) { - self.parent = parent - } - - func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { - parent.viewModel.captureImage(with: info) - } - - func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { - picker.dismiss(animated: true, completion: nil) - } - } -} diff --git a/Kabinett/Presentation/View/ImportLetter/HorizontalPadding.swift b/Kabinett/Presentation/View/ImportLetter/HorizontalPadding.swift deleted file mode 100644 index 114d3991..00000000 --- a/Kabinett/Presentation/View/ImportLetter/HorizontalPadding.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// HorizontalPadding.swift -// Kabinett -// -// Created by 김정우 on 8/25/24. -// - -import SwiftUI - -struct HorizontalPadding: ViewModifier { - @State private var padding: CGFloat = 0 - - func body(content: Content) -> some View { - GeometryReader { geometry in - content - .padding(.horizontal, geometry.size.width * 0.06) - .onAppear { - padding = geometry.size.width * 0.06 - } - } - } -} - -extension View { - func horizontalPadding() -> some View { - self.modifier(HorizontalPadding()) - } -} diff --git a/Kabinett/Presentation/View/ImportLetter/PhotoImporting/ImageDetailView.swift b/Kabinett/Presentation/View/ImportLetter/PhotoImporting/ImageDetailView.swift deleted file mode 100644 index 4b055a29..00000000 --- a/Kabinett/Presentation/View/ImportLetter/PhotoImporting/ImageDetailView.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// ImageDetailView.swift -// Kabinett -// -// Created by 김정우 on 8/19/24. -// - -import FirebaseAnalytics -import SwiftUI - -struct ImageDetailView: View { - let images: [Data] - @State private var currentIndex = 0 - @Binding var showDetailView: Bool - - var body: some View { - NavigationStack { - ZStack { - TabView(selection: $currentIndex) { - ForEach(images.indices, id: \.self) { index in - if let uiImage = UIImage(data: images[index]) { - Image(uiImage: uiImage) - .resizable() - .aspectRatio(contentMode: .fit) - .tag(index) - } - } - } - .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) - - HStack { - Button(action: { currentIndex = max(currentIndex - 1, 0) }) { - Image(systemName: "chevron.left") - .foregroundColor(.white) - } - .opacity(currentIndex > 0 ? 1 : 0) - - Spacer() - - Button(action: { currentIndex = min(currentIndex + 1, images.count - 1) }) { - Image(systemName: "chevron.right") - .foregroundColor(.white) - } - .opacity(currentIndex < images.count - 1 ? 1 : 0) - } - .padding() - } - .background(Color.background.edgesIgnoringSafeArea(.all)) - .navigationTitle("") - .navigationBarTitleDisplayMode(.inline) - } - .analyticsScreen( - name: "\(type(of:self))", - extraParameters: [ - AnalyticsParameterScreenName: "\(type(of:self))", - AnalyticsParameterScreenClass: "\(type(of:self))", - ] - ) - } -} diff --git a/Kabinett/Presentation/View/ImportLetter/PhotoImporting/ImagePickerView.swift b/Kabinett/Presentation/View/ImportLetter/PhotoImporting/ImagePickerView.swift deleted file mode 100644 index 7d07e544..00000000 --- a/Kabinett/Presentation/View/ImportLetter/PhotoImporting/ImagePickerView.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// ImagePickerView.swift -// Kabinett -// -// Created by 김정우 on 8/23/24. -// - -import SwiftUI - -struct ImagePickerView: View { - @ObservedObject var imageViewModel: ImagePickerViewModel - @ObservedObject var customViewModel: CustomTabViewModel - @ObservedObject var envelopeStampSelectionViewModel: EnvelopeStampSelectionViewModel - - var body: some View { - EmptyView() - .photosPicker( - isPresented: $customViewModel.showPhotoLibrary, - selection: $imageViewModel.selectedItems, - maxSelectionCount: 10, - matching: .images - ) - .onChange(of: imageViewModel.selectedItems) { _, newValue in - Task { @MainActor in - imageViewModel.selectedItems = newValue - await imageViewModel.loadImages() - } - } - - .onChange(of: imageViewModel.photoContents) { _, newContents in - if !newContents.isEmpty { - customViewModel.showImagePreview = true - } - } - .fullScreenCover(isPresented: $customViewModel.showImagePreview) { - ImagePreview( - imageViewModel: imageViewModel, - customViewModel: customViewModel, - envelopeStampSelectionViewModel: envelopeStampSelectionViewModel - ) - } - } -} diff --git a/Kabinett/Presentation/View/ImportLetter/PhotoImporting/ImagePreview.swift b/Kabinett/Presentation/View/ImportLetter/PhotoImporting/ImagePreview.swift deleted file mode 100644 index 777f4e3a..00000000 --- a/Kabinett/Presentation/View/ImportLetter/PhotoImporting/ImagePreview.swift +++ /dev/null @@ -1,103 +0,0 @@ -// -// ImagePreview.swift -// Kabinett -// -// Created by 김정우 on 8/19/24. -// - -import FirebaseAnalytics -import SwiftUI - -struct ImagePreview: View { - @ObservedObject private var imageViewModel: ImagePickerViewModel - @ObservedObject private var customViewModel: CustomTabViewModel - @ObservedObject private var envelopeStampSelectionViewModel: EnvelopeStampSelectionViewModel - @Environment(\.dismiss) var dismiss - @State private var showDetailView = false - @State private var showLetterWritingView = false - @State private var navigateToEnvelopeStamp = false - @State private var letterContent = LetterWriteModel() - - init( - imageViewModel: ImagePickerViewModel, - customViewModel: CustomTabViewModel, - envelopeStampSelectionViewModel: EnvelopeStampSelectionViewModel - ) { - self.imageViewModel = imageViewModel - self.customViewModel = customViewModel - self.envelopeStampSelectionViewModel = envelopeStampSelectionViewModel - } - - var body: some View { - NavigationStack { - ZStack { - VStack { - Spacer() - OverlappingImagesView(images: imageViewModel.photoContents, showDetailView: $showDetailView) - Spacer() - Button(action: { - if customViewModel.isLetterWrite { - dismiss() - } else { - showLetterWritingView = true - } - }) { - Text(customViewModel.isLetterWrite ? "사진 동봉하기" : "편지 선택하기") - .font(.system(size: 16)) - .fontWeight(.semibold) - .foregroundStyle(Color.white) - .frame(maxWidth: .infinity, minHeight: 56) - .background(Color.primary900) - .cornerRadius(16) - } - .padding(.horizontal, UIScreen.main.bounds.width * 0.06) - } - .padding(.bottom, LayoutHelper.shared.getSize(forSE: 0.03, forOthers: 0.0)) - } - .navigationTitle("") - .navigationBarTitleDisplayMode(.inline) - .navigationBarItems( - leading: Button(action: { - imageViewModel.resetSelections() - customViewModel.showImagePreview = false - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - customViewModel.showPhotoLibrary = true - } - }) { - Image(systemName: "chevron.left") - .foregroundColor(.contentPrimary) - } - ) - .background(Color.background.edgesIgnoringSafeArea(.all)) - .navigationDestination(isPresented: $showDetailView) { - ImageDetailView( - images: imageViewModel.photoContents, - showDetailView: $showDetailView - ) - } - .sheet(isPresented: $showLetterWritingView) { - LetterWritingView( - viewModel: imageViewModel, - customViewModel: customViewModel, - envelopeStampViewModel: envelopeStampSelectionViewModel, - letterContent: $letterContent, - showEnvelopeStamp: $navigateToEnvelopeStamp - ) - } - .navigationDestination(isPresented: $navigateToEnvelopeStamp) { - EnvelopeStampSelectionView( - letterContent: $letterContent, - customTabViewModel: customViewModel, - imageViewModel: imageViewModel - ) - } - } - .analyticsScreen( - name: "\(type(of:self))", - extraParameters: [ - AnalyticsParameterScreenName: "\(type(of:self))", - AnalyticsParameterScreenClass: "\(type(of:self))", - ] - ) - } -} diff --git a/Kabinett/Presentation/View/ImportLetter/PhotoImporting/LetterCompletionView.swift b/Kabinett/Presentation/View/ImportLetter/PhotoImporting/LetterCompletionView.swift deleted file mode 100644 index cde20158..00000000 --- a/Kabinett/Presentation/View/ImportLetter/PhotoImporting/LetterCompletionView.swift +++ /dev/null @@ -1,163 +0,0 @@ -// -// LetterCompletionView.swift -// Kabinett -// -// Created by 김정우 on 8/20/24. -// - -import FirebaseAnalytics -import SwiftUI -import Kingfisher - -struct LetterCompletionView: View { - @Binding var letterContent: LetterWriteModel - @ObservedObject var viewModel: ImagePickerViewModel - @ObservedObject var customTabViewModel: CustomTabViewModel - @ObservedObject var envelopeStampSelectionViewModel: EnvelopeStampSelectionViewModel - @Environment(\.dismiss) private var dismiss - @State private var envelopeURL: String = "" - @State private var stampURL: String = "" - - var body: some View { - NavigationStack { - ZStack { - Color.background.edgesIgnoringSafeArea(.all) - - VStack(spacing: 20) { - Spacer() - letterPreviewView - completionMessageView - Spacer() - saveButton - } - } - } - .onAppear { - Task { - await viewModel.loadAndUpdateEnvelopeAndStamp() - envelopeURL = viewModel.envelopeURL ?? letterContent.envelopeImageUrlString - stampURL = viewModel.stampURL ?? letterContent.stampImageUrlString - } - } - .analyticsScreen( - name: "\(type(of:self))", - extraParameters: [ - AnalyticsParameterScreenName: "\(type(of:self))", - AnalyticsParameterScreenClass: "\(type(of:self))", - ] - ) - } - - private var letterPreviewView: some View { - ZStack(alignment: .topLeading) { - KFImage(URL(string: letterContent.envelopeImageUrlString)) - .resizable() - .placeholder { - ProgressView() - } - .shadow(color: Color(.primary300), radius: 5, x: 3, y: 3) - .aspectRatio(9/4, contentMode: .fit) - - VStack { - HStack(alignment: .top) { - VStack(alignment: .leading, spacing: 2) { - Text("보내는 사람") - .font(.system(size: 7)) - .foregroundStyle(.contentPrimary) - Text(viewModel.fromUserName) - .font(.system(size: 14)) - .foregroundStyle(.contentPrimary) - .frame(maxWidth: UIScreen.main.bounds.width * 0.57, alignment: .leading) - } - - Spacer() - - ZStack(alignment: .topTrailing) { - KFImage(URL(string: letterContent.stampImageUrlString)) - .resizable() - .placeholder { - ProgressView() - } - .frame(width: UIScreen.main.bounds.width * 0.09, height: UIScreen.main.bounds.height * 0.046) - .aspectRatio(contentMode: .fit) - - Text(viewModel.formattedDate) - .monospaced() - .font(.system(size: 12)) - .fontWeight(.medium) - .foregroundColor(.black) - .padding(2) - .background(Color.clear) - .cornerRadius(4) - .offset(x: -UIScreen.main.bounds.width * 0.06, y: -UIScreen.main.bounds.height * 0.005) - } - } - .padding(.horizontal, 25) - .padding(.top, 25) - - Spacer() - - HStack(alignment: .top) { - Text(viewModel.postScript ?? letterContent.postScript ?? "") - .font(.system(size: 10)) - .foregroundStyle(.contentPrimary) - .frame(width: UIScreen.main.bounds.width * 0.4, alignment: .leading) - - Spacer() - - VStack(alignment: .leading, spacing: 2) { - Text("받는 사람") - .font(.system(size: 7)) - .foregroundStyle(.contentPrimary) - Text(viewModel.toUserName) - .font(.system(size: 14)) - .foregroundStyle(.contentPrimary) - .frame(maxWidth: UIScreen.main.bounds.width * 0.26, alignment: .leading) - } - } - .padding(.horizontal, 25) - .padding(.bottom, 25) - } - } - .frame(width: UIScreen.main.bounds.width * 0.85, height: UIScreen.main.bounds.width * 0.85 * 4/9) - } - - private var completionMessageView: some View { - VStack(spacing: 10) { - Text("편지가 완성되었어요.") - Text("소중한 편지를 보관할게요.") - } - .font(.system(size: 18, weight: .semibold)) - } - - private var saveButton: some View { - Button(action: { - customTabViewModel.navigateToLetterBox() - dismiss() - customTabViewModel.selectedTab = 0 - - Task { - if viewModel.postScript == nil { - viewModel.postScript = letterContent.postScript - } - - let success = await viewModel.saveImportingImage() - NotificationCenter.default.post( - name: .showToast, - object: nil, - userInfo: success ? ["message": "편지가 성공적으로 보관되었어요.", "color": Color.primary900] : ["message": "앗..!! 편지 보관을 실패했어요..", "color": Color.alert]) - } - }) { - Text("편지 보관하기") - .font(.system(size: 16)) - .fontWeight(.semibold) - .foregroundStyle(Color.white) - .frame(maxWidth: .infinity, minHeight: 56) - .background(Color.primary900) - .cornerRadius(16) - } - .padding(.horizontal, UIScreen.main.bounds.width * 0.06) - .padding(.bottom, LayoutHelper.shared.getSize(forSE: 0.03, forOthers: 0.0)) - .disabled(viewModel.isLoading) - } -} diff --git a/Kabinett/Presentation/View/ImportLetter/PhotoImporting/LetterWritingView.swift b/Kabinett/Presentation/View/ImportLetter/PhotoImporting/LetterWritingView.swift deleted file mode 100644 index a757a0a7..00000000 --- a/Kabinett/Presentation/View/ImportLetter/PhotoImporting/LetterWritingView.swift +++ /dev/null @@ -1,274 +0,0 @@ -// -// LetterWritingView.swift -// Kabinett -// -// Created by 김정우 on 8/19/24. -// - -import FirebaseAnalytics -import SwiftUI - -struct LetterWritingView: View { - @ObservedObject var viewModel: ImagePickerViewModel - @ObservedObject var customViewModel: CustomTabViewModel - @ObservedObject var envelopeStampViewModel: EnvelopeStampSelectionViewModel - @Environment(\.dismiss) var dismiss - @Binding var letterContent: LetterWriteModel - @Binding var showEnvelopeStamp: Bool - @State private var showCalendar = false - - var body: some View { - NavigationStack { - ZStack { - Color.primary100.edgesIgnoringSafeArea(.all) - - VStack(spacing: 20) { - FormToUserView(letterContent: $letterContent, viewModel: viewModel) - dateField() - Spacer() - } - .padding([.leading, .trailing], 20) - .padding(.top, 20) - } - .onTapGesture { - UIApplication.shared.endEditing() - } - .navigationBarItems( - trailing: Button(action: { - updateLetterWrite() - dismiss() - showEnvelopeStamp = true - }) { - Text("완료") - .fontWeight(.medium) - .font(.system(size: 19)) - .foregroundColor(.contentPrimary) - } - ) - } - .task { - await viewModel.fetchCurrentWriter() - viewModel.updateDefaultUsers() - updateLetterWriteFromViewModel() - } - .analyticsScreen( - name: "\(type(of:self))", - extraParameters: [ - AnalyticsParameterScreenName: "\(type(of:self))", - AnalyticsParameterScreenClass: "\(type(of:self))", - ] - ) - } - - private func updateLetterWriteFromViewModel() { - letterContent.fromUserName = viewModel.fromUserName - letterContent.fromUserId = viewModel.fromUserId - letterContent.toUserName = viewModel.toUserName - letterContent.toUserId = viewModel.toUserId - - } - - private func dateField() -> some View { - VStack(spacing: 1) { - HStack(alignment: .center, spacing: 10) { - Text("받은/보낸 날짜") - .foregroundStyle(Color.contentPrimary) - .bold() - .font(.system(size: 16)) - .frame(width: 100, alignment: .leading) - - Button(action: { - showCalendar.toggle() - }) { - Text(viewModel.formattedDate) - .foregroundStyle(Color.blue) - .font(.system(size: 15)) - .padding(.horizontal, 15) - .frame(height: 40) - .frame(maxWidth: .infinity) - .background(Color.contentTertiary) - .clipShape(RoundedRectangle(cornerRadius: 8)) - } - } - } - .sheet(isPresented: $showCalendar) { - DatePicker("", selection: $viewModel.date, displayedComponents: [.date]) - .datePickerStyle(.graphical) - .frame(maxHeight: 350) - .background(Color.white) - .cornerRadius(5) - .shadow(color: Color.black.opacity(0.1), radius: 5, x: 0, y: 2) - .padding() - .presentationDetents([.height(470)]) - .presentationBackground(.clear) - .presentationCornerRadius(5) - .interactiveDismissDisabled() - .onChange(of: viewModel.date) { oldValue, newValue in - if oldValue != newValue { - letterContent.date = newValue - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - showCalendar = false - } - } - } - } - } - - private func updateLetterWrite() { - letterContent.fromUserName = viewModel.fromUserName - letterContent.toUserName = viewModel.toUserName - letterContent.date = viewModel.date - letterContent.photoContents = viewModel.photoContents - letterContent.dataSource = .importLetter - } -} - -struct FormToUserView: View { - @Binding var letterContent: LetterWriteModel - @ObservedObject var viewModel: ImagePickerViewModel - - var body: some View { - VStack(spacing: 20) { - userField(title: "보내는 사람", name: $viewModel.fromUserName, search: $viewModel.fromUserSearch, isFromUser: true) - userField(title: "받는 사람", name: $viewModel.toUserName, search: $viewModel.toUserSearch, isFromUser: false) - } - - .onAppear { - if let fromUser = viewModel.fromUser { - letterContent.fromUserId = fromUser.id - letterContent.fromUserName = fromUser.name - viewModel.fromUserName = fromUser.name - viewModel.fromUserKabinettNumber = fromUser.kabinettNumber - } - - letterContent.toUserId = letterContent.fromUserId - letterContent.toUserName = letterContent.fromUserName - viewModel.toUserId = letterContent.fromUserId - viewModel.toUserName = letterContent.fromUserName - viewModel.toUserKabinettNumber = viewModel.fromUserKabinettNumber - } - - } - - private func userField(title: String, name: Binding, search: Binding, isFromUser: Bool) -> some View { - VStack(spacing: 10) { - HStack(alignment: .center, spacing: 10) { - Text(title) - .foregroundStyle(Color.contentPrimary) - .font(.system(size: 16)) - .bold() - .frame(width: 100, alignment: .leading) - - TextField(isFromUser ? name.wrappedValue : "", text: name) - .foregroundStyle(isFromUser ? Color.contentSecondary : .black) - .font(.system(size: 15)) - .padding(.horizontal, 15) - .frame(height: 40) - .background(Color.white) - .clipShape(RoundedRectangle(cornerRadius: 20)) - .multilineTextAlignment(.center) - } - - HStack(alignment: .center, spacing: 10) { - Spacer() - .frame(width: 100) - - SearchResultList(letterContent: $letterContent, searchText: search, viewModel: viewModel, isFromUser: isFromUser) - } - } - } - - struct SearchResultList: View { - @Binding var letterContent: LetterWriteModel - @Binding var searchText: String - @ObservedObject var viewModel: ImagePickerViewModel - let isFromUser: Bool - - var body: some View { - VStack { - HStack { - Image(systemName: "magnifyingglass") - .foregroundStyle(Color.contentPrimary) - - TextField("검색", text: $searchText) - .foregroundStyle(.primary) - - if !searchText.isEmpty { - Button(action: { - self.searchText = "" - }) { - Image(systemName: "xmark.circle.fill") - .foregroundStyle(Color.primary100) - } - } - } - .padding(EdgeInsets(top: 7, leading: 13, bottom: 7, trailing: 13)) - .background(Color(.white)) - .clipShape(.capsule) - - if !searchText.isEmpty { - Divider() - .padding([.leading, .trailing], 10) - .padding(.top, -6) - - List { - Text("\(searchText) 입력") - .onTapGesture { - updateUser(name: searchText) - searchText = "" - UIApplication.shared.endEditing() - } - .padding(.leading, 35) - .listRowSeparator(.hidden) - .foregroundStyle(Color.primary900) - - ForEach(isFromUser ? viewModel.fromUserSearchResults : viewModel.toUserSearchResults, id: \.name) { user in - HStack { - Image(systemName: "person.crop.circle") - .resizable() - .frame(width: 25, height: 25) - .clipShape(.circle) - .foregroundStyle(Color.primary100) - Text(user.name) - .foregroundStyle(Color.primary900) - Spacer() - Text("\(String(user.kabinettNumber.prefix(3)))-\(String(user.kabinettNumber.suffix(3)))") - .foregroundStyle(Color.primary900) - } - .listRowSeparator(.hidden) - .onTapGesture { - updateUser(name: user.name) - searchText = "" - UIApplication.shared.endEditing() - } - } - } - .listStyle(PlainListStyle()) - .frame(height: 200) - .background(Color.white) - .cornerRadius(20) - .padding(.top, -5) - } - } - .padding(.top, 2) - .background(searchText.isEmpty ? Color.clear : Color.white) - .cornerRadius(16) - } - - private func updateUser(name: String) { - if isFromUser { - viewModel.fromUserName = name - letterContent.fromUserName = name - if let user = viewModel.usersData.first(where: { $0.name == name }) { - letterContent.fromUserId = user.id - viewModel.fromUserKabinettNumber = user.kabinettNumber - } else { - viewModel.fromUserId = "" - viewModel.fromUserKabinettNumber = 0 - } - } else { - viewModel.updateSelectedUser(selectedUserName: name) - } - } - } -} diff --git a/Kabinett/Presentation/View/ImportLetter/PhotoImporting/OverlappingImagesView.swift b/Kabinett/Presentation/View/ImportLetter/PhotoImporting/OverlappingImagesView.swift deleted file mode 100644 index 211f329f..00000000 --- a/Kabinett/Presentation/View/ImportLetter/PhotoImporting/OverlappingImagesView.swift +++ /dev/null @@ -1,65 +0,0 @@ -// -// OverlappingImagesView.swift -// Kabinett -// -// Created by 김정우 on 8/19/24. -// - -import SwiftUI - -struct OverlappingImagesView: View { - let images: [Data] - @Binding var showDetailView: Bool - - var body: some View { - GeometryReader { geometry in - ZStack { - ForEach(Array(images.prefix(3).enumerated().reversed()), id: \.offset) { index, imageData in - if let uiImage = UIImage(data: imageData) { - ImageView(uiImage: uiImage, index: index, totalImages: images.prefix(3).count, parentSize: geometry.size) - } - } - } - .frame(width: geometry.size.width, height: geometry.size.height) - .onTapGesture { - showDetailView = true - } - } - } -} - -struct ImageView: View { - let uiImage: UIImage - let index: Int - let totalImages: Int - let parentSize: CGSize - - var body: some View { - Image(uiImage: uiImage) - .resizable() - .aspectRatio(contentMode: .fill) - .frame(width: frameSize.width, height: frameSize.height) - .clipShape(RoundedRectangle(cornerRadius: 2)) - .rotationEffect(.degrees(rotationAngle), anchor: .center) - .position(x: parentSize.width / 2, y: parentSize.height / 2) - .shadow(color: .black.opacity(0.2), radius: 5, x: 0, y: 2) - } - - private var frameSize: CGSize { - let aspectRatio = uiImage.size.width / uiImage.size.height - let maxWidth = parentSize.width * 0.6 - let maxHeight = parentSize.height * 0.5 - - if aspectRatio > 1 { - let height = min(maxHeight, maxWidth / aspectRatio) - return CGSize(width: height * aspectRatio, height: height) - } else { - let width = min(maxWidth, maxHeight * aspectRatio) - return CGSize(width: width, height: width / aspectRatio) - } - } - - private var rotationAngle: Double { - Double(index) * -15 - } -} diff --git a/Kabinett/Presentation/View/WriteLetter/Cells/ContentWriteCell.swift b/Kabinett/Presentation/View/WriteLetter/Cells/ContentWriteCell.swift new file mode 100644 index 00000000..3533a348 --- /dev/null +++ b/Kabinett/Presentation/View/WriteLetter/Cells/ContentWriteCell.swift @@ -0,0 +1,104 @@ +// +// ContentWriteCell.swift +// Kabinett +// +// Created by Song Kim on 3/27/25. +// + +import SwiftUI +import Kingfisher + +struct TypingView: View { + let index: Int + @Binding var letter: WriteLetter + @ObservedObject var viewModel: ContentWriteViewModel + + var body: some View { + ZStack { + KFImage(URL(string: letter.stationeryImageUrlString)) + .placeholder { + ProgressView() + } + .resizable() + .shadow(color: Color(.primary300), radius: 5, x: 3, y: 3) + + VStack { + Text(index == 0 ? letter.toUserName : "") + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.top, screenHeight * 0.05) + .padding(.bottom, screenHeight * 0.01) + .onTapGesture { + UIApplication.shared.endEditing() + } + + GeometryReader { geo in + if index < viewModel.texts.count { + CustomTextEditor( + maxWidth: geo.size.width, + maxHeight: geo.size.height, + font: FontUtility.selectedUIFont(font: letter.fontString ?? "", size: FontUtility.fontSize(font: letter.fontString ?? "")), + text: $viewModel.texts[index] + ) + } + } + .onChange(of: viewModel.texts.count) { + letter.content = viewModel.texts + } + + Text(index == (viewModel.texts.count-1) ? (letter.date).formattedString() : "") + .padding(.bottom, screenHeight * 0.00001) + .frame(maxWidth: .infinity, alignment: .trailing) + + Text(index == (viewModel.texts.count-1) ? letter.fromUserName : "") + .padding(.bottom, screenHeight * 0.05) + .frame(maxWidth: .infinity, alignment: .trailing) + } + .padding(.horizontal, screenWidth * 0.08) + } + } +} + +struct PolaroidView: View { + let index: Int + let uiImage: UIImage + @Binding var letter: WriteLetter + @ObservedObject var viewModel: ContentWriteViewModel + + var body: some View { + ZStack(alignment: .topTrailing) { + Image(uiImage: uiImage) + .resizable() + .clipShape(RoundedRectangle(cornerRadius: 5)) + .aspectRatio(contentMode: .fit) + .padding([.horizontal, .top], 10) + .padding(.bottom, screenWidth * 0.12) + .background(Color.white) + .clipShape(RoundedRectangle(cornerRadius: 5)) + .shadow(color: .primary300, radius: 5, x: 3, y: 3) + .padding([.top, .bottom], 10) + + Button(action: { + viewModel.isDeletePhoto = true + }) { + Image(systemName: "xmark.circle.fill") + .resizable() + .frame(width: 25, height: 25) + .padding(.trailing, -10) + .foregroundColor(Color(.primary900)) + } + .alert(isPresented: $viewModel.isDeletePhoto) { + Alert( + title: Text("Delete Page"), + message: Text("이 사진을 삭제하시겠어요?"), + primaryButton: .destructive(Text("삭제")) { + viewModel.selectedItems.remove(at: viewModel.currentIndex - viewModel.texts.count) + }, + secondaryButton: .cancel(Text("취소")) { + viewModel.isDeletePhoto = false + } + ) + } + } + .frame(width: screenWidth * 0.88) + } +} diff --git a/Kabinett/Presentation/View/WriteLetter/Cells/EnvelopeCell.swift b/Kabinett/Presentation/View/WriteLetter/Cells/EnvelopeCell.swift index e1ddc8a1..8f565872 100644 --- a/Kabinett/Presentation/View/WriteLetter/Cells/EnvelopeCell.swift +++ b/Kabinett/Presentation/View/WriteLetter/Cells/EnvelopeCell.swift @@ -9,7 +9,7 @@ import SwiftUI import Kingfisher struct EnvelopeCell: View { - @Binding var letterContent: LetterWriteModel + @Binding var letter: WriteLetter @Binding var envelopeImageUrl: String @ObservedObject var viewModel: EnvelopeStampSelectionViewModel @@ -37,7 +37,7 @@ struct EnvelopeCell: View { .onTapGesture { viewModel.envelopeSelectStationery(coordinates: (rowIndex, columnIndex)) envelopeImageUrl = viewModel.envelopes[index] - letterContent.envelopeImageUrlString = viewModel.envelopes[index] + letter.envelopeImageUrlString = viewModel.envelopes[index] } if viewModel.isEnvelopeSelected(coordinates: (rowIndex, columnIndex)) { @@ -46,7 +46,7 @@ struct EnvelopeCell: View { .frame(width: 27, height: 27) .padding([.top, .trailing], 20) .onAppear { - letterContent.envelopeImageUrlString = viewModel.envelopes[viewModel.envelopeIndex(row: rowIndex, column: columnIndex)] + letter.envelopeImageUrlString = viewModel.envelopes[viewModel.envelopeIndex(row: rowIndex, column: columnIndex)] } } } diff --git a/Kabinett/Presentation/View/WriteLetter/Cells/StampCell.swift b/Kabinett/Presentation/View/WriteLetter/Cells/StampCell.swift index d6a23a29..c3c9efa7 100644 --- a/Kabinett/Presentation/View/WriteLetter/Cells/StampCell.swift +++ b/Kabinett/Presentation/View/WriteLetter/Cells/StampCell.swift @@ -9,7 +9,7 @@ import SwiftUI import Kingfisher struct StampCell: View { - @Binding var letterContent: LetterWriteModel + @Binding var letter: WriteLetter @Binding var stampImageUrl: String @ObservedObject var viewModel: EnvelopeStampSelectionViewModel @@ -37,7 +37,7 @@ struct StampCell: View { .onTapGesture { viewModel.stampSelectStationery(coordinates: (rowIndex, columnIndex)) stampImageUrl = viewModel.stamps[index] - letterContent.stampImageUrlString = viewModel.stamps[index] + letter.stampImageUrlString = viewModel.stamps[index] } if viewModel.isStampSelected(coordinates: (rowIndex, columnIndex)) { @@ -46,7 +46,7 @@ struct StampCell: View { .frame(width: 27, height: 27) .padding([.top, .trailing], 20) .onAppear { - letterContent.stampImageUrlString = viewModel.stamps[viewModel.stampIndex(row: rowIndex, column: columnIndex)] + letter.stampImageUrlString = viewModel.stamps[viewModel.stampIndex(row: rowIndex, column: columnIndex)] } } } diff --git a/Kabinett/Presentation/View/WriteLetter/Cells/StationeryCell.swift b/Kabinett/Presentation/View/WriteLetter/Cells/StationeryCell.swift index d18b9f59..d3181c32 100644 --- a/Kabinett/Presentation/View/WriteLetter/Cells/StationeryCell.swift +++ b/Kabinett/Presentation/View/WriteLetter/Cells/StationeryCell.swift @@ -12,7 +12,7 @@ struct StationeryCell: View { let index: Int let rowIndex: Int let columnIndex: Int - @Binding var letterContent: LetterWriteModel + @Binding var letter: WriteLetter @ObservedObject var stationerySelectionViewModel: StationerySelectionViewModel var body: some View { @@ -28,7 +28,7 @@ struct StationeryCell: View { .shadow(color: Color(.primary300), radius: 5, x: 3, y: 3) .onTapGesture { stationerySelectionViewModel.selectStationery(coordinates: (rowIndex, columnIndex)) - letterContent.stationeryImageUrlString = stationerySelectionViewModel.stationerys[index] + letter.stationeryImageUrlString = stationerySelectionViewModel.stationerys[index] } } else { EmptyView() @@ -40,7 +40,7 @@ struct StationeryCell: View { .frame(width: 32, height: 32) .padding([.top, .trailing], 20) .onAppear { - letterContent.stationeryImageUrlString = stationerySelectionViewModel.stationerys[index] + letter.stationeryImageUrlString = stationerySelectionViewModel.stationerys[index] } } } diff --git a/Kabinett/Presentation/View/WriteLetter/Cells/WriteLetterEnvelopeCell.swift b/Kabinett/Presentation/View/WriteLetter/Cells/WriteLetterEnvelopeCell.swift index e63b1a37..b4873b84 100644 --- a/Kabinett/Presentation/View/WriteLetter/Cells/WriteLetterEnvelopeCell.swift +++ b/Kabinett/Presentation/View/WriteLetter/Cells/WriteLetterEnvelopeCell.swift @@ -9,7 +9,8 @@ import SwiftUI import Kingfisher struct WriteLetterEnvelopeCell: View { - var letter: Letter + var letter: WriteLetter + var postScript: String var body: some View { ZStack { @@ -40,7 +41,7 @@ struct WriteLetterEnvelopeCell: View { .padding(.bottom, LayoutHelper.shared.getSize(forSE: 0.035 * 1.1, forOthers: 0.03 * 1.1)) HStack(alignment: .top) { - Text(letter.postScript ?? "") + Text(postScript) .font(FontUtility.selectedFont(font: letter.fontString ?? "SFDisplay", size: LayoutHelper.shared.getSize(forSE: 0.012 * 1.1, forOthers: 0.012 * 1.1))) .foregroundStyle(.contentPrimary) .frame(width: LayoutHelper.shared.getWidth(forSE: 0.4 * 1.1, forOthers: 0.4 * 1.1), alignment: .leading) diff --git a/Kabinett/Presentation/View/WriteLetter/Components/CustomTextEditor.swift b/Kabinett/Presentation/View/WriteLetter/Components/CustomTextEditor.swift index 33b5ebc4..0b3240fe 100644 --- a/Kabinett/Presentation/View/WriteLetter/Components/CustomTextEditor.swift +++ b/Kabinett/Presentation/View/WriteLetter/Components/CustomTextEditor.swift @@ -9,10 +9,10 @@ import UIKit import SwiftUI struct CustomTextEditor: UIViewRepresentable { - @Binding var text: String var maxWidth: CGFloat var maxHeight: CGFloat var font: UIFont + @Binding var text: String class Coordinator: NSObject, UITextViewDelegate { var parent: CustomTextEditor diff --git a/Kabinett/Presentation/View/WriteLetter/Components/FontMenuView.swift b/Kabinett/Presentation/View/WriteLetter/Components/FontMenuView.swift index e5edccbe..7b5ca984 100644 --- a/Kabinett/Presentation/View/WriteLetter/Components/FontMenuView.swift +++ b/Kabinett/Presentation/View/WriteLetter/Components/FontMenuView.swift @@ -8,7 +8,7 @@ import SwiftUI struct FontMenuView: View { - @Binding var letterContent: LetterWriteModel + @Binding var letter: WriteLetter @Binding var showFontMenu: Bool @ObservedObject var fontViewModel: FontSelectionViewModel @@ -24,7 +24,7 @@ struct FontMenuView: View { ForEach(0.., - imageViewModel: ImagePickerViewModel, + letter: Binding, customTabViewModel: CustomTabViewModel ) { - @Injected(ImportLetterUseCaseKey.self) var importLetterUseCase: ImportLetterUseCase - self._letterContent = letterContent - self.imageViewModel = imageViewModel self.customTabViewModel = customTabViewModel + self._letter = letter } var body: some View { @@ -39,19 +36,19 @@ struct ContentWriteView: View { } ZStack(alignment: .top) { VStack { - ScrollableLetterView(letterContent: $letterContent, viewModel: viewModel, imageViewModel: imageViewModel, currentIndex: $viewModel.currentIndex) - .font(FontUtility.selectedFont(font: letterContent.fontString ?? "", size: 13)) + ScrollableLetterView(letter: $letter, viewModel: viewModel) + .font(FontUtility.selectedFont(font: letter.fontString ?? "", size: 13)) - Text("\(viewModel.currentIndex+1) / \(viewModel.texts.count+imageViewModel.photoContents.count)") + Text("\(viewModel.currentIndex+1) / \(viewModel.texts.count+viewModel.photoContents.count)") .padding(5) .padding(.horizontal, 8) .background(Color(.primary900).opacity(0.3)) .clipShape(Capsule()) } .padding(.bottom, LayoutHelper.shared.getSize(forSE: 0.03, forOthers: 0.0)) - MiniTabBarView(letterContent: $letterContent, viewModel: viewModel, customTabViewModel: customTabViewModel) + MiniTabBarView(viewModel: viewModel, customTabViewModel: customTabViewModel) - if keyBoard { + if viewModel.isKeyboard { Button(action:{ UIApplication.shared.sendAction( #selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil @@ -60,52 +57,70 @@ struct ContentWriteView: View { Image(systemName: "keyboard.chevron.compact.down") .padding(12) .foregroundStyle(Color.white) - .background(Color.primary900) + .background(Color.secondary) .clipShape(Circle()) } - .padding(.top, UIScreen.main.bounds.height * 0.488) - .padding(.leading, UIScreen.main.bounds.width * 0.85) + .padding(.top, (screenHeight*0.82857)-viewModel.keyboardHeight) + .padding(.leading, screenWidth * 0.85) } } + if viewModel.showCheckmark { + Image(systemName: "checkmark.circle.fill") + .resizable() + .frame(width: 50, height: 50) + .foregroundColor(Color(UIColor(red: 0x13/255, green: 0xA4/255, blue: 0x50/255, alpha: 1))) + .transition(.scale.combined(with: .opacity)) + .animation(.easeInOut(duration: 0.3), value: viewModel.showCheckmark) + .position(x: screenWidth / 2, y: screenHeight * 0.5) + } } .overlay { if viewModel.showFontMenu { - FontMenuView(letterContent: $letterContent, showFontMenu: $viewModel.showFontMenu, fontViewModel: fontViewModel) - } - } - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - NavigationLink(destination: EnvelopeStampSelectionView( - letterContent: $letterContent, - customTabViewModel: customTabViewModel, - imageViewModel: imageViewModel - )) { - Text("다음") - .fontWeight(.medium) - .font(.system(size: 19)) - .foregroundStyle(.contentPrimary) - } + FontMenuView(letter: $letter, showFontMenu: $viewModel.showFontMenu, fontViewModel: fontViewModel) } } .ignoresSafeArea(.keyboard) - .onChange(of: imageViewModel.selectedItems) { _, newValue in + .onChange(of: viewModel.selectedItems) { Task { @MainActor in - imageViewModel.selectedItems = newValue - await imageViewModel.loadImages() - letterContent.photoContents = imageViewModel.photoContents + await viewModel.loadImages() + letter.photoContents = viewModel.photoContents + + viewModel.showCheckmark = true + DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { + viewModel.showCheckmark = false + } } } - .onAppear{ + .onAppear { NotificationCenter.default.addObserver( forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { _ in - keyBoard = true + viewModel.isKeyboard = true } NotificationCenter.default.addObserver( forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { _ in - keyBoard = false + viewModel.isKeyboard = false } + + NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { notification in + if let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect { + viewModel.keyboardHeight = keyboardFrame.height + } + } } - .analyticsScreen( + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + NavigationLink(destination: EnvelopeStampSelectionView( + letter: $letter, + customTabViewModel: customTabViewModel + )) { + Text("다음") + .fontWeight(.medium) + .font(.system(size: 19)) + .foregroundStyle(.contentPrimary) + } + } + } + .analyticsScreen( // 화면 추적 name: "\(type(of:self))", extraParameters: [ AnalyticsParameterScreenName: "\(type(of:self))", @@ -115,138 +130,78 @@ struct ContentWriteView: View { } } -// MARK: ScrollableLetterView +// MARK: - ScrollableLetterView struct ScrollableLetterView: View { - let screenWidth = UIScreen.main.bounds.width - let screenHeight = UIScreen.main.bounds.height - - @Binding var letterContent: LetterWriteModel + @Binding var letter: WriteLetter + @State private var scrollWorkItem: DispatchWorkItem? @ObservedObject var viewModel: ContentWriteViewModel - @ObservedObject var imageViewModel: ImagePickerViewModel - @Binding var currentIndex: Int var body: some View { - GeometryReader { geometry in - ScrollViewReader { scrollViewProxy in - ZStack(alignment: .top) { - ScrollView(.horizontal, showsIndicators: false) { - LazyHStack(alignment: .top, spacing: UIScreen.main.bounds.width * 0.04) { - ForEach(0..= horizontalPadding } - .sorted { geometry[$0.value].x < geometry[$1.value].x } - .first + .onPreferenceChange(ScrollOffsetKey.self) { newOffset in + viewModel.offset = newOffset + + let pageWidth = screenWidth * 0.9204 + let rawIndex = -viewModel.offset / pageWidth + let nearestIndex = Int(round(rawIndex)) - if let leadingAnchor = leadingAnchor, currentIndex != leadingAnchor.key { - currentIndex = leadingAnchor.key + if viewModel.currentIndex != nearestIndex { + viewModel.currentIndex = nearestIndex } } - + } + .onChange(of: viewModel.texts.count) { + withAnimation(.spring()) { + scrollViewProxy.scrollTo(viewModel.currentIndex+1, anchor: .center) + } + } + .onAppear { + DispatchQueue.main.async { + scrollViewProxy.scrollTo(viewModel.currentIndex, anchor: .center) + } } } } } -struct AnchorsKey: PreferenceKey { - typealias Value = [Int: Anchor] - static var defaultValue: Value { [ : ] } - static func reduce(value: inout Value, nextValue: () -> Value) { - value.merge(nextValue()) { $1 } +struct ScrollOffsetKey: PreferenceKey { + static var defaultValue: CGFloat = .zero + static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { + value += nextValue() } } diff --git a/Kabinett/Presentation/View/WriteLetter/EnvelopeStampSelectionView.swift b/Kabinett/Presentation/View/WriteLetter/EnvelopeStampSelectionView.swift index ae4a87ac..6897b46e 100644 --- a/Kabinett/Presentation/View/WriteLetter/EnvelopeStampSelectionView.swift +++ b/Kabinett/Presentation/View/WriteLetter/EnvelopeStampSelectionView.swift @@ -10,25 +10,22 @@ import Kingfisher import FirebaseAnalytics struct EnvelopeStampSelectionView: View { - @Binding var letterContent: LetterWriteModel - @StateObject var viewModel: EnvelopeStampSelectionViewModel - @ObservedObject var imageViewModel: ImagePickerViewModel - @ObservedObject var customTabViewModel: CustomTabViewModel + @Binding var letter: WriteLetter @State private var postScriptText: String = "" @State private var envelopeImageUrl: String @State private var stampImageUrl: String + @StateObject var viewModel: EnvelopeStampSelectionViewModel + @ObservedObject var customTabViewModel: CustomTabViewModel init( - letterContent: Binding, - customTabViewModel: CustomTabViewModel, - imageViewModel: ImagePickerViewModel + letter: Binding, + customTabViewModel: CustomTabViewModel ) { - self._letterContent = letterContent - self.imageViewModel = imageViewModel self.customTabViewModel = customTabViewModel + self._letter = letter - _envelopeImageUrl = State(initialValue: letterContent.wrappedValue.envelopeImageUrlString) - _stampImageUrl = State(initialValue: letterContent.wrappedValue.stampImageUrlString) + _envelopeImageUrl = State(initialValue: letter.wrappedValue.envelopeImageUrlString) + _stampImageUrl = State(initialValue: letter.wrappedValue.stampImageUrlString) @Injected(WriteLetterUseCaseKey.self) var writeLetterUseCase: WriteLetterUseCase _viewModel = StateObject(wrappedValue: EnvelopeStampSelectionViewModel(useCase: writeLetterUseCase)) @@ -42,16 +39,16 @@ struct EnvelopeStampSelectionView: View { } VStack { - WriteLetterEnvelopeCell(letter: Letter(fontString: letterContent.fontString, postScript: postScriptText, envelopeImageUrlString: letterContent.envelopeImageUrlString, stampImageUrlString: letterContent.stampImageUrlString, fromUserId: letterContent.fromUserId, fromUserName: letterContent.fromUserName, fromUserKabinettNumber: letterContent.fromUserKabinettNumber, toUserId: letterContent.toUserId, toUserName: letterContent.toUserName, toUserKabinettNumber: letterContent.toUserKabinettNumber, content: letterContent.content, photoContents: [""], date: letterContent.date, stationeryImageUrlString: letterContent.stationeryImageUrlString, isRead: true)) + WriteLetterEnvelopeCell(letter: letter, postScript: postScriptText) .padding(.top, 10) .padding(.bottom, 50) .onChange(of: viewModel.envelopes) { - if letterContent.envelopeImageUrlString.isEmpty { + if letter.envelopeImageUrlString.isEmpty { envelopeImageUrl = viewModel.envelopes[0] } } .onChange(of: viewModel.stamps) { - if letterContent.stampImageUrlString.isEmpty { + if letter.stampImageUrlString.isEmpty { stampImageUrl = viewModel.stamps[0] } } @@ -68,64 +65,39 @@ struct EnvelopeStampSelectionView: View { .background(Color.white) .clipShape(RoundedRectangle(cornerRadius: 5)) .onChange(of: postScriptText) { - letterContent.postScript = postScriptText + letter.postScript = postScriptText } } .padding(.bottom, 30) - SelectionTabView(envelopeStampSelectionViewModel: viewModel, letterContent: $letterContent, envelopeImageUrl: $envelopeImageUrl, stampImageUrl: $stampImageUrl) + SelectionTabView(letter: $letter, stampImageUrl: $stampImageUrl, envelopeImageUrl: $envelopeImageUrl, envelopeStampSelectionViewModel: viewModel) } .padding(.horizontal, UIScreen.main.bounds.width * 0.06) } .task { await viewModel.loadStamps() await viewModel.loadEnvelopes() - - if letterContent.dataSource == .importLetter { - await imageViewModel.loadAndUpdateEnvelopeAndStamp() - envelopeImageUrl = imageViewModel.envelopeURL ?? "" - stampImageUrl = imageViewModel.stampURL ?? "" - } else { - await imageViewModel.loadAndUpdateEnvelopeAndStamp() - envelopeImageUrl = letterContent.envelopeImageUrlString - stampImageUrl = letterContent.stampImageUrlString - } + envelopeImageUrl = letter.envelopeImageUrlString + stampImageUrl = letter.stampImageUrlString } .onChange(of: envelopeImageUrl) { _, newValue in - imageViewModel.updateEnvelopeAndStamp(envelope: newValue, stamp: stampImageUrl) - letterContent.envelopeImageUrlString = newValue + letter.envelopeImageUrlString = newValue } .onChange(of: stampImageUrl) { _, newValue in - imageViewModel.updateEnvelopeAndStamp(envelope: envelopeImageUrl, stamp: newValue) - letterContent.stampImageUrlString = newValue + letter.stampImageUrlString = newValue } .navigationBarTitleDisplayMode(.inline) .navigationTitle("봉투와 우표 고르기") .toolbar { ToolbarItem(placement: .topBarTrailing) { - if letterContent.dataSource == .importLetter { - NavigationLink(destination: LetterCompletionView( - letterContent: $letterContent, - viewModel: imageViewModel, - customTabViewModel: customTabViewModel, - envelopeStampSelectionViewModel: viewModel - )) { - Text("다음") - .fontWeight(.medium) - .font(.system(size: 19)) - .foregroundStyle(.contentPrimary) - } - } else { - NavigationLink(destination: PreviewLetterView( - letterContent: $letterContent, - customTabViewModel: customTabViewModel, - imagePickerViewModel: imageViewModel - )) { - Text("다음") - .fontWeight(.medium) - .font(.system(size: 19)) - .foregroundStyle(.contentPrimary) - } + NavigationLink(destination: PreviewLetterView( + letter: $letter, + customTabViewModel: customTabViewModel + )) { + Text("다음") + .fontWeight(.medium) + .font(.system(size: 19)) + .foregroundStyle(.contentPrimary) } } } diff --git a/Kabinett/Presentation/View/WriteLetter/PreviewLetterView.swift b/Kabinett/Presentation/View/WriteLetter/PreviewLetterView.swift index 3d9f0405..6892c6e4 100644 --- a/Kabinett/Presentation/View/WriteLetter/PreviewLetterView.swift +++ b/Kabinett/Presentation/View/WriteLetter/PreviewLetterView.swift @@ -11,22 +11,18 @@ import UIKit import FirebaseAnalytics struct PreviewLetterView: View { - @Binding var letterContent: LetterWriteModel + @Binding var letter: WriteLetter @StateObject var viewModel: PreviewLetterViewModel @ObservedObject var customTabViewModel: CustomTabViewModel - @ObservedObject var imagePickerViewModel: ImagePickerViewModel init( - letterContent: Binding, - customTabViewModel: CustomTabViewModel, - imagePickerViewModel: ImagePickerViewModel + letter: Binding, + customTabViewModel: CustomTabViewModel ) { + self.customTabViewModel = customTabViewModel + self._letter = letter @Injected(WriteLetterUseCaseKey.self) var writeLetterUseCase: WriteLetterUseCase - _viewModel = StateObject(wrappedValue: PreviewLetterViewModel(useCase: writeLetterUseCase)) - self._letterContent = letterContent - self.customTabViewModel = customTabViewModel - self.imagePickerViewModel = imagePickerViewModel } var body: some View { @@ -35,14 +31,14 @@ struct PreviewLetterView: View { VStack { Spacer() - WriteLetterEnvelopeCell(letter: Letter(fontString: letterContent.fontString, postScript: letterContent.postScript, envelopeImageUrlString: letterContent.envelopeImageUrlString, stampImageUrlString: letterContent.stampImageUrlString, fromUserId: letterContent.fromUserId, fromUserName: letterContent.fromUserName, fromUserKabinettNumber: letterContent.fromUserKabinettNumber, toUserId: letterContent.toUserId, toUserName: letterContent.toUserName, toUserKabinettNumber: letterContent.toUserKabinettNumber, content: letterContent.content, photoContents: [""], date: letterContent.date, stationeryImageUrlString: letterContent.stationeryImageUrlString, isRead: true)) + WriteLetterEnvelopeCell(letter: letter, postScript: letter.postScript ?? "") .padding(.bottom,30) VStack { Text("편지가 완성되었어요.") .font(.system(size: 18, weight: .semibold)) HStack { - Text("\(letterContent.toUserName == letterContent.fromUserName ? "나" : letterContent.toUserName)") + Text("\(letter.toUserName == letter.fromUserName ? "나" : letter.toUserName)") .font(.system(size: 22, weight: .bold)) .padding(.trailing, -3) Text("에게 편지를 보낼까요?") @@ -54,23 +50,8 @@ struct PreviewLetterView: View { Spacer() Button { - viewModel.saveLetter(font: letterContent.fontString ?? "", - postScript: letterContent.postScript, - envelope: letterContent.envelopeImageUrlString, - stamp: letterContent.stampImageUrlString, - fromUserId: letterContent.fromUserId, - fromUserName: letterContent.fromUserName, - fromUserKabinettNumber: letterContent.fromUserKabinettNumber, - toUserId: letterContent.toUserId, - toUserName: letterContent.toUserName, - toUserKabinettNumber: letterContent.toUserKabinettNumber, - content: letterContent.content, - photoContents: letterContent.photoContents, - date: letterContent.date, - stationery: letterContent.stationeryImageUrlString ?? "", - isRead: false) - customTabViewModel.hideOptions() - imagePickerViewModel.resetSelections() + viewModel.saveLetter(letter: letter) + customTabViewModel.hideWriteView() } label: { Text("편지 보내기") .font(.system(size: 16)) diff --git a/Kabinett/Presentation/View/WriteLetter/StationerySelectionView.swift b/Kabinett/Presentation/View/WriteLetter/StationerySelectionView.swift index 1a6b6390..d4babae3 100644 --- a/Kabinett/Presentation/View/WriteLetter/StationerySelectionView.swift +++ b/Kabinett/Presentation/View/WriteLetter/StationerySelectionView.swift @@ -11,21 +11,18 @@ import FirebaseAnalytics struct StationerySelectionView: View { @Environment(\.dismiss) var dismiss - @Binding var letterContent: LetterWriteModel + @Binding var letter: WriteLetter @StateObject var viewModel : StationerySelectionViewModel @ObservedObject var customViewModel: CustomTabViewModel - @ObservedObject var imageViewModel: ImagePickerViewModel init( - letterContent: Binding, - customViewModel: CustomTabViewModel, - imageViewModel: ImagePickerViewModel + letter: Binding, + customViewModel: CustomTabViewModel ) { @Injected(WriteLetterUseCaseKey.self) var writeLetterUseCase: WriteLetterUseCase _viewModel = StateObject(wrappedValue: StationerySelectionViewModel(useCase: writeLetterUseCase)) - self._letterContent = letterContent self.customViewModel = customViewModel - self.imageViewModel = imageViewModel + self._letter = letter } var body: some View { @@ -42,7 +39,7 @@ struct StationerySelectionView: View { index: index, rowIndex: rowIndex, columnIndex: columnIndex, - letterContent: $letterContent, + letter: $letter, stationerySelectionViewModel: viewModel ) } @@ -57,24 +54,23 @@ struct StationerySelectionView: View { .padding(.horizontal, UIScreen.main.bounds.width * 0.06) } } - .sheet(isPresented: $viewModel.showModal) { - UserSelectionView(letterContent: $letterContent) - .presentationDetents([.height(300), .large]) - } - .onAppear { - viewModel.showModal = true - - Task { - await viewModel.loadStationeries() - } - } .navigationBarTitleDisplayMode(.inline) .navigationTitle("편지지 고르기") .toolbar { + ToolbarItem(placement: .topBarLeading) { + Button(action: { + customViewModel.hideWriteView() + }) { + Image(systemName: "chevron.backward") + .fontWeight(.semibold) + .foregroundColor(Color.primary900) + .imageScale(.large) + .padding(.leading, 3) + } + } ToolbarItem(placement: .topBarTrailing) { NavigationLink(destination: ContentWriteView( - letterContent: $letterContent, - imageViewModel: imageViewModel, + letter: $letter, customTabViewModel: customViewModel )) { Text("다음") diff --git a/Kabinett/Presentation/View/WriteLetter/UserSelectionView.swift b/Kabinett/Presentation/View/WriteLetter/UserSelectionView.swift index 63650c1b..9e5fe6b9 100644 --- a/Kabinett/Presentation/View/WriteLetter/UserSelectionView.swift +++ b/Kabinett/Presentation/View/WriteLetter/UserSelectionView.swift @@ -10,18 +10,23 @@ import Kingfisher import FirebaseAnalytics struct UserSelectionView: View { - @Binding var letterContent: LetterWriteModel @Environment(\.dismiss) var dismiss + @Binding var letter: WriteLetter + @State private var isFullScreen = false @StateObject var viewModel : UserSelectionViewModel + @ObservedObject var customViewModel: CustomTabViewModel - init(letterContent: Binding) { + init( + letter: Binding, + customViewModel: CustomTabViewModel + ) { @Injected(WriteLetterUseCaseKey.self) var writeLetterUseCase: WriteLetterUseCase _viewModel = StateObject(wrappedValue: UserSelectionViewModel(useCase: writeLetterUseCase)) - self._letterContent = letterContent + self.customViewModel = customViewModel + self._letter = letter } var body: some View { - ZStack { Color(.primary100).ignoresSafeArea() .onTapGesture { @@ -32,21 +37,29 @@ struct UserSelectionView: View { HStack { Spacer() Button("완료") { - dismiss() + isFullScreen = true } } .fontWeight(.medium) .font(.system(size: 19)) .foregroundColor(.contentPrimary) .padding(.bottom, -3) + .fullScreenCover(isPresented: $isFullScreen) { + NavigationStack { + StationerySelectionView( + letter: $letter, + customViewModel: customViewModel + ) + } + } - FormToUser(letterContent: $letterContent, viewModel: viewModel) + FormToUser(letter: $letter, viewModel: viewModel) HStack { if viewModel.checkLogin { Spacer(minLength: 95) VStack { - SearchBar(letterContent: $letterContent, searchText: $viewModel.searchText, viewModel: viewModel) + SearchBar(letter: $letter, searchText: $viewModel.searchText, viewModel: viewModel) Group { Text("정확한 닉네임") .bold() + @@ -106,7 +119,7 @@ struct UserSelectionView: View { // MARK: - FormToUserView struct FormToUser: View { - @Binding var letterContent: LetterWriteModel + @Binding var letter: WriteLetter @ObservedObject var viewModel : UserSelectionViewModel var body: some View { @@ -125,17 +138,17 @@ struct FormToUser: View { } .padding(.top, 15) .onChange(of: viewModel.fromUser?.kabinettNumber) { - letterContent.fromUserId = viewModel.fromUser?.id - letterContent.fromUserKabinettNumber = viewModel.fromUser?.kabinettNumber - letterContent.toUserId = viewModel.toUser?.id - letterContent.toUserKabinettNumber = viewModel.toUser?.kabinettNumber - letterContent.date = Date() + letter.fromUserId = viewModel.fromUser?.id + letter.fromUserKabinettNumber = viewModel.fromUser?.kabinettNumber + letter.toUserId = viewModel.toUser?.id + letter.toUserKabinettNumber = viewModel.toUser?.kabinettNumber + letter.date = Date() if viewModel.checkLogin { - letterContent.fromUserName = viewModel.fromUser?.name ?? "" - letterContent.toUserName = viewModel.toUser?.name ?? "" + letter.fromUserName = viewModel.fromUser?.name ?? "" + letter.toUserName = viewModel.toUser?.name ?? "" } else { - letterContent.fromUserName = "나" - letterContent.toUserName = "나" + letter.fromUserName = "나" + letter.toUserName = "나" } } @@ -145,8 +158,8 @@ struct FormToUser: View { .font(.system(size: 16)) .bold() Spacer(minLength: 37) - let toName = letterContent.toUserName.isEmpty ? viewModel.toUser?.name ?? "" : letterContent.toUserName - let toKabi = letterContent.toUserName.isEmpty ? viewModel.fromUser?.kabinettNumber ?? 0 : letterContent.toUserKabinettNumber + let toName = letter.toUserName.isEmpty ? viewModel.toUser?.name ?? "" : letter.toUserName + let toKabi = letter.toUserName.isEmpty ? viewModel.fromUser?.kabinettNumber ?? 0 : letter.toUserKabinettNumber Text(viewModel.checkLogin ? "\(toName) \(viewModel.checkMe(kabiNumber: toKabi ?? 0))" : "나") .foregroundStyle(viewModel.checkLogin ? Color.black : Color("ContentSecondary")) .font(.system(size: 15)) @@ -161,7 +174,7 @@ struct FormToUser: View { // MARK: - SearchBarView struct SearchBar: View { - @Binding var letterContent: LetterWriteModel + @Binding var letter: WriteLetter @Binding var searchText: String @ObservedObject var viewModel: UserSelectionViewModel @State var isSearchBar: Bool = true @@ -200,7 +213,7 @@ struct SearchBar: View { List { Text("\(viewModel.debouncedSearchText) 입력") .onTapGesture { - viewModel.updateToUser(&letterContent, toUserName: viewModel.debouncedSearchText) + viewModel.updateToUser(&letter, toUserName: viewModel.debouncedSearchText) searchText = "" UIApplication.shared.endEditing() isSearchBar = false @@ -235,7 +248,7 @@ struct SearchBar: View { } .listRowSeparator(.hidden) .onTapGesture { - viewModel.updateToUser(&letterContent, toUserName: user.name) + viewModel.updateToUser(&letter, toUserName: user.name) searchText = "" UIApplication.shared.endEditing() isSearchBar = false diff --git a/Kabinett/Presentation/ViewModel/ImportLetter/.gitkeep b/Kabinett/Presentation/ViewModel/ImportLetter/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/Kabinett/Presentation/ViewModel/ImportLetter/CameraViewModel.swift b/Kabinett/Presentation/ViewModel/ImportLetter/CameraViewModel.swift deleted file mode 100644 index 4c0c554c..00000000 --- a/Kabinett/Presentation/ViewModel/ImportLetter/CameraViewModel.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// CameraViewModel.swift -// Kabinett -// -// Created by 김정우 on 8/15/24. -// - -import SwiftUI -import AVFoundation - -// MARK: - ViewModel -final class CameraViewModel: ObservableObject { - @Published var capturedImage: UIImage? - - // MARK: Method(캡쳐된 이미지 처리) - func captureImage(with info: [UIImagePickerController.InfoKey : Any]) { - if let image = info[.originalImage] as? UIImage { - capturedImage = image - } - } -} diff --git a/Kabinett/Presentation/ViewModel/ImportLetter/ImagePickerViewModel.swift b/Kabinett/Presentation/ViewModel/ImportLetter/ImagePickerViewModel.swift deleted file mode 100644 index 69434b63..00000000 --- a/Kabinett/Presentation/ViewModel/ImportLetter/ImagePickerViewModel.swift +++ /dev/null @@ -1,319 +0,0 @@ -// -// ImagePickerViewModel.swift -// Kabinett -// -// Created by 김정우 on 8/13/24. -// - -import SwiftUI -import PhotosUI -import Combine - -final class ImagePickerViewModel: ObservableObject { - - @Published var selectedItems: [PhotosPickerItem] = [] - - @Published var photoContents: [Data] = [] - @Published var date: Date = Date() - @Published var isRead: Bool = false - - @Published var postScript: String? - @Published var envelopeURL: String? - @Published var stampURL: String? - - @Published var fromUserId: String? - @Published var fromUserName: String = "" - @Published var fromUserKabinettNumber: Int? = nil - - @Published var toUserId: String? - @Published var toUserName: String = "" - @Published var toUserKabinettNumber: Int? = nil - - @Published var fromUserSearch: String = "" - @Published var toUserSearch: String = "" - @Published var debouncedSearchText: String = "" - - @Published var fromUser: Writer? = nil - @Published var toUser: Writer? = nil - @Published var usersData: [Writer] = [] - - @Published var fromUserSearchResults: [(name: String, kabinettNumber: String)] = [] - @Published var toUserSearchResults: [(name: String, kabinettNumber: String)] = [] - @Published var userKabiNumber: String? - - @Published var checkLogin: Bool = false - @Published var isLoading: Bool = false - @Published var error: Error? - @Published var isAnonymous: Bool = false - - private var cancellables = Set() - private let componentsUseCase: ImportLetterUseCase - - init(componentsUseCase: ImportLetterUseCase) { - self.componentsUseCase = componentsUseCase - - setupBindings() - Task { - await fetchCurrentWriter() - } - } - - - @MainActor - func updateDefaultUsers() { - if let fromUser = fromUser { - fromUserName = isAnonymous ? "나" : fromUser.name - fromUserId = fromUser.id - fromUserKabinettNumber = fromUser.kabinettNumber - - if isAnonymous { - toUserName = "나" - toUserId = fromUserId - toUserKabinettNumber = nil - } else { - toUserName = toUserName - toUserId = toUserId - toUserKabinettNumber = toUserKabinettNumber - } - } - } - - private func setupBindings() { - $fromUserSearch - .debounce(for: .seconds(1), scheduler: RunLoop.main) - .removeDuplicates() - .sink { [weak self] text in - Task { [weak self] in - await self?.searchUsers(query: text, isFromUser: true) - } - } - .store(in: &cancellables) - - $toUserSearch - .debounce(for: .seconds(1), scheduler: RunLoop.main) - .removeDuplicates() - .sink { [weak self] text in - Task { [weak self] in - await self?.searchUsers(query: text, isFromUser: false) - } - } - .store(in: &cancellables) - } - - // MARK: 현재 사용자 정보 업데이트 - func updateFromUser() { - if let fromUser = fromUser { - checkLogin = fromUser.kabinettNumber != 0 - isAnonymous = fromUser.kabinettNumber == 0 - fromUserId = fromUser.id - fromUserName = isAnonymous ? "나" : fromUser.name - fromUserKabinettNumber = fromUser.kabinettNumber - userKabiNumber = String(format: "%06d", fromUser.kabinettNumber) - - if checkLogin { - toUser = fromUser - toUserId = fromUser.id - toUserName = fromUser.name - toUserKabinettNumber = fromUser.kabinettNumber - } else { - toUser = nil - toUserId = nil - toUserName = "나" - toUserKabinettNumber = nil - } - } - } - - // MARK: 사용자 검색 기능 - @MainActor - func searchUsers(query: String, isFromUser: Bool) async { - guard !query.isEmpty else { - if isFromUser { - self.fromUserSearchResults = [] - } else { - self.toUserSearchResults = [] - } - return - } - - let results = await componentsUseCase.findWriter(by: query) - let formattedResults = results.map { (name: $0.name, kabinettNumber: String(format: "%06d", $0.kabinettNumber)) } - - if isFromUser { - self.fromUserSearchResults = formattedResults - } else { - self.toUserSearchResults = formattedResults - } - } - - - func updateSelectedUser(selectedUserName: String) { - if let user = usersData.first(where: { $0.name == selectedUserName }) { - toUser = Writer(id: user.id, name: user.name, kabinettNumber: user.kabinettNumber, profileImage: user.profileImage) - } else { - toUser = Writer(name: selectedUserName, kabinettNumber: 0, profileImage: nil) - } - self.toUserId = toUser?.id - self.toUserName = toUser?.name ?? "" - self.toUserKabinettNumber = toUser?.kabinettNumber - - } - - - // MARK: 현재 로그인한 사용자 정보 가져오기 - @MainActor - func fetchCurrentWriter() async { - let publisher = componentsUseCase.getCurrentWriter() - - for await writer in publisher.values { - self.fromUser = writer - updateFromUser() - break - } - } - - // MARK: 선택된 이미지 로드 - private func loadImagesTask() async throws -> [Data] { - try await withThrowingTaskGroup(of: Data?.self) { group -> [Data] in - for item in selectedItems { - group.addTask { - do { - if let data = try await item.loadTransferable(type: Data.self) { - return data - } - } catch { - print("Failed to load image: \(error)") - } - return nil - } - } - - var results: [Data] = [] - for try await result in group { - if let result = result { - results.append(result) - } - } - return results - } - } - - @MainActor - func loadImages() async { - isLoading = true - error = nil - - do { - let newImageContents = try await loadImagesTask() - self.photoContents = newImageContents - } catch { - self.error = error - } - - isLoading = false - } - - func updatePostScript(_ postScript: String) { - self.postScript = postScript - } - - func updateEnvelopeAndStamp(envelope: String?, stamp: String?) { - if let envelope = envelope { - self.envelopeURL = envelope - } - if let stamp = stamp { - self.stampURL = stamp - } - } - - @MainActor - func loadAndUpdateEnvelopeAndStamp() async { - isLoading = true - error = nil - - do { - let envelopes = try await componentsUseCase.loadEnvelopes().get() - let stamps = try await componentsUseCase.loadStamps().get() - - if self.envelopeURL == nil, let firstEnvelope = envelopes.first { - self.envelopeURL = firstEnvelope - } - if self.stampURL == nil, let firstStamp = stamps.first { - self.stampURL = firstStamp - } - isLoading = false - } catch { - self.error = error - isLoading = false - print("Failed to load envelope and stamp: \(error)") - } - } - - // MARK: 편지저장 - @MainActor - func saveImportingImage() async -> Bool { - isLoading = true - error = nil - - if fromUserId == nil || fromUserKabinettNumber == nil { - await fetchCurrentWriter() - } - - let result = await componentsUseCase.saveLetter( - postScript: postScript, - envelope: envelopeURL ?? "", - stamp: stampURL ?? "", - fromUserId: fromUserId, - fromUserName: fromUserName, - fromUserKabinettNumber: fromUserKabinettNumber ?? 0, - toUserId: toUserId, - toUserName: toUserName, - toUserKabinettNumber: toUserKabinettNumber ?? 0, - photoContents: photoContents, - date: date, - isRead: false - ) - switch result { - case .success: - resetState() - isLoading = false - return true - case .failure(let error): - print("Failed to save letter: \(error)") - self.error = error - isLoading = false - return false - } - } - - // MARK: Methods (편지 저장 후 초기화) - func resetState() { - selectedItems = [] - photoContents = [] - fromUserName = "" - toUserName = "" - date = Date() - postScript = nil - envelopeURL = nil - stampURL = nil - } - - func resetSelections() { - selectedItems = [] - photoContents = [] - postScript = nil - envelopeURL = nil - stampURL = nil - toUserId = nil - toUserName = "" - toUserKabinettNumber = nil - toUserSearch = "" - toUserSearchResults = [] - } - - var formattedDate: String { - let formatter = DateFormatter() - formatter.dateFormat = "yyyy.MM.dd" - return formatter.string(from: date) - } -} diff --git a/Kabinett/Presentation/ViewModel/WriteLetter/ContentWriteViewModel.swift b/Kabinett/Presentation/ViewModel/WriteLetter/ContentWriteViewModel.swift index ab647ac2..6411cc0c 100644 --- a/Kabinett/Presentation/ViewModel/WriteLetter/ContentWriteViewModel.swift +++ b/Kabinett/Presentation/ViewModel/WriteLetter/ContentWriteViewModel.swift @@ -5,17 +5,34 @@ // Created by Song Kim on 8/22/24. // -import Foundation import SwiftUI +import PhotosUI +import Combine class ContentWriteViewModel: ObservableObject { + @Published var offset: CGFloat = 0 + @Published var texts: [String] = [""] @Published var currentIndex: Int = 0 @Published var isDeleteAlertPresented = false @Published var showFontMenu: Bool = false @Published var isFontEdit: Bool = true - + @Published var isKeyboard: Bool = false + + @Published var selectedItems: [PhotosPickerItem] = [] + @Published var photoContents: [Data] = [] + + @Published var isLoading: Bool = false + @Published var error: Error? + + @Published var isDeletePhoto: Bool = false + @Published var showCheckmark: Bool = false + + @Published var keyboardHeight: CGFloat = 0 + + private var cancellables = Set() + func toggleFontView() { showFontMenu.toggle() } @@ -29,4 +46,49 @@ class ContentWriteViewModel: ObservableObject { texts.remove(at: idx) } } + + func resetSelections() { + selectedItems = [] + photoContents = [] + } + + @MainActor + func loadImages() async { + isLoading = true + error = nil + + do { + let newImageContents = try await loadImagesTask() + self.photoContents = newImageContents + } catch { + self.error = error + } + + isLoading = false + } + + private func loadImagesTask() async throws -> [Data] { + try await withThrowingTaskGroup(of: Data?.self) { group -> [Data] in + for item in selectedItems { + group.addTask { + do { + if let data = try await item.loadTransferable(type: Data.self) { + return data + } + } catch { + print("Failed to load image: \(error)") + } + return nil + } + } + + var results: [Data] = [] + for try await result in group { + if let result = result { + results.append(result) + } + } + return results + } + } } diff --git a/Kabinett/Presentation/ViewModel/WriteLetter/FontSelectionViewModel.swift b/Kabinett/Presentation/ViewModel/WriteLetter/FontSelectionViewModel.swift index b36338b8..7bc876fb 100644 --- a/Kabinett/Presentation/ViewModel/WriteLetter/FontSelectionViewModel.swift +++ b/Kabinett/Presentation/ViewModel/WriteLetter/FontSelectionViewModel.swift @@ -32,7 +32,7 @@ class FontSelectionViewModel: ObservableObject { init() { updateText() } - + private func updateText() { for _ in 0.. String {