diff --git a/Noonbody/Noonbody.xcodeproj/project.pbxproj b/Noonbody/Noonbody.xcodeproj/project.pbxproj index f7cb3aa..ca81d34 100644 --- a/Noonbody/Noonbody.xcodeproj/project.pbxproj +++ b/Noonbody/Noonbody.xcodeproj/project.pbxproj @@ -154,6 +154,7 @@ AAF2D42527240CC00029219D /* swiftgen.yml in Resources */ = {isa = PBXBuildFile; fileRef = AAF2D42427240CC00029219D /* swiftgen.yml */; }; AAF2D42727240E4C0029219D /* Assets+Generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAF2D42627240E4C0029219D /* Assets+Generated.swift */; }; AAF2D4292724137E0029219D /* Image.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AAF2D4282724137E0029219D /* Image.xcassets */; }; + FC107F382A4B5F3B00F51DE7 /* PanoramaAlbumViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC107F372A4B5F3B00F51DE7 /* PanoramaAlbumViewModel.swift */; }; FC1F6A4127E9DB05005AB343 /* FetchAlbumUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC1F6A4027E9DB05005AB343 /* FetchAlbumUseCase.swift */; }; FC1F6A4327E9DB1F005AB343 /* CreateAlbumUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC1F6A4227E9DB1F005AB343 /* CreateAlbumUseCase.swift */; }; FC1F6A4527E9DB2F005AB343 /* DeleteAlbumUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC1F6A4427E9DB2F005AB343 /* DeleteAlbumUseCase.swift */; }; @@ -174,6 +175,7 @@ FC76A54427E4B18600CFEA5A /* DefaultSendFeedbackRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC76A54327E4B18600CFEA5A /* DefaultSendFeedbackRepository.swift */; }; FC76A54627E4B2B600CFEA5A /* SendFeedbackRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC76A54527E4B2B600CFEA5A /* SendFeedbackRepository.swift */; }; FC76A54827E4B38500CFEA5A /* SendFeedbackUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC76A54727E4B38500CFEA5A /* SendFeedbackUseCase.swift */; }; + FC787CBC2A2E368100D0D2DF /* PanoramaAlbumViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC787CBB2A2E368100D0D2DF /* PanoramaAlbumViewController.swift */; }; FC7D38D12799FFEB001D43D0 /* RenamedAlbum.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC7D38D02799FFEB001D43D0 /* RenamedAlbum.swift */; }; FC7D798127D09A3E0022C223 /* Realm in Frameworks */ = {isa = PBXBuildFile; productRef = FC7D798027D09A3E0022C223 /* Realm */; }; FC7D798327D09A3E0022C223 /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = FC7D798227D09A3E0022C223 /* RealmSwift */; }; @@ -182,10 +184,10 @@ FC91997427A1552800A6CD0D /* FeedbackCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC91997327A1552800A6CD0D /* FeedbackCollectionReusableView.swift */; }; FC91997627A180D200A6CD0D /* FeedbackPopUpViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC91997527A180D200A6CD0D /* FeedbackPopUpViewController.swift */; }; FC91997C27A2CD3100A6CD0D /* FeedbackRequestModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC91997B27A2CD3100A6CD0D /* FeedbackRequestModel.swift */; }; - FC9A7268272D267100DD9B35 /* PanoramaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9A7267272D267100DD9B35 /* PanoramaViewController.swift */; }; + FC9A7268272D267100DD9B35 /* GridAlbumViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9A7267272D267100DD9B35 /* GridAlbumViewController.swift */; }; FC9B76D027DDEFA300F80F21 /* AlbumManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9B76CF27DDEFA300F80F21 /* AlbumManager.swift */; }; FC9D30C128ABA79600E7FD48 /* MottoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9D30C028ABA79600E7FD48 /* MottoViewController.swift */; }; - FC9E3B692730E8AD00FBFDBF /* PanoramaViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9E3B682730E8AD00FBFDBF /* PanoramaViewModel.swift */; }; + FC9E3B692730E8AD00FBFDBF /* GridAlbumViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC9E3B682730E8AD00FBFDBF /* GridAlbumViewModel.swift */; }; FCA92EE92731C9360070CA2A /* BottomCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCA92EE82731C9360070CA2A /* BottomCollectionViewCell.swift */; }; FCAE821127832D3B007A729D /* PanoramaUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCAE820E27832D3B007A729D /* PanoramaUseCase.swift */; }; FCAE821227832D3B007A729D /* DefaultPanoramaRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = FCAE821027832D3B007A729D /* DefaultPanoramaRepository.swift */; }; @@ -353,6 +355,7 @@ AAF2D42427240CC00029219D /* swiftgen.yml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.yaml; path = swiftgen.yml; sourceTree = ""; }; AAF2D42627240E4C0029219D /* Assets+Generated.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Assets+Generated.swift"; sourceTree = ""; }; AAF2D4282724137E0029219D /* Image.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Image.xcassets; sourceTree = ""; }; + FC107F372A4B5F3B00F51DE7 /* PanoramaAlbumViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanoramaAlbumViewModel.swift; sourceTree = ""; }; FC1F6A4027E9DB05005AB343 /* FetchAlbumUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchAlbumUseCase.swift; sourceTree = ""; }; FC1F6A4227E9DB1F005AB343 /* CreateAlbumUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateAlbumUseCase.swift; sourceTree = ""; }; FC1F6A4427E9DB2F005AB343 /* DeleteAlbumUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteAlbumUseCase.swift; sourceTree = ""; }; @@ -374,16 +377,17 @@ FC76A54327E4B18600CFEA5A /* DefaultSendFeedbackRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultSendFeedbackRepository.swift; sourceTree = ""; }; FC76A54527E4B2B600CFEA5A /* SendFeedbackRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendFeedbackRepository.swift; sourceTree = ""; }; FC76A54727E4B38500CFEA5A /* SendFeedbackUseCase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendFeedbackUseCase.swift; sourceTree = ""; }; + FC787CBB2A2E368100D0D2DF /* PanoramaAlbumViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanoramaAlbumViewController.swift; sourceTree = ""; }; FC7D38D02799FFEB001D43D0 /* RenamedAlbum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenamedAlbum.swift; sourceTree = ""; }; FC7D798527D0AA600022C223 /* RMAlbum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RMAlbum.swift; sourceTree = ""; }; FC87D7CC2746E80B003EEB9B /* CameraCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraCollectionViewCell.swift; sourceTree = ""; }; FC91997327A1552800A6CD0D /* FeedbackCollectionReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackCollectionReusableView.swift; sourceTree = ""; }; FC91997527A180D200A6CD0D /* FeedbackPopUpViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackPopUpViewController.swift; sourceTree = ""; }; FC91997B27A2CD3100A6CD0D /* FeedbackRequestModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedbackRequestModel.swift; sourceTree = ""; }; - FC9A7267272D267100DD9B35 /* PanoramaViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanoramaViewController.swift; sourceTree = ""; }; + FC9A7267272D267100DD9B35 /* GridAlbumViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridAlbumViewController.swift; sourceTree = ""; }; FC9B76CF27DDEFA300F80F21 /* AlbumManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlbumManager.swift; sourceTree = ""; }; FC9D30C028ABA79600E7FD48 /* MottoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MottoViewController.swift; sourceTree = ""; }; - FC9E3B682730E8AD00FBFDBF /* PanoramaViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanoramaViewModel.swift; sourceTree = ""; }; + FC9E3B682730E8AD00FBFDBF /* GridAlbumViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridAlbumViewModel.swift; sourceTree = ""; }; FCA92EE82731C9360070CA2A /* BottomCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BottomCollectionViewCell.swift; sourceTree = ""; }; FCAE820E27832D3B007A729D /* PanoramaUseCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PanoramaUseCase.swift; sourceTree = ""; }; FCAE821027832D3B007A729D /* DefaultPanoramaRepository.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultPanoramaRepository.swift; sourceTree = ""; }; @@ -659,7 +663,8 @@ AA7E0A6526FB0BFD00106DC2 /* ViewControllers */ = { isa = PBXGroup; children = ( - FC9A7267272D267100DD9B35 /* PanoramaViewController.swift */, + FC9A7267272D267100DD9B35 /* GridAlbumViewController.swift */, + FC787CBB2A2E368100D0D2DF /* PanoramaAlbumViewController.swift */, ); path = ViewControllers; sourceTree = ""; @@ -667,7 +672,8 @@ AA7E0A6626FB0C0300106DC2 /* ViewModels */ = { isa = PBXGroup; children = ( - FC9E3B682730E8AD00FBFDBF /* PanoramaViewModel.swift */, + FC9E3B682730E8AD00FBFDBF /* GridAlbumViewModel.swift */, + FC107F372A4B5F3B00F51DE7 /* PanoramaAlbumViewModel.swift */, ); path = ViewModels; sourceTree = ""; @@ -1336,6 +1342,7 @@ AAC7ACB22729F2B100B2BE1E /* Fonts.swift in Sources */, FC76A53127E39B0A00CFEA5A /* FetchAlbumsUseCase.swift in Sources */, AA73AFB9274FF8AC00FD7C70 /* BaseTargetType.swift in Sources */, + FC787CBC2A2E368100D0D2DF /* PanoramaAlbumViewController.swift in Sources */, AA74A0E327497E0000FA6678 /* ProfileViewModel.swift in Sources */, AAC7AD182735A11C00B2BE1E /* AppDate.swift in Sources */, AA4EFD2327623F3F005017FC /* NBSegmentedControl.swift in Sources */, @@ -1366,8 +1373,8 @@ FC76A54627E4B2B600CFEA5A /* SendFeedbackRepository.swift in Sources */, AA2F317B2759F5E200855861 /* NotificationService.swift in Sources */, AAE2FAB3276B4F6800FA2C5C /* UIImage+resizeImage.swift in Sources */, - FC9A7268272D267100DD9B35 /* PanoramaViewController.swift in Sources */, - FC9E3B692730E8AD00FBFDBF /* PanoramaViewModel.swift in Sources */, + FC9A7268272D267100DD9B35 /* GridAlbumViewController.swift in Sources */, + FC9E3B692730E8AD00FBFDBF /* GridAlbumViewModel.swift in Sources */, FC751D4028B6172500A6F4E1 /* AuthenticationPopupViewController.swift in Sources */, AA74A0DE27496F8E00FA6678 /* UITableView+Generic.swift in Sources */, AA74A0E9274993CF00FA6678 /* DefaultPreferenceRepository.swift in Sources */, @@ -1433,6 +1440,7 @@ AAE2FA7F27628A5E00FA2C5C /* UIViewController+ToastView.swift in Sources */, FCF13C1C28ACFCC6003D3B60 /* BiometricsAuth.swift in Sources */, AAEA239C27938E01005A08C9 /* DefaultVideoRepository.swift in Sources */, + FC107F382A4B5F3B00F51DE7 /* PanoramaAlbumViewModel.swift in Sources */, FCC59B9E2842634E00702FA8 /* RealmMigrationService.swift in Sources */, FCA92EE92731C9360070CA2A /* BottomCollectionViewCell.swift in Sources */, AABD837B2784CC75004E8BF8 /* AlbumRepository.swift in Sources */, diff --git a/Noonbody/Noonbody/Resource/Constants/Assets+Generated.swift b/Noonbody/Noonbody/Resource/Constants/Assets+Generated.swift index 3a097f2..45071e5 100644 --- a/Noonbody/Noonbody/Resource/Constants/Assets+Generated.swift +++ b/Noonbody/Noonbody/Resource/Constants/Assets+Generated.swift @@ -113,6 +113,7 @@ internal enum Asset { internal static let samplePose = ImageAsset(name: "samplePose") internal static let selectedClose = ImageAsset(name: "selectedClose") internal static let setting = ImageAsset(name: "setting") + internal static let trash = ImageAsset(name: "trash") internal static let womanLower01 = ImageAsset(name: "woman_lower01") internal static let womanUpper02 = ImageAsset(name: "woman_upper02") internal static let womanWhole01 = ImageAsset(name: "woman_whole01") diff --git a/Noonbody/Noonbody/Resource/Image.xcassets/trash.imageset/Contents.json b/Noonbody/Noonbody/Resource/Image.xcassets/trash.imageset/Contents.json new file mode 100644 index 0000000..63b3de6 --- /dev/null +++ b/Noonbody/Noonbody/Resource/Image.xcassets/trash.imageset/Contents.json @@ -0,0 +1,52 @@ +{ + "images" : [ + { + "filename" : "trash.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "trash-2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Noonbody/Noonbody/Resource/Image.xcassets/trash.imageset/trash-2.png b/Noonbody/Noonbody/Resource/Image.xcassets/trash.imageset/trash-2.png new file mode 100644 index 0000000..6804a31 Binary files /dev/null and b/Noonbody/Noonbody/Resource/Image.xcassets/trash.imageset/trash-2.png differ diff --git a/Noonbody/Noonbody/Resource/Image.xcassets/trash.imageset/trash.png b/Noonbody/Noonbody/Resource/Image.xcassets/trash.imageset/trash.png new file mode 100644 index 0000000..2817807 Binary files /dev/null and b/Noonbody/Noonbody/Resource/Image.xcassets/trash.imageset/trash.png differ diff --git a/Noonbody/Noonbody/Source/Domain/Entity/Album.swift b/Noonbody/Noonbody/Source/Domain/Entity/Album.swift index bcc0b8e..cbc1d46 100644 --- a/Noonbody/Noonbody/Source/Domain/Entity/Album.swift +++ b/Noonbody/Noonbody/Source/Domain/Entity/Album.swift @@ -61,6 +61,17 @@ enum BodyPart: String, Codable { case lower case upper case whole + + var title: String { + switch self { + case .lower: + return "하체" + case .upper: + return "상체" + case .whole: + return "전신" + } + } } typealias Albums = [Album] diff --git a/Noonbody/Noonbody/Source/Presentation/Home/ViewControllers/HomeViewController.swift b/Noonbody/Noonbody/Source/Presentation/Home/ViewControllers/HomeViewController.swift index 22d978c..204f11a 100644 --- a/Noonbody/Noonbody/Source/Presentation/Home/ViewControllers/HomeViewController.swift +++ b/Noonbody/Noonbody/Source/Presentation/Home/ViewControllers/HomeViewController.swift @@ -259,7 +259,7 @@ extension HomeViewController { extension HomeViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - let viewController = PanoramaViewController(albumId: albumData[indexPath.row].id, albumData: albumData[indexPath.row]) + let viewController = GridAlbumViewController(albumId: albumData[indexPath.row].id, albumData: albumData[indexPath.row]) Mixpanel.mainInstance().track(event: "main/album") self.navigationController?.pushViewController(viewController, animated: true) } diff --git a/Noonbody/Noonbody/Source/Presentation/Panorama/Cells/TopCollectionViewCell.swift b/Noonbody/Noonbody/Source/Presentation/Panorama/Cells/TopCollectionViewCell.swift index 279949e..5d21f4e 100644 --- a/Noonbody/Noonbody/Source/Presentation/Panorama/Cells/TopCollectionViewCell.swift +++ b/Noonbody/Noonbody/Source/Presentation/Panorama/Cells/TopCollectionViewCell.swift @@ -81,7 +81,6 @@ class TopCollectionViewCell: UICollectionViewCell { func setPhotoCell(albumId: Int, bodyPart: String, imageName: Int, contentMode: Bool, fileExtension: FileExtension) { panoramaImage.image = AlbumManager.loadImageFromDocumentDirectory(from: "\(albumId)/\(bodyPart)/\(imageName).\(fileExtension)") - if !UIDevice.current.hasNotch { panoramaImage.contentMode = contentMode ? .scaleAspectFill : .scaleAspectFit } diff --git a/Noonbody/Noonbody/Source/Presentation/Panorama/ViewControllers/PanoramaViewController.swift b/Noonbody/Noonbody/Source/Presentation/Panorama/ViewControllers/GridAlbumViewController.swift similarity index 63% rename from Noonbody/Noonbody/Source/Presentation/Panorama/ViewControllers/PanoramaViewController.swift rename to Noonbody/Noonbody/Source/Presentation/Panorama/ViewControllers/GridAlbumViewController.swift index 9bc3d6a..e6ec5c4 100644 --- a/Noonbody/Noonbody/Source/Presentation/Panorama/ViewControllers/PanoramaViewController.swift +++ b/Noonbody/Noonbody/Source/Presentation/Panorama/ViewControllers/GridAlbumViewController.swift @@ -1,5 +1,5 @@ // -// PanoramaViewController.swift +// GridAlbumViewController.swift // everyBody-iOS // // Created by kong on 2021/10/30. @@ -14,49 +14,25 @@ import RxRelay import RealmSwift import Mixpanel -class PanoramaViewController: BaseViewController { +class GridAlbumViewController: BaseViewController { // MARK: - UI Components - - private var gridButton = UIButton().then { - $0.setImage(Asset.Image.grid.image, for: .normal) - $0.setImage(Asset.Image.list.image, for: .selected) - $0.addTarget(self, action: #selector(gridButtonDidTap), for: .touchUpInside) - } - + private let bodyPartSegmentControl = NBSegmentedControl(buttonStyle: .basic, numOfButton: 3).then { $0.spacing = 10 } - private var topCollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewLayout()).then { + private var gridAlbumCollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewLayout()).then { + let collectionViewFlowLayout = UICollectionViewFlowLayout() + collectionViewFlowLayout.scrollDirection = .vertical + $0.collectionViewLayout = collectionViewFlowLayout $0.register(TopCollectionViewCell.self) $0.register(CameraCollectionViewCell.self) $0.backgroundColor = .clear $0.showsHorizontalScrollIndicator = false - $0.bounces = false - $0.isScrollEnabled = false - } - - private var bottomCollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewLayout()).then { - $0.register(BottomCollectionViewCell.self) - let layout = UICollectionViewFlowLayout() - layout.scrollDirection = .horizontal - $0.collectionViewLayout = layout - $0.bounces = false - $0.backgroundColor = .clear - $0.showsHorizontalScrollIndicator = false - $0.allowsMultipleSelection = false - $0.setContentHuggingPriority(.defaultLow, for: .vertical) - } - - private var stackView = UIStackView().then { - $0.axis = .vertical - $0.spacing = UIDevice.current.hasNotch ? 22 : 5 - $0.distribution = .fill - $0.backgroundColor = .clear + $0.bounces = true } - - private var emptyView = AlbumEmptyView(type: .picture).then { + private lazy var emptyView = AlbumEmptyView(type: .picture).then { $0.isHidden = true $0.button.addTarget(self, action: #selector(cameraButtonDidTap), for: .touchUpInside) } @@ -65,27 +41,21 @@ class PanoramaViewController: BaseViewController { private let cellSpacing: CGFloat = 2 private var cellWidth: CGFloat = 0 - private let viewModel = PanoramaViewModel(fetchAlbumUseCase: DefaultFetchAlbumUseCase(repository: LocalAlbumRepositry()), + private let viewModel = GridAlbumViewModel(fetchAlbumUseCase: DefaultFetchAlbumUseCase(repository: LocalAlbumRepositry()), renameAlbumUseCase: DefaultRenameAlbumUseCase(repository: LocalAlbumRepositry()), deleteAlbumUseCase: DefaultDeleteAlbumUseCase(repository: LocalAlbumRepositry()), deletePictureUseCase: DefaultDeletePictureUseCase(repository: LocalPictureRepository())) - private var popUpForPicturesDeletion = PopUpViewController(type: .delete).then { - $0.confirmButton.addTarget(self, action: #selector(deletePictureCompleteButtonDidTap), - for: .touchUpInside) - $0.cancelButton.addTarget(self, action: #selector(deletePictureCancleButtonDidTap), - for: .touchUpInside) - } - private var popUpForAlbumDeletion = PopUpViewController(type: .delete).then { - $0.confirmButton.addTarget(self, action: #selector(deleteAlbumCompleteButtonDidTap), - for: .touchUpInside) - $0.cancelButton.addTarget(self, action: #selector(deleteAlbumCancleButtonDidTap), - for: .touchUpInside) - } - private var popUpForAlbumRenaming = PopUpViewController(type: .textField).then { - $0.confirmButton.addTarget(self, action: #selector(renameAlbumCompleteButtonDidTap), - for: .touchUpInside) - $0.cancelButton.addTarget(self, action: #selector(renameAlbumCancleButtonDidTap), - for: .touchUpInside) + private lazy var popUpForPicturesDeletion = PopUpViewController(type: .delete).then { + $0.confirmButton.addTarget(self, action: #selector(deletePictureCompleteButtonDidTap), for: .touchUpInside) + $0.cancelButton.addTarget(self, action: #selector(deletePictureCancleButtonDidTap), for: .touchUpInside) + } + private lazy var popUpForAlbumDeletion = PopUpViewController(type: .delete).then { + $0.confirmButton.addTarget(self, action: #selector(deleteAlbumCompleteButtonDidTap), for: .touchUpInside) + $0.cancelButton.addTarget(self, action: #selector(deleteAlbumCancleButtonDidTap), for: .touchUpInside) + } + private lazy var popUpForAlbumRenaming = PopUpViewController(type: .textField).then { + $0.confirmButton.addTarget(self, action: #selector(renameAlbumCompleteButtonDidTap), for: .touchUpInside) + $0.cancelButton.addTarget(self, action: #selector(renameAlbumCancleButtonDidTap), for: .touchUpInside) } private var cameraViewcontroller = CameraViewController() private var albumId: Int @@ -93,44 +63,33 @@ class PanoramaViewController: BaseViewController { private var albumName: String private var bodyPartIndex = 0 private var bodyPart: BodyPart = .whole - private var seletedPictures = BehaviorRelay<[Int: Int]>(value: [:]) private var seletedPicturesValue: [Int: Int] private var bodyPartData: [PictureInfo] { didSet { setHide() - reloadCollectionView() + gridAlbumCollectionView.reloadData() editMode ? initEditNavigationBar() : initNavigationBar() } } - private var gridMode = false { + private var gridMode = true { didSet { - topCollectionView.reloadData() - topCollectionView.bounces = gridMode - topCollectionView.isScrollEnabled = gridMode - topCollectionView.allowsSelection = !gridMode + gridAlbumCollectionView.reloadData() + gridAlbumCollectionView.bounces = gridMode + gridAlbumCollectionView.allowsSelection = !gridMode } } private var editMode = false { didSet { - topCollectionView.reloadData() - topCollectionView.isScrollEnabled = editMode - topCollectionView.allowsMultipleSelection = editMode - gridButton.isHidden = editMode + gridAlbumCollectionView.reloadData() + gridAlbumCollectionView.allowsMultipleSelection = editMode setHide() } } - - private var verticalFlowLayout = UICollectionViewFlowLayout().then { - $0.scrollDirection = .vertical - } - - private var horizontalFlowLayout = UICollectionViewFlowLayout().then { - $0.scrollDirection = .horizontal - } + private var centerCell: BottomCollectionViewCell? private var selectedIndexByPart = Array(repeating: IndexPath(item: 0, section: 0), count: 3) private var isSelectedEvent: Bool = false @@ -157,7 +116,6 @@ class PanoramaViewController: BaseViewController { self.albumData = albumData self.bodyPartData = albumData.pictures.whole self.albumName = self.albumData.name - self.seletedPicturesValue = seletedPictures.value super.init(nibName: nil, bundle: nil) } @@ -169,7 +127,6 @@ class PanoramaViewController: BaseViewController { override func viewDidLoad() { initNavigationBar() setupViewHierarchy() - setupCollectionView() setDelegation() setupConstraint() initSegementData() @@ -186,8 +143,6 @@ class PanoramaViewController: BaseViewController { override func viewDidLayoutSubviews() { setHide() - bottomCollectionView.reloadData() - moveCellToCenter(animated: false) } override func viewWillDisappear(_ animated: Bool) { @@ -195,12 +150,11 @@ class PanoramaViewController: BaseViewController { Mixpanel.mainInstance().track(event: "viewAlbum/btn/back") } } - - + // MARK: - Methods private func bind() { - let input = PanoramaViewModel.Input(cameraViewDidDisappear: cameraViewcontroller.rx.viewDidDisappear.map { _ in }, + let input = GridAlbumViewModel.Input(viewWillAppear: rx.viewWillAppear.map { _ in }, albumId: albumId, albumNameTextField: popUpForAlbumRenaming.textField.rx.text.orEmpty.asObservable(), deletePictureData: seletedPictures.asObservable(), @@ -211,12 +165,11 @@ class PanoramaViewController: BaseViewController { output.album .drive(onNext: { [weak self] data in - guard let self = self else { return } + guard let self else { return } if let data = data { self.albumData = data self.initBodyPartData(index: self.bodyPartIndex) } - self.moveCellToCenter(animated: false) }) .disposed(by: disposeBag) @@ -226,7 +179,7 @@ class PanoramaViewController: BaseViewController { output.renamedAlbum .drive(onNext: { [weak self] name in - guard let self = self else { return } + guard let self else { return } if let name = name { self.title = name self.albumName = name @@ -236,7 +189,7 @@ class PanoramaViewController: BaseViewController { output.deletePictureCount .drive(onNext: { [weak self] count in - guard let self = self else { return } + guard let self else { return } self.title = self.editMode ? "\(count)장" : self.albumName if self.editMode { self.navigationItem.rightBarButtonItem?.isEnabled = count > 0 @@ -245,9 +198,9 @@ class PanoramaViewController: BaseViewController { output.deletePictureStatusCode .drive(onNext: { [weak self] statusCode in - guard let self = self else { return } + guard let self else { return } if statusCode == 200 { - self.seletedPictures.value.forEach { key, value in + seletedPictures.value.forEach { key, value in self.deleteAlbumData(id: value) self.bodyPartData.removeAll(where: {$0.id == value}) self.seletedPicturesValue.removeValue(forKey: key) @@ -255,13 +208,13 @@ class PanoramaViewController: BaseViewController { self.updateSeletedIndex(index: key) } self.showToast(type: .delete) - self.dismiss(animated: true, completion: self.topCollectionView.reloadData) + self.dismiss(animated: true, completion: self.gridAlbumCollectionView.reloadData) } }).disposed(by: disposeBag) output.deleteAlbumStatusCode .drive(onNext: { [weak self] statusCode in - guard let self = self else { return } + guard let self else { return } if statusCode == 204 { self.dismiss(animated: true, completion: nil) self.navigationController?.popViewController(animated: false) @@ -299,10 +252,8 @@ class PanoramaViewController: BaseViewController { private func setDelegation() { bodyPartSegmentControl.delegate = self - [topCollectionView, bottomCollectionView].forEach { collectionView in - collectionView.delegate = self - collectionView.dataSource = self - } + gridAlbumCollectionView.delegate = self + gridAlbumCollectionView.dataSource = self } private func setDefaultTab() { @@ -322,9 +273,10 @@ class PanoramaViewController: BaseViewController { } private func initSegementData() { - let bodyPartsArray = ["전신", "상체", "하체"] - for (index, title) in bodyPartsArray.enumerated() { - bodyPartSegmentControl.setTitle(at: index, title: title) + let bodyPartsArray: [BodyPart] = [.whole, .upper, .lower] + + for (index, bodyPart) in bodyPartsArray.enumerated() { + bodyPartSegmentControl.setTitle(at: index, title: bodyPart.title) } } @@ -373,38 +325,8 @@ class PanoramaViewController: BaseViewController { selectedIndexByPart[bodyPartIndex] = IndexPath(item: updatedIndexPathItem, section: 0) } - private func switchPanoramaMode() { - if gridMode || editMode { - topCollectionView.setCollectionViewLayout(verticalFlowLayout, animated: false) - bottomCollectionView.isHidden = true - } else { - topCollectionView.setCollectionViewLayout(horizontalFlowLayout, animated: false) - bottomCollectionView.isHidden = false - moveCellToCenter(animated: false) - } - } - - private func reloadCollectionView() { - topCollectionView.reloadData() - bottomCollectionView.reloadData() - } - private func setHide() { emptyView.isHidden = bodyPartData.isEmpty && !editMode ? false : true - gridButton.isHidden = bodyPartData.isEmpty || editMode ? true : false - } - - func moveCellToCenter(animated: Bool) { - if !(bottomCollectionView.isHidden || bodyPartData.isEmpty) { - bottomCollectionView.selectItem(at: selectedIndexByPart[bodyPartIndex], animated: false, scrollPosition: .centeredHorizontally) - setCollectionViewContentOffset(animated: false) - } - } - - func setCollectionViewContentOffset(animated: Bool) { - topCollectionView.setContentOffset(CGPoint(x: topCollectionView.frame.maxX * CGFloat(selectedIndexByPart[bodyPartIndex].row), - y: 0.0), animated: false) - bottomCollectionView.setContentOffset(CGPoint(x: cellWidth * CGFloat(selectedIndexByPart[bodyPartIndex].row), y: 0.0), animated: animated) } private func editAlbumButtonDidTap() { @@ -463,9 +385,6 @@ class PanoramaViewController: BaseViewController { resetDeleteData() editMode ? initNavigationBar() : initEditNavigationBar() editMode.toggle() - if !gridMode { - switchPanoramaMode() - } } @objc private func deletePictureButtonDidTap() { @@ -477,23 +396,14 @@ class PanoramaViewController: BaseViewController { Mixpanel.mainInstance().track(event: "selectPhoto/btn/delete") } - - @objc private func gridButtonDidTap() { - gridButton.isSelected.toggle() - gridMode.toggle() - switchPanoramaMode() - } - @objc func menuButtonDidTap() { Mixpanel.mainInstance().track(event: "viewAlbum/btn/setting") } - @objc func cameraButtonDidTap() { self.navigationController?.pushViewController(cameraViewcontroller, animated: true) Mixpanel.mainInstance().track(event: "selectPhoto/btn/addPhoto") } - @objc func deletePictureCancleButtonDidTap() { Mixpanel.mainInstance().track(event: "selectPhoto/deletePhotoModal/btn/cancle") } @@ -517,41 +427,22 @@ class PanoramaViewController: BaseViewController { // MARK: - View Layout -extension PanoramaViewController { - private func setupCollectionView() { - topCollectionView.collectionViewLayout = horizontalFlowLayout - } - +extension GridAlbumViewController { private func setupViewHierarchy() { - view.addSubviews(bodyPartSegmentControl, gridButton, stackView, emptyView) - stackView.addArrangedSubviews([topCollectionView, bottomCollectionView]) + view.addSubviews(bodyPartSegmentControl, gridAlbumCollectionView, emptyView) } private func setupConstraint() { bodyPartSegmentControl.snp.makeConstraints { $0.top.equalTo(view.safeAreaLayoutGuide.snp.top) $0.leading.equalToSuperview().offset(22) + $0.height.equalTo(56) $0.width.equalTo(152) } - - gridButton.snp.makeConstraints { - $0.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(16) - $0.trailing.equalToSuperview().offset(-22) - $0.height.width.equalTo(24) - $0.centerY.equalTo(bodyPartSegmentControl) - } - - stackView.snp.makeConstraints { + gridAlbumCollectionView.snp.makeConstraints { $0.top.equalTo(bodyPartSegmentControl.snp.bottom) - $0.leading.trailing.equalToSuperview() - $0.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom) - } - - topCollectionView.snp.makeConstraints { - let ratio = UIDevice.current.hasNotch ? (4.0/3.0) : (423/375) - $0.height.equalTo(Constant.Size.screenWidth * ratio).priority(999) + $0.leading.trailing.bottom.equalTo(view.safeAreaLayoutGuide) } - emptyView.snp.makeConstraints { $0.center.equalToSuperview() $0.width.equalTo(270 * Constant.Size.screenWidth / Constant.Size.figmaWidth) @@ -562,84 +453,30 @@ extension PanoramaViewController { // MARK: - Extension -extension PanoramaViewController: UICollectionViewDelegateFlowLayout { +extension GridAlbumViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - if collectionView == bottomCollectionView { - cellWidth = collectionView.frame.height * 0.54 + cellSpacing - return CGSize(width: cellWidth, height: collectionView.frame.height ) - } else { let length = Constant.Size.screenWidth - let ratio = UIDevice.current.hasNotch ? (4.0/3.0) : (423/375) - return gridMode || editMode ? CGSize(width: (length - 4)/3, height: (length - 4)/3) : CGSize(width: length, height: length * ratio) - } - } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { - if collectionView == bottomCollectionView { - let inset = (collectionView.frame.width - (collectionView.frame.height * 0.54 + cellSpacing)) / 2 - return UIEdgeInsets(top: 0, left: inset, bottom: 0, right: inset) - } - return .zero + return CGSize(width: (length - 4)/3, height: (length - 4)/3) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { - return collectionView == topCollectionView && gridMode || editMode ? 2 : 0 + return 2 } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { return 2 } - - func scrollViewDidScroll(_ scrollView: UIScrollView) { - if isSelectedEvent { return } - - if scrollView == bottomCollectionView && !bodyPartData.isEmpty { - let centerPoint = CGPoint(x: bottomCollectionView.contentOffset.x + bottomCollectionView.frame.midX, y: 100) - - if let indexPath = bottomCollectionView.indexPathForItem(at: centerPoint), centerCell == nil { - centerCell = bottomCollectionView.cellForItem(at: indexPath) as? BottomCollectionViewCell - topCollectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: false) - selectedIndexByPart[bodyPartIndex] = indexPath - centerCell?.transformToCenter() - } - - if let cell = centerCell { - let offsetX = centerPoint.x - cell.center.x - if offsetX < -cell.frame.width/2 || offsetX > cell.frame.width/2 { - cell.transformToStandard() - bottomCollectionView.deselectItem(at: selectedIndexByPart[bodyPartIndex], animated: false) - self.centerCell = nil - } - } - } - } - - func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { - moveCellToCenter(animated: true) - } - - func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { - isSelectedEvent = false - } - - func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { - if !decelerate { - moveCellToCenter(animated: true) - } - } } -extension PanoramaViewController: UICollectionViewDataSource { +extension GridAlbumViewController: UICollectionViewDataSource { typealias CameraCell = CameraCollectionViewCell typealias TopCell = TopCollectionViewCell - typealias BottomCell = BottomCollectionViewCell func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return collectionView == topCollectionView && editMode ? bodyPartData.count + 1 : bodyPartData.count + return collectionView == gridAlbumCollectionView && editMode ? bodyPartData.count + 1 : bodyPartData.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - if collectionView == topCollectionView { if editMode && indexPath.row == 0 { let cell: CameraCell = collectionView.dequeueReusableCell(for: indexPath) return cell @@ -650,60 +487,42 @@ extension PanoramaViewController: UICollectionViewDataSource { let indexPath = editMode ? indexPath.row - 1 : indexPath.row cell.setPhotoCell(albumId: albumData.id, bodyPart: "\(bodyPart)", imageName: bodyPartData[indexPath].id, contentMode: gridMode, fileExtension: .png) return cell - } - - let cell: BottomCell = collectionView.dequeueReusableCell(for: indexPath) - cell.setCell(albumId: albumData.id, bodyPart: "\(bodyPart)", imageName: bodyPartData[indexPath.row].id, index: indexPath.row, fileExtension: .png) - if indexPath.item == selectedIndexByPart[bodyPartIndex].row { - collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .init()) - } else { - collectionView.deselectItem(at: indexPath, animated: false) - } - return cell } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - if collectionView == topCollectionView && editMode { + if editMode { if indexPath.row == 0 { cameraButtonDidTap() } else { seletedPicturesValue[indexPath.row - 1] = bodyPartData[indexPath.row - 1].id seletedPictures.accept(seletedPicturesValue) - } - } else if !bodyPartData.isEmpty { - if selectedIndexByPart[bodyPartIndex] == indexPath { return } - isSelectedEvent = true - centerCell?.transformToStandard() - selectedIndexByPart[bodyPartIndex] = indexPath - - guard let bottomCell = bottomCollectionView.cellForItem(at: indexPath) as? BottomCollectionViewCell else { return } - centerCell = bottomCell - setCollectionViewContentOffset(animated: true) + } else { + let viewController = PanoramaAlbumViewController(pictureInfos: bodyPartData, bodyPart: bodyPart, pictureIndex: indexPath) + navigationController?.pushViewController(viewController, animated: true) } } func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { - if collectionView == topCollectionView && editMode { + if collectionView == gridAlbumCollectionView && editMode { seletedPicturesValue.removeValue(forKey: indexPath.row - 1) seletedPictures.accept(seletedPicturesValue) } } } -extension PanoramaViewController: NBSegmentedControlDelegate { +extension GridAlbumViewController: NBSegmentedControlDelegate { func changeToIndex(_ segmentControl: NBSegmentedControl, at index: Int) { bodyPartIndex = index initBodyPartData(index: bodyPartIndex) resetDeleteData() if !bodyPartData.isEmpty { selectedIndexByPart[bodyPartIndex] = selectedIndexByPart[index] - moveCellToCenter(animated: false) } } } -extension PanoramaViewController: PopUpActionProtocol { +extension GridAlbumViewController: PopUpActionProtocol { func cancelButtonDidTap(_ button: UIButton) { self.dismiss(animated: true, completion: nil) Mixpanel.mainInstance().track(event: "selectPhoto/deletePhotoModal/btn/cancle") @@ -711,7 +530,6 @@ extension PanoramaViewController: PopUpActionProtocol { func confirmButtonDidTap(_ button: UIButton, textInfo: String) { self.dismiss(animated: true, completion: nil) - Mixpanel.mainInstance().track(event: "selectPhoto/deletePhotoModal/btn/complete") } } diff --git a/Noonbody/Noonbody/Source/Presentation/Panorama/ViewControllers/PanoramaAlbumViewController.swift b/Noonbody/Noonbody/Source/Presentation/Panorama/ViewControllers/PanoramaAlbumViewController.swift new file mode 100644 index 0000000..c06db53 --- /dev/null +++ b/Noonbody/Noonbody/Source/Presentation/Panorama/ViewControllers/PanoramaAlbumViewController.swift @@ -0,0 +1,297 @@ +// +// PanoramaAlbumViewController.swift +// Noonbody +// +// Created by kong on 2023/06/06. +// + +import UIKit + +import Then +import Mixpanel +import RxSwift +import RxCocoa + +final class PanoramaAlbumViewController: BaseViewController { + + private var stackView = UIStackView().then { + $0.axis = .vertical + $0.spacing = UIDevice.current.hasNotch ? 22 : 5 + $0.distribution = .fill + $0.backgroundColor = .clear + } + + private var panoramaImageView = UIImageView().then { + $0.contentMode = .scaleAspectFit + } + + private var bottomCollectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewLayout()).then { + $0.register(BottomCollectionViewCell.self) + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .horizontal + $0.collectionViewLayout = layout + $0.bounces = false + $0.backgroundColor = .clear + $0.showsHorizontalScrollIndicator = false + $0.allowsMultipleSelection = false + $0.setContentHuggingPriority(.defaultLow, for: .vertical) + } + + private lazy var popUpForPicturesDeletion = PopUpViewController(type: .delete).then { + $0.confirmButton.addTarget(self, action: #selector(deletePictureCompleteButtonDidTap), + for: .touchUpInside) + $0.cancelButton.addTarget(self, action: #selector(deletePictureCancleButtonDidTap), + for: .touchUpInside) + } + + private var centerCell: BottomCollectionViewCell? + private let cellSpacing: CGFloat = 2 + private var cellWidth: CGFloat = 0 + private var isSelectedEvent: Bool = false + + private var pictureInfos: [PictureInfo] { + didSet { + navigationItem.rightBarButtonItem?.isEnabled = !pictureInfos.isEmpty + } + } + private var bodyPart: BodyPart + private var pictureIndex: IndexPath { + didSet { + if !pictureInfos.isEmpty { + selectedPictureId.accept(pictureInfos[pictureIndex.row].id) + } + } + } + private var pictureIndexPath: IndexPath = IndexPath(item: -1, section: 0) + private var selectedPictureId = PublishRelay() + + private var viewModel: PanoramaAlbumViewModel = PanoramaAlbumViewModel(deletePictureUseCase: DefaultDeletePictureUseCase(repository: LocalPictureRepository())) + + init(pictureInfos: [PictureInfo], bodyPart: BodyPart, pictureIndex: IndexPath) { + self.pictureInfos = pictureInfos + self.bodyPart = bodyPart + self.pictureIndex = pictureIndex + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + initNavigationBar() + bind() + setupConstraint() + collectionViewDelegation() + } + + override func viewWillAppear(_ animated: Bool) { + setupView() + } + + private func setupView() { + selectedPictureId.accept(pictureInfos[pictureIndex.row].id) + setupPanormaImageView(pictureIndex: pictureIndex.row) + moveCellToCenter(animated: false) + } + + private func collectionViewDelegation() { + bottomCollectionView.delegate = self + bottomCollectionView.dataSource = self + } + + private func initNavigationBar() { + navigationController?.initNavigationBar(navigationItem: self.navigationItem, + rightButtonImages: [Asset.Image.trash.image], + rightActions: [#selector(deletePictureButtonDidTap)]) + navigationItem.leftBarButtonItems = nil + title = bodyPart.title + } + + private func bind() { + let input = PanoramaAlbumViewModel.Input(deletePictureId: selectedPictureId.asObservable(), + deletePictureButtonControlEvent: popUpForPicturesDeletion.confirmButton.rx.tap) + let output = viewModel.transform(input: input) + output.deletePictureStatusCode + .drive(onNext: { [weak self] statusCode in + guard let self else { return } + if statusCode == 200 { + self.showToast(type: .delete) + self.dismiss(animated: true, completion: bottomCollectionView.reloadData) + } + }) + .disposed(by: disposeBag) + + output.deletedPictureId + .drive(onNext: { [weak self] id in + guard let self else { return } + pictureInfos.removeAll(where: { $0.id == id }) + bottomCollectionView.reloadData() + pictureIndex = pictureIndex.row > 0 ? IndexPath(item: (pictureIndex.row) - 1, section: 0) : pictureIndex + setupPanormaImageView(pictureIndex: pictureIndex.row) + moveCellToCenter(animated: true) + }) + .disposed(by: disposeBag) + } + + private func setupConstraint() { + view.addSubviews(stackView) + stackView.snp.makeConstraints { + $0.edges.equalTo(view.safeAreaLayoutGuide) + } + stackView.addArrangedSubviews([panoramaImageView, bottomCollectionView]) + panoramaImageView.snp.makeConstraints { + let ratio = UIDevice.current.hasNotch ? (4.0/3.0) : (423/375) + $0.height.equalTo(Constant.Size.screenWidth * ratio).priority(999) + } + } + + private func setupPanormaImageView(pictureIndex: Int) { + if pictureInfos.isEmpty { + panoramaImageView.image = nil + } else { + panoramaImageView.image = AlbumManager.loadImageFromDocumentDirectory(from: "\(pictureInfos[pictureIndex].albumID)/\(pictureInfos[pictureIndex].bodyPart)/\(pictureInfos[pictureIndex].id).\(FileExtension.png)") + } + } + + @objc private func deletePictureButtonDidTap() { + popUpForPicturesDeletion.modalTransitionStyle = .crossDissolve + popUpForPicturesDeletion.modalPresentationStyle = .overCurrentContext + popUpForPicturesDeletion.delegate = self + popUpForPicturesDeletion.titleLabel.text = "사진을 삭제하시겠습니까?" + popUpForPicturesDeletion.descriptionLabel.text = "삭제를 누르시면 사진이 완전히 삭제됩니다." + popUpForPicturesDeletion.setDeleteButton() + self.present(popUpForPicturesDeletion, animated: true, completion: nil) + + Mixpanel.mainInstance().track(event: "selectPhoto/btn/delete") + } + + @objc func deletePictureCancleButtonDidTap() { + Mixpanel.mainInstance().track(event: "selectPhoto/deletePhotoModal/btn/cancle") + } + @objc func deletePictureCompleteButtonDidTap() { + Mixpanel.mainInstance().track(event: "selectPhoto/deletePhotoModal/btn/complete") + } + + func moveCellToCenter(animated: Bool) { + if !pictureInfos.isEmpty { + DispatchQueue.main.async { [weak self] in + guard let self else { return } + bottomCollectionView.scrollToItem(at: pictureIndex, at: .centeredHorizontally, animated: animated) + } + } + } +} + +// MARK: - Extension + +extension PanoramaAlbumViewController: UICollectionViewDelegateFlowLayout { + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + cellWidth = collectionView.frame.height * 0.54 + cellSpacing + return CGSize(width: cellWidth, height: collectionView.frame.height ) + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets { + let inset = (collectionView.frame.width - (collectionView.frame.height * 0.54 + cellSpacing)) / 2 + return UIEdgeInsets(top: 0, left: inset, bottom: 0, right: inset) + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + return 2 + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + return 2 + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if isSelectedEvent { return } + + if !pictureInfos.isEmpty { + let centerPoint = CGPoint(x: bottomCollectionView.contentOffset.x + bottomCollectionView.frame.midX, y: 100) + if let indexPath = bottomCollectionView.indexPathForItem(at: centerPoint), centerCell == nil { + centerCell = bottomCollectionView.cellForItem(at: indexPath) as? BottomCollectionViewCell + setupPanormaImageView(pictureIndex: indexPath.row) + pictureIndex = indexPath + pictureIndexPath = indexPath + centerCell?.transformToCenter() + } + + if let cell = centerCell { + let offsetX = centerPoint.x - cell.center.x + if offsetX < -cell.frame.width/2 || offsetX > cell.frame.width/2 { + cell.transformToStandard() + bottomCollectionView.deselectItem(at: pictureIndex, animated: false) + self.centerCell = nil + } + } + } + } + + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + moveCellToCenter(animated: true) + } + + func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { + isSelectedEvent = false + } + + func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + if !decelerate { + moveCellToCenter(animated: true) + } + } +} + +extension PanoramaAlbumViewController: UICollectionViewDataSource { + typealias CameraCell = CameraCollectionViewCell + typealias BottomCell = BottomCollectionViewCell + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return pictureInfos.count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell: BottomCell = collectionView.dequeueReusableCell(for: indexPath) + cell.setCell(albumId: pictureInfos[indexPath.row].albumID, + bodyPart: pictureInfos[indexPath.row].bodyPart.rawValue, + imageName: pictureInfos[indexPath.row].id, + index: indexPath.row, + fileExtension: .png) + + if indexPath.item == pictureIndex.row { + collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .init()) + } else { + collectionView.deselectItem(at: indexPath, animated: false) + } + return cell + } + + func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + if !pictureInfos.isEmpty { + moveCellToCenter(animated: true) + if pictureIndex == indexPath { return } + isSelectedEvent = true + centerCell?.transformToStandard() + pictureIndex = indexPath + pictureIndexPath = indexPath + setupPanormaImageView(pictureIndex: indexPath.row) + + guard let bottomCell = bottomCollectionView.cellForItem(at: indexPath) as? BottomCollectionViewCell else { return } + centerCell = bottomCell + } + } +} + +extension PanoramaAlbumViewController: PopUpActionProtocol { + func cancelButtonDidTap(_ button: UIButton) { + self.dismiss(animated: true, completion: nil) + Mixpanel.mainInstance().track(event: "selectPhoto/deletePhotoModal/btn/cancle") + } + + func confirmButtonDidTap(_ button: UIButton, textInfo: String) { + self.dismiss(animated: true, completion: nil) + Mixpanel.mainInstance().track(event: "selectPhoto/deletePhotoModal/btn/complete") + } +} diff --git a/Noonbody/Noonbody/Source/Presentation/Panorama/ViewModels/PanoramaViewModel.swift b/Noonbody/Noonbody/Source/Presentation/Panorama/ViewModels/GridAlbumViewModel.swift similarity index 96% rename from Noonbody/Noonbody/Source/Presentation/Panorama/ViewModels/PanoramaViewModel.swift rename to Noonbody/Noonbody/Source/Presentation/Panorama/ViewModels/GridAlbumViewModel.swift index fceb3b2..669a1e7 100644 --- a/Noonbody/Noonbody/Source/Presentation/Panorama/ViewModels/PanoramaViewModel.swift +++ b/Noonbody/Noonbody/Source/Presentation/Panorama/ViewModels/GridAlbumViewModel.swift @@ -10,14 +10,14 @@ import Foundation import RxSwift import RxCocoa -final class PanoramaViewModel { +final class GridAlbumViewModel { private let fetchAlbumUseCase: FetchAlbumUseCase private let renameAlbumUseCase: RenameAlbumUseCase private let deleteAlbumUseCase: DeleteAlbumUseCase private let deletePictureUseCase: DeletePictureUseCase struct Input { - let cameraViewDidDisappear: Observable + let viewWillAppear: Observable let albumId: Int let albumNameTextField: Observable let deletePictureData: Observable<[Int: Int]> @@ -52,7 +52,7 @@ final class PanoramaViewModel { } .share() - let album = input.cameraViewDidDisappear + let album = input.viewWillAppear .flatMap { _ in self.fetchAlbumUseCase.album(albumId: input.albumId) } .map { $0 } diff --git a/Noonbody/Noonbody/Source/Presentation/Panorama/ViewModels/PanoramaAlbumViewModel.swift b/Noonbody/Noonbody/Source/Presentation/Panorama/ViewModels/PanoramaAlbumViewModel.swift new file mode 100644 index 0000000..28d8a86 --- /dev/null +++ b/Noonbody/Noonbody/Source/Presentation/Panorama/ViewModels/PanoramaAlbumViewModel.swift @@ -0,0 +1,53 @@ +// +// PanoramaAlbumViewModel.swift +// Noonbody +// +// Created by kong on 2023/06/28. +// + +import Foundation + +import RxSwift +import RxCocoa + +final class PanoramaAlbumViewModel { + private let deletePictureUseCase: DeletePictureUseCase + + struct Input { + let deletePictureId: Observable + let deletePictureButtonControlEvent: ControlEvent + } + + struct Output { + let deletePictureStatusCode: Driver + let deletedPictureId: Driver + } + + init(deletePictureUseCase: DeletePictureUseCase) { + self.deletePictureUseCase = deletePictureUseCase + } + + func transform(input: Input) -> Output { + let deletePictureResponse = input.deletePictureButtonControlEvent + .withLatestFrom(input.deletePictureId) + .flatMap { id in + self.deletePictureUseCase.delete(pictureId: id) + } + .share() + + let deletePictureStatusCode = deletePictureResponse + .compactMap { $0 } + .map { result -> Int in + return result + }.asDriver(onErrorJustReturn: 404) + + let deletedPictureId = deletePictureResponse + .compactMap { $0 } + .withLatestFrom(input.deletePictureId) + .map { id in + return id + }.asDriver(onErrorJustReturn: -1) + + return Output(deletePictureStatusCode: deletePictureStatusCode, deletedPictureId: deletedPictureId) + } + }