diff --git a/KillingPart.xcodeproj/project.pbxproj b/KillingPart.xcodeproj/project.pbxproj index 1ed0e92..bb10e70 100644 --- a/KillingPart.xcodeproj/project.pbxproj +++ b/KillingPart.xcodeproj/project.pbxproj @@ -458,7 +458,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = KillingPart/KillingPart.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 46; + CURRENT_PROJECT_VERSION = 47; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = GQ89YG5G9R; ENABLE_APP_SANDBOX = YES; @@ -483,7 +483,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 1.2.11; + MARKETING_VERSION = 1.2.12; PRODUCT_BUNDLE_IDENTIFIER = com.killingpoint.killingpart; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; @@ -503,7 +503,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = KillingPart/KillingPart.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 46; + CURRENT_PROJECT_VERSION = 47; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = GQ89YG5G9R; ENABLE_APP_SANDBOX = YES; @@ -528,7 +528,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 14.0; - MARKETING_VERSION = 1.2.11; + MARKETING_VERSION = 1.2.12; PRODUCT_BUNDLE_IDENTIFIER = com.killingpoint.killingpart; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; diff --git a/KillingPart/Assets.xcassets/my_background.imageset/my_background 1.png b/KillingPart/Assets.xcassets/my_background.imageset/my_background 1.png deleted file mode 100644 index 14335cb..0000000 Binary files a/KillingPart/Assets.xcassets/my_background.imageset/my_background 1.png and /dev/null differ diff --git a/KillingPart/Assets.xcassets/my_background.imageset/my_background 2.png b/KillingPart/Assets.xcassets/my_background.imageset/my_background 2.png deleted file mode 100644 index 14335cb..0000000 Binary files a/KillingPart/Assets.xcassets/my_background.imageset/my_background 2.png and /dev/null differ diff --git a/KillingPart/Assets.xcassets/my_background.imageset/my_background.png b/KillingPart/Assets.xcassets/my_background.imageset/my_background.png deleted file mode 100644 index 14335cb..0000000 Binary files a/KillingPart/Assets.xcassets/my_background.imageset/my_background.png and /dev/null differ diff --git a/KillingPart/Assets.xcassets/my_background.imageset/Contents.json b/KillingPart/Assets.xcassets/my_background_v2.imageset/Contents.json similarity index 78% rename from KillingPart/Assets.xcassets/my_background.imageset/Contents.json rename to KillingPart/Assets.xcassets/my_background_v2.imageset/Contents.json index e2d3f84..8c3ecf7 100644 --- a/KillingPart/Assets.xcassets/my_background.imageset/Contents.json +++ b/KillingPart/Assets.xcassets/my_background_v2.imageset/Contents.json @@ -6,12 +6,10 @@ "scale" : "1x" }, { - "filename" : "my_background 1.png", "idiom" : "universal", "scale" : "2x" }, { - "filename" : "my_background 2.png", "idiom" : "universal", "scale" : "3x" } diff --git a/KillingPart/Assets.xcassets/my_background_v2.imageset/my_background.png b/KillingPart/Assets.xcassets/my_background_v2.imageset/my_background.png new file mode 100644 index 0000000..6a3a885 Binary files /dev/null and b/KillingPart/Assets.xcassets/my_background_v2.imageset/my_background.png differ diff --git a/KillingPart/Resources/Videos/sc 9-16 v2.mp4 b/KillingPart/Resources/Videos/sc 9-16 v2.mp4 new file mode 100644 index 0000000..7750a60 Binary files /dev/null and b/KillingPart/Resources/Videos/sc 9-16 v2.mp4 differ diff --git a/KillingPart/Views/Components/KillingPartBackgroundView.swift b/KillingPart/Views/Components/KillingPartBackgroundView.swift new file mode 100644 index 0000000..cedca6d --- /dev/null +++ b/KillingPart/Views/Components/KillingPartBackgroundView.swift @@ -0,0 +1,33 @@ +import SwiftUI + +struct KillingPartBackgroundView: View { + private static let artworkAspectRatio: CGFloat = 9 / 16 + + var body: some View { + GeometryReader { geometry in + let artworkHeight = geometry.size.width / Self.artworkAspectRatio + + ZStack(alignment: .top) { + Color(hex: "#060606") + + Image("my_background_v2") + .resizable() + .scaledToFit() + .frame( + width: geometry.size.width, + height: artworkHeight, + alignment: .top + ) + } + .frame( + width: geometry.size.width, + height: geometry.size.height, + alignment: .top + ) + .clipped() + } + .ignoresSafeArea() + .allowsHitTesting(false) + .accessibilityHidden(true) + } +} diff --git a/KillingPart/Views/Screens/Main/Add/AddSearchDetail/components/AddSearchDetailAlbumArtworkView.swift b/KillingPart/Views/Screens/Main/Add/AddSearchDetail/components/AddSearchDetailAlbumArtworkView.swift index 346d608..77c38ba 100644 --- a/KillingPart/Views/Screens/Main/Add/AddSearchDetail/components/AddSearchDetailAlbumArtworkView.swift +++ b/KillingPart/Views/Screens/Main/Add/AddSearchDetail/components/AddSearchDetailAlbumArtworkView.swift @@ -4,11 +4,12 @@ import UIKit struct AddSearchDetailAlbumArtworkView: View { let url: URL? let preloadedImage: UIImage? - let coverSize: CGFloat + let coverSize: CGSize + let coverCornerRadius: CGFloat @State private var isRotating = false - private var fixedLayoutDiskSize: CGFloat { coverSize } - private var diskSize: CGFloat { coverSize * 0.9 } + private var fixedLayoutDiskSize: CGFloat { coverSize.width } + private var diskSize: CGFloat { coverSize.width * 0.9 } private var centerLabelSize: CGFloat { diskSize * 0.34 } private var grooveCount: Int { 9 } private var grooveBaseInset: CGFloat { diskSize * 0.08 } @@ -19,11 +20,25 @@ struct AddSearchDetailAlbumArtworkView: View { init( url: URL?, preloadedImage: UIImage? = nil, - coverSize: CGFloat = 124 + coverSize: CGFloat = 124, + coverCornerRadius: CGFloat = 16 + ) { + self.url = url + self.preloadedImage = preloadedImage + self.coverSize = CGSize(width: coverSize, height: coverSize) + self.coverCornerRadius = coverCornerRadius + } + + init( + url: URL?, + preloadedImage: UIImage? = nil, + coverSize: CGSize, + coverCornerRadius: CGFloat = 16 ) { self.url = url self.preloadedImage = preloadedImage self.coverSize = coverSize + self.coverCornerRadius = coverCornerRadius } var body: some View { @@ -35,20 +50,23 @@ struct AddSearchDetailAlbumArtworkView: View { .onAppear { isRotating = true } - .offset(x: coverSize * 0.55) + .offset(x: coverSize.width * 0.55) .zIndex(0) albumCover - .frame(width: coverSize, height: coverSize) - .clipShape(RoundedRectangle(cornerRadius: 16)) - .overlay { - RoundedRectangle(cornerRadius: 16) - .stroke(Color.white.opacity(0.12), lineWidth: 1) - } + .frame(width: coverSize.width, height: coverSize.height) + .clipShape(RoundedRectangle(cornerRadius: coverCornerRadius)) + .overlay { + RoundedRectangle(cornerRadius: coverCornerRadius) + .stroke(Color.white.opacity(0.12), lineWidth: 1) + } .zIndex(2) } - .frame(width: coverSize + fixedLayoutDiskSize * 0.48, height: coverSize, alignment: .leading) - + .frame( + width: coverSize.width + fixedLayoutDiskSize * 0.48, + height: coverSize.height, + alignment: .leading + ) } private var albumCover: some View { diff --git a/KillingPart/Views/Screens/Main/My/MyCollection/MyKillingPart/[diaryId]/MyCollectionDiary.swift b/KillingPart/Views/Screens/Main/My/MyCollection/MyKillingPart/[diaryId]/MyCollectionDiary.swift index 980cf7a..0dd8329 100644 --- a/KillingPart/Views/Screens/Main/My/MyCollection/MyKillingPart/[diaryId]/MyCollectionDiary.swift +++ b/KillingPart/Views/Screens/Main/My/MyCollection/MyKillingPart/[diaryId]/MyCollectionDiary.swift @@ -49,12 +49,7 @@ struct MyCollectionDiary: View { let bottomContentInset = proxy.safeAreaInsets.bottom + keyboardCompensation + extraBottomInset ZStack { - Image("my_background") - .resizable() - .scaledToFill() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .clipped() - .ignoresSafeArea(.container, edges: .all) + KillingPartBackgroundView() if viewModel.isDeleted { MyCollectionDiaryDeletedPlaceholder() diff --git a/KillingPart/Views/Screens/Main/My/MyCollection/MyKillingPart/[diaryId]/components/MyCollectionDiaryShareExport.swift b/KillingPart/Views/Screens/Main/My/MyCollection/MyKillingPart/[diaryId]/components/MyCollectionDiaryShareExport.swift index 68afbe6..1212df2 100644 --- a/KillingPart/Views/Screens/Main/My/MyCollection/MyKillingPart/[diaryId]/components/MyCollectionDiaryShareExport.swift +++ b/KillingPart/Views/Screens/Main/My/MyCollection/MyKillingPart/[diaryId]/components/MyCollectionDiaryShareExport.swift @@ -120,8 +120,8 @@ enum MyCollectionDiaryShareExportError: LocalizedError { } enum MyCollectionDiaryShareImageRenderer { - private static let canvasSize = CGSize(width: 360, height: 640) - private static let canvasScale: CGFloat = 3 + private static let canvasSize = MyCollectionDiaryShareImageLayout.canvasSize + private static let canvasScale = MyCollectionDiaryShareImageLayout.renderScale @MainActor static func render( @@ -178,6 +178,57 @@ enum MyCollectionDiaryShareImageRenderer { } } +private enum MyCollectionDiaryShareImageLayout { + static let renderScale: CGFloat = 3 + static let canvasSize = CGSize(width: 360, height: 640) + static let contentTopPadding: CGFloat = 90 / renderScale + static let contentHorizontalPadding: CGFloat = 132 / renderScale + static let cardSpacing: CGFloat = 35 / renderScale + static let trackCardHeight: CGFloat = 942 / renderScale + static let trackCardCornerRadius: CGFloat = 72.52941 / renderScale + static let trackCardBackgroundOpacity: CGFloat = 0.4 + static let trackCardBackdropBlurRadius: CGFloat = 4 + static let trackCardBackdropOffsetY = ( + canvasSize.height - trackCardHeight + ) / 2 - contentTopPadding + static let trackCardShadowRadius: CGFloat = 2 / renderScale + static let trackCardShadowY: CGFloat = 4 / renderScale + static let commentCardHeight: CGFloat = 728.22681 / renderScale + static let commentCardCornerRadius: CGFloat = 45.58705 / renderScale + static let albumArtworkSize = CGSize( + width: 428.416748046875 / renderScale, + height: 427.9999694824219 / renderScale + ) + static let albumArtworkCornerRadius: CGFloat = 17.23639 / renderScale + static let musicTitleFontSize: CGFloat = 48 / renderScale + static let artistFontSize: CGFloat = 38 / renderScale + static let artistTextWidth: CGFloat = 377.04599 / renderScale + static let timelineTopSpacing: CGFloat = 84 / renderScale + static let timelineLabelFontSize: CGFloat = 32 / renderScale + static let commentFontSize: CGFloat = 32 / renderScale + static let commentTextSize = CGSize( + width: 704 / renderScale, + height: 488 / renderScale + ) + static let commentHorizontalInset = ( + canvasSize.width - contentHorizontalPadding * 2 - commentTextSize.width + ) / 2 + static let metadataFontSize: CGFloat = 26 / renderScale + static let dateTextSize = CGSize( + width: 167.01755 / renderScale, + height: 37.4258 / renderScale + ) + static let appIconSize = CGSize( + width: 108.8 / renderScale, + height: 112 / renderScale + ) + static let appIconCornerRadius: CGFloat = 8 + static let appIconShadowRadius: CGFloat = 1.96087 / renderScale + static let appIconShadowY: CGFloat = 3.92174 / renderScale + static let appIconStrokeWidth: CGFloat = 1.96087 / renderScale + static let metadataColor = Color(red: 0.48, green: 0.48, blue: 0.48) +} + enum MyCollectionDiaryPhotoSaver { static func save(_ image: UIImage) async throws { let status = await PHPhotoLibrary.requestAuthorization(for: .addOnly) @@ -391,46 +442,61 @@ private struct MyCollectionDiaryShareImageView: View { let endProgress: CGFloat var body: some View { - ZStack { - Image("my_background") - .resizable() - .scaledToFill() - .frame(width: 360, height: 640) - .clipped() - - Color.black.opacity(0.48) + backgroundLayer + .overlay(alignment: .top) { + VStack(spacing: MyCollectionDiaryShareImageLayout.cardSpacing) { + trackCard + .frame(height: MyCollectionDiaryShareImageLayout.trackCardHeight) + + commentCard + .frame(height: MyCollectionDiaryShareImageLayout.commentCardHeight) + } + .padding(.horizontal, MyCollectionDiaryShareImageLayout.contentHorizontalPadding) + .padding(.top, MyCollectionDiaryShareImageLayout.contentTopPadding) + .frame( + width: MyCollectionDiaryShareImageLayout.canvasSize.width, + height: MyCollectionDiaryShareImageLayout.canvasSize.height, + alignment: .top + ) + } + .frame( + width: MyCollectionDiaryShareImageLayout.canvasSize.width, + height: MyCollectionDiaryShareImageLayout.canvasSize.height + ) + .clipped() + } - VStack(spacing: 18) { - trackCard - .frame(height: 292) + private var backgroundLayer: some View { + backgroundArtwork + .overlay(Color.black.opacity(0.30)) + } - commentCard - .frame(height: 238) - } - .padding(.horizontal, 21) - .padding(.top, 38) - .frame(width: 360, height: 640, alignment: .top) - } - .frame(width: 360, height: 640) - .clipped() + private var backgroundArtwork: some View { + Image("my_background_v2") + .resizable() + .scaledToFill() + .frame( + width: MyCollectionDiaryShareImageLayout.canvasSize.width, + height: MyCollectionDiaryShareImageLayout.canvasSize.height + ) + .clipped() } private var trackCard: some View { ZStack { - RoundedRectangle(cornerRadius: 28, style: .continuous) - .fill(Color.black.opacity(0.72)) - .overlay { - RoundedRectangle(cornerRadius: 28, style: .continuous) - .stroke(Color.white.opacity(0.04), lineWidth: 1) - } + trackCardBackground VStack(spacing: 0) { AddSearchDetailAlbumArtworkView( url: artworkURL, preloadedImage: artworkImage, - coverSize: 160 + coverSize: MyCollectionDiaryShareImageLayout.albumArtworkSize, + coverCornerRadius: MyCollectionDiaryShareImageLayout.albumArtworkCornerRadius + ) + .frame( + height: MyCollectionDiaryShareImageLayout.albumArtworkSize.height, + alignment: .center ) - .frame(width: 238, height: 160, alignment: .center) .padding(.top, 24) trackTextSection @@ -441,30 +507,62 @@ private struct MyCollectionDiaryShareImageView: View { endMinuteSecondText: endMinuteSecondText, startProgress: startProgress, endProgress: endProgress, - trackHeight: 2.5, - segmentHeight: 6, + trackHeight: 4, + segmentHeight: 8, labelWidth: 40, labelY: 24, height: 38, - startFontSize: 10, - endFontSize: 10, + startFontSize: MyCollectionDiaryShareImageLayout.timelineLabelFontSize, + endFontSize: MyCollectionDiaryShareImageLayout.timelineLabelFontSize, trackColor: Color.white.opacity(0.42), segmentColor: AppColors.primary600, - startLabelColor: Color.white.opacity(0.62), - endLabelColor: Color.white.opacity(0.62) + startLabelColor: MyCollectionDiaryShareImageLayout.metadataColor, + endLabelColor: MyCollectionDiaryShareImageLayout.metadataColor, + usesThinLabelFont: true ) .padding(.horizontal, 36) - .padding(.top, 10) + .padding(.top, MyCollectionDiaryShareImageLayout.timelineTopSpacing) Spacer(minLength: 12) } } } + private var trackCardBackground: some View { + let shape = RoundedRectangle( + cornerRadius: MyCollectionDiaryShareImageLayout.trackCardCornerRadius, + style: .continuous + ) + + return shape + .fill(Color.clear) + .overlay { + backgroundArtwork + .offset(y: MyCollectionDiaryShareImageLayout.trackCardBackdropOffsetY) + .compositingGroup() + .blur( + radius: MyCollectionDiaryShareImageLayout.trackCardBackdropBlurRadius, + opaque: true + ) + } + .clipShape(shape) + .overlay { + shape.fill( + Color.black.opacity(MyCollectionDiaryShareImageLayout.trackCardBackgroundOpacity) + ) + } + .shadow( + color: .black.opacity(0.25), + radius: MyCollectionDiaryShareImageLayout.trackCardShadowRadius, + x: 0, + y: MyCollectionDiaryShareImageLayout.trackCardShadowY + ) + } + private var trackTextSection: some View { VStack(spacing: 5) { Text(musicTitle) - .font(AppFont.paperlogy7Bold(size: 19)) + .font(AppFont.paperlogy7Bold(size: MyCollectionDiaryShareImageLayout.musicTitleFontSize)) .foregroundStyle(.white) .lineLimit(2) .minimumScaleFactor(0.74) @@ -472,51 +570,58 @@ private struct MyCollectionDiaryShareImageView: View { .frame(maxWidth: 266) Text(artist) - .font(AppFont.paperlogy4Regular(size: 14)) - .foregroundStyle(.white.opacity(0.82)) + .font(AppFont.paperlogy4Regular(size: MyCollectionDiaryShareImageLayout.artistFontSize)) + .multilineTextAlignment(.center) + .foregroundStyle(.white) .lineLimit(1) .minimumScaleFactor(0.82) - .frame(maxWidth: 266) + .frame(width: MyCollectionDiaryShareImageLayout.artistTextWidth, alignment: .top) } .frame(maxWidth: .infinity) } private var commentCard: some View { ZStack(alignment: .bottom) { - RoundedRectangle(cornerRadius: 13, style: .continuous) - .fill(Color.white.opacity(0.10)) - .overlay { - RoundedRectangle(cornerRadius: 13, style: .continuous) - .stroke(Color.white.opacity(0.10), lineWidth: 1) - } + RoundedRectangle( + cornerRadius: MyCollectionDiaryShareImageLayout.commentCardCornerRadius, + style: .continuous + ) + .fill(Color.kpGray700) Text(displayedContent.isEmpty ? "작성된 코멘트가 없어요." : displayedContent) - .font(AppFont.paperlogy4Regular(size: 13)) - .foregroundStyle(.white.opacity(0.92)) + .font(AppFont.paperlogy4Regular(size: MyCollectionDiaryShareImageLayout.commentFontSize)) + .foregroundStyle(.white) .multilineTextAlignment(.leading) - .lineSpacing(3) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - .padding(.horizontal, 22) - .padding(.top, 24) - .padding(.bottom, 74) + .lineSpacing(2.5) + .frame( + width: MyCollectionDiaryShareImageLayout.commentTextSize.width, + height: MyCollectionDiaryShareImageLayout.commentTextSize.height, + alignment: .topLeading + ) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) + .offset(y: 24) HStack(alignment: .bottom) { VStack(alignment: .leading, spacing: 5) { Text(createdDateText) - .font(AppFont.paperlogy4Regular(size: 10.5)) - .foregroundStyle(.white.opacity(0.52)) + .font(AppFont.paperlogy4Regular(size: MyCollectionDiaryShareImageLayout.metadataFontSize)) + .foregroundStyle(MyCollectionDiaryShareImageLayout.metadataColor) + .frame( + width: MyCollectionDiaryShareImageLayout.dateTextSize.width, + height: MyCollectionDiaryShareImageLayout.dateTextSize.height, + alignment: .topLeading + ) Text(tagText) - .font(AppFont.paperlogy5Medium(size: 11.5)) - .foregroundStyle(.white.opacity(0.70)) + .font(AppFont.paperlogy5Medium(size: MyCollectionDiaryShareImageLayout.metadataFontSize)) + .foregroundStyle(MyCollectionDiaryShareImageLayout.metadataColor) } Spacer() appIcon } - .padding(.leading, 22) - .padding(.trailing, 18) + .padding(.horizontal, MyCollectionDiaryShareImageLayout.commentHorizontalInset) .padding(.bottom, 18) } } @@ -526,18 +631,60 @@ private struct MyCollectionDiaryShareImageView: View { if let image = MyCollectionDiaryAppIconImageProvider.image { Image(uiImage: image) .resizable() - .scaledToFit() - .frame(width: 46, height: 46) - .clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous)) + .scaledToFill() + .frame( + width: MyCollectionDiaryShareImageLayout.appIconSize.width, + height: MyCollectionDiaryShareImageLayout.appIconSize.height + ) + .clipped() + .background(Color(red: 0.81, green: 1, blue: 0.26)) + .clipShape( + RoundedRectangle( + cornerRadius: MyCollectionDiaryShareImageLayout.appIconCornerRadius, + style: .continuous + ) + ) + .shadow( + color: .black.opacity(0.25), + radius: MyCollectionDiaryShareImageLayout.appIconShadowRadius, + x: 0, + y: MyCollectionDiaryShareImageLayout.appIconShadowY + ) + .overlay { + RoundedRectangle( + cornerRadius: MyCollectionDiaryShareImageLayout.appIconCornerRadius, + style: .continuous + ) + .stroke(.black, lineWidth: MyCollectionDiaryShareImageLayout.appIconStrokeWidth) + } } else { - RoundedRectangle(cornerRadius: 10, style: .continuous) - .fill(AppColors.primary600) - .frame(width: 46, height: 46) + RoundedRectangle( + cornerRadius: MyCollectionDiaryShareImageLayout.appIconCornerRadius, + style: .continuous + ) + .fill(Color(red: 0.81, green: 1, blue: 0.26)) + .frame( + width: MyCollectionDiaryShareImageLayout.appIconSize.width, + height: MyCollectionDiaryShareImageLayout.appIconSize.height + ) .overlay { Text("KP") - .font(AppFont.paperlogy8ExtraBold(size: 18)) + .font(AppFont.paperlogy8ExtraBold(size: 14)) .foregroundStyle(.black) } + .shadow( + color: .black.opacity(0.25), + radius: MyCollectionDiaryShareImageLayout.appIconShadowRadius, + x: 0, + y: MyCollectionDiaryShareImageLayout.appIconShadowY + ) + .overlay { + RoundedRectangle( + cornerRadius: MyCollectionDiaryShareImageLayout.appIconCornerRadius, + style: .continuous + ) + .stroke(.black, lineWidth: MyCollectionDiaryShareImageLayout.appIconStrokeWidth) + } } } } diff --git a/KillingPart/Views/Screens/Main/My/MyCollection/MyKillingPart/[diaryId]/components/MyCollectionDiaryTimelineRangeView.swift b/KillingPart/Views/Screens/Main/My/MyCollection/MyKillingPart/[diaryId]/components/MyCollectionDiaryTimelineRangeView.swift index 22e7d44..72fa774 100644 --- a/KillingPart/Views/Screens/Main/My/MyCollection/MyKillingPart/[diaryId]/components/MyCollectionDiaryTimelineRangeView.swift +++ b/KillingPart/Views/Screens/Main/My/MyCollection/MyKillingPart/[diaryId]/components/MyCollectionDiaryTimelineRangeView.swift @@ -17,6 +17,7 @@ struct MyCollectionDiaryTimelineRangeView: View { var segmentColor: Color = AppColors.primary600.opacity(0.95) var startLabelColor: Color = AppColors.primary600.opacity(0.98) var endLabelColor: Color = AppColors.primary600.opacity(0.9) + var usesThinLabelFont = false var body: some View { GeometryReader { proxy in @@ -61,13 +62,21 @@ struct MyCollectionDiaryTimelineRangeView: View { .offset(x: startX, y: 3) Text(startMinuteSecondText) - .font(AppFont.paperlogy6SemiBold(size: startFontSize)) + .font( + usesThinLabelFont + ? AppFont.paperlogy3Light(size: startFontSize) + : AppFont.paperlogy6SemiBold(size: startFontSize) + ) .foregroundStyle(startLabelColor) .frame(width: labelWidth, alignment: .center) .position(x: startLabelX, y: labelY) Text(endMinuteSecondText) - .font(AppFont.paperlogy5Medium(size: endFontSize)) + .font( + usesThinLabelFont + ? AppFont.paperlogy3Light(size: endFontSize) + : AppFont.paperlogy5Medium(size: endFontSize) + ) .foregroundStyle(endLabelColor) .frame(width: labelWidth, alignment: .center) .position(x: endLabelX, y: labelY) diff --git a/KillingPart/Views/Screens/Main/My/MyCollection/StoreKillingPart/[diaryId]/StoreKillingPartDetail/StoreKillingPartDetail.swift b/KillingPart/Views/Screens/Main/My/MyCollection/StoreKillingPart/[diaryId]/StoreKillingPartDetail/StoreKillingPartDetail.swift index ce3cb1b..ba91194 100644 --- a/KillingPart/Views/Screens/Main/My/MyCollection/StoreKillingPart/[diaryId]/StoreKillingPartDetail/StoreKillingPartDetail.swift +++ b/KillingPart/Views/Screens/Main/My/MyCollection/StoreKillingPart/[diaryId]/StoreKillingPartDetail/StoreKillingPartDetail.swift @@ -44,12 +44,7 @@ struct StoreKillingPartDetail: View { let topContentInset = min(proxy.safeAreaInsets.top, AppSpacing.l) + AppSpacing.s ZStack { - Image("my_background") - .resizable() - .scaledToFill() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .clipped() - .ignoresSafeArea(.container, edges: .all) + KillingPartBackgroundView() ScrollView { VStack(alignment: .leading, spacing: AppSpacing.m) { diff --git a/KillingPart/Views/Screens/Main/My/MyTabView.swift b/KillingPart/Views/Screens/Main/My/MyTabView.swift index 6d2c4ae..4b8d329 100644 --- a/KillingPart/Views/Screens/Main/My/MyTabView.swift +++ b/KillingPart/Views/Screens/Main/My/MyTabView.swift @@ -16,47 +16,35 @@ struct MyTabView: View { var body: some View { NavigationStack { - GeometryReader { geometry in - let bottomContentInset = min(geometry.safeAreaInsets.bottom, AppSpacing.xl) + AppSpacing.l - - ZStack { - Image("my_background") - .resizable() - .scaledToFill() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .clipped() - .ignoresSafeArea() - - VStack(spacing: AppSpacing.m) { - topToggleTabs - - ZStack { - if selectedTab == .myCollection { - MyCollectionView(onSessionEnded: onLogout) - .transition(tabContentTransition) - } else if selectedTab == .playKillingPart { - PlayKillingPartView( - isParentActive: isRootTabSelected && selectedTab == .playKillingPart, - preloadedCollectionViewModel: startupPayload?.playCollectionViewModel - ) - .transition(tabContentTransition) - } else { - MusicCalendarView() - .transition(tabContentTransition) - } + ZStack { + KillingPartBackgroundView() + + VStack(spacing: AppSpacing.m) { + topToggleTabs + + ZStack { + if selectedTab == .myCollection { + MyCollectionView(onSessionEnded: onLogout) + .transition(tabContentTransition) + } else if selectedTab == .playKillingPart { + PlayKillingPartView( + isParentActive: isRootTabSelected && selectedTab == .playKillingPart, + preloadedCollectionViewModel: startupPayload?.playCollectionViewModel + ) + .transition(tabContentTransition) + } else { + MusicCalendarView() + .transition(tabContentTransition) } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .clipped() - .padding(.bottom, bottomContentInset) } - .padding(.horizontal, AppSpacing.m) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .padding(.bottom, bottomContentInset) + .clipped() } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .toolbar(.hidden, for: .navigationBar) - .padding(.bottom, bottomContentInset) + .padding(.horizontal, AppSpacing.m) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .toolbar(.hidden, for: .navigationBar) } .onReceive(NotificationCenter.default.publisher(for: .navigateToPlayKillingPart)) { _ in selectTab(.playKillingPart) diff --git a/KillingPart/Views/Screens/Main/Randoms/RandomSearchView.swift b/KillingPart/Views/Screens/Main/Randoms/RandomSearchView.swift index ba005e2..66b135a 100644 --- a/KillingPart/Views/Screens/Main/Randoms/RandomSearchView.swift +++ b/KillingPart/Views/Screens/Main/Randoms/RandomSearchView.swift @@ -13,60 +13,46 @@ struct RandomSearchView: View { var body: some View { NavigationStack { - GeometryReader { geometry in - let bottomInset = min(geometry.safeAreaInsets.bottom, AppSpacing.xl) + AppSpacing.xl - let extraBottomInset = AppSpacing.xl - - ZStack { - Image("my_background") - .resizable() - .scaledToFill() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .clipped() - .ignoresSafeArea() - - VStack(spacing: 0) { - FeedSectionView( - viewModel: feedViewModel, - isParentActive: isRootTabSelected, - makeCollectionViewModel: { user in - socialViewModel.makeSocialMyCollectionViewModel( - for: resolvedCollectionUser(from: user) - ) - }, - onPageChanged: { oldIndex, newIndex, diaryId in - exploreSwipeCountInSession += 1 - AmplitudeClient.shared.track( - eventType: "explore_feed_card_viewed", - properties: [ - "from_index": oldIndex, - "to_index": newIndex, - "swipe_count_in_session": exploreSwipeCountInSession, - "diary_id": diaryId - ] - ) - } + ZStack { + KillingPartBackgroundView() + + FeedSectionView( + viewModel: feedViewModel, + isParentActive: isRootTabSelected, + makeCollectionViewModel: { user in + socialViewModel.makeSocialMyCollectionViewModel( + for: resolvedCollectionUser(from: user) + ) + }, + onPageChanged: { oldIndex, newIndex, diaryId in + exploreSwipeCountInSession += 1 + AmplitudeClient.shared.track( + eventType: "explore_feed_card_viewed", + properties: [ + "from_index": oldIndex, + "to_index": newIndex, + "swipe_count_in_session": exploreSwipeCountInSession, + "diary_id": diaryId + ] ) } - .padding(.top, AppSpacing.l) - .padding(.horizontal, AppSpacing.m) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .padding(.bottom, bottomInset + extraBottomInset) + ) + .padding(.top, AppSpacing.l) + .padding(.horizontal, AppSpacing.m) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - if isRefreshingFromTabEvent { - Color.black.opacity(0.28) - .ignoresSafeArea() + if isRefreshingFromTabEvent { + Color.black.opacity(0.28) + .ignoresSafeArea() - ProgressView() - .tint(AppColors.primary600) - .padding(AppSpacing.l) - .background(Color.black.opacity(0.6), in: RoundedRectangle(cornerRadius: 12)) - } + ProgressView() + .tint(AppColors.primary600) + .padding(AppSpacing.l) + .background(Color.black.opacity(0.6), in: RoundedRectangle(cornerRadius: 12)) } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .toolbar(.hidden, for: .navigationBar) - .padding(.bottom, bottomInset) } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .toolbar(.hidden, for: .navigationBar) } .task { async let loadFeedTask: Void = feedViewModel.loadInitialDataIfNeeded() diff --git a/KillingPart/Views/Screens/Main/Social/FeedSection/FeedSectionView.swift b/KillingPart/Views/Screens/Main/Social/FeedSection/FeedSectionView.swift index 9cea3e9..e5f73db 100644 --- a/KillingPart/Views/Screens/Main/Social/FeedSection/FeedSectionView.swift +++ b/KillingPart/Views/Screens/Main/Social/FeedSection/FeedSectionView.swift @@ -40,44 +40,40 @@ struct FeedSectionView: View { } var body: some View { - GeometryReader { geometry in - let bottomInset = max(geometry.safeAreaInsets.bottom, AppSpacing.m) + AppSpacing.xl - Group { - if viewModel.isLoadingInitial { - ProgressView() - .tint(AppColors.primary600) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .padding(.top, AppSpacing.xl) - } else if let errorMessage = viewModel.errorMessage, viewModel.feeds.isEmpty { - VStack(spacing: AppSpacing.s) { - Text(errorMessage) - .font(AppFont.paperlogy4Regular(size: 13)) - .foregroundStyle(Color.white.opacity(0.8)) - .multilineTextAlignment(.center) - - Button("다시 시도") { - Task { await viewModel.refresh() } - } - .font(AppFont.paperlogy5Medium(size: 13)) - .foregroundStyle(AppColors.primary600) - } + Group { + if viewModel.isLoadingInitial { + ProgressView() + .tint(AppColors.primary600) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) .padding(.top, AppSpacing.xl) - } else if viewModel.feeds.isEmpty { - Text("표시할 피드가 없어요.") + } else if let errorMessage = viewModel.errorMessage, viewModel.feeds.isEmpty { + VStack(spacing: AppSpacing.s) { + Text(errorMessage) .font(AppFont.paperlogy4Regular(size: 13)) - .foregroundStyle(Color.white.opacity(0.7)) - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .padding(.top, AppSpacing.xl) - } else { - feedPager + .foregroundStyle(Color.white.opacity(0.8)) + .multilineTextAlignment(.center) + + Button("다시 시도") { + Task { await viewModel.refresh() } + } + .font(AppFont.paperlogy5Medium(size: 13)) + .foregroundStyle(AppColors.primary600) } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) + .padding(.top, AppSpacing.xl) + } else if viewModel.feeds.isEmpty { + Text("표시할 피드가 없어요.") + .font(AppFont.paperlogy4Regular(size: 13)) + .foregroundStyle(Color.white.opacity(0.7)) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) + .padding(.top, AppSpacing.xl) + } else { + feedPager } - .padding(.bottom, bottomInset) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .overlay(alignment: .center) { - SocialInteractionFeedbackOverlay(feedback: interactionFeedbackPresenter.activeFeedback) - } + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .overlay(alignment: .center) { + SocialInteractionFeedbackOverlay(feedback: interactionFeedbackPresenter.activeFeedback) } .onAppear { isViewActive = true diff --git a/KillingPart/Views/Screens/Main/Social/FriendsSection/SocialMyCollection/[userId]/[diaryId]/SocialMyCollectionDiary.swift b/KillingPart/Views/Screens/Main/Social/FriendsSection/SocialMyCollection/[userId]/[diaryId]/SocialMyCollectionDiary.swift index 39768e8..b215252 100644 --- a/KillingPart/Views/Screens/Main/Social/FriendsSection/SocialMyCollection/[userId]/[diaryId]/SocialMyCollectionDiary.swift +++ b/KillingPart/Views/Screens/Main/Social/FriendsSection/SocialMyCollection/[userId]/[diaryId]/SocialMyCollectionDiary.swift @@ -55,12 +55,7 @@ struct SocialMyCollectionDiary: View { let bottomContentInset = proxy.safeAreaInsets.bottom + keyboardCompensation + extraBottomInset ZStack { - Image("my_background") - .resizable() - .scaledToFill() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .clipped() - .ignoresSafeArea(.container, edges: .all) + KillingPartBackgroundView() if viewModel.isDeleted { MyCollectionDiaryDeletedPlaceholder() diff --git a/KillingPart/Views/Screens/Main/Social/Notification/NotificationListView.swift b/KillingPart/Views/Screens/Main/Social/Notification/NotificationListView.swift index 5a64ffc..3be9fc7 100644 --- a/KillingPart/Views/Screens/Main/Social/Notification/NotificationListView.swift +++ b/KillingPart/Views/Screens/Main/Social/Notification/NotificationListView.swift @@ -10,10 +10,7 @@ struct NotificationListView: View { let listBottomInset = bottomInset + 56 ZStack { - Image("my_background") - .resizable() - .scaledToFill() - .ignoresSafeArea() + KillingPartBackgroundView() VStack(spacing: 0) { header diff --git a/KillingPart/Views/Screens/Main/Social/SocialView.swift b/KillingPart/Views/Screens/Main/Social/SocialView.swift index 78da78d..0a317e1 100644 --- a/KillingPart/Views/Screens/Main/Social/SocialView.swift +++ b/KillingPart/Views/Screens/Main/Social/SocialView.swift @@ -31,16 +31,10 @@ struct SocialView: View { var body: some View { NavigationStack { GeometryReader { geometry in - let bottomInset = min(geometry.safeAreaInsets.bottom, AppSpacing.xl) + AppSpacing.l let friendsBottomContentInset = max(geometry.safeAreaInsets.bottom, AppSpacing.m) + AppSpacing.xl ZStack { - Image("my_background") - .resizable() - .scaledToFill() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .clipped() - .ignoresSafeArea() + KillingPartBackgroundView() VStack(spacing: AppSpacing.m) { HStack(spacing: AppSpacing.s) { @@ -91,12 +85,10 @@ struct SocialView: View { } .padding(.horizontal, AppSpacing.m) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) - .padding(.bottom, bottomInset) .background(navigationLinks) } .frame(maxWidth: .infinity, maxHeight: .infinity) .toolbar(.hidden, for: .navigationBar) - .padding(.bottom, bottomInset) } } .task(id: selectedTopTab) { @@ -144,7 +136,11 @@ struct SocialView: View { private var navigationLinks: some View { ZStack { notificationNavigationLink - alarmRouteNavigationLinks + + // NotificationListView owns these routes while it is on the stack. + if !isNotificationListActive { + alarmRouteNavigationLinks + } } } diff --git a/KillingPart/Views/Screens/Setup/components/TutorialDiaryDetailScreen.swift b/KillingPart/Views/Screens/Setup/components/TutorialDiaryDetailScreen.swift index 631e449..bc3408a 100644 --- a/KillingPart/Views/Screens/Setup/components/TutorialDiaryDetailScreen.swift +++ b/KillingPart/Views/Screens/Setup/components/TutorialDiaryDetailScreen.swift @@ -138,10 +138,7 @@ struct TutorialDiaryDetailScreen: View { } .scrollIndicators(.hidden) .background( - Image("my_background") - .resizable() - .scaledToFill() - .ignoresSafeArea() + KillingPartBackgroundView() ) .safeAreaInset(edge: .top, spacing: 0) { HStack { diff --git a/KillingPart/Views/Screens/Setup/components/TutorialHomeScreen.swift b/KillingPart/Views/Screens/Setup/components/TutorialHomeScreen.swift index e883105..ff227f4 100644 --- a/KillingPart/Views/Screens/Setup/components/TutorialHomeScreen.swift +++ b/KillingPart/Views/Screens/Setup/components/TutorialHomeScreen.swift @@ -43,10 +43,7 @@ struct TutorialHomeScreen: View { .padding(.top, AppSpacing.l) .padding(.bottom, AppSpacing.l) .background( - Image("my_background") - .resizable() - .scaledToFill() - .ignoresSafeArea() + KillingPartBackgroundView() ) .task { await viewModel.loadTutorialHomeData() diff --git a/KillingPart/Views/Screens/Splash/SplashView.swift b/KillingPart/Views/Screens/Splash/SplashView.swift index d417bf6..f2a017c 100644 --- a/KillingPart/Views/Screens/Splash/SplashView.swift +++ b/KillingPart/Views/Screens/Splash/SplashView.swift @@ -103,7 +103,7 @@ private final class SplashVideoPlayer: ObservableObject { private let fallbackDuration: TimeInterval = 1.8 init() { - guard let videoURL = Bundle.main.url(forResource: "sc 9-16", withExtension: "mp4") else { + guard let videoURL = Bundle.main.url(forResource: "sc 9-16 v2", withExtension: "mp4") else { isConfigured = false return }