From 291a399fc78f313bda9d6af5f88392e06e7f277d Mon Sep 17 00:00:00 2001 From: Gene <76485998+eyatsenkoperpetio@users.noreply.github.com> Date: Mon, 12 Feb 2024 14:04:54 +0100 Subject: [PATCH] Improvements for Download videos (#279) * chore: click loader and open download detail * chore: new delete video alert * chore: remove question mark * chore: back config directory * chore: resolve PR comments --- .../warning.imageset/Contents.json | 22 + .../warning.imageset/exclamation-mark 1.svg | 3 + .../warning.imageset/exclamation-mark 2.svg | 3 + Core/Core/Extensions/ViewExtension.swift | 13 + Core/Core/SwiftGen/Assets.swift | 1 + Core/Core/View/Base/AlertView.swift | 545 ++++++++++-------- Core/Core/View/Base/DownloadView.swift | 29 +- .../Container/CourseContainerView.swift | 2 +- .../Container/CourseContainerViewModel.swift | 8 + Course/Course/Presentation/CourseRouter.swift | 11 +- .../Downloads/DownloadsView.swift | 63 +- .../Downloads/DownloadsViewModel.swift | 5 + .../CourseStructureNestedListView.swift | 23 +- .../CourseVideoDownloadBarViewModel.swift | 6 +- OpenEdX/Router.swift | 11 +- .../Subviews/ProfileSupportInfoView.swift | 1 - 16 files changed, 462 insertions(+), 284 deletions(-) create mode 100644 Core/Core/Assets.xcassets/warning.imageset/Contents.json create mode 100644 Core/Core/Assets.xcassets/warning.imageset/exclamation-mark 1.svg create mode 100644 Core/Core/Assets.xcassets/warning.imageset/exclamation-mark 2.svg diff --git a/Core/Core/Assets.xcassets/warning.imageset/Contents.json b/Core/Core/Assets.xcassets/warning.imageset/Contents.json new file mode 100644 index 000000000..417f9d554 --- /dev/null +++ b/Core/Core/Assets.xcassets/warning.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "filename" : "exclamation-mark 1.svg", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "exclamation-mark 2.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Core/Core/Assets.xcassets/warning.imageset/exclamation-mark 1.svg b/Core/Core/Assets.xcassets/warning.imageset/exclamation-mark 1.svg new file mode 100644 index 000000000..e79501009 --- /dev/null +++ b/Core/Core/Assets.xcassets/warning.imageset/exclamation-mark 1.svg @@ -0,0 +1,3 @@ + + + diff --git a/Core/Core/Assets.xcassets/warning.imageset/exclamation-mark 2.svg b/Core/Core/Assets.xcassets/warning.imageset/exclamation-mark 2.svg new file mode 100644 index 000000000..86b215583 --- /dev/null +++ b/Core/Core/Assets.xcassets/warning.imageset/exclamation-mark 2.svg @@ -0,0 +1,3 @@ + + + diff --git a/Core/Core/Extensions/ViewExtension.swift b/Core/Core/Extensions/ViewExtension.swift index 1c8833612..ef2e493a2 100644 --- a/Core/Core/Extensions/ViewExtension.swift +++ b/Core/Core/Extensions/ViewExtension.swift @@ -256,6 +256,19 @@ public extension View { } } +public extension View { + @ViewBuilder + func sheetNavigation(isSheet: Bool) -> some View { + if isSheet { + NavigationView { + self + } + } else { + self + } + } +} + private struct FirstAppear: ViewModifier { let action: () -> Void diff --git a/Core/Core/SwiftGen/Assets.swift b/Core/Core/SwiftGen/Assets.swift index 1d1c84625..50f49634f 100644 --- a/Core/Core/SwiftGen/Assets.swift +++ b/Core/Core/SwiftGen/Assets.swift @@ -109,6 +109,7 @@ public enum CoreAssets { public static let playVideo = ImageAsset(name: "playVideo") public static let star = ImageAsset(name: "star") public static let starOutline = ImageAsset(name: "star_outline") + public static let warning = ImageAsset(name: "warning") public static let warningFilled = ImageAsset(name: "warning_filled") } // swiftlint:enable identifier_name line_length nesting type_body_length type_name diff --git a/Core/Core/View/Base/AlertView.swift b/Core/Core/View/Base/AlertView.swift index ad501bdb9..70e99bd2b 100644 --- a/Core/Core/View/Base/AlertView.swift +++ b/Core/Core/View/Base/AlertView.swift @@ -13,12 +13,13 @@ public enum AlertViewType: Equatable { case action(String, SwiftUI.Image) case logOut case leaveProfile - + case deleteVideo + var contentPadding: CGFloat { switch self { case .`default`: return 16 - case .action, .logOut, .leaveProfile: + case .action, .logOut, .leaveProfile, .deleteVideo: return 36 } } @@ -69,248 +70,342 @@ public struct AlertView: View { self.nextSectionTapped = nextSectionTapped type = .action(mainAction, image) } - + public var body: some View { ZStack(alignment: .center) { Color.black.opacity(0.5) .onTapGesture { onCloseTapped() } - ZStack(alignment: .topTrailing) { - adaptiveStack(spacing: isHorizontal ? 10 : 20, isHorizontal: (type == .leaveProfile && isHorizontal)) { - if type == .logOut { - HStack { - Spacer(minLength: 100) - CoreAssets.logOut.swiftUIImage - .padding(.top, isHorizontal ? 20 : 54) - Spacer(minLength: 100) - } - Text(alertMessage) - .font(Theme.Fonts.titleLarge) - .padding(.vertical, isHorizontal ? 6 : 40) - .multilineTextAlignment(.center) - .padding(.horizontal, 40) - .frame(maxWidth: 250) - } else if type == .leaveProfile { + content + } + .ignoresSafeArea() + } + + private var content: some View { + ZStack(alignment: .topTrailing) { + adaptiveStack( + spacing: isHorizontal ? 10 : 20, + isHorizontal: (type == .leaveProfile && isHorizontal) + ) { + titles + buttons + } + close + } + .frame(maxWidth: type == .logOut ? 390 : nil) + .background( + Theme.Shapes.cardShape + .fill(Theme.Colors.cardViewBackground) + .shadow(radius: 24) + .fixedSize(horizontal: false, vertical: false) + ) + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke( + style: .init( + lineWidth: 1, + lineCap: .round, + lineJoin: .round, + miterLimit: 1 + ) + ) + .foregroundColor(Theme.Colors.backgroundStroke) + .fixedSize(horizontal: false, vertical: false) + ) + .frame(maxWidth: isHorizontal ? nil : 390) + .padding(40) + } + + private var close: some View { + Button { + onCloseTapped() + } label: { + Image(systemName: "xmark") + .padding(.trailing, 40) + .padding(.top, 24) + } + } + + @ViewBuilder + private var titles: some View { + switch type { + case .logOut: + HStack { + Spacer(minLength: 100) + CoreAssets.logOut.swiftUIImage + .padding(.top, isHorizontal ? 20 : 54) + Spacer(minLength: 100) + } + Text(alertMessage) + .font(Theme.Fonts.titleLarge) + .padding(.vertical, isHorizontal ? 6 : 40) + .multilineTextAlignment(.center) + .padding(.horizontal, 40) + .frame(maxWidth: 250) + case .leaveProfile, .deleteVideo: + VStack(spacing: 20) { + if type == .deleteVideo { + CoreAssets.warning.swiftUIImage + .padding(.top, isHorizontal ? 20 : 54) + } else { + CoreAssets.leaveProfile.swiftUIImage + .padding(.top, isHorizontal ? 20 : 54) + } + Text(alertTitle) + .font(Theme.Fonts.titleLarge) + .padding(.horizontal, 40) + Text(alertMessage) + .font(Theme.Fonts.bodyMedium) + .multilineTextAlignment(.center) + .padding(.horizontal, 40) + + } + .padding(.bottom, 20) + default: + HStack { + VStack(alignment: .center, spacing: 10) { + if case let .action(_, image) = type { + image.padding(.top, 48) + } + if case let .default(_, image) = type { + image.flatMap { $0.padding(.top, 48) } + } + Text(alertTitle) + .font(Theme.Fonts.titleLarge) + .padding(.horizontal, 40) + .padding(.top, 10) + Text(alertMessage) + .font(Theme.Fonts.bodyMedium) + .multilineTextAlignment(.center) + .padding(.horizontal, 40) + .frame(maxWidth: 250) + } + if isHorizontal { + if case let .action(action, _) = type { VStack(spacing: 20) { - CoreAssets.leaveProfile.swiftUIImage - .padding(.top, isHorizontal ? 20 : 54) - Text(alertTitle) - .font(Theme.Fonts.titleLarge) - .padding(.horizontal, 40) - Text(alertMessage) - .font(Theme.Fonts.bodyMedium) - .multilineTextAlignment(.center) - .padding(.horizontal, 40) - - }.padding(.bottom, 20) - } else { - HStack { - VStack(alignment: .center, spacing: 10) { - if case let .action(_, image) = type { - image.padding(.top, 48) - } - if case let .default(_, image) = type { - image.flatMap { $0.padding(.top, 48) } - } - Text(alertTitle) - .font(Theme.Fonts.titleLarge) + if nextSectionName != nil { + UnitButtonView(type: .nextSection, action: { nextSectionTapped() }) + .frame(maxWidth: 215) + } + UnitButtonView(type: .custom(action), + bgColor: .clear, + action: { okTapped() }) + .frame(maxWidth: 215) + + if let nextSectionName { + Group { + Text(CoreLocalization.Courseware.nextSectionDescriptionFirst) + + Text(nextSectionName) + + Text(CoreLocalization.Courseware.nextSectionDescriptionLast) + }.frame(maxWidth: 215) .padding(.horizontal, 40) - .padding(.top, 10) - Text(alertMessage) - .font(Theme.Fonts.bodyMedium) .multilineTextAlignment(.center) - .padding(.horizontal, 40) - .frame(maxWidth: 250) + .font(Theme.Fonts.labelSmall) + .foregroundColor(Theme.Colors.textSecondary) } - if isHorizontal { - if case let .action(action, _) = type { - VStack(spacing: 20) { - if nextSectionName != nil { - UnitButtonView(type: .nextSection, action: { nextSectionTapped() }) - .frame(maxWidth: 215) - } - UnitButtonView(type: .custom(action), - bgColor: .clear, - action: { okTapped() }) - .frame(maxWidth: 215) - - if let nextSectionName { - Group { - Text(CoreLocalization.Courseware.nextSectionDescriptionFirst) + - Text(nextSectionName) + - Text(CoreLocalization.Courseware.nextSectionDescriptionLast) - }.frame(maxWidth: 215) - .padding(.horizontal, 40) - .multilineTextAlignment(.center) - .font(Theme.Fonts.labelSmall) - .foregroundColor(Theme.Colors.textSecondary) - } - }.padding(.top, 70) - .padding(.trailing, 20) - } - } - } + }.padding(.top, 70) + .padding(.trailing, 20) } - HStack { - switch type { - case let .`default`(positiveAction, _): - HStack { - StyledButton(positiveAction, action: { okTapped() }) - .frame(maxWidth: 135) - StyledButton(CoreLocalization.Alert.cancel, action: { onCloseTapped() }) - .frame(maxWidth: 135) - .saturation(0) - } - .padding(.leading, 10) - .padding(.trailing, 10) - .padding(.bottom, 10) - case let .action(action, _): - if !isHorizontal { - VStack(spacing: 20) { - if nextSectionName != nil { - UnitButtonView(type: .nextSection, action: { nextSectionTapped() }) - .frame(maxWidth: 215) - } - UnitButtonView(type: .custom(action), - bgColor: .clear, - action: { okTapped() }) - .frame(maxWidth: 215) - - if let nextSectionName { - Group { - Text(CoreLocalization.Courseware.nextSectionDescriptionFirst) + - Text(nextSectionName) + - Text(CoreLocalization.Courseware.nextSectionDescriptionLast) - }.frame(maxWidth: 215) - .padding(.horizontal, 40) - .multilineTextAlignment(.center) - .font(Theme.Fonts.labelSmall) - .foregroundColor(Theme.Colors.textSecondary) - } - } - } else { - EmptyView() - } - case .logOut: - Button(action: { - okTapped() - }, label: { - ZStack { - Text(CoreLocalization.Alert.logout) - .foregroundColor(.black) - .font(Theme.Fonts.labelLarge) - .frame(maxWidth: .infinity) - .padding(.horizontal, 16) - Image(systemName: "rectangle.portrait.and.arrow.right") - .foregroundColor(.black) - .frame(minWidth: 190, minHeight: 48, alignment: .trailing) - } - .frame(maxWidth: 215, minHeight: 48) - }) - .background( - Theme.Shapes.buttonShape - .fill(Theme.Colors.warning) - ) - .overlay( - RoundedRectangle(cornerRadius: 8) - .stroke(style: .init( - lineWidth: 1, - lineCap: .round, - lineJoin: .round, - miterLimit: 1 - )) - .foregroundColor(.clear) - ) - .frame(maxWidth: 215) - case .leaveProfile: - VStack(spacing: 0) { - Button(action: { - okTapped() - }, label: { - ZStack { - Text(CoreLocalization.Alert.leave) - .foregroundColor(.black) - .font(Theme.Fonts.labelLarge) - .frame(maxWidth: .infinity) - .padding(.horizontal, 16) - } - .frame(maxWidth: 215, minHeight: 48) - }) - .background( - Theme.Shapes.buttonShape - .fill(Theme.Colors.warning) - ) - .overlay( - RoundedRectangle(cornerRadius: 8) - .stroke(style: .init( - lineWidth: 1, - lineCap: .round, - lineJoin: .round, - miterLimit: 1 - )) - .foregroundColor(.clear) - ) - .frame(maxWidth: 215) - .padding(.bottom, isHorizontal ? 10 : 24) - Button(action: { - onCloseTapped() - }, label: { - ZStack { - Text(CoreLocalization.Alert.keepEditing) - .foregroundColor(Theme.Colors.textPrimary) - .font(Theme.Fonts.labelLarge) - .frame(maxWidth: .infinity) - .padding(.horizontal, 16) - } - .frame(maxWidth: 215, minHeight: 48) - }) - .background( - Theme.Shapes.buttonShape - .fill(.clear) - ) - .overlay( - RoundedRectangle(cornerRadius: 8) - .stroke(style: .init( - lineWidth: 1, - lineCap: .round, - lineJoin: .round, - miterLimit: 1 - )) - .foregroundColor(Theme.Colors.textPrimary) - ) + } + } + } + } + + private var buttons: some View { + HStack { + switch type { + case let .`default`(positiveAction, _): + HStack { + StyledButton(positiveAction, action: { okTapped() }) + .frame(maxWidth: 135) + StyledButton(CoreLocalization.Alert.cancel, action: { onCloseTapped() }) + .frame(maxWidth: 135) + .saturation(0) + } + .padding(.leading, 10) + .padding(.trailing, 10) + .padding(.bottom, 10) + case let .action(action, _): + if !isHorizontal { + VStack(spacing: 20) { + if nextSectionName != nil { + UnitButtonView(type: .nextSection, action: { nextSectionTapped() }) .frame(maxWidth: 215) - }.padding(.trailing, isHorizontal ? 20 : 0) + } + UnitButtonView(type: .custom(action), + bgColor: .clear, + action: { okTapped() }) + .frame(maxWidth: 215) + + if let nextSectionName { + Group { + Text(CoreLocalization.Courseware.nextSectionDescriptionFirst) + + Text(nextSectionName) + + Text(CoreLocalization.Courseware.nextSectionDescriptionLast) + }.frame(maxWidth: 215) + .padding(.horizontal, 40) + .multilineTextAlignment(.center) + .font(Theme.Fonts.labelSmall) + .foregroundColor(Theme.Colors.textSecondary) } } - .padding(.top, 16) - .padding(.bottom, isHorizontal ? 16 : type.contentPadding) + } else { + EmptyView() } + case .logOut: Button(action: { - onCloseTapped() + okTapped() }, label: { - Image(systemName: "xmark") - .padding(.trailing, 40) - .padding(.top, 24) + ZStack { + Text(CoreLocalization.Alert.logout) + .foregroundColor(.black) + .font(Theme.Fonts.labelLarge) + .frame(maxWidth: .infinity) + .padding(.horizontal, 16) + Image(systemName: "rectangle.portrait.and.arrow.right") + .foregroundColor(.black) + .frame(minWidth: 190, minHeight: 48, alignment: .trailing) + } + .frame(maxWidth: 215, minHeight: 48) }) - - }.frame(maxWidth: type == .logOut ? 390 : nil) - .background( - Theme.Shapes.cardShape - .fill(Theme.Colors.cardViewBackground) - .shadow(radius: 24) - .fixedSize(horizontal: false, vertical: false) - ) - .overlay( - RoundedRectangle(cornerRadius: 12) - .stroke(style: .init(lineWidth: 1, lineCap: .round, lineJoin: .round, miterLimit: 1)) - .foregroundColor(Theme.Colors.backgroundStroke) - .fixedSize(horizontal: false, vertical: false) - ) - .frame(maxWidth: isHorizontal ? nil : 390) - .padding(40) + .background( + Theme.Shapes.buttonShape + .fill(Theme.Colors.warning) + ) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(style: .init( + lineWidth: 1, + lineCap: .round, + lineJoin: .round, + miterLimit: 1 + )) + .foregroundColor(.clear) + ) + .frame(maxWidth: 215) + case .leaveProfile: + VStack(spacing: 0) { + Button(action: { + okTapped() + }, label: { + ZStack { + Text(CoreLocalization.Alert.leave) + .foregroundColor(.black) + .font(Theme.Fonts.labelLarge) + .frame(maxWidth: .infinity) + .padding(.horizontal, 16) + } + .frame(maxWidth: 215, minHeight: 48) + }) + .background( + Theme.Shapes.buttonShape + .fill(Theme.Colors.warning) + ) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(style: .init( + lineWidth: 1, + lineCap: .round, + lineJoin: .round, + miterLimit: 1 + )) + .foregroundColor(.clear) + ) + .frame(maxWidth: 215) + .padding(.bottom, isHorizontal ? 10 : 24) + Button(action: { + onCloseTapped() + }, label: { + ZStack { + Text(CoreLocalization.Alert.keepEditing) + .foregroundColor(Theme.Colors.textPrimary) + .font(Theme.Fonts.labelLarge) + .frame(maxWidth: .infinity) + .padding(.horizontal, 16) + } + .frame(maxWidth: 215, minHeight: 48) + }) + .background( + Theme.Shapes.buttonShape + .fill(.clear) + ) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(style: .init( + lineWidth: 1, + lineCap: .round, + lineJoin: .round, + miterLimit: 1 + )) + .foregroundColor(Theme.Colors.textPrimary) + ) + .frame(maxWidth: 215) + } + .padding(.trailing, isHorizontal ? 20 : 0) + case .deleteVideo: + VStack(spacing: 0) { + Button { + okTapped() + } label: { + ZStack { + Text(CoreLocalization.Alert.delete) + .foregroundColor(.black) + .font(Theme.Fonts.labelLarge) + .frame(maxWidth: .infinity) + .padding(.horizontal, 16) + } + .frame(maxWidth: 215, minHeight: 48) + } + .background( + Theme.Shapes.buttonShape + .fill(Theme.Colors.warning) + ) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(style: .init( + lineWidth: 1, + lineCap: .round, + lineJoin: .round, + miterLimit: 1 + )) + .foregroundColor(.clear) + ) + .frame(maxWidth: 215) + .padding(.bottom, isHorizontal ? 10 : 24) + Button(action: { + onCloseTapped() + }, label: { + ZStack { + Text(CoreLocalization.Alert.cancel) + .foregroundColor(Theme.Colors.textPrimary) + .font(Theme.Fonts.labelLarge) + .frame(maxWidth: .infinity) + .padding(.horizontal, 16) + } + .frame(maxWidth: 215, minHeight: 48) + }) + .background( + Theme.Shapes.buttonShape + .fill(.clear) + ) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(style: .init( + lineWidth: 1, + lineCap: .round, + lineJoin: .round, + miterLimit: 1 + )) + .foregroundColor(Theme.Colors.textPrimary) + ) + .frame(maxWidth: 215) + } + .padding(.trailing, isHorizontal ? 20 : 0) + } } - .ignoresSafeArea() + .padding(.top, 16) + .padding(.bottom, isHorizontal ? 16 : type.contentPadding) } } diff --git a/Core/Core/View/Base/DownloadView.swift b/Core/Core/View/Base/DownloadView.swift index 806aee51b..37f63e41d 100644 --- a/Core/Core/View/Base/DownloadView.swift +++ b/Core/Core/View/Base/DownloadView.swift @@ -19,11 +19,14 @@ public struct DownloadAvailableView: View { } public var body: some View { - CoreAssets.startDownloading.swiftUIImage.renderingMode(.template) - .resizable() - .scaledToFit() - .frame(width: 24, height: 24) - .foregroundColor(Theme.Colors.textPrimary) + VStack(spacing: 0) { + CoreAssets.startDownloading.swiftUIImage.renderingMode(.template) + .resizable() + .scaledToFit() + .frame(width: 24, height: 24) + .foregroundColor(Theme.Colors.textPrimary) + } + .frame(width: 30, height: 30) } } @@ -33,13 +36,12 @@ public struct DownloadProgressView: View { public var body: some View { ZStack { - ProgressBar(size: 36, lineWidth: 1.75) + ProgressBar(size: 30, lineWidth: 1.75) CoreAssets.stopDownloading.swiftUIImage.renderingMode(.template) .resizable() .scaledToFit() .frame(width: 20, height: 20) .foregroundColor(Theme.Colors.textPrimary) - .padding(6) } } } @@ -49,10 +51,13 @@ public struct DownloadFinishedView: View { } public var body: some View { - CoreAssets.deleteDownloading.swiftUIImage.renderingMode(.template) - .resizable() - .scaledToFit() - .frame(width: 24, height: 24) - .foregroundColor(Theme.Colors.textPrimary) + VStack(spacing: 0) { + CoreAssets.deleteDownloading.swiftUIImage.renderingMode(.template) + .resizable() + .scaledToFit() + .frame(width: 24, height: 24) + .foregroundColor(Theme.Colors.textPrimary) + } + .frame(width: 30, height: 30) } } diff --git a/Course/Course/Presentation/Container/CourseContainerView.swift b/Course/Course/Presentation/Container/CourseContainerView.swift index 2e154ba69..79a797ddd 100644 --- a/Course/Course/Presentation/Container/CourseContainerView.swift +++ b/Course/Course/Presentation/Container/CourseContainerView.swift @@ -20,8 +20,8 @@ public struct CourseContainerView: View { case course case videos - case dates case discussion + case dates case handounds var title: String { diff --git a/Course/Course/Presentation/Container/CourseContainerViewModel.swift b/Course/Course/Presentation/Container/CourseContainerViewModel.swift index ce5463cd9..9a7a34c4c 100644 --- a/Course/Course/Presentation/Container/CourseContainerViewModel.swift +++ b/Course/Course/Presentation/Container/CourseContainerViewModel.swift @@ -165,6 +165,14 @@ public class CourseContainerViewModel: BaseCourseViewModel { return verticals.flatMap { $0.vertical.childs.filter { $0.isDownloadable } } } + func getTasks(sequential: CourseSequential) -> [DownloadDataTask] { + let blocks = verticalsBlocksDownloadable(by: sequential) + let tasks = blocks.compactMap { block in + courseDownloadTasks.first(where: { $0.id == block.id}) + } + return tasks + } + func continueDownload() { guard let blocks = waitingDownloads else { return diff --git a/Course/Course/Presentation/CourseRouter.swift b/Course/Course/Presentation/CourseRouter.swift index 35619bc2d..d4cd7c68a 100644 --- a/Course/Course/Presentation/CourseRouter.swift +++ b/Course/Course/Presentation/CourseRouter.swift @@ -55,6 +55,11 @@ public protocol CourseRouter: BaseRouter { componentID: String, courseStructure: CourseStructure ) + + func showDownloads( + downloads: [DownloadDataTask], + manager: DownloadManagerProtocol + ) } // Mark - For testing and SwiftUI preview @@ -108,6 +113,10 @@ public class CourseRouterMock: BaseRouterMock, CourseRouter { componentID: String, courseStructure: CourseStructure ) {} - + + public func showDownloads( + downloads: [Core.DownloadDataTask], + manager: Core.DownloadManagerProtocol + ) {} } #endif diff --git a/Course/Course/Presentation/Downloads/DownloadsView.swift b/Course/Course/Presentation/Downloads/DownloadsView.swift index c6b0262d1..958380a6d 100644 --- a/Course/Course/Presentation/Downloads/DownloadsView.swift +++ b/Course/Course/Presentation/Downloads/DownloadsView.swift @@ -10,49 +10,64 @@ import Core import Theme import Combine -struct DownloadsView: View { +public struct DownloadsView: View { // MARK: - Properties @Environment(\.dismiss) private var dismiss @StateObject private var viewModel: DownloadsViewModel - init( + var isSheet: Bool = true + + public init( + isSheet: Bool = true, courseId: String? = nil, + downloads: [DownloadDataTask] = [], manager: DownloadManagerProtocol ) { + self.isSheet = isSheet self._viewModel = .init( - wrappedValue: .init(courseId: courseId, manager: manager) + wrappedValue: .init( + courseId: courseId, + downloads: downloads, + manager: manager + ) ) } // MARK: - Body - var body: some View { - NavigationView { - ScrollView { - LazyVStack { - ForEach( - viewModel.downloads, - content: cell - ) - } + public var body: some View { + content + .sheetNavigation(isSheet: isSheet) + } + + private var content: some View { + ScrollView { + LazyVStack { + ForEach( + viewModel.downloads, + content: cell + ) } - .navigationBarTitleDisplayMode(.inline) - .navigationTitle(CourseLocalization.Download.downloads) - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - Button { - dismiss() - } label: { - Image(systemName: "xmark") - .foregroundColor(Theme.Colors.accentColor) + } + .navigationBarTitleDisplayMode(.inline) + .navigationTitle(CourseLocalization.Download.downloads) + .if(isSheet) { view in + view + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button { + dismiss() + } label: { + Image(systemName: "xmark") + .foregroundColor(Theme.Colors.accentColor) + } + .accessibilityIdentifier("close_button") } - .accessibilityIdentifier("close_button") } - } - .padding(.top, 1) } + .padding(.top, 1) } // MARK: - Views diff --git a/Course/Course/Presentation/Downloads/DownloadsViewModel.swift b/Course/Course/Presentation/Downloads/DownloadsViewModel.swift index b71566f58..78c063778 100644 --- a/Course/Course/Presentation/Downloads/DownloadsViewModel.swift +++ b/Course/Course/Presentation/Downloads/DownloadsViewModel.swift @@ -21,10 +21,12 @@ final class DownloadsViewModel: ObservableObject { init( courseId: String? = nil, + downloads: [DownloadDataTask] = [], manager: DownloadManagerProtocol ) { self.courseId = courseId self.manager = manager + self.downloads = downloads Task { await configure() } observers() } @@ -52,6 +54,9 @@ final class DownloadsViewModel: ObservableObject { defer { filter() } + if !downloads.isEmpty { + return + } if let courseId = courseId { downloads = await manager.getDownloadTasksForCourse(courseId) return diff --git a/Course/Course/Presentation/Outline/CourseStructure/CourseStructureNestedListView.swift b/Course/Course/Presentation/Outline/CourseStructure/CourseStructureNestedListView.swift index 8cada0ce0..176594c23 100644 --- a/Course/Course/Presentation/Outline/CourseStructure/CourseStructureNestedListView.swift +++ b/Course/Course/Presentation/Outline/CourseStructure/CourseStructureNestedListView.swift @@ -131,22 +131,16 @@ struct CourseStructureNestedListView: View { .accessibilityElement(children: .ignore) .accessibilityLabel(CourseLocalization.Accessibility.download) } - downloadCount(sequential: sequential) } case .downloading: if viewModel.isInternetAvaliable { Button { - Task { - await viewModel.onDownloadViewTap( - chapter: chapter, - blockId: sequential.id, - state: state - ) - } + viewModel.router.showDownloads( + downloads: viewModel.getTasks(sequential: sequential), + manager: viewModel.manager + ) } label: { - DownloadProgressView() - .accessibilityElement(children: .ignore) - .accessibilityLabel(CourseLocalization.Accessibility.cancelDownload) + ProgressBar(size: 30, lineWidth: 1.75) } } case .finished: @@ -168,18 +162,15 @@ struct CourseStructureNestedListView: View { } viewModel.router.dismiss(animated: true) }, - type: .default( - positiveAction: CoreLocalization.Alert.delete, - image: CoreAssets.bgDelete.swiftUIImage - ) + type: .deleteVideo ) } label: { DownloadFinishedView() .accessibilityElement(children: .ignore) .accessibilityLabel(CourseLocalization.Accessibility.deleteDownload) } - downloadCount(sequential: sequential) } + downloadCount(sequential: sequential) } } diff --git a/Course/Course/Presentation/Subviews/CourseVideoDownloadBarView/CourseVideoDownloadBarViewModel.swift b/Course/Course/Presentation/Subviews/CourseVideoDownloadBarView/CourseVideoDownloadBarViewModel.swift index 4a64dbf4f..91b0cd281 100644 --- a/Course/Course/Presentation/Subviews/CourseVideoDownloadBarView/CourseVideoDownloadBarViewModel.swift +++ b/Course/Course/Presentation/Subviews/CourseVideoDownloadBarView/CourseVideoDownloadBarViewModel.swift @@ -134,7 +134,7 @@ final class CourseVideoDownloadBarViewModel: ObservableObject { } self.courseViewModel.router.dismiss(animated: true) }, - type: .default(positiveAction: CoreLocalization.Alert.delete, image: CoreAssets.bgDelete.swiftUIImage) + type: .deleteVideo ) return } @@ -142,7 +142,7 @@ final class CourseVideoDownloadBarViewModel: ObservableObject { if isOn { courseViewModel.router.presentAlert( alertTitle: "Warning", - alertMessage: "\(CourseLocalization.Alert.stopDownloading) \"\(courseStructure.displayName)\"?", + alertMessage: "\(CourseLocalization.Alert.stopDownloading) \"\(courseStructure.displayName)\"", positiveAction: CoreLocalization.Alert.accept, onCloseTapped: { [weak self] in self?.courseViewModel.router.dismiss(animated: true) @@ -154,7 +154,7 @@ final class CourseVideoDownloadBarViewModel: ObservableObject { } self.courseViewModel.router.dismiss(animated: true) }, - type: .default(positiveAction: CoreLocalization.Alert.accept, image: nil) + type: .deleteVideo ) return } diff --git a/OpenEdX/Router.swift b/OpenEdX/Router.swift index adc46d1f6..10a267a53 100644 --- a/OpenEdX/Router.swift +++ b/OpenEdX/Router.swift @@ -410,7 +410,16 @@ public class Router: AuthorizationRouter, } } } - + + public func showDownloads( + downloads: [DownloadDataTask], + manager: DownloadManagerProtocol + ) { + let downloadsView = DownloadsView(isSheet: false, downloads: downloads, manager: manager) + let controller = UIHostingController(rootView: downloadsView) + navigationController.pushViewController(controller, animated: true) + } + public func replaceCourseUnit( courseName: String, blockId: String, diff --git a/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift b/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift index 81b669355..da2695845 100644 --- a/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift +++ b/Profile/Profile/Presentation/Profile/Subviews/ProfileSupportInfoView.swift @@ -45,7 +45,6 @@ struct ProfileSupportInfoView: View { ), isEmailSupport: true ) - } private func terms(url: URL) -> some View {