From 9154c48b1749dd80739842e85b5a459d63f7eb03 Mon Sep 17 00:00:00 2001 From: forgotvas Date: Tue, 23 Jan 2024 13:40:05 +0300 Subject: [PATCH 1/6] fix: arrow buttons in unit content set next and prev buttons in unit content to be horizontal if COURSE_UNIT_PROGRESS_ENABLED key is true --- Core/Core/View/Base/UnitButtonView.swift | 23 ++- .../Unit/CourseNavigationView.swift | 177 +++++++++++------- 2 files changed, 124 insertions(+), 76 deletions(-) diff --git a/Core/Core/View/Base/UnitButtonView.swift b/Core/Core/View/Base/UnitButtonView.swift index 99f1f34d6..d716051c9 100644 --- a/Core/Core/View/Base/UnitButtonView.swift +++ b/Core/Core/View/Base/UnitButtonView.swift @@ -49,11 +49,26 @@ public struct UnitButtonView: View { private let action: () -> Void private let type: UnitButtonType private let bgColor: Color? + private let isVerticalNavigation: Bool - public init(type: UnitButtonType, bgColor: Color? = nil, action: @escaping () -> Void) { + private var nextButtonDegrees: Double { + isVerticalNavigation ? -90 : 180 + } + + private var prevButtonDegrees: Double { + isVerticalNavigation ? 90 : 0 + } + + public init( + type: UnitButtonType, + isVerticalNavigation: Bool = true, + bgColor: Color? = nil, + action: @escaping () -> Void + ) { self.type = type self.bgColor = bgColor self.action = action + self.isVerticalNavigation = isVerticalNavigation } public var body: some View { @@ -68,7 +83,7 @@ public struct UnitButtonView: View { .font(Theme.Fonts.labelLarge) CoreAssets.arrowLeft.swiftUIImage.renderingMode(.template) .foregroundColor(Theme.Colors.styledButtonText) - .rotationEffect(Angle.degrees(-90)) + .rotationEffect(Angle.degrees(nextButtonDegrees)) }.padding(.horizontal, 16) case .next, .nextBig: HStack { @@ -81,7 +96,7 @@ public struct UnitButtonView: View { } CoreAssets.arrowLeft.swiftUIImage.renderingMode(.template) .foregroundColor(Theme.Colors.styledButtonText) - .rotationEffect(Angle.degrees(-90)) + .rotationEffect(Angle.degrees(nextButtonDegrees)) .padding(.trailing, 20) } case .previous: @@ -91,7 +106,7 @@ public struct UnitButtonView: View { .font(Theme.Fonts.labelLarge) .padding(.leading, 20) CoreAssets.arrowLeft.swiftUIImage.renderingMode(.template) - .rotationEffect(Angle.degrees(90)) + .rotationEffect(Angle.degrees(prevButtonDegrees)) .padding(.trailing, 20) .foregroundColor(Theme.Colors.accentColor) diff --git a/Course/Course/Presentation/Unit/CourseNavigationView.swift b/Course/Course/Presentation/Unit/CourseNavigationView.swift index d84595ab8..6d86d3660 100644 --- a/Course/Course/Presentation/Unit/CourseNavigationView.swift +++ b/Course/Course/Presentation/Unit/CourseNavigationView.swift @@ -30,10 +30,8 @@ struct CourseNavigationView: View { HStack(alignment: .top, spacing: 7) { if viewModel.selectedLesson() == viewModel.verticals[viewModel.verticalIndex].childs.first && viewModel.verticals[viewModel.verticalIndex].childs.count != 1 { - UnitButtonView(type: .nextBig, action: { - playerStateSubject.send(VideoPlayerState.pause) - viewModel.select(move: .next) - }).frame(width: 215) + nextBigButton + .frame(width: 215) } else { if viewModel.selectedLesson() == viewModel.verticals[viewModel.verticalIndex].childs.last { if viewModel.selectedLesson() != viewModel.verticals[viewModel.verticalIndex].childs.first { @@ -42,82 +40,117 @@ struct CourseNavigationView: View { viewModel.select(move: .previous) }) } - UnitButtonView(type: .last, action: { - let currentVertical = viewModel.verticals[viewModel.verticalIndex] - - viewModel.router.presentAlert( - alertTitle: CourseLocalization.Courseware.goodWork, - alertMessage: (CourseLocalization.Courseware.section - + currentVertical.displayName + CourseLocalization.Courseware.isFinished), - nextSectionName: { - if let data = viewModel.nextData, - let vertical = viewModel.vertical(for: data) { - return vertical.displayName - } - return nil - }(), - action: CourseLocalization.Courseware.backToOutline, - image: CoreAssets.goodWork.swiftUIImage, - onCloseTapped: { viewModel.router.dismiss(animated: false) }, - okTapped: { - playerStateSubject.send(VideoPlayerState.pause) - playerStateSubject.send(VideoPlayerState.kill) - - viewModel.trackFinishVerticalBackToOutlineClicked() - viewModel.router.dismiss(animated: false) - viewModel.router.back(animated: true) - }, - nextSectionTapped: { - playerStateSubject.send(VideoPlayerState.pause) - playerStateSubject.send(VideoPlayerState.kill) - viewModel.router.dismiss(animated: false) - - viewModel.analytics - .finishVerticalNextSectionClicked( - courseId: viewModel.courseID, - courseName: viewModel.courseName, - blockId: viewModel.selectedLesson().blockId, - blockName: viewModel.selectedLesson().displayName - ) - - guard let data = viewModel.nextData else { return } - viewModel.router.replaceCourseUnit( - courseName: viewModel.courseName, - blockId: viewModel.lessonID, - courseID: viewModel.courseID, - sectionName: viewModel.selectedLesson().displayName, - verticalIndex: data.verticalIndex, - chapters: viewModel.chapters, - chapterIndex: data.chapterIndex, - sequentialIndex: data.sequentialIndex, - animated: true - ) - } - ) - playerStateSubject.send(VideoPlayerState.pause) - viewModel.analytics.finishVerticalClicked( - courseId: viewModel.courseID, - courseName: viewModel.courseName, - blockId: viewModel.selectedLesson().blockId, - blockName: viewModel.selectedLesson().displayName - ) - }) + lastButton } else { if viewModel.selectedLesson() != viewModel.verticals[viewModel.verticalIndex].childs.first { - UnitButtonView(type: .previous, action: { - playerStateSubject.send(VideoPlayerState.pause) - viewModel.select(move: .previous) - }) + prevButton } - UnitButtonView(type: .next, action: { - playerStateSubject.send(VideoPlayerState.pause) - viewModel.select(move: .next) - }) + nextButton } } }.padding(.horizontal, 24) } + + private var nextBigButton: some View { + UnitButtonView( + type: .nextBig, + isVerticalNavigation: !viewModel.courseUnitProgressEnabled, + action: { + playerStateSubject.send(VideoPlayerState.pause) + viewModel.select(move: .next) + } + ) + } + + private var nextButton: some View { + UnitButtonView( + type: .next, + isVerticalNavigation: !viewModel.courseUnitProgressEnabled, + action: { + playerStateSubject.send(VideoPlayerState.pause) + viewModel.select(move: .next) + } + ) + } + + private var prevButton: some View { + UnitButtonView( + type: .previous, + isVerticalNavigation: !viewModel.courseUnitProgressEnabled, + action: { + playerStateSubject.send(VideoPlayerState.pause) + viewModel.select(move: .previous) + } + ) + } + + private var lastButton: some View { + UnitButtonView( + type: .last, + isVerticalNavigation: !viewModel.courseUnitProgressEnabled, + action: { + let currentVertical = viewModel.verticals[viewModel.verticalIndex] + + viewModel.router.presentAlert( + alertTitle: CourseLocalization.Courseware.goodWork, + alertMessage: (CourseLocalization.Courseware.section + + currentVertical.displayName + CourseLocalization.Courseware.isFinished), + nextSectionName: { + if let data = viewModel.nextData, + let vertical = viewModel.vertical(for: data) { + return vertical.displayName + } + return nil + }(), + action: CourseLocalization.Courseware.backToOutline, + image: CoreAssets.goodWork.swiftUIImage, + onCloseTapped: { viewModel.router.dismiss(animated: false) }, + okTapped: { + playerStateSubject.send(VideoPlayerState.pause) + playerStateSubject.send(VideoPlayerState.kill) + + viewModel.trackFinishVerticalBackToOutlineClicked() + viewModel.router.dismiss(animated: false) + viewModel.router.back(animated: true) + }, + nextSectionTapped: { + playerStateSubject.send(VideoPlayerState.pause) + playerStateSubject.send(VideoPlayerState.kill) + viewModel.router.dismiss(animated: false) + + viewModel.analytics + .finishVerticalNextSectionClicked( + courseId: viewModel.courseID, + courseName: viewModel.courseName, + blockId: viewModel.selectedLesson().blockId, + blockName: viewModel.selectedLesson().displayName + ) + + guard let data = viewModel.nextData else { return } + viewModel.router.replaceCourseUnit( + courseName: viewModel.courseName, + blockId: viewModel.lessonID, + courseID: viewModel.courseID, + sectionName: viewModel.selectedLesson().displayName, + verticalIndex: data.verticalIndex, + chapters: viewModel.chapters, + chapterIndex: data.chapterIndex, + sequentialIndex: data.sequentialIndex, + animated: true + ) + } + ) + playerStateSubject.send(VideoPlayerState.pause) + viewModel.analytics.finishVerticalClicked( + courseId: viewModel.courseID, + courseName: viewModel.courseName, + blockId: viewModel.selectedLesson().blockId, + blockName: viewModel.selectedLesson().displayName + ) + } + ) + } } #if DEBUG From ee9723e6b32e745e907611ccd55b46b03ab7da75 Mon Sep 17 00:00:00 2001 From: forgotvas Date: Tue, 23 Jan 2024 13:54:04 +0300 Subject: [PATCH 2/6] fix: animation set next and prev animation in unit content to be horizontal if COURSE_UNIT_PROGRESS_ENABLED key is true --- .../Unit/CourseNavigationView.swift | 7 ++--- .../Presentation/Unit/CourseUnitView.swift | 26 ++++++++++++------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/Course/Course/Presentation/Unit/CourseNavigationView.swift b/Course/Course/Presentation/Unit/CourseNavigationView.swift index 6d86d3660..46a26d870 100644 --- a/Course/Course/Presentation/Unit/CourseNavigationView.swift +++ b/Course/Course/Presentation/Unit/CourseNavigationView.swift @@ -35,10 +35,7 @@ struct CourseNavigationView: View { } else { if viewModel.selectedLesson() == viewModel.verticals[viewModel.verticalIndex].childs.last { if viewModel.selectedLesson() != viewModel.verticals[viewModel.verticalIndex].childs.first { - UnitButtonView(type: .previous, action: { - playerStateSubject.send(VideoPlayerState.pause) - viewModel.select(move: .previous) - }) + prevButton } lastButton } else { @@ -82,7 +79,7 @@ struct CourseNavigationView: View { playerStateSubject.send(VideoPlayerState.pause) viewModel.select(move: .previous) } - ) + ) } private var lastButton: some View { diff --git a/Course/Course/Presentation/Unit/CourseUnitView.swift b/Course/Course/Presentation/Unit/CourseUnitView.swift index 3ec30cb81..c1b79d46f 100644 --- a/Course/Course/Presentation/Unit/CourseUnitView.swift +++ b/Course/Course/Presentation/Unit/CourseUnitView.swift @@ -27,7 +27,7 @@ public struct CourseUnitView: View { } } } - @State var offsetView: CGFloat = 0 + @State var offsetView: CGPoint = .zero @State var showDiscussion: Bool = false @Environment(\.isPresented) private var isPresented @Environment(\.isHorizontal) private var isHorizontal @@ -159,7 +159,7 @@ public struct CourseUnitView: View { // swiftlint:disable function_body_length private func content(reader: GeometryProxy) -> some View { - LazyVStack(alignment: .leading, spacing: 0) { + LazyHStack(alignment: .top, spacing: 0) { let data = Array(viewModel.verticals[viewModel.verticalIndex].childs.enumerated()) ForEach(data, id: \.offset) { index, block in VStack(spacing: 0) { @@ -282,32 +282,32 @@ public struct CourseUnitView: View { .id(index) } } - .offset(y: offsetView) + .offset(x: offsetView.x, y: offsetView.y) .clipped() .onAppear { - offsetView = -(reader.size.height * CGFloat(viewModel.index)) + offsetView = viewOffset(for: viewModel.index, with: reader.size) } .onAppear { NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { _ in - offsetView = -(reader.size.height * CGFloat(viewModel.index)) + offsetView = viewOffset(for: viewModel.index, with: reader.size) } NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidShowNotification, object: nil, queue: .main) { _ in - offsetView = -(reader.size.height * CGFloat(viewModel.index)) + offsetView = viewOffset(for: viewModel.index, with: reader.size) } NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidHideNotification, object: nil, queue: .main) { _ in - offsetView = -(reader.size.height * CGFloat(viewModel.index)) + offsetView = viewOffset(for: viewModel.index, with: reader.size) } } .onChange(of: UIDevice.current.orientation, perform: { _ in - offsetView = -(reader.size.height * CGFloat(viewModel.index)) + offsetView = viewOffset(for: viewModel.index, with: reader.size) }) .onChange(of: viewModel.verticalIndex, perform: { index in DispatchQueue.main.async { withAnimation(Animation.easeInOut(duration: 0.2)) { - offsetView = -(reader.size.height * CGFloat(index)) + offsetView = viewOffset(for: index, with: reader.size) } } @@ -315,7 +315,7 @@ public struct CourseUnitView: View { .onChange(of: viewModel.index, perform: { index in DispatchQueue.main.async { withAnimation(Animation.easeInOut(duration: 0.2)) { - offsetView = -(reader.size.height * CGFloat(index)) + offsetView = viewOffset(for: index, with: reader.size) DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { showDiscussion = viewModel.selectedLesson().type == .discussion } @@ -336,6 +336,12 @@ public struct CourseUnitView: View { } // swiftlint:enable function_body_length + private func viewOffset(for index: Int, with size: CGSize) -> CGPoint { + let x: CGFloat = viewModel.courseUnitProgressEnabled ? -(size.width * CGFloat(index)) : 0 + let y: CGFloat = viewModel.courseUnitProgressEnabled ? 0 : -(size.height * CGFloat(index)) + return CGPoint(x: x, y: y) + } + private func dropdown(block: CourseBlock) -> some View { HStack { if block.type == .video { From 5cf01978dc40563b4b874445d2a4ee5fe03e4b6a Mon Sep 17 00:00:00 2001 From: forgotvas Date: Tue, 23 Jan 2024 14:17:08 +0300 Subject: [PATCH 3/6] fix: different navigation stacks --- Course/Course.xcodeproj/project.pbxproj | 8 ++- .../Presentation/Unit/CourseUnitView.swift | 4 +- .../Unit/Subviews/UnitStack.swift | 69 +++++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 Course/Course/Presentation/Unit/Subviews/UnitStack.swift diff --git a/Course/Course.xcodeproj/project.pbxproj b/Course/Course.xcodeproj/project.pbxproj index da3f8d6f0..d59f57105 100644 --- a/Course/Course.xcodeproj/project.pbxproj +++ b/Course/Course.xcodeproj/project.pbxproj @@ -58,6 +58,7 @@ 02F78AEB29E6BCA20038DE30 /* VideoPlayerViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F78AEA29E6BCA20038DE30 /* VideoPlayerViewModelTests.swift */; }; 02F98A8128F8224200DE94C0 /* Discussion.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02F98A8028F8224200DE94C0 /* Discussion.framework */; }; 02FFAD0D29E4347300140E46 /* VideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02FFAD0C29E4347300140E46 /* VideoPlayerViewModel.swift */; }; + 060E8BCA2B5FD68C0080C952 /* UnitStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 060E8BC92B5FD68C0080C952 /* UnitStack.swift */; }; 068DDA5F2B1E198700FF8CCB /* CourseUnitDropDownList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 068DDA5B2B1E198700FF8CCB /* CourseUnitDropDownList.swift */; }; 068DDA602B1E198700FF8CCB /* CourseUnitVerticalsDropdownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 068DDA5C2B1E198700FF8CCB /* CourseUnitVerticalsDropdownView.swift */; }; 068DDA612B1E198700FF8CCB /* CourseUnitDropDownCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 068DDA5D2B1E198700FF8CCB /* CourseUnitDropDownCell.swift */; }; @@ -70,8 +71,8 @@ 0766DFD0299AB29000EBEF6A /* PlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0766DFCF299AB29000EBEF6A /* PlayerViewController.swift */; }; 197FB8EA8F92F00A8F383D82 /* Pods_App_Course.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E5E795BD160CDA7D9C120DE6 /* Pods_App_Course.framework */; }; B8F50317B6B830A0E520C954 /* Pods_App_Course_CourseTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50E59D2B81E12610964282C5 /* Pods_App_Course_CourseTests.framework */; }; - BAD9CA2D2B2736BB00DE790A /* LessonLineProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAD9CA2C2B2736BB00DE790A /* LessonLineProgressView.swift */; }; BAAD62C82AFD00EE000E6103 /* CourseExpandableContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAAD62C72AFD00EE000E6103 /* CourseExpandableContentView.swift */; }; + BAD9CA2D2B2736BB00DE790A /* LessonLineProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAD9CA2C2B2736BB00DE790A /* LessonLineProgressView.swift */; }; DB205BFB2AE81B1200136EC2 /* CourseDateViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB205BFA2AE81B1200136EC2 /* CourseDateViewModelTests.swift */; }; DB7D6EAC2ADFCAC50036BB13 /* CourseDatesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7D6EAB2ADFCAC40036BB13 /* CourseDatesView.swift */; }; DB7D6EAE2ADFCB4A0036BB13 /* CourseDatesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB7D6EAD2ADFCB4A0036BB13 /* CourseDatesViewModel.swift */; }; @@ -143,6 +144,7 @@ 02F78AEA29E6BCA20038DE30 /* VideoPlayerViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = VideoPlayerViewModelTests.swift; path = CourseTests/Presentation/Unit/VideoPlayerViewModelTests.swift; sourceTree = SOURCE_ROOT; }; 02F98A8028F8224200DE94C0 /* Discussion.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Discussion.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 02FFAD0C29E4347300140E46 /* VideoPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewModel.swift; sourceTree = ""; }; + 060E8BC92B5FD68C0080C952 /* UnitStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitStack.swift; sourceTree = ""; }; 068DDA5B2B1E198700FF8CCB /* CourseUnitDropDownList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseUnitDropDownList.swift; sourceTree = ""; }; 068DDA5C2B1E198700FF8CCB /* CourseUnitVerticalsDropdownView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseUnitVerticalsDropdownView.swift; sourceTree = ""; }; 068DDA5D2B1E198700FF8CCB /* CourseUnitDropDownCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseUnitDropDownCell.swift; sourceTree = ""; }; @@ -169,8 +171,8 @@ A47C63D9EB0D866F303D4588 /* Pods-App-Course.releasestage.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Course.releasestage.xcconfig"; path = "Target Support Files/Pods-App-Course/Pods-App-Course.releasestage.xcconfig"; sourceTree = ""; }; ADC2A1B8183A674705F5F7E2 /* Pods-App-Course.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Course.debug.xcconfig"; path = "Target Support Files/Pods-App-Course/Pods-App-Course.debug.xcconfig"; sourceTree = ""; }; B196A14555D0E006995A5683 /* Pods-App-CourseDetails.releaseprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-CourseDetails.releaseprod.xcconfig"; path = "Target Support Files/Pods-App-CourseDetails/Pods-App-CourseDetails.releaseprod.xcconfig"; sourceTree = ""; }; - BAD9CA2C2B2736BB00DE790A /* LessonLineProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonLineProgressView.swift; sourceTree = ""; }; BAAD62C72AFD00EE000E6103 /* CourseExpandableContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseExpandableContentView.swift; sourceTree = ""; }; + BAD9CA2C2B2736BB00DE790A /* LessonLineProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LessonLineProgressView.swift; sourceTree = ""; }; DB205BFA2AE81B1200136EC2 /* CourseDateViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDateViewModelTests.swift; sourceTree = ""; }; DB7D6EAB2ADFCAC40036BB13 /* CourseDatesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CourseDatesView.swift; sourceTree = ""; }; DB7D6EAD2ADFCB4A0036BB13 /* CourseDatesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDatesViewModel.swift; sourceTree = ""; }; @@ -238,6 +240,7 @@ 02454CA72A2619890043052A /* DiscussionView.swift */, 02454CA92A2619B40043052A /* LessonProgressView.swift */, BAD9CA2C2B2736BB00DE790A /* LessonLineProgressView.swift */, + 060E8BC92B5FD68C0080C952 /* UnitStack.swift */, ); path = Subviews; sourceTree = ""; @@ -766,6 +769,7 @@ DB7D6EB02ADFDA0E0036BB13 /* CourseDates.swift in Sources */, BAD9CA2D2B2736BB00DE790A /* LessonLineProgressView.swift in Sources */, 02E685C028E4B629000AE015 /* CourseDetailsViewModel.swift in Sources */, + 060E8BCA2B5FD68C0080C952 /* UnitStack.swift in Sources */, 0295C889299BBE8200ABE571 /* CourseNavigationView.swift in Sources */, 06FD7EDF2B1F29F3008D632B /* CourseVerticalImageView.swift in Sources */, DB7D6EAE2ADFCB4A0036BB13 /* CourseDatesViewModel.swift in Sources */, diff --git a/Course/Course/Presentation/Unit/CourseUnitView.swift b/Course/Course/Presentation/Unit/CourseUnitView.swift index c1b79d46f..652acdd6f 100644 --- a/Course/Course/Presentation/Unit/CourseUnitView.swift +++ b/Course/Course/Presentation/Unit/CourseUnitView.swift @@ -158,8 +158,10 @@ public struct CourseUnitView: View { } // swiftlint:disable function_body_length + @ViewBuilder private func content(reader: GeometryProxy) -> some View { - LazyHStack(alignment: .top, spacing: 0) { + let alignment = UnitAlignment(horizontalAlignment: .top, verticalAlignment: .leading) + UnitStack(isVerticalNavigation: !viewModel.courseUnitProgressEnabled, alignment: alignment, spacing: 0) { let data = Array(viewModel.verticals[viewModel.verticalIndex].childs.enumerated()) ForEach(data, id: \.offset) { index, block in VStack(spacing: 0) { diff --git a/Course/Course/Presentation/Unit/Subviews/UnitStack.swift b/Course/Course/Presentation/Unit/Subviews/UnitStack.swift new file mode 100644 index 000000000..a1cfb5959 --- /dev/null +++ b/Course/Course/Presentation/Unit/Subviews/UnitStack.swift @@ -0,0 +1,69 @@ +// +// UnitStack.swift +// Course +// +// Created by Vadim Kuznetsov on 23.01.24. +// + +import SwiftUI + +public struct UnitAlignment: Equatable { + var horizontalAlignment: VerticalAlignment + var verticalAlignment: HorizontalAlignment +} + +public struct UnitStack: View where Content: View { + let alignment: UnitAlignment + let spacing: CGFloat? + let pinnedViews: PinnedScrollableViews + let content: () -> Content + var isVerticalNavigation: Bool + + public init( + isVerticalNavigation: Bool, + alignment: UnitAlignment, + spacing: CGFloat? = nil, + pinnedViews: PinnedScrollableViews = .init(), + @ViewBuilder content: @escaping () -> Content + ) { + self.isVerticalNavigation = isVerticalNavigation + self.alignment = alignment + self.spacing = spacing + self.pinnedViews = pinnedViews + self.content = content + } + + public var body: some View { + if isVerticalNavigation { + LazyVStack( + alignment: alignment.verticalAlignment, + spacing: spacing, + pinnedViews: pinnedViews, + content: content + ) + } else { + LazyHStack( + alignment: alignment.horizontalAlignment, + spacing: spacing, + pinnedViews: pinnedViews, + content: content + ) + } + } +} +#if DEBUG +#Preview { + VStack { + let alignment = UnitAlignment(horizontalAlignment: .top, verticalAlignment: .leading) + UnitStack(isVerticalNavigation: true, alignment: alignment) { + Text("First element") + Text("Second element") + } + Divider() + UnitStack(isVerticalNavigation: false, alignment: alignment) { + Text("First element") + Text("Second element") + } + } +} +#endif From 1a3164605cbc45c103cee17141b5d643a3cff7d3 Mon Sep 17 00:00:00 2001 From: forgotvas Date: Tue, 23 Jan 2024 15:57:34 +0300 Subject: [PATCH 4/6] fix: offset in landscape mode --- .../Presentation/Unit/CourseUnitView.swift | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Course/Course/Presentation/Unit/CourseUnitView.swift b/Course/Course/Presentation/Unit/CourseUnitView.swift index 652acdd6f..e952a7d05 100644 --- a/Course/Course/Presentation/Unit/CourseUnitView.swift +++ b/Course/Course/Presentation/Unit/CourseUnitView.swift @@ -281,35 +281,36 @@ public struct CourseUnitView: View { width: isHorizontal ? reader.size.width - 16 : reader.size.width, height: reader.size.height ) + .padding(.trailing, isHorizontal ? reader.safeAreaInsets.trailing + 16 : 0) .id(index) } } .offset(x: offsetView.x, y: offsetView.y) .clipped() .onAppear { - offsetView = viewOffset(for: viewModel.index, with: reader.size) + offsetView = viewOffset(for: viewModel.index, with: reader.size, insets: reader.safeAreaInsets) } .onAppear { NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { _ in - offsetView = viewOffset(for: viewModel.index, with: reader.size) + offsetView = viewOffset(for: viewModel.index, with: reader.size, insets: reader.safeAreaInsets) } NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidShowNotification, object: nil, queue: .main) { _ in - offsetView = viewOffset(for: viewModel.index, with: reader.size) + offsetView = viewOffset(for: viewModel.index, with: reader.size, insets: reader.safeAreaInsets) } NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidHideNotification, object: nil, queue: .main) { _ in - offsetView = viewOffset(for: viewModel.index, with: reader.size) + offsetView = viewOffset(for: viewModel.index, with: reader.size, insets: reader.safeAreaInsets) } } .onChange(of: UIDevice.current.orientation, perform: { _ in - offsetView = viewOffset(for: viewModel.index, with: reader.size) + offsetView = viewOffset(for: viewModel.index, with: reader.size, insets: reader.safeAreaInsets) }) .onChange(of: viewModel.verticalIndex, perform: { index in DispatchQueue.main.async { withAnimation(Animation.easeInOut(duration: 0.2)) { - offsetView = viewOffset(for: index, with: reader.size) + offsetView = viewOffset(for: index, with: reader.size, insets: reader.safeAreaInsets) } } @@ -317,7 +318,7 @@ public struct CourseUnitView: View { .onChange(of: viewModel.index, perform: { index in DispatchQueue.main.async { withAnimation(Animation.easeInOut(duration: 0.2)) { - offsetView = viewOffset(for: index, with: reader.size) + offsetView = viewOffset(for: index, with: reader.size, insets: reader.safeAreaInsets) DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { showDiscussion = viewModel.selectedLesson().type == .discussion } @@ -338,8 +339,8 @@ public struct CourseUnitView: View { } // swiftlint:enable function_body_length - private func viewOffset(for index: Int, with size: CGSize) -> CGPoint { - let x: CGFloat = viewModel.courseUnitProgressEnabled ? -(size.width * CGFloat(index)) : 0 + private func viewOffset(for index: Int, with size: CGSize, insets: EdgeInsets) -> CGPoint { + let x: CGFloat = viewModel.courseUnitProgressEnabled ? -(size.width * CGFloat(index) + (isHorizontal ? insets.trailing * CGFloat(index) : 0) ) : 0 let y: CGFloat = viewModel.courseUnitProgressEnabled ? 0 : -(size.height * CGFloat(index)) return CGPoint(x: x, y: y) } From 7f3c4a25bf877671624810d4b991dad0331ace45 Mon Sep 17 00:00:00 2001 From: forgotvas Date: Tue, 23 Jan 2024 16:15:32 +0300 Subject: [PATCH 5/6] fix: offset on orientation change --- .../Presentation/Unit/CourseUnitView.swift | 46 ++++--------------- 1 file changed, 8 insertions(+), 38 deletions(-) diff --git a/Course/Course/Presentation/Unit/CourseUnitView.swift b/Course/Course/Presentation/Unit/CourseUnitView.swift index e952a7d05..d8d8aecd0 100644 --- a/Course/Course/Presentation/Unit/CourseUnitView.swift +++ b/Course/Course/Presentation/Unit/CourseUnitView.swift @@ -161,6 +161,7 @@ public struct CourseUnitView: View { @ViewBuilder private func content(reader: GeometryProxy) -> some View { let alignment = UnitAlignment(horizontalAlignment: .top, verticalAlignment: .leading) + let offset = viewOffset(for: viewModel.index, with: reader.size, insets: reader.safeAreaInsets) UnitStack(isVerticalNavigation: !viewModel.courseUnitProgressEnabled, alignment: alignment, spacing: 0) { let data = Array(viewModel.verticals[viewModel.verticalIndex].childs.enumerated()) ForEach(data, id: \.offset) { index, block in @@ -285,47 +286,15 @@ public struct CourseUnitView: View { .id(index) } } - .offset(x: offsetView.x, y: offsetView.y) + .offset(x: offset.x, y: offset.y) + .animation(.easeInOut(duration: 0.2), value: viewModel.index) .clipped() - .onAppear { - offsetView = viewOffset(for: viewModel.index, with: reader.size, insets: reader.safeAreaInsets) - } - .onAppear { - NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, - object: nil, queue: .main) { _ in - offsetView = viewOffset(for: viewModel.index, with: reader.size, insets: reader.safeAreaInsets) - } - NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidShowNotification, - object: nil, queue: .main) { _ in - offsetView = viewOffset(for: viewModel.index, with: reader.size, insets: reader.safeAreaInsets) - } - NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidHideNotification, - object: nil, queue: .main) { _ in - offsetView = viewOffset(for: viewModel.index, with: reader.size, insets: reader.safeAreaInsets) - } - } - .onChange(of: UIDevice.current.orientation, perform: { _ in - offsetView = viewOffset(for: viewModel.index, with: reader.size, insets: reader.safeAreaInsets) - }) - .onChange(of: viewModel.verticalIndex, perform: { index in - DispatchQueue.main.async { - withAnimation(Animation.easeInOut(duration: 0.2)) { - offsetView = viewOffset(for: index, with: reader.size, insets: reader.safeAreaInsets) - } - } - - }) .onChange(of: viewModel.index, perform: { index in - DispatchQueue.main.async { - withAnimation(Animation.easeInOut(duration: 0.2)) { - offsetView = viewOffset(for: index, with: reader.size, insets: reader.safeAreaInsets) - DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { - showDiscussion = viewModel.selectedLesson().type == .discussion - } - } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + showDiscussion = viewModel.selectedLesson().type == .discussion } - }) + .onReceive( NotificationCenter.default.publisher( for: NSNotification.blockCompletion @@ -340,7 +309,8 @@ public struct CourseUnitView: View { // swiftlint:enable function_body_length private func viewOffset(for index: Int, with size: CGSize, insets: EdgeInsets) -> CGPoint { - let x: CGFloat = viewModel.courseUnitProgressEnabled ? -(size.width * CGFloat(index) + (isHorizontal ? insets.trailing * CGFloat(index) : 0) ) : 0 + let rightInset = (isHorizontal ? insets.trailing * CGFloat(index) : 0) + let x: CGFloat = viewModel.courseUnitProgressEnabled ? -(size.width * CGFloat(index) + rightInset) : 0 let y: CGFloat = viewModel.courseUnitProgressEnabled ? 0 : -(size.height * CGFloat(index)) return CGPoint(x: x, y: y) } From bfd5507295878c7ee38d318bcb0df930249d64ef Mon Sep 17 00:00:00 2001 From: forgotvas Date: Tue, 23 Jan 2024 18:03:37 +0300 Subject: [PATCH 6/6] fix: unit tests --- .../Presentation/Unit/CourseUnitViewModelTests.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Course/CourseTests/Presentation/Unit/CourseUnitViewModelTests.swift b/Course/CourseTests/Presentation/Unit/CourseUnitViewModelTests.swift index 25d731196..611d48d3d 100644 --- a/Course/CourseTests/Presentation/Unit/CourseUnitViewModelTests.swift +++ b/Course/CourseTests/Presentation/Unit/CourseUnitViewModelTests.swift @@ -13,7 +13,8 @@ import Alamofire import SwiftUI final class CourseUnitViewModelTests: XCTestCase { - + var config = Config() + static let blocks = [ CourseBlock(blockId: "1", id: "1", @@ -123,6 +124,7 @@ final class CourseUnitViewModelTests: XCTestCase { sequentialIndex: 0, verticalIndex: 0, interactor: interactor, + config: config, router: router, analytics: analytics, connectivity: connectivity, @@ -151,6 +153,7 @@ final class CourseUnitViewModelTests: XCTestCase { sequentialIndex: 0, verticalIndex: 0, interactor: interactor, + config: config, router: router, analytics: analytics, connectivity: connectivity, @@ -184,6 +187,7 @@ final class CourseUnitViewModelTests: XCTestCase { sequentialIndex: 0, verticalIndex: 0, interactor: interactor, + config: config, router: router, analytics: analytics, connectivity: connectivity, @@ -219,6 +223,7 @@ final class CourseUnitViewModelTests: XCTestCase { sequentialIndex: 0, verticalIndex: 0, interactor: interactor, + config: config, router: router, analytics: analytics, connectivity: connectivity, @@ -253,6 +258,7 @@ final class CourseUnitViewModelTests: XCTestCase { sequentialIndex: 0, verticalIndex: 0, interactor: interactor, + config: config, router: router, analytics: analytics, connectivity: connectivity,