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
+