From 16b17b6b86b8f215c00e6dcb7b048a2d6ad2727b Mon Sep 17 00:00:00 2001 From: IvanStepanok <128456094+IvanStepanok@users.noreply.github.com> Date: Wed, 10 Apr 2024 16:44:33 +0300 Subject: [PATCH] feat: [FC-0047] move the certificate view from the banner to the message section (#387) * feat: move the certificate view from the banner to the message section * refactor: Rename badge to certificateBadge and improve code structure - Rename `badge` to `certificateBadge` on Assets to enhance semantic clarity. - Refactor conditional unwrapping of `certificate.url` in viewModel for improved code readability. Add SFPro-Light font to whitelabel configuration * fix: update Theming_implementation add light font --- .../certificateBadge.imageset/Contents.json | 15 ++++ .../certificateIcon.svg | 3 + Core/Core/SwiftGen/Assets.swift | 1 + Course/Course.xcodeproj/project.pbxproj | 12 ++++ .../Outline/CourseOutlineView.swift | 69 ++++++++----------- .../MessageSectionView.swift | 66 ++++++++++++++++++ Course/Course/SwiftGen/Strings.swift | 14 ++-- Course/Course/en.lproj/Localizable.strings | 3 +- Course/Course/uk.lproj/Localizable.strings | 3 +- Documentation/Theming_implementation.md | 2 + Theme/Theme/Fonts/FontParser.swift | 2 +- Theme/Theme/Fonts/fonts.json | 1 + Theme/Theme/Theme.swift | 1 + config_script/whitelabel.py | 3 +- 14 files changed, 143 insertions(+), 52 deletions(-) create mode 100644 Core/Core/Assets.xcassets/certificateBadge.imageset/Contents.json create mode 100644 Core/Core/Assets.xcassets/certificateBadge.imageset/certificateIcon.svg create mode 100644 Course/Course/Presentation/Subviews/MessageSectionView/MessageSectionView.swift diff --git a/Core/Core/Assets.xcassets/certificateBadge.imageset/Contents.json b/Core/Core/Assets.xcassets/certificateBadge.imageset/Contents.json new file mode 100644 index 000000000..30dcc384e --- /dev/null +++ b/Core/Core/Assets.xcassets/certificateBadge.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "certificateIcon.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/Core/Core/Assets.xcassets/certificateBadge.imageset/certificateIcon.svg b/Core/Core/Assets.xcassets/certificateBadge.imageset/certificateIcon.svg new file mode 100644 index 000000000..fda75f3b0 --- /dev/null +++ b/Core/Core/Assets.xcassets/certificateBadge.imageset/certificateIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/Core/Core/SwiftGen/Assets.swift b/Core/Core/SwiftGen/Assets.swift index 2252597b1..17b1fad70 100644 --- a/Core/Core/SwiftGen/Assets.swift +++ b/Core/Core/SwiftGen/Assets.swift @@ -91,6 +91,7 @@ public enum CoreAssets { public static let arrowLeft = ImageAsset(name: "arrowLeft") public static let arrowRight16 = ImageAsset(name: "arrowRight16") public static let certificate = ImageAsset(name: "certificate") + public static let certificateBadge = ImageAsset(name: "certificateBadge") public static let check = ImageAsset(name: "check") public static let checkEmail = ImageAsset(name: "checkEmail") public static let clearInput = ImageAsset(name: "clearInput") diff --git a/Course/Course.xcodeproj/project.pbxproj b/Course/Course.xcodeproj/project.pbxproj index 6d7eab45a..3026925aa 100644 --- a/Course/Course.xcodeproj/project.pbxproj +++ b/Course/Course.xcodeproj/project.pbxproj @@ -45,6 +45,7 @@ 02B6B3BC28E1D14F00232911 /* CourseRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B6B3BB28E1D14F00232911 /* CourseRepository.swift */; }; 02B6B3BE28E1D15C00232911 /* CourseEndpoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02B6B3BD28E1D15C00232911 /* CourseEndpoint.swift */; }; 02B6B3C928E1E68100232911 /* Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 02B6B3C828E1E68100232911 /* Core.framework */; }; + 02D4FC2E2BBD7C9C00C47748 /* MessageSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02D4FC2D2BBD7C9C00C47748 /* MessageSectionView.swift */; }; 02F0144F28F46474002E513D /* CourseContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F0144E28F46474002E513D /* CourseContainerView.swift */; }; 02F0145728F4A2FF002E513D /* CourseContainerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F0145628F4A2FF002E513D /* CourseContainerViewModel.swift */; }; 02F066E829DC71750073E13B /* SubtittlesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02F066E729DC71750073E13B /* SubtittlesView.swift */; }; @@ -141,6 +142,7 @@ 02B6B3BD28E1D15C00232911 /* CourseEndpoint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseEndpoint.swift; sourceTree = ""; }; 02B6B3C228E1DCD100232911 /* CourseDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseDetails.swift; sourceTree = ""; }; 02B6B3C828E1E68100232911 /* Core.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Core.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 02D4FC2D2BBD7C9C00C47748 /* MessageSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageSectionView.swift; sourceTree = ""; }; 02ED50CF29A64BB6008341CD /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; 02F0144E28F46474002E513D /* CourseContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseContainerView.swift; sourceTree = ""; }; 02F0145628F4A2FF002E513D /* CourseContainerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CourseContainerViewModel.swift; sourceTree = ""; }; @@ -362,6 +364,14 @@ name = Frameworks; sourceTree = ""; }; + 02D4FC2C2BBD7C7500C47748 /* MessageSectionView */ = { + isa = PBXGroup; + children = ( + 02D4FC2D2BBD7C9C00C47748 /* MessageSectionView.swift */, + ); + path = MessageSectionView; + sourceTree = ""; + }; 02DFC65029ACC20A00EA4BB9 /* Handouts */ = { isa = PBXGroup; children = ( @@ -562,6 +572,7 @@ BAD9CA482B2C88D500DE790A /* Subviews */ = { isa = PBXGroup; children = ( + 02D4FC2C2BBD7C7500C47748 /* MessageSectionView */, BAC0E0D92B32F0A2006B68A9 /* CourseVideoDownloadBarView */, BA58CF622B471047005B102E /* VideoDownloadQualityBarView */, ); @@ -831,6 +842,7 @@ 0270210328E736E700F54332 /* CourseOutlineView.swift in Sources */, 068DDA602B1E198700FF8CCB /* CourseUnitVerticalsDropdownView.swift in Sources */, 022C64E029ADEA9B000F532B /* Data_UpdatesResponse.swift in Sources */, + 02D4FC2E2BBD7C9C00C47748 /* MessageSectionView.swift in Sources */, 02454CA02A2618E70043052A /* YouTubeView.swift in Sources */, 02454CA22A26190A0043052A /* EncodedVideoView.swift in Sources */, 065275352BB1B39C0093BCCA /* PlayerViewControllerHolder.swift in Sources */, diff --git a/Course/Course/Presentation/Outline/CourseOutlineView.swift b/Course/Course/Presentation/Outline/CourseOutlineView.swift index 54a069049..c22d2219b 100644 --- a/Course/Course/Presentation/Outline/CourseOutlineView.swift +++ b/Course/Course/Presentation/Outline/CourseOutlineView.swift @@ -77,7 +77,8 @@ public struct CourseOutlineView: View { } downloadQualityBars - + certificateView + if let continueWith = viewModel.continueWith, let courseStructure = viewModel.courseStructure, !isVideo { @@ -100,7 +101,7 @@ public struct CourseOutlineView: View { viewModel.trackResumeCourseClicked( blockId: continueBlock?.id ?? "" ) - + if let course = viewModel.courseStructure { viewModel.router.showCourseUnit( courseName: course.displayName, @@ -270,6 +271,33 @@ public struct CourseOutlineView: View { } } } + + @ViewBuilder + private var certificateView: some View { + // MARK: - Course Certificate + if let certificate = viewModel.courseStructure?.certificate, + let url = certificate.url, + url.count > 0 { + MessageSectionView( + title: CourseLocalization.Outline.passedTheCourse(title), + actionTitle: CourseLocalization.Outline.viewCertificate, + action: { + openCertificateView = true + viewModel.trackViewCertificateClicked(courseID: courseID) + } + ) + .padding(.horizontal, 24) + .fullScreenCover( + isPresented: $openCertificateView, + content: { + WebBrowser( + url: url, + pageTitle: CourseLocalization.Outline.certificate + ) + } + ) + } + } private func courseBanner(proxy: GeometryProxy) -> some View { ZStack { @@ -282,43 +310,6 @@ public struct CourseOutlineView: View { .aspectRatio(contentMode: .fill) .frame(maxWidth: proxy.size.width - 12, maxHeight: .infinity) } - - // MARK: - Course Certificate - if let certificate = viewModel.courseStructure?.certificate { - if let url = certificate.url, url.count > 0 { - Theme.Colors.certificateForeground - VStack(alignment: .center, spacing: 8) { - CoreAssets.certificate.swiftUIImage - Text(CourseLocalization.Outline.congratulations) - .multilineTextAlignment(.center) - .font(Theme.Fonts.headlineMedium) - Text(CourseLocalization.Outline.passedTheCourse) - .font(Theme.Fonts.bodyMedium) - .multilineTextAlignment(.center) - StyledButton( - CourseLocalization.Outline.viewCertificate, - action: { - openCertificateView = true - viewModel.trackViewCertificateClicked(courseID: courseID) - }, - isTransparent: true - ) - .frame(width: 141) - .padding(.top, 8) - - .fullScreenCover( - isPresented: $openCertificateView, - content: { - WebBrowser( - url: url, - pageTitle: CourseLocalization.Outline.certificate - ) - }) - }.padding(.horizontal, 24) - .padding(.top, 8) - .foregroundColor(.white) - } - } } .frame(maxHeight: 250) .cornerRadius(12) diff --git a/Course/Course/Presentation/Subviews/MessageSectionView/MessageSectionView.swift b/Course/Course/Presentation/Subviews/MessageSectionView/MessageSectionView.swift new file mode 100644 index 000000000..87e6feb65 --- /dev/null +++ b/Course/Course/Presentation/Subviews/MessageSectionView/MessageSectionView.swift @@ -0,0 +1,66 @@ +// +// MessageSectionView.swift +// Course +// +// Created by  Stepanok Ivan on 03.04.2024. +// + +import SwiftUI +import Core +import Theme + +public struct MessageSectionView: View { + + private let title: String + private let actionTitle: String + private var action: (() -> Void) = {} + + public init( + title: String, + actionTitle: String, + action: @escaping () -> Void + ) { + self.title = title + self.actionTitle = actionTitle + self.action = action + } + + public var body: some View { + HStack(alignment: .top, spacing: 8) { + CoreAssets.certificateBadge.swiftUIImage + .resizable() + .frame(width: 24, height: 24) + .foregroundColor(Theme.Colors.textPrimary) + VStack(alignment: .leading, spacing: 8) { + Text(title) + .font(Theme.Fonts.bodyMicro) + .foregroundColor(Theme.Colors.textPrimary) + + Button(action: { + action() + }, label: { + Text(actionTitle) + .font(Theme.Fonts.bodyMicro) + .underline() + .foregroundColor(Theme.Colors.textPrimary) + }) + } + } + .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) + .padding(.all, 12) + .background(RoundedRectangle(cornerRadius: 8) + .stroke(lineWidth: 1) + .fill(Theme.Colors.textInputStroke) + ) + } +} + +#Preview { + MessageSectionView( + title: "Congratulations, you have earned this course certificate in “Course Title.”", + actionTitle: "View Certificate", + action: { + } + ) + .loadFonts() +} diff --git a/Course/Course/SwiftGen/Strings.swift b/Course/Course/SwiftGen/Strings.swift index bdcfddabe..78e663241 100644 --- a/Course/Course/SwiftGen/Strings.swift +++ b/Course/Course/SwiftGen/Strings.swift @@ -237,17 +237,17 @@ public enum CourseLocalization { public enum Outline { /// Certificate public static let certificate = CourseLocalization.tr("Localizable", "OUTLINE.CERTIFICATE", fallback: "Certificate") - /// Localizable.strings - /// Course - /// - /// Created by  Stepanok Ivan on 26.09.2022. - public static let congratulations = CourseLocalization.tr("Localizable", "OUTLINE.CONGRATULATIONS", fallback: "Congratulations!") /// This course hasn't started yet. public static let courseHasntStarted = CourseLocalization.tr("Localizable", "OUTLINE.COURSE_HASNT_STARTED", fallback: "This course hasn't started yet.") /// Course videos public static let courseVideos = CourseLocalization.tr("Localizable", "OUTLINE.COURSE_VIDEOS", fallback: "Course videos") - /// You’ve completed the course - public static let passedTheCourse = CourseLocalization.tr("Localizable", "OUTLINE.PASSED_THE_COURSE", fallback: "You’ve completed the course") + /// Localizable.strings + /// Course + /// + /// Created by  Stepanok Ivan on 26.09.2022. + public static func passedTheCourse(_ p1: Any) -> String { + return CourseLocalization.tr("Localizable", "OUTLINE.PASSED_THE_COURSE", String(describing: p1), fallback: "Congratulations, you have earned this course certificate in “%@.”") + } /// View certificate public static let viewCertificate = CourseLocalization.tr("Localizable", "OUTLINE.VIEW_CERTIFICATE", fallback: "View certificate") } diff --git a/Course/Course/en.lproj/Localizable.strings b/Course/Course/en.lproj/Localizable.strings index 5f785ca8d..b22fce96b 100644 --- a/Course/Course/en.lproj/Localizable.strings +++ b/Course/Course/en.lproj/Localizable.strings @@ -6,8 +6,7 @@ */ -"OUTLINE.CONGRATULATIONS" = "Congratulations!"; -"OUTLINE.PASSED_THE_COURSE" = "You’ve completed the course"; +"OUTLINE.PASSED_THE_COURSE" = "Congratulations, you have earned this course certificate in “%@\.”"; "OUTLINE.VIEW_CERTIFICATE" = "View certificate"; "OUTLINE.CERTIFICATE" = "Certificate"; "OUTLINE.COURSE_VIDEOS" = "Course videos"; diff --git a/Course/Course/uk.lproj/Localizable.strings b/Course/Course/uk.lproj/Localizable.strings index 38de538b8..59a57991a 100644 --- a/Course/Course/uk.lproj/Localizable.strings +++ b/Course/Course/uk.lproj/Localizable.strings @@ -6,8 +6,7 @@ */ -"OUTLINE.CONGRATULATIONS" = "Вітаємо!"; -"OUTLINE.PASSED_THE_COURSE" = "Ви пройшли курс"; +"OUTLINE.PASSED_THE_COURSE" = "Вітаємо, ви отримали сертифікат курсу “%@\.“"; "OUTLINE.VIEW_CERTIFICATE" = "Переглянути сертифікат"; "OUTLINE.CERTIFICATE" = "Сертифікат"; "OUTLINE.COURSE_VIDEOS" = "Відео з курсу"; diff --git a/Documentation/Theming_implementation.md b/Documentation/Theming_implementation.md index 77d1b74cf..33e5b577d 100644 --- a/Documentation/Theming_implementation.md +++ b/Documentation/Theming_implementation.md @@ -79,6 +79,7 @@ assets: ### Font The `whitelabel.yaml` configuration may contain the path to a font file, and an existing font in the project will be replaced with this font. This ttf file must contain multiple ttf fonts "merged" into a single ttf file. Font types used in the application: +- light - regular - medium - semiBold @@ -91,6 +92,7 @@ font: project_font_file_path: 'path/to/font/file/in/project/font.ttf' # path to existing ttf font file in project project_font_names_json_path: 'path/to/names/file/in project/fonts.json' # path to existing font names json-file in project font_names: + light: 'FontName-Light' regular: 'FontName-Regular' medium: 'FontName-Medium' semiBold: 'FontName-Semibold' diff --git a/Theme/Theme/Fonts/FontParser.swift b/Theme/Theme/Fonts/FontParser.swift index cd2adbba6..d7ed99b24 100644 --- a/Theme/Theme/Fonts/FontParser.swift +++ b/Theme/Theme/Fonts/FontParser.swift @@ -8,7 +8,7 @@ import Foundation public enum FontIdentifier: String { - case regular, medium, semiBold, bold + case light, regular, medium, semiBold, bold } public class FontParser { diff --git a/Theme/Theme/Fonts/fonts.json b/Theme/Theme/Fonts/fonts.json index 030a32bc3..2af091657 100644 --- a/Theme/Theme/Fonts/fonts.json +++ b/Theme/Theme/Fonts/fonts.json @@ -1,4 +1,5 @@ { + "light": "SFPro-Light", "regular": "SFPro-Regular", "medium": "SFPro-Medium", "semiBold": "SFPro-Semibold", diff --git a/Theme/Theme/Theme.swift b/Theme/Theme/Theme.swift index fba4e1b06..a535683d9 100644 --- a/Theme/Theme/Theme.swift +++ b/Theme/Theme/Theme.swift @@ -197,6 +197,7 @@ public struct Theme { public static let bodyLarge: Font = .custom(fontsParser.fontName(for: .regular), size: 16) public static let bodyMedium: Font = .custom(fontsParser.fontName(for: .regular), size: 14) public static let bodySmall: Font = .custom(fontsParser.fontName(for: .regular), size: 12) + public static let bodyMicro: Font = .custom(fontsParser.fontName(for: .light), size: 11) public static let labelLarge: Font = .custom(fontsParser.fontName(for: .medium), size: 14) public static let labelMedium: Font = .custom(fontsParser.fontName(for: .regular), size: 12) diff --git a/config_script/whitelabel.py b/config_script/whitelabel.py index a6c3b41a3..00eab9f92 100644 --- a/config_script/whitelabel.py +++ b/config_script/whitelabel.py @@ -54,6 +54,7 @@ class WhitelabelApp: project_font_file_path: 'path/to/font/file/in/project/font.ttf' # path to existing ttf font file in project project_font_names_json_path: 'path/to/names/file/in project/fonts.json' # path to existing font names json-file in project font_names: + light: 'SFPro-Light' regular: 'FontName-Regular' medium: 'FontName-Medium' semiBold: 'FontName-Semibold' @@ -545,4 +546,4 @@ def main(): if __name__ == "__main__": main() - \ No newline at end of file +