From 3d0eb60423a0e386528fbc2270a5e1e8288a55ad Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Fri, 1 Dec 2023 11:02:20 +0100 Subject: [PATCH 01/31] chore: added whitelabel script, added login background color --- .../Presentation/Login/SignInView.swift | 2 +- .../LoginBackground.colorset/Contents.json | 38 ++++ Core/Core/SwiftGen/Assets.swift | 1 + Core/Core/Theme.swift | 1 + OpenEdX/Info.plist | 8 +- Podfile.lock | 2 +- config_script/whitelabel.py | 165 ++++++++++++++++++ config_script/whitelabel.yaml | 18 ++ 8 files changed, 229 insertions(+), 6 deletions(-) create mode 100644 Core/Core/Assets.xcassets/Colors/LoginBackground.colorset/Contents.json create mode 100644 config_script/whitelabel.py create mode 100644 config_script/whitelabel.yaml diff --git a/Authorization/Authorization/Presentation/Login/SignInView.swift b/Authorization/Authorization/Presentation/Login/SignInView.swift index 8ad4a9949..3e659d4d4 100644 --- a/Authorization/Authorization/Presentation/Login/SignInView.swift +++ b/Authorization/Authorization/Presentation/Login/SignInView.swift @@ -116,7 +116,7 @@ public struct SignInView: View { } .padding(.horizontal, 24) .padding(.top, 50) - }.roundedBackground(Theme.Colors.background) + }.roundedBackground(Theme.Colors.loginBackground) .scrollAvoidKeyboard(dismissKeyboardByTap: true) } diff --git a/Core/Core/Assets.xcassets/Colors/LoginBackground.colorset/Contents.json b/Core/Core/Assets.xcassets/Colors/LoginBackground.colorset/Contents.json new file mode 100644 index 000000000..5ca627f9f --- /dev/null +++ b/Core/Core/Assets.xcassets/Colors/LoginBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x2E", + "green" : "0x20", + "red" : "0x18" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Core/Core/SwiftGen/Assets.swift b/Core/Core/SwiftGen/Assets.swift index bd88577ed..2de752d12 100644 --- a/Core/Core/SwiftGen/Assets.swift +++ b/Core/Core/SwiftGen/Assets.swift @@ -35,6 +35,7 @@ public enum CoreAssets { public static let cardViewStroke = ColorAsset(name: "CardViewStroke") public static let certificateForeground = ColorAsset(name: "CertificateForeground") public static let commentCellBackground = ColorAsset(name: "CommentCellBackground") + public static let loginBackground = ColorAsset(name: "LoginBackground") public static let shadowColor = ColorAsset(name: "ShadowColor") public static let snackbarErrorColor = ColorAsset(name: "SnackbarErrorColor") public static let snackbarErrorTextColor = ColorAsset(name: "SnackbarErrorTextColor") diff --git a/Core/Core/Theme.swift b/Core/Core/Theme.swift index b63ad99f9..af08124de 100644 --- a/Core/Core/Theme.swift +++ b/Core/Core/Theme.swift @@ -15,6 +15,7 @@ public struct Theme { public private(set) static var alert = CoreAssets.alert.swiftUIColor public private(set) static var avatarStroke = CoreAssets.avatarStroke.swiftUIColor public private(set) static var background = CoreAssets.background.swiftUIColor + public private(set) static var loginBackground = CoreAssets.loginBackground.swiftUIColor public private(set) static var backgroundStroke = CoreAssets.backgroundStroke.swiftUIColor public private(set) static var cardViewBackground = CoreAssets.cardViewBackground.swiftUIColor public private(set) static var cardViewStroke = CoreAssets.cardViewStroke.swiftUIColor diff --git a/OpenEdX/Info.plist b/OpenEdX/Info.plist index 452ba08ae..e4bdcba95 100644 --- a/OpenEdX/Info.plist +++ b/OpenEdX/Info.plist @@ -10,10 +10,6 @@ ITSAppUsesNonExemptEncryption - UIAppFonts - - UIViewControllerBasedStatusBarAppearance - LSApplicationQueriesSchemes googlegmail @@ -24,5 +20,9 @@ fastmail protonmail + UIAppFonts + + UIViewControllerBasedStatusBarAppearance + diff --git a/Podfile.lock b/Podfile.lock index 4afedd8c7..8fbb7f282 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -182,4 +182,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: a44d8de5a5803eb3e3c995134c79c3dad959dbf7 -COCOAPODS: 1.13.0 +COCOAPODS: 1.14.2 diff --git a/config_script/whitelabel.py b/config_script/whitelabel.py new file mode 100644 index 000000000..b3be25a4f --- /dev/null +++ b/config_script/whitelabel.py @@ -0,0 +1,165 @@ +import argparse +import logging +import os +import shutil +import subprocess +import sys +import yaml +import json + +class WhitelabelApp: + def __init__(self, **kwargs): + EXAMPLE_CONFIG_FILE = """ + --- + # Notes: + # Config file can contain next optins: + + assets: + images: + image1: # Asset name + imageName: 'some_image.svg' # image file name in Assets.xcassets/image1.imageset + imageNameImport: 'new_image.pdf' # optional: image to replace imageName, placed in Config. Don't need if file name is the same + image2: # Asset name + currentPath: 'SomeFolder' # Path to image2.imageset inside Assets.xcassets + imageName: 'Rectangle.png' # image file name in Assets.xcassets/SomeFolder/image2.imageset + darkImageName: 'RectangleDark.png' # image file name for dark appearance in Assets.xcassets/SomeFolder/image2.imageset + darkImageNameImport: 'NewDarkRectangle.png' # optional: image to replace darkImageName, placed in Config. Don't need if file name is the same + colors: + LoginBackground: # color asset name in Assets + light: '#FFFFFF' + dark: '#ED5C13' + """ + + self.project_assets = kwargs.get('project_assets') + if not self.project_assets: + self.project_assets = '.' + + self.assets_dir = kwargs.get('assets_dir') + if not self.assets_dir: + self.assets_dir = '.' + + self.assets = kwargs.get('assets', {}) + + def whitelabel(self): + # Update the properties, resources, and configuration of the current app. + # self.update_plist() + self.copy_assets() + + def copy_assets(self): + self.replace_images() + self.replace_colors() + + def replace_images(self): + if self.assets: + for name, image in self.assets["images"].items(): + currentPath = image["currentPath"] if "currentPath" in image else "" + hasDark = True if "darkImageName" in image else False + imageName = image["imageName"] + imageNameImport = image["imageNameImport"] if "imageNameImport" in image else imageName + darkImageName = image["darkImageName"] if "darkImageName" in image else "" + darkImageNameImport = image["darkImageNameImport"] if "darkImageNameImport" in image else darkImageName + path_to_imageset = os.path.join(self.project_assets, currentPath, name+'.imageset') + # Delete current file(s) + file_path = os.path.join(path_to_imageset, imageName) + if os.path.exists(file_path): + os.remove(file_path) + if hasDark: + dark_file_path = os.path.join(path_to_imageset, darkImageName) + if os.path.exists(dark_file_path): + os.remove(dark_file_path) + # Change Contents.json + content_json_path = os.path.join(path_to_imageset, 'Contents.json') + with open(content_json_path, 'r') as openfile: + contents_string = openfile.read() + contents_string = contents_string.replace(imageName, imageNameImport) + if hasDark: + contents_string = contents_string.replace(darkImageName, darkImageNameImport) + with open(content_json_path, 'w') as openfile: + openfile.write(contents_string) + # Copy new file(s) + file_to_copy_path = os.path.join(self.assets_dir, imageNameImport) + shutil.copy(file_to_copy_path, path_to_imageset) + if hasDark: + dark_file_to_copy_path = os.path.join(self.assets_dir, darkImageNameImport) + shutil.copy(dark_file_to_copy_path, path_to_imageset) + else: + logging.debug("No assets to copy to %s", self.project_assets) + + def replace_colors(self): + if self.assets: + for name, color in self.assets["colors"].items(): + currentPath = color["currentPath"] if "currentPath" in color else "" + path_to_colorset = os.path.join(self.project_assets, 'Colors', currentPath, name+'.colorset') + light_color = color["light"] + dark_color = color["dark"] + # Change Contents.json + content_json_path = os.path.join(path_to_colorset, 'Contents.json') + with open(content_json_path, 'r') as openfile: + json_object = json.load(openfile) + for key in range(len(json_object["colors"])): + if "appearances" in json_object["colors"][key]: + # dark + changed_components = self.change_color_components(json_object["colors"][key]["color"]["components"], dark_color, name) + json_object["colors"][key]["color"]["components"] = changed_components + else: + # light + changed_components = self.change_color_components(json_object["colors"][key]["color"]["components"], light_color, name) + json_object["colors"][key]["color"]["components"] = changed_components + new_json = json.dumps(json_object) + with open(content_json_path, 'w') as openfile: + openfile.write(new_json) + else: + logging.debug("No assets to copy to %s", self.project_assets) + + def change_color_components(self, components, color, name): + color = color.replace("#", "") + if len(color) != 6: + print('Config for color "'+name+'" is incorrect') + else: + components["red"] = "0x"+color[0]+color[1] + components["green"] = "0x"+color[2]+color[3] + components["blue"] = "0x"+color[4]+color[5] + return components + +def main(): + """ + Parse the command line arguments, and pass them to WhitelabelApp. + """ + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--help-config-file', action='store_true', help="Print out a sample config-file, and exit") + parser.add_argument('--config-file', '-c', help="Path to the configuration file") + parser.add_argument('--verbose', '-v', action='count', help="Enable verbose logging. Repeat -v for more output.") + args = parser.parse_args() + + # DEBUG VARS + # args.config_file = "whitelabel.yaml" + # args.verbose = 2 + + if args.help_config_file: + print(WhitelabelApp.EXAMPLE_CONFIG_FILE) + sys.exit(0) + + if not args.config_file: + parser.print_help() + sys.exit(1) + + log_level = logging.WARN + if args.verbose > 0: + log_level = logging.INFO + if args.verbose > 1: + log_level = logging.DEBUG + logging.basicConfig(level=log_level) + + with open(args.config_file) as f: + config = yaml.safe_load(f) or {} + + # Use the config_file's directory as the default config_dir + config.setdefault('config_dir', os.path.dirname(args.config_file)) + + whitelabeler = WhitelabelApp(**config) + whitelabeler.whitelabel() + + +if __name__ == "__main__": + main() + \ No newline at end of file diff --git a/config_script/whitelabel.yaml b/config_script/whitelabel.yaml new file mode 100644 index 000000000..add0698ef --- /dev/null +++ b/config_script/whitelabel.yaml @@ -0,0 +1,18 @@ +project_assets: './Core/Core/Assets.xcassets' +assets_dir: './edx-mobile-config/openEdXAssets' + +assets: + images: + appLogo: + imageName: 'Frame 4 1.svg' + imageNameImport: 'logoEdX.pdf' + authBackground: + currentPath: 'Auth' + imageName: 'Rectangle.png' + darkImageName: 'Rectangle-2.png' + colors: + LoginBackground: + currrentPath: '' + light: '#FFFFFF' + dark: '#ED5C13' + From 103cfec4cbe886060d3e3a9cd9bae5ab95d97635 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Fri, 1 Dec 2023 13:25:57 +0100 Subject: [PATCH 02/31] style: added rounded corner FF --- Core/Core.xcodeproj/project.pbxproj | 61 +++++++++++++++++++ .../AccentButtonColor.colorset/Contents.json | 38 ++++++++++++ Core/Core/Configuration/Config/Config.swift | 1 + .../Configuration/Config/ThemeConfig.swift | 28 +++++++++ Core/Core/SwiftGen/Assets.swift | 1 + Core/Core/Theme.swift | 15 ++++- Core/Core/View/Base/StyledButton.swift | 9 +-- config_script/whitelabel.yaml | 4 ++ 8 files changed, 151 insertions(+), 6 deletions(-) create mode 100644 Core/Core/Assets.xcassets/Colors/AccentButtonColor.colorset/Contents.json create mode 100644 Core/Core/Configuration/Config/ThemeConfig.swift diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 036526d61..d02e3348f 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -120,6 +120,26 @@ 0770DE7928D0C4A9006D8A5D /* RoundedCorners.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE7828D0C4A9006D8A5D /* RoundedCorners.swift */; }; 0770DE7B28D0C78C006D8A5D /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE7A28D0C78C006D8A5D /* Theme.swift */; }; 07DDFCBD29A780BB00572595 /* UINavigationController+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07DDFCBC29A780BB00572595 /* UINavigationController+Animation.swift */; }; + A55D4EBA2B19F8F0006628AA /* ThemeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A55D4EB92B19F8F0006628AA /* ThemeConfig.swift */; }; + BA8B3A2F2AD546A700D25EF5 /* DebugLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8B3A2E2AD546A700D25EF5 /* DebugLog.swift */; }; + BA8FA6612AD5974300EA029A /* AppleSingInProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA6602AD5974300EA029A /* AppleSingInProvider.swift */; }; + BA8FA6682AD59A5700EA029A /* LabelButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA6672AD59A5700EA029A /* LabelButton.swift */; }; + BA8FA66A2AD59B5500EA029A /* GoogleSingInProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA6692AD59B5500EA029A /* GoogleSingInProvider.swift */; }; + BA8FA66C2AD59BBC00EA029A /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = BA8FA66B2AD59BBC00EA029A /* GoogleSignIn */; }; + BA8FA66E2AD59E7D00EA029A /* FacebookSingInProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA66D2AD59E7D00EA029A /* FacebookSingInProvider.swift */; }; + BA8FA6702AD59EA300EA029A /* MicrosoftSingInProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8FA66F2AD59EA300EA029A /* MicrosoftSingInProvider.swift */; }; + BAAD62C62AFCF00B000E6103 /* CustomDisclosureGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAAD62C52AFCF00B000E6103 /* CustomDisclosureGroup.swift */; }; + BADB3F532AD6B3A5004D5CFA /* MSAL in Frameworks */ = {isa = PBXBuildFile; productRef = BADB3F522AD6B3A5004D5CFA /* MSAL */; }; + BADB3F5B2AD6EC56004D5CFA /* ResultExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BADB3F5A2AD6EC56004D5CFA /* ResultExtension.swift */; }; + BAF0D4CB2AD6AE14007AC334 /* FacebookLogin in Frameworks */ = {isa = PBXBuildFile; productRef = BAF0D4CA2AD6AE14007AC334 /* FacebookLogin */; }; + BAFB99822B0E2354007D09F9 /* FacebookConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */; }; + BAFB99842B0E282E007D09F9 /* MicrosoftConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99832B0E282E007D09F9 /* MicrosoftConfig.swift */; }; + BAFB99892B0F4E80007D09F9 /* ScrollSlidingTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99862B0F4E80007D09F9 /* ScrollSlidingTabBar.swift */; }; + BAFB998A2B0F4E80007D09F9 /* SlidingTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99872B0F4E80007D09F9 /* SlidingTabBar.swift */; }; + BAFB998B2B0F4E80007D09F9 /* FrameReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99882B0F4E80007D09F9 /* FrameReader.swift */; }; + BAFB998E2B0F70F1007D09F9 /* CustomError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB998D2B0F70F1007D09F9 /* CustomError.swift */; }; + BAFB99902B14B377007D09F9 /* GoogleConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB998F2B14B377007D09F9 /* GoogleConfig.swift */; }; + BAFB99922B14E23D007D09F9 /* AppleSignInConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAFB99912B14E23D007D09F9 /* AppleSignInConfig.swift */; }; C8C446EF233F81B9FABB77D2 /* Pods_App_Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 349B90CD6579F7B8D257E515 /* Pods_App_Core.framework */; }; CFC84952299F8B890055E497 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = CFC84951299F8B890055E497 /* Debounce.swift */; }; DB4EBE9E2B1075E100CB4DC4 /* ConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB4EBE9D2B1075E100CB4DC4 /* ConfigTests.swift */; }; @@ -261,6 +281,23 @@ 3B74C6685E416657F3C5F5A8 /* Pods-App-Core.releaseprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.releaseprod.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.releaseprod.xcconfig"; sourceTree = ""; }; 60153262DBC2F9E660D7E11B /* Pods-App-Core.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.release.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.release.xcconfig"; sourceTree = ""; }; 9D5B06CAA99EA5CD49CBE2BB /* Pods-App-Core.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugdev.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugdev.xcconfig"; sourceTree = ""; }; + A55D4EB92B19F8F0006628AA /* ThemeConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeConfig.swift; sourceTree = ""; }; + BA8B3A2E2AD546A700D25EF5 /* DebugLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugLog.swift; sourceTree = ""; }; + BA8FA6602AD5974300EA029A /* AppleSingInProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleSingInProvider.swift; sourceTree = ""; }; + BA8FA6672AD59A5700EA029A /* LabelButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelButton.swift; sourceTree = ""; }; + BA8FA6692AD59B5500EA029A /* GoogleSingInProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleSingInProvider.swift; sourceTree = ""; }; + BA8FA66D2AD59E7D00EA029A /* FacebookSingInProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FacebookSingInProvider.swift; sourceTree = ""; }; + BA8FA66F2AD59EA300EA029A /* MicrosoftSingInProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosoftSingInProvider.swift; sourceTree = ""; }; + BAAD62C52AFCF00B000E6103 /* CustomDisclosureGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDisclosureGroup.swift; sourceTree = ""; }; + BADB3F5A2AD6EC56004D5CFA /* ResultExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResultExtension.swift; sourceTree = ""; }; + BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FacebookConfig.swift; sourceTree = ""; }; + BAFB99832B0E282E007D09F9 /* MicrosoftConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrosoftConfig.swift; sourceTree = ""; }; + BAFB99862B0F4E80007D09F9 /* ScrollSlidingTabBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollSlidingTabBar.swift; sourceTree = ""; }; + BAFB99872B0F4E80007D09F9 /* SlidingTabBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SlidingTabBar.swift; sourceTree = ""; }; + BAFB99882B0F4E80007D09F9 /* FrameReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FrameReader.swift; sourceTree = ""; }; + BAFB998D2B0F70F1007D09F9 /* CustomError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomError.swift; sourceTree = ""; }; + BAFB998F2B14B377007D09F9 /* GoogleConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleConfig.swift; sourceTree = ""; }; + BAFB99912B14E23D007D09F9 /* AppleSignInConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleSignInConfig.swift; sourceTree = ""; }; C7E5BCE79CE297B20777B27A /* Pods-App-Core.debugprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugprod.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugprod.xcconfig"; sourceTree = ""; }; CFC84951299F8B890055E497 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = ""; }; DB4EBE9D2B1075E100CB4DC4 /* ConfigTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigTests.swift; sourceTree = ""; }; @@ -611,6 +648,24 @@ DBF6F2472B01E20A0098414B /* ConfigTests.swift */, ); path = Configuration; + sourceTree = ""; + }; + BAFB99852B0F4E80007D09F9 /* ScrollSlidingTabBar */ = { + isa = PBXGroup; + children = ( + BAFB99862B0F4E80007D09F9 /* ScrollSlidingTabBar.swift */, + BAFB99872B0F4E80007D09F9 /* SlidingTabBar.swift */, + BAFB99882B0F4E80007D09F9 /* FrameReader.swift */, + ); + path = ScrollSlidingTabBar; + sourceTree = ""; + }; + BAFB998C2B0F70F1007D09F9 /* Error */ = { + isa = PBXGroup; + children = ( + BAFB998D2B0F70F1007D09F9 /* CustomError.swift */, + ); + path = Error; sourceTree = ""; }; C9DFE47E699CFFA85A77AF2C /* Pods */ = { @@ -660,6 +715,11 @@ DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */, DBF6F2492B0380E00098414B /* FeaturesConfig.swift */, DBF6F2452B01DAFE0098414B /* AgreementConfig.swift */, + BAFB99812B0E2354007D09F9 /* FacebookConfig.swift */, + BAFB99832B0E282E007D09F9 /* MicrosoftConfig.swift */, + BAFB998F2B14B377007D09F9 /* GoogleConfig.swift */, + BAFB99912B14E23D007D09F9 /* AppleSignInConfig.swift */, + A55D4EB92B19F8F0006628AA /* ThemeConfig.swift */, ); path = Config; sourceTree = ""; @@ -893,6 +953,7 @@ 024FCD0028EF1CD300232339 /* WebBrowser.swift in Sources */, 027BD3B52909475900392132 /* KeyboardStateObserver.swift in Sources */, 0283347D28D4D3DE00C828FC /* Data_Discovery.swift in Sources */, + A55D4EBA2B19F8F0006628AA /* ThemeConfig.swift in Sources */, 07DDFCBD29A780BB00572595 /* UINavigationController+Animation.swift in Sources */, 023A4DD4299E66BD006C0E48 /* OfflineSnackBarView.swift in Sources */, 021D925728DCF12900ACC565 /* AlertView.swift in Sources */, diff --git a/Core/Core/Assets.xcassets/Colors/AccentButtonColor.colorset/Contents.json b/Core/Core/Assets.xcassets/Colors/AccentButtonColor.colorset/Contents.json new file mode 100644 index 000000000..00d59cb46 --- /dev/null +++ b/Core/Core/Assets.xcassets/Colors/AccentButtonColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "0.408", + "red" : "0.235" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.976", + "green" : "0.471", + "red" : "0.329" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Core/Core/Configuration/Config/Config.swift b/Core/Core/Configuration/Config/Config.swift index 49f1ffdc5..139e3dc30 100644 --- a/Core/Core/Configuration/Config/Config.swift +++ b/Core/Core/Configuration/Config/Config.swift @@ -16,6 +16,7 @@ public protocol ConfigProtocol { var agreement: AgreementConfig { get } var firebase: FirebaseConfig { get } var features: FeaturesConfig { get } + var theme: ThemeConfig { get } } public enum TokenType: String { diff --git a/Core/Core/Configuration/Config/ThemeConfig.swift b/Core/Core/Configuration/Config/ThemeConfig.swift new file mode 100644 index 000000000..aa7c98769 --- /dev/null +++ b/Core/Core/Configuration/Config/ThemeConfig.swift @@ -0,0 +1,28 @@ +// +// ThemeConfig.swift +// Core +// +// Created by Anton Yarmolenka on 01/12/2023. +// + +import Foundation + +private enum ThemeKeys: String { + case isRoundedCorners = "ROUNDED_CORNERS_STYLE" +} + +public final class ThemeConfig: NSObject { + public var isRoundedCorners: Bool = true + + init(dictionary: [String: AnyObject]) { + super.init() + isRoundedCorners = dictionary[ThemeKeys.isRoundedCorners.rawValue] as? Bool == true + } +} + +private let ThemeKey = "THEME" +extension Config { + public var theme: ThemeConfig { + ThemeConfig(dictionary: self[ThemeKey] as? [String: AnyObject] ?? [:]) + } +} diff --git a/Core/Core/SwiftGen/Assets.swift b/Core/Core/SwiftGen/Assets.swift index 2de752d12..e7e943b89 100644 --- a/Core/Core/SwiftGen/Assets.swift +++ b/Core/Core/SwiftGen/Assets.swift @@ -26,6 +26,7 @@ public typealias AssetImageTypeAlias = ImageAsset.Image public enum CoreAssets { public static let authBackground = ImageAsset(name: "authBackground") public static let checkEmail = ImageAsset(name: "checkEmail") + public static let accentButtonColor = ColorAsset(name: "AccentButtonColor") public static let accentColor = ColorAsset(name: "AccentColor") public static let alert = ColorAsset(name: "Alert") public static let avatarStroke = ColorAsset(name: "AvatarStroke") diff --git a/Core/Core/Theme.swift b/Core/Core/Theme.swift index af08124de..1448a9437 100644 --- a/Core/Core/Theme.swift +++ b/Core/Core/Theme.swift @@ -7,11 +7,13 @@ import Foundation import SwiftUI +import Swinject public struct Theme { public struct Colors { public private(set) static var accentColor = CoreAssets.accentColor.swiftUIColor + public private(set) static var accentButtonColor = CoreAssets.accentButtonColor.swiftUIColor public private(set) static var alert = CoreAssets.alert.swiftUIColor public private(set) static var avatarStroke = CoreAssets.avatarStroke.swiftUIColor public private(set) static var background = CoreAssets.background.swiftUIColor @@ -110,8 +112,17 @@ public struct Theme { public struct Shapes { public static let screenBackgroundRadius = 24.0 public static let cardImageRadius = 10.0 - public static let textInputShape = RoundedRectangle(cornerRadius: 8) - public static let buttonShape = RoundedCorners(tl: 8, tr: 8, bl: 8, br: 8) + public static let textInputShape = { + let config = Container.shared.resolve(ConfigProtocol.self)! + let radius: CGFloat = config.theme.isRoundedCorners ? 8 : 0 + return RoundedRectangle(cornerRadius: radius) + }() + public static let buttonShape = { + let config = Container.shared.resolve(ConfigProtocol.self)! + let radius: CGFloat = config.theme.isRoundedCorners ? 8 : 0 + return RoundedCorners(tl: radius, tr: radius, bl: radius, br: radius) + }() + public static let squareButtonShape = Rectangle() public static let unitButtonShape = RoundedCorners(tl: 21, tr: 21, bl: 21, br: 21) public static let roundedScreenBackgroundShape = RoundedCorners( tl: Theme.Shapes.screenBackgroundRadius, diff --git a/Core/Core/View/Base/StyledButton.swift b/Core/Core/View/Base/StyledButton.swift index fdad6a1d1..1000a7d53 100644 --- a/Core/Core/View/Base/StyledButton.swift +++ b/Core/Core/View/Base/StyledButton.swift @@ -20,7 +20,7 @@ public struct StyledButton: View { public init(_ title: String, action: @escaping () -> Void, isTransparent: Bool = false, - color: Color = Theme.Colors.accentColor, + color: Color = Theme.Colors.accentButtonColor, isActive: Bool = true) { self.title = title self.action = action @@ -51,9 +51,10 @@ public struct StyledButton: View { .fill(isTransparent ? .clear : buttonColor) ) .overlay( - RoundedRectangle(cornerRadius: 8) - .stroke(style: .init(lineWidth: 1, lineCap: .round, lineJoin: .round, miterLimit: 1)) - .foregroundColor(isTransparent ? .white : .clear) + Theme.Shapes.buttonShape + .stroke(style: .init(lineWidth: 1, lineCap: .round, lineJoin: .round, miterLimit: 1)) + .foregroundColor(isTransparent ? .white : .clear) + ) .accessibilityElement(children: .ignore) .accessibilityLabel(title) diff --git a/config_script/whitelabel.yaml b/config_script/whitelabel.yaml index add0698ef..21da97d65 100644 --- a/config_script/whitelabel.yaml +++ b/config_script/whitelabel.yaml @@ -15,4 +15,8 @@ assets: currrentPath: '' light: '#FFFFFF' dark: '#ED5C13' + AccentButtonColor: + light: '#D23228' + dark: '#D23228' + From 7d16778710a18e613de8cd054718b6b4fcd27efd Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Fri, 1 Dec 2023 15:06:18 +0100 Subject: [PATCH 03/31] chore: changed assets for progress bar and unibutton color --- Core/Core/View/Base/ProgressBar.swift | 2 +- Core/Core/View/Base/UnitButtonView.swift | 12 ++++++------ .../Elements/WhatsNewNavigationButton.swift | 14 +++++++++----- config_script/whitelabel.yaml | 3 +++ 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/Core/Core/View/Base/ProgressBar.swift b/Core/Core/View/Base/ProgressBar.swift index 9e75e7985..720f18a32 100644 --- a/Core/Core/View/Base/ProgressBar.swift +++ b/Core/Core/View/Base/ProgressBar.swift @@ -38,7 +38,7 @@ public struct ProgressBar: View { ZStack { Circle() .stroke(lineWidth: lineWidth) - .foregroundColor(Color.blue.opacity(0.3)) + .foregroundColor(Theme.Colors.accentColor.opacity(0.3)) .frame(width: size, height: size) Circle() diff --git a/Core/Core/View/Base/UnitButtonView.swift b/Core/Core/View/Base/UnitButtonView.swift index e6d658c49..bcb66cdc0 100644 --- a/Core/Core/View/Base/UnitButtonView.swift +++ b/Core/Core/View/Base/UnitButtonView.swift @@ -142,22 +142,22 @@ public struct UnitButtonView: View { Theme.Shapes.buttonShape .fill(type == .previous ? Theme.Colors.background - : Theme.Colors.accentColor) + : Theme.Colors.accentButtonColor) .shadow(color: Color.black.opacity(0.25), radius: 21, y: 4) .overlay( - RoundedRectangle(cornerRadius: 8) + Theme.Shapes.buttonShape .stroke(style: .init( lineWidth: 1, lineCap: .round, lineJoin: .round, miterLimit: 1) ) - .foregroundColor(Theme.Colors.accentColor) + .foregroundColor(Theme.Colors.accentButtonColor) ) case .continueLesson, .nextSection, .reload, .finish, .custom: Theme.Shapes.buttonShape - .fill(bgColor ?? Theme.Colors.accentColor) + .fill(bgColor ?? Theme.Colors.accentButtonColor) .shadow(color: (type == .first || type == .next @@ -167,14 +167,14 @@ public struct UnitButtonView: View { || type == .reload) ? Color.black.opacity(0.25) : .clear, radius: 21, y: 4) .overlay( - RoundedRectangle(cornerRadius: 8) + Theme.Shapes.buttonShape .stroke(style: .init( lineWidth: 1, lineCap: .round, lineJoin: .round, miterLimit: 1 )) - .foregroundColor(Theme.Colors.accentColor) + .foregroundColor(Theme.Colors.accentButtonColor) ) } } diff --git a/WhatsNew/WhatsNew/Presentation/Elements/WhatsNewNavigationButton.swift b/WhatsNew/WhatsNew/Presentation/Elements/WhatsNewNavigationButton.swift index 03206348e..d92aed0af 100644 --- a/WhatsNew/WhatsNew/Presentation/Elements/WhatsNewNavigationButton.swift +++ b/WhatsNew/WhatsNew/Presentation/Elements/WhatsNewNavigationButton.swift @@ -45,17 +45,21 @@ struct WhatsNewNavigationButton: View { }.padding(.horizontal, 20) .padding(.vertical, 9) }.fixedSize() - .background(type == .previous + .background( + Theme.Shapes.buttonShape + .fill( + type == .previous ? Theme.Colors.background - : Theme.Colors.accentColor) + : Theme.Colors.accentButtonColor + ) + ) .accessibilityElement(children: .ignore) .accessibilityLabel(type == .previous ? WhatsNewLocalization.buttonPrevious : (type == .next ? WhatsNewLocalization.buttonNext : WhatsNewLocalization.buttonDone )) - .cornerRadius(8) .overlay( - RoundedRectangle(cornerRadius: 8) + Theme.Shapes.buttonShape .stroke(type == .previous - ? Theme.Colors.accentColor + ? Theme.Colors.accentButtonColor : Theme.Colors.background, lineWidth: 1) ) .onTapGesture { action() } diff --git a/config_script/whitelabel.yaml b/config_script/whitelabel.yaml index 21da97d65..7e5d325e9 100644 --- a/config_script/whitelabel.yaml +++ b/config_script/whitelabel.yaml @@ -18,5 +18,8 @@ assets: AccentButtonColor: light: '#D23228' dark: '#D23228' + AccentColor: + light: '#454545' + dark: '#454545' From 038db77b40c230d46dac10ce4ff0682644fdb2ec Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Fri, 1 Dec 2023 17:26:03 +0100 Subject: [PATCH 04/31] chore: added multiple assets supporting --- config_script/whitelabel.py | 38 ++++++++++++----------- config_script/whitelabel.yaml | 57 +++++++++++++++++++++++------------ 2 files changed, 58 insertions(+), 37 deletions(-) diff --git a/config_script/whitelabel.py b/config_script/whitelabel.py index b3be25a4f..6784bd267 100644 --- a/config_script/whitelabel.py +++ b/config_script/whitelabel.py @@ -30,9 +30,9 @@ def __init__(self, **kwargs): dark: '#ED5C13' """ - self.project_assets = kwargs.get('project_assets') - if not self.project_assets: - self.project_assets = '.' + # self.project_assets = kwargs.get('project_assets') + # if not self.project_assets: + # self.project_assets = '.' self.assets_dir = kwargs.get('assets_dir') if not self.assets_dir: @@ -46,19 +46,26 @@ def whitelabel(self): self.copy_assets() def copy_assets(self): - self.replace_images() - self.replace_colors() - - def replace_images(self): if self.assets: - for name, image in self.assets["images"].items(): + for asset in self.assets.items(): + self.replace_images(asset[1]) + self.replace_colors(asset[1]) + else: + logging.debug("Assets not found") + + + + def replace_images(self, asset): + if "images" in asset : + assetPath = asset["imagesPath"] if "imagesPath" in asset else "" + for name, image in asset["images"].items(): currentPath = image["currentPath"] if "currentPath" in image else "" hasDark = True if "darkImageName" in image else False imageName = image["imageName"] imageNameImport = image["imageNameImport"] if "imageNameImport" in image else imageName darkImageName = image["darkImageName"] if "darkImageName" in image else "" darkImageNameImport = image["darkImageNameImport"] if "darkImageNameImport" in image else darkImageName - path_to_imageset = os.path.join(self.project_assets, currentPath, name+'.imageset') + path_to_imageset = os.path.join(assetPath, currentPath, name+'.imageset') # Delete current file(s) file_path = os.path.join(path_to_imageset, imageName) if os.path.exists(file_path): @@ -82,14 +89,13 @@ def replace_images(self): if hasDark: dark_file_to_copy_path = os.path.join(self.assets_dir, darkImageNameImport) shutil.copy(dark_file_to_copy_path, path_to_imageset) - else: - logging.debug("No assets to copy to %s", self.project_assets) - def replace_colors(self): - if self.assets: - for name, color in self.assets["colors"].items(): + def replace_colors(self, asset): + if "colors" in asset: + colorsPath = asset["colorsPath"] if "colorsPath" in asset else "" + for name, color in asset["colors"].items(): currentPath = color["currentPath"] if "currentPath" in color else "" - path_to_colorset = os.path.join(self.project_assets, 'Colors', currentPath, name+'.colorset') + path_to_colorset = os.path.join(colorsPath, currentPath, name+'.colorset') light_color = color["light"] dark_color = color["dark"] # Change Contents.json @@ -108,8 +114,6 @@ def replace_colors(self): new_json = json.dumps(json_object) with open(content_json_path, 'w') as openfile: openfile.write(new_json) - else: - logging.debug("No assets to copy to %s", self.project_assets) def change_color_components(self, components, color, name): color = color.replace("#", "") diff --git a/config_script/whitelabel.yaml b/config_script/whitelabel.yaml index 7e5d325e9..093d65ce6 100644 --- a/config_script/whitelabel.yaml +++ b/config_script/whitelabel.yaml @@ -1,25 +1,42 @@ -project_assets: './Core/Core/Assets.xcassets' +# project_assets: './Core/Core/Assets.xcassets' assets_dir: './edx-mobile-config/openEdXAssets' assets: - images: - appLogo: - imageName: 'Frame 4 1.svg' - imageNameImport: 'logoEdX.pdf' - authBackground: - currentPath: 'Auth' - imageName: 'Rectangle.png' - darkImageName: 'Rectangle-2.png' - colors: - LoginBackground: - currrentPath: '' - light: '#FFFFFF' - dark: '#ED5C13' - AccentButtonColor: - light: '#D23228' - dark: '#D23228' - AccentColor: - light: '#454545' - dark: '#454545' + Core: + imagesPath: 'Core/Core/Assets.xcassets' + colorsPath: 'Core/Core/Assets.xcassets/Colors' + images: + appLogo: + imageName: 'Frame 4 1.svg' + imageNameImport: 'logoEdX.pdf' + authBackground: + currentPath: 'Auth' + imageName: 'Rectangle.png' + darkImageName: 'Rectangle-2.png' + colors: + LoginBackground: + currrentPath: '' + light: '#FFFFFF' + dark: '#ED5C13' + AccentButtonColor: + light: '#D23228' + dark: '#D23228' + AccentColor: + light: '#454545' + dark: '#454545' + OpenEdX: + imagesPath: 'OpenEdX/Assets.xcassets' + colorsPath: 'OpenEdX/Assets.xcassets' + images: + appLogo: + imageName: 'Group 21.svg' + imageNameImport: 'appIcon.png' + colors: + AccentColor: + light: '#454545' + dark: '#454545' + SplachBackground: + light: '#0E3639' + dark: '#0E3639' From e3b362232bc848734c45894340532fc4a1594084 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Fri, 1 Dec 2023 17:34:09 +0100 Subject: [PATCH 05/31] chore: move whitelabel config into config repo --- config_script/whitelabel.yaml | 42 ----------------------------------- 1 file changed, 42 deletions(-) delete mode 100644 config_script/whitelabel.yaml diff --git a/config_script/whitelabel.yaml b/config_script/whitelabel.yaml deleted file mode 100644 index 093d65ce6..000000000 --- a/config_script/whitelabel.yaml +++ /dev/null @@ -1,42 +0,0 @@ -# project_assets: './Core/Core/Assets.xcassets' -assets_dir: './edx-mobile-config/openEdXAssets' - -assets: - Core: - imagesPath: 'Core/Core/Assets.xcassets' - colorsPath: 'Core/Core/Assets.xcassets/Colors' - images: - appLogo: - imageName: 'Frame 4 1.svg' - imageNameImport: 'logoEdX.pdf' - authBackground: - currentPath: 'Auth' - imageName: 'Rectangle.png' - darkImageName: 'Rectangle-2.png' - colors: - LoginBackground: - currrentPath: '' - light: '#FFFFFF' - dark: '#ED5C13' - AccentButtonColor: - light: '#D23228' - dark: '#D23228' - AccentColor: - light: '#454545' - dark: '#454545' - OpenEdX: - imagesPath: 'OpenEdX/Assets.xcassets' - colorsPath: 'OpenEdX/Assets.xcassets' - images: - appLogo: - imageName: 'Group 21.svg' - imageNameImport: 'appIcon.png' - colors: - AccentColor: - light: '#454545' - dark: '#454545' - SplachBackground: - light: '#0E3639' - dark: '#0E3639' - - From 53be070e1104babac199617ad2451911199d47b7 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Wed, 6 Dec 2023 13:56:03 +0100 Subject: [PATCH 06/31] chore: moved extra Assets to Theme target --- .../Colors/AccentButtonColor.colorset/Contents.json | 0 .../Colors/LoginBackground.colorset/Contents.json | 12 ++++++------ Theme/Theme/SwiftGen/ThemeAssets.swift | 2 ++ Theme/Theme/Theme.swift | 2 ++ 4 files changed, 10 insertions(+), 6 deletions(-) rename {Core/Core => Theme/Theme}/Assets.xcassets/Colors/AccentButtonColor.colorset/Contents.json (100%) rename {Core/Core => Theme/Theme}/Assets.xcassets/Colors/LoginBackground.colorset/Contents.json (76%) diff --git a/Core/Core/Assets.xcassets/Colors/AccentButtonColor.colorset/Contents.json b/Theme/Theme/Assets.xcassets/Colors/AccentButtonColor.colorset/Contents.json similarity index 100% rename from Core/Core/Assets.xcassets/Colors/AccentButtonColor.colorset/Contents.json rename to Theme/Theme/Assets.xcassets/Colors/AccentButtonColor.colorset/Contents.json diff --git a/Core/Core/Assets.xcassets/Colors/LoginBackground.colorset/Contents.json b/Theme/Theme/Assets.xcassets/Colors/LoginBackground.colorset/Contents.json similarity index 76% rename from Core/Core/Assets.xcassets/Colors/LoginBackground.colorset/Contents.json rename to Theme/Theme/Assets.xcassets/Colors/LoginBackground.colorset/Contents.json index 5ca627f9f..8fef18d07 100644 --- a/Core/Core/Assets.xcassets/Colors/LoginBackground.colorset/Contents.json +++ b/Theme/Theme/Assets.xcassets/Colors/LoginBackground.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0xFF", - "green" : "0xFF", - "red" : "0xFF" + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" } }, "idiom" : "universal" @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0x2E", - "green" : "0x20", - "red" : "0x18" + "blue" : "0.184", + "green" : "0.129", + "red" : "0.098" } }, "idiom" : "universal" diff --git a/Theme/Theme/SwiftGen/ThemeAssets.swift b/Theme/Theme/SwiftGen/ThemeAssets.swift index 5ce3fb3bf..d3609ec38 100644 --- a/Theme/Theme/SwiftGen/ThemeAssets.swift +++ b/Theme/Theme/SwiftGen/ThemeAssets.swift @@ -25,6 +25,7 @@ public typealias AssetImageTypeAlias = ImageAsset.Image // swiftlint:disable identifier_name line_length nesting type_body_length type_name public enum ThemeAssets { public static let authBackground = ImageAsset(name: "authBackground") + public static let accentButtonColor = ColorAsset(name: "AccentButtonColor") public static let accentColor = ColorAsset(name: "AccentColor") public static let alert = ColorAsset(name: "Alert") public static let avatarStroke = ColorAsset(name: "AvatarStroke") @@ -34,6 +35,7 @@ public enum ThemeAssets { public static let cardViewStroke = ColorAsset(name: "CardViewStroke") public static let certificateForeground = ColorAsset(name: "CertificateForeground") public static let commentCellBackground = ColorAsset(name: "CommentCellBackground") + public static let loginBackground = ColorAsset(name: "LoginBackground") public static let shadowColor = ColorAsset(name: "ShadowColor") public static let snackbarErrorColor = ColorAsset(name: "SnackbarErrorColor") public static let snackbarErrorTextColor = ColorAsset(name: "SnackbarErrorTextColor") diff --git a/Theme/Theme/Theme.swift b/Theme/Theme/Theme.swift index f593091a6..e4159fd7c 100644 --- a/Theme/Theme/Theme.swift +++ b/Theme/Theme/Theme.swift @@ -14,9 +14,11 @@ public struct Theme { public struct Colors { public private(set) static var accentColor = ThemeAssets.accentColor.swiftUIColor + public private(set) static var accentButtonColor = ThemeAssets.accentButtonColor.swiftUIColor public private(set) static var alert = ThemeAssets.alert.swiftUIColor public private(set) static var avatarStroke = ThemeAssets.avatarStroke.swiftUIColor public private(set) static var background = ThemeAssets.background.swiftUIColor + public private(set) static var loginBackground = ThemeAssets.loginBackground.swiftUIColor public private(set) static var backgroundStroke = ThemeAssets.backgroundStroke.swiftUIColor public private(set) static var cardViewBackground = ThemeAssets.cardViewBackground.swiftUIColor public private(set) static var cardViewStroke = ThemeAssets.cardViewStroke.swiftUIColor From 7b18e9f1f6ab7b7817935e1f2b79ddfb4184ed30 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Wed, 6 Dec 2023 15:54:32 +0100 Subject: [PATCH 07/31] chore: return back splash screen background color --- .../SplachBackground.colorset/Contents.json | 38 +++++++++++++++++++ Theme/Theme/SwiftGen/ThemeAssets.swift | 1 + 2 files changed, 39 insertions(+) create mode 100644 Theme/Theme/Assets.xcassets/Colors/SplachBackground.colorset/Contents.json diff --git a/Theme/Theme/Assets.xcassets/Colors/SplachBackground.colorset/Contents.json b/Theme/Theme/Assets.xcassets/Colors/SplachBackground.colorset/Contents.json new file mode 100644 index 000000000..99fc4a9bb --- /dev/null +++ b/Theme/Theme/Assets.xcassets/Colors/SplachBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFE", + "green" : "0x7B", + "red" : "0x51" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x2F", + "green" : "0x21", + "red" : "0x19" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Theme/Theme/SwiftGen/ThemeAssets.swift b/Theme/Theme/SwiftGen/ThemeAssets.swift index d3609ec38..910aa663d 100644 --- a/Theme/Theme/SwiftGen/ThemeAssets.swift +++ b/Theme/Theme/SwiftGen/ThemeAssets.swift @@ -40,6 +40,7 @@ public enum ThemeAssets { public static let snackbarErrorColor = ColorAsset(name: "SnackbarErrorColor") public static let snackbarErrorTextColor = ColorAsset(name: "SnackbarErrorTextColor") public static let snackbarInfoAlert = ColorAsset(name: "SnackbarInfoAlert") + public static let splachBackground = ColorAsset(name: "SplachBackground") public static let styledButtonBackground = ColorAsset(name: "StyledButtonBackground") public static let styledButtonText = ColorAsset(name: "StyledButtonText") public static let textPrimary = ColorAsset(name: "TextPrimary") From c1a6922c03a9b7b5d682fa12920fe6595113825f Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Wed, 6 Dec 2023 18:00:53 +0100 Subject: [PATCH 08/31] chore: added logs for whitelabel script --- config_script/whitelabel.py | 114 ++++++++++++++++++++++++------------ 1 file changed, 77 insertions(+), 37 deletions(-) diff --git a/config_script/whitelabel.py b/config_script/whitelabel.py index 6784bd267..8cab6d0c8 100644 --- a/config_script/whitelabel.py +++ b/config_script/whitelabel.py @@ -6,6 +6,7 @@ import sys import yaml import json +import coloredlogs class WhitelabelApp: def __init__(self, **kwargs): @@ -48,14 +49,16 @@ def whitelabel(self): def copy_assets(self): if self.assets: for asset in self.assets.items(): - self.replace_images(asset[1]) - self.replace_colors(asset[1]) + self.replace_images(asset) + self.replace_colors(asset) else: logging.debug("Assets not found") - def replace_images(self, asset): + def replace_images(self, assetData): + asset = assetData[1] + assetName = assetData[0] if "images" in asset : assetPath = asset["imagesPath"] if "imagesPath" in asset else "" for name, image in asset["images"].items(): @@ -66,31 +69,61 @@ def replace_images(self, asset): darkImageName = image["darkImageName"] if "darkImageName" in image else "" darkImageNameImport = image["darkImageNameImport"] if "darkImageNameImport" in image else darkImageName path_to_imageset = os.path.join(assetPath, currentPath, name+'.imageset') - # Delete current file(s) + # conditions to start updating file_path = os.path.join(path_to_imageset, imageName) - if os.path.exists(file_path): - os.remove(file_path) + dark_file_path = os.path.join(path_to_imageset, darkImageName) + files_to_changes_exist = os.path.exists(file_path) # 1 if hasDark: - dark_file_path = os.path.join(path_to_imageset, darkImageName) - if os.path.exists(dark_file_path): - os.remove(dark_file_path) - # Change Contents.json + files_to_changes_exist = files_to_changes_exist and os.path.exists(dark_file_path) content_json_path = os.path.join(path_to_imageset, 'Contents.json') - with open(content_json_path, 'r') as openfile: - contents_string = openfile.read() - contents_string = contents_string.replace(imageName, imageNameImport) - if hasDark: - contents_string = contents_string.replace(darkImageName, darkImageNameImport) - with open(content_json_path, 'w') as openfile: - openfile.write(contents_string) - # Copy new file(s) + contents_json_is_good = os.path.exists(content_json_path) # 2 + if contents_json_is_good: + with open(content_json_path, 'r') as openfile: + contents_string = openfile.read() + contents_json_is_good = contents_json_is_good and imageName in contents_string + if hasDark: + contents_json_is_good = contents_json_is_good and darkImageName in contents_string + + path_to_imageset_exists = os.path.exists(path_to_imageset) # 3 file_to_copy_path = os.path.join(self.assets_dir, imageNameImport) - shutil.copy(file_to_copy_path, path_to_imageset) + dark_file_to_copy_path = os.path.join(self.assets_dir, darkImageNameImport) + files_to_copy_exist = os.path.exists(file_to_copy_path) # 4 if hasDark: - dark_file_to_copy_path = os.path.join(self.assets_dir, darkImageNameImport) - shutil.copy(dark_file_to_copy_path, path_to_imageset) + files_to_copy_exist = files_to_copy_exist and os.path.exists(dark_file_to_copy_path) - def replace_colors(self, asset): + if files_to_changes_exist and contents_json_is_good and path_to_imageset_exists and files_to_copy_exist: + # Delete current file(s) + os.remove(file_path) + if hasDark: + os.remove(dark_file_path) + # Change Contents.json + with open(content_json_path, 'r') as openfile: + contents_string = openfile.read() + contents_string = contents_string.replace(imageName, imageNameImport) + if hasDark: + contents_string = contents_string.replace(darkImageName, darkImageNameImport) + with open(content_json_path, 'w') as openfile: + openfile.write(contents_string) + # Copy new file(s) + shutil.copy(file_to_copy_path, path_to_imageset) + logging.debug(assetName+"->images->"+name+": 'light mode'/universal image was updated with "+imageNameImport) + if hasDark: + shutil.copy(dark_file_to_copy_path, path_to_imageset) + logging.debug(assetName+"->images->"+name+": 'dark mode' image was updated with "+darkImageNameImport) + else: + # Handle errors + if not files_to_changes_exist: + logging.error(assetName+"->images->"+name+": original file(s) doesn't exist") + elif not contents_json_is_good: + logging.error(assetName+"->images->"+name+": Contents.json doesn't exist or wrong original file(s) in config") + elif not path_to_imageset_exists: + logging.error(assetName+"->images->"+name+": "+ path_to_imageset + " doesn't exist") + elif not files_to_copy_exist: + logging.error(assetName+"->images->"+name+": file(s) to copy doesn't exist") + + def replace_colors(self, assetData): + asset = assetData[1] + assetName = assetData[0] if "colors" in asset: colorsPath = asset["colorsPath"] if "colorsPath" in asset else "" for name, color in asset["colors"].items(): @@ -100,20 +133,24 @@ def replace_colors(self, asset): dark_color = color["dark"] # Change Contents.json content_json_path = os.path.join(path_to_colorset, 'Contents.json') - with open(content_json_path, 'r') as openfile: - json_object = json.load(openfile) - for key in range(len(json_object["colors"])): - if "appearances" in json_object["colors"][key]: - # dark - changed_components = self.change_color_components(json_object["colors"][key]["color"]["components"], dark_color, name) - json_object["colors"][key]["color"]["components"] = changed_components - else: - # light - changed_components = self.change_color_components(json_object["colors"][key]["color"]["components"], light_color, name) - json_object["colors"][key]["color"]["components"] = changed_components - new_json = json.dumps(json_object) - with open(content_json_path, 'w') as openfile: - openfile.write(new_json) + if os.path.exists(content_json_path): + with open(content_json_path, 'r') as openfile: + json_object = json.load(openfile) + for key in range(len(json_object["colors"])): + if "appearances" in json_object["colors"][key]: + # dark + changed_components = self.change_color_components(json_object["colors"][key]["color"]["components"], dark_color, name) + json_object["colors"][key]["color"]["components"] = changed_components + else: + # light + changed_components = self.change_color_components(json_object["colors"][key]["color"]["components"], light_color, name) + json_object["colors"][key]["color"]["components"] = changed_components + new_json = json.dumps(json_object) + with open(content_json_path, 'w') as openfile: + openfile.write(new_json) + logging.debug(assetName+"->colors->"+name+": color was updated with light:'"+light_color+"' dark:'"+dark_color+"'") + else: + logging.error(assetName+"->colors->"+name+": " + content_json_path + " doesn't exist") def change_color_components(self, components, color, name): color = color.replace("#", "") @@ -136,7 +173,7 @@ def main(): args = parser.parse_args() # DEBUG VARS - # args.config_file = "whitelabel.yaml" + # args.config_file = "../edx-mobile-config/openEdXAssets/whitelabel.yaml" # args.verbose = 2 if args.help_config_file: @@ -153,6 +190,9 @@ def main(): if args.verbose > 1: log_level = logging.DEBUG logging.basicConfig(level=log_level) + logger = logging.getLogger(name='whitelabel_config') + coloredlogs.install(level=log_level, logger=logger) + # logger.propagate = False with open(args.config_file) as f: config = yaml.safe_load(f) or {} From 5b4d9762207fa9bab229dbbcacfae8dba1d6f1ad Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Wed, 6 Dec 2023 20:36:43 +0100 Subject: [PATCH 09/31] chore: rounded buttons theming --- Core/Core/Configuration/Config/ThemeConfig.swift | 2 +- OpenEdX/AppDelegate.swift | 10 ++++++---- Theme/Theme/Theme.swift | 11 +++++++++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Core/Core/Configuration/Config/ThemeConfig.swift b/Core/Core/Configuration/Config/ThemeConfig.swift index aa7c98769..5f2acf90d 100644 --- a/Core/Core/Configuration/Config/ThemeConfig.swift +++ b/Core/Core/Configuration/Config/ThemeConfig.swift @@ -16,7 +16,7 @@ public final class ThemeConfig: NSObject { init(dictionary: [String: AnyObject]) { super.init() - isRoundedCorners = dictionary[ThemeKeys.isRoundedCorners.rawValue] as? Bool == true + isRoundedCorners = dictionary[ThemeKeys.isRoundedCorners.rawValue] as? Bool != false } } diff --git a/OpenEdX/AppDelegate.swift b/OpenEdX/AppDelegate.swift index 225c07506..5ebb369e8 100644 --- a/OpenEdX/AppDelegate.swift +++ b/OpenEdX/AppDelegate.swift @@ -34,10 +34,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { initDI() - if let config = Container.shared.resolve(ConfigProtocol.self), - let configuration = config.firebase.firebaseOptions { - FirebaseApp.configure(options: configuration) - Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true) + if let config = Container.shared.resolve(ConfigProtocol.self) { + Theme.Shapes.isRoundedCorners = config.theme.isRoundedCorners + if let configuration = config.firebase.firebaseOptions { + FirebaseApp.configure(options: configuration) + Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(true) + } } Theme.Fonts.registerFonts() diff --git a/Theme/Theme/Theme.swift b/Theme/Theme/Theme.swift index e4159fd7c..845a6fd9b 100644 --- a/Theme/Theme/Theme.swift +++ b/Theme/Theme/Theme.swift @@ -114,10 +114,17 @@ public struct Theme { } public struct Shapes { + public static var isRoundedCorners: Bool = true public static let screenBackgroundRadius = 24.0 public static let cardImageRadius = 10.0 - public static let textInputShape = RoundedRectangle(cornerRadius: 8) - public static let buttonShape = RoundedCorners(tl: 8, tr: 8, bl: 8, br: 8) + public static let textInputShape = { + let radius: CGFloat = isRoundedCorners ? 8 : 0 + return RoundedRectangle(cornerRadius: radius) + }() + public static let buttonShape = { + let radius: CGFloat = isRoundedCorners ? 8 : 0 + return RoundedCorners(tl: radius, tr: radius, bl: radius, br: radius) + }() public static let unitButtonShape = RoundedCorners(tl: 21, tr: 21, bl: 21, br: 21) public static let roundedScreenBackgroundShape = RoundedCorners( tl: Theme.Shapes.screenBackgroundRadius, From 4667c1f07e5de293261606f06d47e34931b1dc15 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Thu, 7 Dec 2023 16:40:32 +0100 Subject: [PATCH 10/31] chore: changes in whitelabel script --- config_script/whitelabel.py | 46 ++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/config_script/whitelabel.py b/config_script/whitelabel.py index 8cab6d0c8..c86233fd1 100644 --- a/config_script/whitelabel.py +++ b/config_script/whitelabel.py @@ -9,33 +9,33 @@ import coloredlogs class WhitelabelApp: - def __init__(self, **kwargs): - EXAMPLE_CONFIG_FILE = """ + EXAMPLE_CONFIG_FILE = """ --- # Notes: # Config file can contain next optins: - + import_dir: 'path/to/asset/Images' # folder where importing images are placed assets: - images: - image1: # Asset name - imageName: 'some_image.svg' # image file name in Assets.xcassets/image1.imageset - imageNameImport: 'new_image.pdf' # optional: image to replace imageName, placed in Config. Don't need if file name is the same - image2: # Asset name - currentPath: 'SomeFolder' # Path to image2.imageset inside Assets.xcassets - imageName: 'Rectangle.png' # image file name in Assets.xcassets/SomeFolder/image2.imageset - darkImageName: 'RectangleDark.png' # image file name for dark appearance in Assets.xcassets/SomeFolder/image2.imageset - darkImageNameImport: 'NewDarkRectangle.png' # optional: image to replace darkImageName, placed in Config. Don't need if file name is the same - colors: - LoginBackground: # color asset name in Assets - light: '#FFFFFF' - dark: '#ED5C13' + AssetName: + imagesPath: 'Theme/Theme/Assets.xcassets' # path where images in this Asset are placed + colorsPath: 'Theme/Theme/Assets.xcassets/Colors' # path where colors in this Asset are placed + images: + image1: # Asset name + imageName: 'some_image.svg' # image file name in Assets.xcassets/image1.imageset + imageNameImport: 'new_image.pdf' # optional: image to replace imageName, placed in Config. Don't need if file name is the same + image2: # Asset name + currentPath: 'SomeFolder' # Path to image2.imageset inside Assets.xcassets + imageName: 'Rectangle.png' # image file name in Assets.xcassets/SomeFolder/image2.imageset + darkImageName: 'RectangleDark.png' # image file name for dark appearance in Assets.xcassets/SomeFolder/image2.imageset + darkImageNameImport: 'NewDarkRectangle.png' # optional: image to replace darkImageName, placed in Config. Don't need if file name is the same + colors: + LoginBackground: # color asset name in Assets + currrentPath: '' # optional: path to color inside colorsPath + light: '#FFFFFF' + dark: '#ED5C13' """ - - # self.project_assets = kwargs.get('project_assets') - # if not self.project_assets: - # self.project_assets = '.' - self.assets_dir = kwargs.get('assets_dir') + def __init__(self, **kwargs): + self.assets_dir = kwargs.get('import_dir') if not self.assets_dir: self.assets_dir = '.' @@ -43,7 +43,6 @@ def __init__(self, **kwargs): def whitelabel(self): # Update the properties, resources, and configuration of the current app. - # self.update_plist() self.copy_assets() def copy_assets(self): @@ -184,6 +183,8 @@ def main(): parser.print_help() sys.exit(1) + if args.verbose is None: + args.verbose = 0 log_level = logging.WARN if args.verbose > 0: log_level = logging.INFO @@ -192,7 +193,6 @@ def main(): logging.basicConfig(level=log_level) logger = logging.getLogger(name='whitelabel_config') coloredlogs.install(level=log_level, logger=logger) - # logger.propagate = False with open(args.config_file) as f: config = yaml.safe_load(f) or {} From cc20eba7ad07db738645462f13b6b7d1dc526692 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Fri, 8 Dec 2023 13:12:18 +0100 Subject: [PATCH 11/31] chore: fix after merge --- Core/Core.xcodeproj/project.pbxproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 5bdd59a8e..5cbebac71 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -118,6 +118,7 @@ 0770DE5F28D0B22C006D8A5D /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE5E28D0B22C006D8A5D /* Strings.swift */; }; 0770DE6128D0B2CB006D8A5D /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE6028D0B2CB006D8A5D /* Assets.swift */; }; 07DDFCBD29A780BB00572595 /* UINavigationController+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07DDFCBC29A780BB00572595 /* UINavigationController+Animation.swift */; }; + A53A32352B233DEC005FE38A /* ThemeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A32342B233DEC005FE38A /* ThemeConfig.swift */; }; BA30427F2B20B320009B64B7 /* SocialAuthError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA30427D2B20B299009B64B7 /* SocialAuthError.swift */; }; BA76135C2B21BC7300B599B7 /* SocialAuthResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA76135B2B21BC7300B599B7 /* SocialAuthResponse.swift */; }; BA8B3A2F2AD546A700D25EF5 /* DebugLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8B3A2E2AD546A700D25EF5 /* DebugLog.swift */; }; @@ -288,6 +289,7 @@ 3B74C6685E416657F3C5F5A8 /* Pods-App-Core.releaseprod.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.releaseprod.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.releaseprod.xcconfig"; sourceTree = ""; }; 60153262DBC2F9E660D7E11B /* Pods-App-Core.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.release.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.release.xcconfig"; sourceTree = ""; }; 9D5B06CAA99EA5CD49CBE2BB /* Pods-App-Core.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugdev.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugdev.xcconfig"; sourceTree = ""; }; + A53A32342B233DEC005FE38A /* ThemeConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeConfig.swift; sourceTree = ""; }; BA30427D2B20B299009B64B7 /* SocialAuthError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocialAuthError.swift; sourceTree = ""; }; BA76135B2B21BC7300B599B7 /* SocialAuthResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialAuthResponse.swift; sourceTree = ""; }; BA8B3A2E2AD546A700D25EF5 /* DebugLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugLog.swift; sourceTree = ""; }; @@ -690,6 +692,7 @@ DBF6F2422B014AF30098414B /* Config */ = { isa = PBXGroup; children = ( + A53A32342B233DEC005FE38A /* ThemeConfig.swift */, 0604C9A92B22FACF00AD5DBF /* UIComponentsConfig.swift */, 0727876F28D23411002E9142 /* Config.swift */, DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */, @@ -1028,6 +1031,7 @@ 024BE3DF29B2615500BCDEE2 /* CGColorExtension.swift in Sources */, 0770DE6128D0B2CB006D8A5D /* Assets.swift in Sources */, 0727878928D31734002E9142 /* User.swift in Sources */, + A53A32352B233DEC005FE38A /* ThemeConfig.swift in Sources */, 02280F5B294B4E6F0032823A /* Connectivity.swift in Sources */, 02066B482906F73400F4307E /* PickerMenu.swift in Sources */, ); From 9053dc073cb88fe8b8927ce4f6e6f921fc16af16 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Fri, 8 Dec 2023 14:27:21 +0100 Subject: [PATCH 12/31] chore: added themed button shape for social login buttons --- Core/Core/View/Base/SocialAuthButton.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/Core/View/Base/SocialAuthButton.swift b/Core/Core/View/Base/SocialAuthButton.swift index 71ea25dbc..3ebd367e7 100644 --- a/Core/Core/View/Base/SocialAuthButton.swift +++ b/Core/Core/View/Base/SocialAuthButton.swift @@ -6,6 +6,7 @@ // import SwiftUI +import Theme public struct SocialAuthButton: View { @@ -54,7 +55,7 @@ public struct SocialAuthButton: View { .frame(maxWidth: idiom == .pad ? 260: .infinity, minHeight: 42) .background(backgroundColor) .clipShape( - RoundedRectangle(cornerRadius: cornerRadius) + Theme.Shapes.buttonShape ) } From 91822f432277911c15147334475bf468b9445160 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Fri, 8 Dec 2023 16:39:14 +0100 Subject: [PATCH 13/31] style: add login navigation text color as separate --- .../Registration/SignUpView.swift | 4 +- .../Reset Password/ResetPasswordView.swift | 4 +- .../Contents.json | 38 +++++++++++++++++++ Theme/Theme/SwiftGen/ThemeAssets.swift | 1 + Theme/Theme/Theme.swift | 1 + 5 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 Theme/Theme/Assets.xcassets/Colors/LoginNavigationText.colorset/Contents.json diff --git a/Authorization/Authorization/Presentation/Registration/SignUpView.swift b/Authorization/Authorization/Presentation/Registration/SignUpView.swift index 13320ee9b..580d6f10a 100644 --- a/Authorization/Authorization/Presentation/Registration/SignUpView.swift +++ b/Authorization/Authorization/Presentation/Registration/SignUpView.swift @@ -39,12 +39,12 @@ public struct SignUpView: View { ZStack { HStack { Text(AuthLocalization.SignIn.registerBtn) - .titleSettings(color: Theme.Colors.white) + .titleSettings(color: Theme.Colors.loginNavigationText) } VStack { Button(action: { viewModel.router.back() }, label: { CoreAssets.arrowLeft.swiftUIImage.renderingMode(.template) - .backButtonStyle(color: Theme.Colors.white) + .backButtonStyle(color: Theme.Colors.loginNavigationText) }) .foregroundColor(Theme.Colors.styledButtonText) .padding(.leading, isHorizontal ? 48 : 0) diff --git a/Authorization/Authorization/Presentation/Reset Password/ResetPasswordView.swift b/Authorization/Authorization/Presentation/Reset Password/ResetPasswordView.swift index acb2a6df3..ea790f6d3 100644 --- a/Authorization/Authorization/Presentation/Reset Password/ResetPasswordView.swift +++ b/Authorization/Authorization/Presentation/Reset Password/ResetPasswordView.swift @@ -34,8 +34,8 @@ public struct ResetPasswordView: View { VStack(alignment: .center) { NavigationBar(title: AuthLocalization.Forgot.title, - titleColor: Theme.Colors.white, - leftButtonColor: Theme.Colors.white, + titleColor: Theme.Colors.loginNavigationText, + leftButtonColor: Theme.Colors.loginNavigationText, leftButtonAction: { viewModel.router.back() }).padding(.leading, isHorizontal ? 48 : 0) diff --git a/Theme/Theme/Assets.xcassets/Colors/LoginNavigationText.colorset/Contents.json b/Theme/Theme/Assets.xcassets/Colors/LoginNavigationText.colorset/Contents.json new file mode 100644 index 000000000..22c4bb0a8 --- /dev/null +++ b/Theme/Theme/Assets.xcassets/Colors/LoginNavigationText.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Theme/Theme/SwiftGen/ThemeAssets.swift b/Theme/Theme/SwiftGen/ThemeAssets.swift index 910aa663d..5acedcf91 100644 --- a/Theme/Theme/SwiftGen/ThemeAssets.swift +++ b/Theme/Theme/SwiftGen/ThemeAssets.swift @@ -36,6 +36,7 @@ public enum ThemeAssets { public static let certificateForeground = ColorAsset(name: "CertificateForeground") public static let commentCellBackground = ColorAsset(name: "CommentCellBackground") public static let loginBackground = ColorAsset(name: "LoginBackground") + public static let loginNavigationText = ColorAsset(name: "LoginNavigationText") public static let shadowColor = ColorAsset(name: "ShadowColor") public static let snackbarErrorColor = ColorAsset(name: "SnackbarErrorColor") public static let snackbarErrorTextColor = ColorAsset(name: "SnackbarErrorTextColor") diff --git a/Theme/Theme/Theme.swift b/Theme/Theme/Theme.swift index 845a6fd9b..8e9ec9aab 100644 --- a/Theme/Theme/Theme.swift +++ b/Theme/Theme/Theme.swift @@ -38,6 +38,7 @@ public struct Theme { public private(set) static var textInputUnfocusedStroke = ThemeAssets.textInputUnfocusedStroke.swiftUIColor public private(set) static var warning = ThemeAssets.warning.swiftUIColor public private(set) static var white = ThemeAssets.white.swiftUIColor + public private(set) static var loginNavigationText = ThemeAssets.loginNavigationText.swiftUIColor public static func update( accentColor: Color = ThemeAssets.accentColor.swiftUIColor, From 97116ff3b9e32c55d3ac5a6da2be40646eada3bc Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Tue, 12 Dec 2023 21:21:44 +0100 Subject: [PATCH 14/31] chore: added app icon changing --- config_script/whitelabel.py | 57 +++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 3 deletions(-) diff --git a/config_script/whitelabel.py b/config_script/whitelabel.py index c86233fd1..c0a99c1df 100644 --- a/config_script/whitelabel.py +++ b/config_script/whitelabel.py @@ -6,7 +6,8 @@ import sys import yaml import json -import coloredlogs +import coloredlogs #TODO: ADD TO DOCUMENTATION pip install coloredlogs +from PIL import Image #TODO: ADD TO DOCUMENTATION pip install pillow class WhitelabelApp: EXAMPLE_CONFIG_FILE = """ @@ -16,8 +17,9 @@ class WhitelabelApp: import_dir: 'path/to/asset/Images' # folder where importing images are placed assets: AssetName: - imagesPath: 'Theme/Theme/Assets.xcassets' # path where images in this Asset are placed - colorsPath: 'Theme/Theme/Assets.xcassets/Colors' # path where colors in this Asset are placed + imagesPath: 'Theme/Theme/Assets.xcassets' # path where images are placed in this Asset + colorsPath: 'Theme/Theme/Assets.xcassets/Colors' # path where colors are placed in this Asset + iconPath: 'Theme/Assets.xcassets' # path where app icon is placed in this Asset images: image1: # Asset name imageName: 'some_image.svg' # image file name in Assets.xcassets/image1.imageset @@ -32,6 +34,10 @@ class WhitelabelApp: currrentPath: '' # optional: path to color inside colorsPath light: '#FFFFFF' dark: '#ED5C13' + icon: + AppIcon: + currrentPath: '' # optional: path to icon inside iconPath + imageName: 'appIcon.jpg' # image to replace current AppIcon """ def __init__(self, **kwargs): @@ -50,6 +56,7 @@ def copy_assets(self): for asset in self.assets.items(): self.replace_images(asset) self.replace_colors(asset) + self.replace_app_icon(asset) else: logging.debug("Assets not found") @@ -160,6 +167,50 @@ def change_color_components(self, components, color, name): components["green"] = "0x"+color[2]+color[3] components["blue"] = "0x"+color[4]+color[5] return components + + def replace_app_icon(self, assetData): + asset = assetData[1] + assetName = assetData[0] + if "icon" in asset: + iconPath = asset["iconPath"] if "iconPath" in asset else "" + for name, icon in asset["icon"].items(): + currentPath = icon["currentPath"] if "currentPath" in icon else "" + path_to_iconset = os.path.join(iconPath, currentPath, name+'.appiconset') + content_json_path = os.path.join(path_to_iconset, 'Contents.json') + with open(content_json_path, 'r') as openfile: + json_object = json.load(openfile) + json_icon = json_object["images"][0] + file_to_change = json_icon["filename"] + size_to_change = json_icon["size"] + file_to_copy = icon["imageName"] + file_to_copy_path = os.path.join(self.assets_dir, file_to_copy) + file_to_change_path = os.path.join(path_to_iconset, file_to_change) + if os.path.exists(file_to_change_path): + if os.path.exists(file_to_copy_path): + # get new file width and height + img = Image.open(file_to_copy_path) + # get width and height + width = img.width + height = img.height + # Delete current file + os.remove(file_to_change_path) + # Change Contents.json + with open(content_json_path, 'r') as openfile: + contents_string = openfile.read() + contents_string = contents_string.replace(file_to_change, file_to_copy) + contents_string = contents_string.replace(size_to_change, str(width)+'x'+str(height)) + with open(content_json_path, 'w') as openfile: + openfile.write(contents_string) + # Copy new file + shutil.copy(file_to_copy_path, path_to_iconset) + logging.debug(assetName+"->icon->"+name+": 'app icon was updated with "+file_to_copy) + else: + logging.error(assetName+"->icon->"+name+": " + file_to_copy_path + " doesn't exist") + else: + logging.error(assetName+"->icon->"+name+": " + file_to_change_path + " doesn't exist") + + + def main(): """ From 0093d2905160a586c842dcbbd07ee07c2e016d33 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Tue, 12 Dec 2023 21:30:20 +0100 Subject: [PATCH 15/31] chore: add comment --- config_script/whitelabel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config_script/whitelabel.py b/config_script/whitelabel.py index c0a99c1df..3d85397cf 100644 --- a/config_script/whitelabel.py +++ b/config_script/whitelabel.py @@ -37,7 +37,7 @@ class WhitelabelApp: icon: AppIcon: currrentPath: '' # optional: path to icon inside iconPath - imageName: 'appIcon.jpg' # image to replace current AppIcon + imageName: 'appIcon.jpg' # image to replace current AppIcon - png or jpg are supported """ def __init__(self, **kwargs): From af35119f519e48bbde9a7d21260ce44fe8249bbf Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Fri, 15 Dec 2023 13:34:54 +0100 Subject: [PATCH 16/31] chore: added bundle id changing --- config_script/whitelabel.py | 56 ++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/config_script/whitelabel.py b/config_script/whitelabel.py index 3d85397cf..702bd7e58 100644 --- a/config_script/whitelabel.py +++ b/config_script/whitelabel.py @@ -38,6 +38,13 @@ class WhitelabelApp: AppIcon: currrentPath: '' # optional: path to icon inside iconPath imageName: 'appIcon.jpg' # image to replace current AppIcon - png or jpg are supported + projectConfig: + projectPath: 'path/to/project/project.pbxproj' # path to project.pbxproj file + appBundleID: + configurations: + config1: # configuration name - can be any + from_id: "bundle.id.app.old" # bundle ID to be changed + to_id: "bundle.id.app.new" # bundle ID which should be set """ def __init__(self, **kwargs): @@ -46,10 +53,17 @@ def __init__(self, **kwargs): self.assets_dir = '.' self.assets = kwargs.get('assets', {}) + self.project_config = kwargs.get('projectConfig', {}) + + if "projectPath" in self.project_config: + self.config_project_path = self.project_config["projectPath"] + else: + logging.error("Path to project file is not defined") def whitelabel(self): # Update the properties, resources, and configuration of the current app. self.copy_assets() + self.set_app_project_config() def copy_assets(self): if self.assets: @@ -208,7 +222,47 @@ def replace_app_icon(self, assetData): logging.error(assetName+"->icon->"+name+": " + file_to_copy_path + " doesn't exist") else: logging.error(assetName+"->icon->"+name+": " + file_to_change_path + " doesn't exist") - + + def set_app_project_config(self): + self.set_app_bundle_ids() + + + def set_app_bundle_ids(self): + if "appBundleID" in self.project_config: + app_bundle_id = self.project_config["appBundleID"] + # read project file + with open(self.config_project_path, 'r') as openfile: + config_file_string = openfile.read() + errors_texts = [] + for name, config in app_bundle_id["configurations"].items(): + # if from_id and to_id are configured + if "from_id" in config and "to_id" in config: + from_id = config["from_id"] + from_id_string = "PRODUCT_BUNDLE_IDENTIFIER = "+from_id+";" + to_id = config["to_id"] + to_id_string = "PRODUCT_BUNDLE_IDENTIFIER = "+to_id+";" + if to_id != '': + # if from_id is in project file + if from_id_string in config_file_string: + config_file_string = config_file_string.replace(from_id_string, to_id_string) + # else if to_id is not set already + elif to_id_string not in config_file_string: + errors_texts.append("appBundleID->configurations->"+name+": bundle id '"+from_id+"' was not found in project") + else: + errors_texts.append("appBundleID->configurations->"+name+": 'to_id' parameter is empty in config") + else: + errors_texts.append("appBundleID->configurations->"+name+": bundle ids were not found in config") + # write to project file + with open(self.config_project_path, 'w') as openfile: + openfile.write(config_file_string) + # print success message or errors if are presented + if len(errors_texts) == 0: + logging.debug("Bundle ids were successfully changed") + else: + for error in errors_texts: + logging.error(error) + else: + logging.error("Bundle ids config is not defined") From 676c616e1162718772d9bd11296b30cd069222e7 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Fri, 15 Dec 2023 15:08:26 +0100 Subject: [PATCH 17/31] chore: set dev team in project file --- config_script/whitelabel.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/config_script/whitelabel.py b/config_script/whitelabel.py index 702bd7e58..6749d4613 100644 --- a/config_script/whitelabel.py +++ b/config_script/whitelabel.py @@ -8,6 +8,7 @@ import json import coloredlogs #TODO: ADD TO DOCUMENTATION pip install coloredlogs from PIL import Image #TODO: ADD TO DOCUMENTATION pip install pillow +import re class WhitelabelApp: EXAMPLE_CONFIG_FILE = """ @@ -40,6 +41,7 @@ class WhitelabelApp: imageName: 'appIcon.jpg' # image to replace current AppIcon - png or jpg are supported projectConfig: projectPath: 'path/to/project/project.pbxproj' # path to project.pbxproj file + devTeam: '1234567890' # apple development team id appBundleID: configurations: config1: # configuration name - can be any @@ -225,6 +227,7 @@ def replace_app_icon(self, assetData): def set_app_project_config(self): self.set_app_bundle_ids() + self.set_dev_team() def set_app_bundle_ids(self): @@ -263,6 +266,27 @@ def set_app_bundle_ids(self): logging.error(error) else: logging.error("Bundle ids config is not defined") + + def set_dev_team(self): + if "devTeam" in self.project_config: + devTeam = self.project_config["devTeam"] + if devTeam != '': + # read project file + with open(self.config_project_path, 'r') as openfile: + config_file_string = openfile.read() + config_file_string_out = re.sub('DEVELOPMENT_TEAM = .{10};','DEVELOPMENT_TEAM = '+devTeam+';', config_file_string) + # if any entries were found and replaced + if config_file_string_out != config_file_string: + # write to project file + with open(self.config_project_path, 'w') as openfile: + openfile.write(config_file_string_out) + logging.debug("Dev Team was set successfuly") + else: + logging.error("No dev Team is found in project file") + else: + logging.error("Dev Team is empty in config") + else: + logging.error("Dev Team is not defined") From 228ae3dc47a8563401c8d045f11a29ebb2846ad1 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Mon, 18 Dec 2023 13:55:07 +0100 Subject: [PATCH 18/31] chore: got rid original images names from config, now read from Content.json --- config_script/whitelabel.py | 53 ++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/config_script/whitelabel.py b/config_script/whitelabel.py index 6749d4613..8bc1a38a7 100644 --- a/config_script/whitelabel.py +++ b/config_script/whitelabel.py @@ -2,7 +2,6 @@ import logging import os import shutil -import subprocess import sys import yaml import json @@ -23,13 +22,11 @@ class WhitelabelApp: iconPath: 'Theme/Assets.xcassets' # path where app icon is placed in this Asset images: image1: # Asset name - imageName: 'some_image.svg' # image file name in Assets.xcassets/image1.imageset - imageNameImport: 'new_image.pdf' # optional: image to replace imageName, placed in Config. Don't need if file name is the same + imageName: 'some_image.svg' # image to replace existing for image1 Asset (light/universal) image2: # Asset name currentPath: 'SomeFolder' # Path to image2.imageset inside Assets.xcassets - imageName: 'Rectangle.png' # image file name in Assets.xcassets/SomeFolder/image2.imageset - darkImageName: 'RectangleDark.png' # image file name for dark appearance in Assets.xcassets/SomeFolder/image2.imageset - darkImageNameImport: 'NewDarkRectangle.png' # optional: image to replace darkImageName, placed in Config. Don't need if file name is the same + imageName: 'Rectangle.png' # image to replace existing for image2 Asset (light/universal) + darkImageName: 'RectangleDark.png' # image to replace existing dark appearance for image2 Asset (dark) colors: LoginBackground: # color asset name in Assets currrentPath: '' # optional: path to color inside colorsPath @@ -85,26 +82,32 @@ def replace_images(self, assetData): assetPath = asset["imagesPath"] if "imagesPath" in asset else "" for name, image in asset["images"].items(): currentPath = image["currentPath"] if "currentPath" in image else "" - hasDark = True if "darkImageName" in image else False - imageName = image["imageName"] - imageNameImport = image["imageNameImport"] if "imageNameImport" in image else imageName - darkImageName = image["darkImageName"] if "darkImageName" in image else "" - darkImageNameImport = image["darkImageNameImport"] if "darkImageNameImport" in image else darkImageName path_to_imageset = os.path.join(assetPath, currentPath, name+'.imageset') + content_json_path = os.path.join(path_to_imageset, 'Contents.json') + imageNameOriginal = '' + darkImageNameOriginal = '' + with open(content_json_path, 'r') as openfile: + json_object = json.load(openfile) + for json_image in json_object["images"]: + if "appearances" in json_image: + # dark + darkImageNameOriginal = json_image["filename"] + else: + # light + imageNameOriginal = json_image["filename"] + hasDark = True if "darkImageName" in image else False + imageNameImport = image["imageName"] if "imageName" in image else '' + darkImageNameImport = image["darkImageName"] if "darkImageName" in image else '' + # conditions to start updating - file_path = os.path.join(path_to_imageset, imageName) - dark_file_path = os.path.join(path_to_imageset, darkImageName) - files_to_changes_exist = os.path.exists(file_path) # 1 + file_path = os.path.join(path_to_imageset, imageNameOriginal) + dark_file_path = os.path.join(path_to_imageset, darkImageNameOriginal) + files_to_changes_exist = os.path.exists(file_path) and imageNameOriginal != '' # 1 if hasDark: - files_to_changes_exist = files_to_changes_exist and os.path.exists(dark_file_path) - content_json_path = os.path.join(path_to_imageset, 'Contents.json') - contents_json_is_good = os.path.exists(content_json_path) # 2 - if contents_json_is_good: - with open(content_json_path, 'r') as openfile: - contents_string = openfile.read() - contents_json_is_good = contents_json_is_good and imageName in contents_string - if hasDark: - contents_json_is_good = contents_json_is_good and darkImageName in contents_string + files_to_changes_exist = files_to_changes_exist and os.path.exists(dark_file_path) and darkImageNameOriginal != '' + contents_json_is_good = os.path.exists(content_json_path) and imageNameOriginal != '' # 2 + if hasDark: + contents_json_is_good = contents_json_is_good and darkImageNameOriginal != '' path_to_imageset_exists = os.path.exists(path_to_imageset) # 3 file_to_copy_path = os.path.join(self.assets_dir, imageNameImport) @@ -121,9 +124,9 @@ def replace_images(self, assetData): # Change Contents.json with open(content_json_path, 'r') as openfile: contents_string = openfile.read() - contents_string = contents_string.replace(imageName, imageNameImport) + contents_string = contents_string.replace(imageNameOriginal, imageNameImport) if hasDark: - contents_string = contents_string.replace(darkImageName, darkImageNameImport) + contents_string = contents_string.replace(darkImageNameOriginal, darkImageNameImport) with open(content_json_path, 'w') as openfile: openfile.write(contents_string) # Copy new file(s) From 33c2fa6dd30a4a9918c952c1374cf917f44f80d7 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Mon, 18 Dec 2023 14:15:13 +0100 Subject: [PATCH 19/31] chore: reorder configs --- Core/Core.xcodeproj/project.pbxproj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Core/Core.xcodeproj/project.pbxproj b/Core/Core.xcodeproj/project.pbxproj index 40a5d221f..6a9f795c5 100644 --- a/Core/Core.xcodeproj/project.pbxproj +++ b/Core/Core.xcodeproj/project.pbxproj @@ -119,7 +119,6 @@ 0770DE6128D0B2CB006D8A5D /* Assets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0770DE6028D0B2CB006D8A5D /* Assets.swift */; }; 07DDFCBD29A780BB00572595 /* UINavigationController+Animation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 07DDFCBC29A780BB00572595 /* UINavigationController+Animation.swift */; }; A53A32352B233DEC005FE38A /* ThemeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A32342B233DEC005FE38A /* ThemeConfig.swift */; }; - BAAD62C62AFCF00B000E6103 /* CustomDisclosureGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = BAAD62C52AFCF00B000E6103 /* CustomDisclosureGroup.swift */; }; BA30427F2B20B320009B64B7 /* SocialAuthError.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA30427D2B20B299009B64B7 /* SocialAuthError.swift */; }; BA76135C2B21BC7300B599B7 /* SocialAuthResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA76135B2B21BC7300B599B7 /* SocialAuthResponse.swift */; }; BA8B3A2F2AD546A700D25EF5 /* DebugLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA8B3A2E2AD546A700D25EF5 /* DebugLog.swift */; }; @@ -293,7 +292,6 @@ 60153262DBC2F9E660D7E11B /* Pods-App-Core.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.release.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.release.xcconfig"; sourceTree = ""; }; 9D5B06CAA99EA5CD49CBE2BB /* Pods-App-Core.debugdev.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App-Core.debugdev.xcconfig"; path = "Target Support Files/Pods-App-Core/Pods-App-Core.debugdev.xcconfig"; sourceTree = ""; }; A53A32342B233DEC005FE38A /* ThemeConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThemeConfig.swift; sourceTree = ""; }; - BAAD62C52AFCF00B000E6103 /* CustomDisclosureGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDisclosureGroup.swift; sourceTree = ""; }; BA30427D2B20B299009B64B7 /* SocialAuthError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SocialAuthError.swift; sourceTree = ""; }; BA76135B2B21BC7300B599B7 /* SocialAuthResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SocialAuthResponse.swift; sourceTree = ""; }; BA8B3A2E2AD546A700D25EF5 /* DebugLog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugLog.swift; sourceTree = ""; }; @@ -699,7 +697,6 @@ DBF6F2422B014AF30098414B /* Config */ = { isa = PBXGroup; children = ( - A53A32342B233DEC005FE38A /* ThemeConfig.swift */, 0604C9A92B22FACF00AD5DBF /* UIComponentsConfig.swift */, 0727876F28D23411002E9142 /* Config.swift */, DBF6F2402B014ADA0098414B /* FirebaseConfig.swift */, @@ -709,6 +706,7 @@ BAFB99832B0E282E007D09F9 /* MicrosoftConfig.swift */, BAFB998F2B14B377007D09F9 /* GoogleConfig.swift */, BAFB99912B14E23D007D09F9 /* AppleSignInConfig.swift */, + A53A32342B233DEC005FE38A /* ThemeConfig.swift */, ); path = Config; sourceTree = ""; From abb16b89839e685c216352969b799e54c9e7c288 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Mon, 18 Dec 2023 17:27:38 +0100 Subject: [PATCH 20/31] chore: added documentation --- Documentation/Theming_implementation.md | 68 +++++++++++++++++++++++++ config_script/whitelabel.py | 26 ++++------ 2 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 Documentation/Theming_implementation.md diff --git a/Documentation/Theming_implementation.md b/Documentation/Theming_implementation.md new file mode 100644 index 000000000..bb7c166d0 --- /dev/null +++ b/Documentation/Theming_implementation.md @@ -0,0 +1,68 @@ +# Theming Implementation +This documentation provides instructions on how to implement Theme assets for the OpenEdX iOS project. + +## Python dependecies +The `whitelabel.py` theming script requires the following Python dependencies to be installed: +- `pip install coloredlogs` +- `pip install pillow` + +## How to Run the Script +The theming script `whitelabel.py` can be ran from the OpenEdX iOS root project folder with the following command: +```bash +python config_script/whitelabel.py --config-file=path/to/configfile/whitelabel.yaml -v +``` +Where +- `config_script/whitelabel.py` is the path to the `whitelabel.py` script. +- `--config-file=path/to/configfile/whitelabel.yaml` is the path to the configuration file `whitelabel.yaml` +- `-v` sets the log level. + +## Config Options +The config file `whitelabel.yaml` can be created by yourself or obtained from some config repo. +This config can contain the following options: +### Folder with source assets +This is the folder where all image assets, which should be copied into the project, are placed: +```yaml +import_dir: 'path/to/images/source' +``` +### Xcode Project Settings +The theming script can change the development team and app bundle ID: +```yaml +projectConfig: + projectPath: 'path/to/project/project.pbxproj' # path to project.pbxproj file + devTeam: '1234567890' # Apple development team ID + appBundleID: + configurations: + config1: # Configuration name - can be any + from_id: "bundle.id.app.old" # Bundle ID to be changed + to_id: "bundle.id.app.new" # Bundle ID to be set +``` +### Assets +The config whitelabel.yaml can contain a few Asset items (every added Xcode project can have its own Assets). +Every Asset item can be configured with images, colors, and app Icon Assets: +```yaml +assets: + AssetName: + imagesPath: 'Theme/Theme/Assets.xcassets' # path where images are placed in this Asset + colorsPath: 'Theme/Theme/Assets.xcassets/Colors' # path where colors are placed in this Asset + iconPath: 'Theme/Assets.xcassets' # path where app icon is placed in this Asset + images: + image1: # Asset name + imageName: 'some_image.svg' # image to replace the existing one for image1 Asset (light/universal) + image2: # Asset name + currentPath: 'SomeFolder' # Path to image2.imageset inside Assets.xcassets + imageName: 'Rectangle.png' # image to replace the existing one for image2 Asset (light/universal) + darkImageName: 'RectangleDark.png' # image to replace the existing dark appearance for image2 Asset (dark) + colors: + LoginBackground: # color asset name in Assets + currrentPath: '' # optional: path to color inside colorsPath + light: '#FFFFFF' + dark: '#ED5C13' + icon: + AppIcon: + currrentPath: '' # optional: path to icon inside iconPath + imageName: 'appIcon.jpg' # image to replace the current AppIcon - png or jpg are supported +``` +### Log level +You can set the log level to 'DEBUG' by adding the `-v` parameter to the script running. +The default log level is 'WARN' +## \ No newline at end of file diff --git a/config_script/whitelabel.py b/config_script/whitelabel.py index 8bc1a38a7..f90c31103 100644 --- a/config_script/whitelabel.py +++ b/config_script/whitelabel.py @@ -5,8 +5,8 @@ import sys import yaml import json -import coloredlogs #TODO: ADD TO DOCUMENTATION pip install coloredlogs -from PIL import Image #TODO: ADD TO DOCUMENTATION pip install pillow +import coloredlogs +from PIL import Image import re class WhitelabelApp: @@ -19,23 +19,23 @@ class WhitelabelApp: AssetName: imagesPath: 'Theme/Theme/Assets.xcassets' # path where images are placed in this Asset colorsPath: 'Theme/Theme/Assets.xcassets/Colors' # path where colors are placed in this Asset - iconPath: 'Theme/Assets.xcassets' # path where app icon is placed in this Asset + iconPath: 'Theme/Assets.xcassets' # path where the app icon is placed in this Asset images: image1: # Asset name - imageName: 'some_image.svg' # image to replace existing for image1 Asset (light/universal) + imageName: 'some_image.svg' # image to replace the existing one for image1 Asset (light/universal) image2: # Asset name currentPath: 'SomeFolder' # Path to image2.imageset inside Assets.xcassets - imageName: 'Rectangle.png' # image to replace existing for image2 Asset (light/universal) - darkImageName: 'RectangleDark.png' # image to replace existing dark appearance for image2 Asset (dark) + imageName: 'Rectangle.png' # image to replace the existing one for image2 Asset (light/universal) + darkImageName: 'RectangleDark.png' # image to replace the existing dark appearance for image2 Asset (dark) colors: LoginBackground: # color asset name in Assets - currrentPath: '' # optional: path to color inside colorsPath + currentPath: '' # optional: path to color inside colorsPath light: '#FFFFFF' dark: '#ED5C13' icon: AppIcon: - currrentPath: '' # optional: path to icon inside iconPath - imageName: 'appIcon.jpg' # image to replace current AppIcon - png or jpg are supported + currentPath: '' # optional: path to icon inside iconPath + imageName: 'appIcon.jpg' # image to replace the current AppIcon - png or jpg are supported projectConfig: projectPath: 'path/to/project/project.pbxproj' # path to project.pbxproj file devTeam: '1234567890' # apple development team id @@ -300,13 +300,9 @@ def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument('--help-config-file', action='store_true', help="Print out a sample config-file, and exit") parser.add_argument('--config-file', '-c', help="Path to the configuration file") - parser.add_argument('--verbose', '-v', action='count', help="Enable verbose logging. Repeat -v for more output.") + parser.add_argument('--verbose', '-v', action='count', help="Enable verbose logging.") args = parser.parse_args() - # DEBUG VARS - # args.config_file = "../edx-mobile-config/openEdXAssets/whitelabel.yaml" - # args.verbose = 2 - if args.help_config_file: print(WhitelabelApp.EXAMPLE_CONFIG_FILE) sys.exit(0) @@ -319,8 +315,6 @@ def main(): args.verbose = 0 log_level = logging.WARN if args.verbose > 0: - log_level = logging.INFO - if args.verbose > 1: log_level = logging.DEBUG logging.basicConfig(level=log_level) logger = logging.getLogger(name='whitelabel_config') From 20da8aabc83021e297081469452f13ea50c46d4c Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Mon, 18 Dec 2023 17:31:38 +0100 Subject: [PATCH 21/31] chore: missed quotes --- Documentation/Theming_implementation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Theming_implementation.md b/Documentation/Theming_implementation.md index bb7c166d0..ff0dfe592 100644 --- a/Documentation/Theming_implementation.md +++ b/Documentation/Theming_implementation.md @@ -37,7 +37,7 @@ projectConfig: to_id: "bundle.id.app.new" # Bundle ID to be set ``` ### Assets -The config whitelabel.yaml can contain a few Asset items (every added Xcode project can have its own Assets). +The config `whitelabel.yaml` can contain a few Asset items (every added Xcode project can have its own Assets). Every Asset item can be configured with images, colors, and app Icon Assets: ```yaml assets: From 2beed6741cf5cece1e69c8cd357203418dc0aedc Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Mon, 18 Dec 2023 17:39:38 +0100 Subject: [PATCH 22/31] refactor: theme config --- Core/Core/Configuration/Config/ThemeConfig.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/Core/Configuration/Config/ThemeConfig.swift b/Core/Core/Configuration/Config/ThemeConfig.swift index 5f2acf90d..239cd69fe 100644 --- a/Core/Core/Configuration/Config/ThemeConfig.swift +++ b/Core/Core/Configuration/Config/ThemeConfig.swift @@ -12,11 +12,11 @@ private enum ThemeKeys: String { } public final class ThemeConfig: NSObject { - public var isRoundedCorners: Bool = true + public var isRoundedCorners: Bool init(dictionary: [String: AnyObject]) { - super.init() isRoundedCorners = dictionary[ThemeKeys.isRoundedCorners.rawValue] as? Bool != false + super.init() } } From 54d8bcfb0427d6e3c11542c29f1c732d97f91d4f Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Tue, 19 Dec 2023 13:36:25 +0100 Subject: [PATCH 23/31] chore: backbutton color on sign in view --- Authorization/Authorization/Presentation/Login/SignInView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Authorization/Authorization/Presentation/Login/SignInView.swift b/Authorization/Authorization/Presentation/Login/SignInView.swift index cc00fbecb..78f7ca877 100644 --- a/Authorization/Authorization/Presentation/Login/SignInView.swift +++ b/Authorization/Authorization/Presentation/Login/SignInView.swift @@ -35,7 +35,7 @@ public struct SignInView: View { VStack { Button(action: { viewModel.router.back() }, label: { CoreAssets.arrowLeft.swiftUIImage.renderingMode(.template) - .backButtonStyle(color: .white) + .backButtonStyle(color: Theme.Colors.loginNavigationText) }) .foregroundColor(Theme.Colors.styledButtonText) .padding(.leading, isHorizontal ? 48 : 0) From 3d9e4dd8ceb18d4f0c7b99dfedb2cf3fa2e8178f Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Tue, 19 Dec 2023 13:48:47 +0100 Subject: [PATCH 24/31] chore: fixed misspelling --- OpenEdX/Base.lproj/LaunchScreen.storyboard | 10 +++++----- .../Contents.json | 0 Theme/Theme/SwiftGen/ThemeAssets.swift | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) rename Theme/Theme/Assets.xcassets/Colors/{SplachBackground.colorset => SplashBackground.colorset}/Contents.json (100%) diff --git a/OpenEdX/Base.lproj/LaunchScreen.storyboard b/OpenEdX/Base.lproj/LaunchScreen.storyboard index 7bc7cbbed..5cb3986ad 100644 --- a/OpenEdX/Base.lproj/LaunchScreen.storyboard +++ b/OpenEdX/Base.lproj/LaunchScreen.storyboard @@ -1,9 +1,9 @@ - + - + @@ -26,7 +26,7 @@ - + @@ -39,8 +39,8 @@ - - + + diff --git a/Theme/Theme/Assets.xcassets/Colors/SplachBackground.colorset/Contents.json b/Theme/Theme/Assets.xcassets/Colors/SplashBackground.colorset/Contents.json similarity index 100% rename from Theme/Theme/Assets.xcassets/Colors/SplachBackground.colorset/Contents.json rename to Theme/Theme/Assets.xcassets/Colors/SplashBackground.colorset/Contents.json diff --git a/Theme/Theme/SwiftGen/ThemeAssets.swift b/Theme/Theme/SwiftGen/ThemeAssets.swift index 5acedcf91..b50937911 100644 --- a/Theme/Theme/SwiftGen/ThemeAssets.swift +++ b/Theme/Theme/SwiftGen/ThemeAssets.swift @@ -41,7 +41,7 @@ public enum ThemeAssets { public static let snackbarErrorColor = ColorAsset(name: "SnackbarErrorColor") public static let snackbarErrorTextColor = ColorAsset(name: "SnackbarErrorTextColor") public static let snackbarInfoAlert = ColorAsset(name: "SnackbarInfoAlert") - public static let splachBackground = ColorAsset(name: "SplachBackground") + public static let splashBackground = ColorAsset(name: "SplashBackground") public static let styledButtonBackground = ColorAsset(name: "StyledButtonBackground") public static let styledButtonText = ColorAsset(name: "StyledButtonText") public static let textPrimary = ColorAsset(name: "TextPrimary") From d1cb76a203cb940d78fd1cf8543a7b5d37b30be3 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Tue, 19 Dec 2023 15:01:34 +0100 Subject: [PATCH 25/31] refactor: change to snake_style stage1 --- Documentation/Theming_implementation.md | 28 ++++----- config_script/whitelabel.py | 78 ++++++++++++------------- 2 files changed, 53 insertions(+), 53 deletions(-) diff --git a/Documentation/Theming_implementation.md b/Documentation/Theming_implementation.md index ff0dfe592..bd944b49c 100644 --- a/Documentation/Theming_implementation.md +++ b/Documentation/Theming_implementation.md @@ -27,10 +27,10 @@ import_dir: 'path/to/images/source' ### Xcode Project Settings The theming script can change the development team and app bundle ID: ```yaml -projectConfig: - projectPath: 'path/to/project/project.pbxproj' # path to project.pbxproj file - devTeam: '1234567890' # Apple development team ID - appBundleID: +project_config: + project_path: 'path/to/project/project.pbxproj' # path to project.pbxproj file + dev_team: '1234567890' # Apple development team ID + app_bundle_id: configurations: config1: # Configuration name - can be any from_id: "bundle.id.app.old" # Bundle ID to be changed @@ -42,25 +42,25 @@ Every Asset item can be configured with images, colors, and app Icon Assets: ```yaml assets: AssetName: - imagesPath: 'Theme/Theme/Assets.xcassets' # path where images are placed in this Asset - colorsPath: 'Theme/Theme/Assets.xcassets/Colors' # path where colors are placed in this Asset - iconPath: 'Theme/Assets.xcassets' # path where app icon is placed in this Asset + images_path: 'Theme/Theme/Assets.xcassets' # path where images are placed in this Asset + colors_path: 'Theme/Theme/Assets.xcassets/Colors' # path where colors are placed in this Asset + icon_path: 'Theme/Assets.xcassets' # path where app icon is placed in this Asset images: image1: # Asset name - imageName: 'some_image.svg' # image to replace the existing one for image1 Asset (light/universal) + image_name: 'some_image.svg' # image to replace the existing one for image1 Asset (light/universal) image2: # Asset name - currentPath: 'SomeFolder' # Path to image2.imageset inside Assets.xcassets - imageName: 'Rectangle.png' # image to replace the existing one for image2 Asset (light/universal) - darkImageName: 'RectangleDark.png' # image to replace the existing dark appearance for image2 Asset (dark) + current_path: 'SomeFolder' # Path to image2.imageset inside Assets.xcassets + image_name: 'Rectangle.png' # image to replace the existing one for image2 Asset (light/universal) + dark_image_name: 'RectangleDark.png' # image to replace the existing dark appearance for image2 Asset (dark) colors: LoginBackground: # color asset name in Assets - currrentPath: '' # optional: path to color inside colorsPath + current_path: '' # optional: path to color inside colors_path light: '#FFFFFF' dark: '#ED5C13' icon: AppIcon: - currrentPath: '' # optional: path to icon inside iconPath - imageName: 'appIcon.jpg' # image to replace the current AppIcon - png or jpg are supported + current_path: '' # optional: path to icon inside icon_path + image_name: 'appIcon.jpg' # image to replace the current AppIcon - png or jpg are supported ``` ### Log level You can set the log level to 'DEBUG' by adding the `-v` parameter to the script running. diff --git a/config_script/whitelabel.py b/config_script/whitelabel.py index f90c31103..5f2752a44 100644 --- a/config_script/whitelabel.py +++ b/config_script/whitelabel.py @@ -17,29 +17,29 @@ class WhitelabelApp: import_dir: 'path/to/asset/Images' # folder where importing images are placed assets: AssetName: - imagesPath: 'Theme/Theme/Assets.xcassets' # path where images are placed in this Asset - colorsPath: 'Theme/Theme/Assets.xcassets/Colors' # path where colors are placed in this Asset - iconPath: 'Theme/Assets.xcassets' # path where the app icon is placed in this Asset + images_path: 'Theme/Theme/Assets.xcassets' # path where images are placed in this Asset + colors_path: 'Theme/Theme/Assets.xcassets/Colors' # path where colors are placed in this Asset + icon_path: 'Theme/Assets.xcassets' # path where the app icon is placed in this Asset images: image1: # Asset name - imageName: 'some_image.svg' # image to replace the existing one for image1 Asset (light/universal) + image_name: 'some_image.svg' # image to replace the existing one for image1 Asset (light/universal) image2: # Asset name - currentPath: 'SomeFolder' # Path to image2.imageset inside Assets.xcassets - imageName: 'Rectangle.png' # image to replace the existing one for image2 Asset (light/universal) - darkImageName: 'RectangleDark.png' # image to replace the existing dark appearance for image2 Asset (dark) + current_path: 'SomeFolder' # Path to image2.imageset inside Assets.xcassets + image_name: 'Rectangle.png' # image to replace the existing one for image2 Asset (light/universal) + dark_image_name: 'RectangleDark.png' # image to replace the existing dark appearance for image2 Asset (dark) colors: LoginBackground: # color asset name in Assets - currentPath: '' # optional: path to color inside colorsPath + current_path: '' # optional: path to color inside colors_path light: '#FFFFFF' dark: '#ED5C13' icon: AppIcon: - currentPath: '' # optional: path to icon inside iconPath - imageName: 'appIcon.jpg' # image to replace the current AppIcon - png or jpg are supported - projectConfig: - projectPath: 'path/to/project/project.pbxproj' # path to project.pbxproj file - devTeam: '1234567890' # apple development team id - appBundleID: + current_path: '' # optional: path to icon inside icon_path + image_name: 'appIcon.jpg' # image to replace the current AppIcon - png or jpg are supported + project_config: + project_path: 'path/to/project/project.pbxproj' # path to project.pbxproj file + dev_team: '1234567890' # apple development team id + app_bundle_id: configurations: config1: # configuration name - can be any from_id: "bundle.id.app.old" # bundle ID to be changed @@ -52,10 +52,10 @@ def __init__(self, **kwargs): self.assets_dir = '.' self.assets = kwargs.get('assets', {}) - self.project_config = kwargs.get('projectConfig', {}) + self.project_config = kwargs.get('project_config', {}) - if "projectPath" in self.project_config: - self.config_project_path = self.project_config["projectPath"] + if "project_path" in self.project_config: + self.config_project_path = self.project_config["project_path"] else: logging.error("Path to project file is not defined") @@ -79,10 +79,10 @@ def replace_images(self, assetData): asset = assetData[1] assetName = assetData[0] if "images" in asset : - assetPath = asset["imagesPath"] if "imagesPath" in asset else "" + assetPath = asset["images_path"] if "images_path" in asset else "" for name, image in asset["images"].items(): - currentPath = image["currentPath"] if "currentPath" in image else "" - path_to_imageset = os.path.join(assetPath, currentPath, name+'.imageset') + current_path = image["current_path"] if "current_path" in image else "" + path_to_imageset = os.path.join(assetPath, current_path, name+'.imageset') content_json_path = os.path.join(path_to_imageset, 'Contents.json') imageNameOriginal = '' darkImageNameOriginal = '' @@ -95,9 +95,9 @@ def replace_images(self, assetData): else: # light imageNameOriginal = json_image["filename"] - hasDark = True if "darkImageName" in image else False - imageNameImport = image["imageName"] if "imageName" in image else '' - darkImageNameImport = image["darkImageName"] if "darkImageName" in image else '' + hasDark = True if "dark_image_name" in image else False + imageNameImport = image["image_name"] if "image_name" in image else '' + darkImageNameImport = image["dark_image_name"] if "dark_image_name" in image else '' # conditions to start updating file_path = os.path.join(path_to_imageset, imageNameOriginal) @@ -150,10 +150,10 @@ def replace_colors(self, assetData): asset = assetData[1] assetName = assetData[0] if "colors" in asset: - colorsPath = asset["colorsPath"] if "colorsPath" in asset else "" + colors_path = asset["colors_path"] if "colors_path" in asset else "" for name, color in asset["colors"].items(): - currentPath = color["currentPath"] if "currentPath" in color else "" - path_to_colorset = os.path.join(colorsPath, currentPath, name+'.colorset') + current_path = color["current_path"] if "current_path" in color else "" + path_to_colorset = os.path.join(colors_path, current_path, name+'.colorset') light_color = color["light"] dark_color = color["dark"] # Change Contents.json @@ -191,17 +191,17 @@ def replace_app_icon(self, assetData): asset = assetData[1] assetName = assetData[0] if "icon" in asset: - iconPath = asset["iconPath"] if "iconPath" in asset else "" + icon_path = asset["icon_path"] if "icon_path" in asset else "" for name, icon in asset["icon"].items(): - currentPath = icon["currentPath"] if "currentPath" in icon else "" - path_to_iconset = os.path.join(iconPath, currentPath, name+'.appiconset') + current_path = icon["current_path"] if "current_path" in icon else "" + path_to_iconset = os.path.join(icon_path, current_path, name+'.appiconset') content_json_path = os.path.join(path_to_iconset, 'Contents.json') with open(content_json_path, 'r') as openfile: json_object = json.load(openfile) json_icon = json_object["images"][0] file_to_change = json_icon["filename"] size_to_change = json_icon["size"] - file_to_copy = icon["imageName"] + file_to_copy = icon["image_name"] file_to_copy_path = os.path.join(self.assets_dir, file_to_copy) file_to_change_path = os.path.join(path_to_iconset, file_to_change) if os.path.exists(file_to_change_path): @@ -234,8 +234,8 @@ def set_app_project_config(self): def set_app_bundle_ids(self): - if "appBundleID" in self.project_config: - app_bundle_id = self.project_config["appBundleID"] + if "app_bundle_id" in self.project_config: + app_bundle_id = self.project_config["app_bundle_id"] # read project file with open(self.config_project_path, 'r') as openfile: config_file_string = openfile.read() @@ -253,11 +253,11 @@ def set_app_bundle_ids(self): config_file_string = config_file_string.replace(from_id_string, to_id_string) # else if to_id is not set already elif to_id_string not in config_file_string: - errors_texts.append("appBundleID->configurations->"+name+": bundle id '"+from_id+"' was not found in project") + errors_texts.append("app_bundle_id->configurations->"+name+": bundle id '"+from_id+"' was not found in project") else: - errors_texts.append("appBundleID->configurations->"+name+": 'to_id' parameter is empty in config") + errors_texts.append("app_bundle_id->configurations->"+name+": 'to_id' parameter is empty in config") else: - errors_texts.append("appBundleID->configurations->"+name+": bundle ids were not found in config") + errors_texts.append("app_bundle_id->configurations->"+name+": bundle ids were not found in config") # write to project file with open(self.config_project_path, 'w') as openfile: openfile.write(config_file_string) @@ -271,13 +271,13 @@ def set_app_bundle_ids(self): logging.error("Bundle ids config is not defined") def set_dev_team(self): - if "devTeam" in self.project_config: - devTeam = self.project_config["devTeam"] - if devTeam != '': + if "dev_team" in self.project_config: + dev_team = self.project_config["dev_team"] + if dev_team != '': # read project file with open(self.config_project_path, 'r') as openfile: config_file_string = openfile.read() - config_file_string_out = re.sub('DEVELOPMENT_TEAM = .{10};','DEVELOPMENT_TEAM = '+devTeam+';', config_file_string) + config_file_string_out = re.sub('DEVELOPMENT_TEAM = .{10};','DEVELOPMENT_TEAM = '+dev_team+';', config_file_string) # if any entries were found and replaced if config_file_string_out != config_file_string: # write to project file From 9827da126d707869eb463e18fc1d05cef331eff5 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Tue, 19 Dec 2023 15:19:02 +0100 Subject: [PATCH 26/31] refactor: changed to snake_style stage2 --- config_script/whitelabel.py | 90 ++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/config_script/whitelabel.py b/config_script/whitelabel.py index 5f2752a44..c89ed5799 100644 --- a/config_script/whitelabel.py +++ b/config_script/whitelabel.py @@ -75,80 +75,80 @@ def copy_assets(self): - def replace_images(self, assetData): - asset = assetData[1] - assetName = assetData[0] + def replace_images(self, asset_data): + asset = asset_data[1] + asset_name = asset_data[0] if "images" in asset : - assetPath = asset["images_path"] if "images_path" in asset else "" + asset_path = asset["images_path"] if "images_path" in asset else "" for name, image in asset["images"].items(): current_path = image["current_path"] if "current_path" in image else "" - path_to_imageset = os.path.join(assetPath, current_path, name+'.imageset') + path_to_imageset = os.path.join(asset_path, current_path, name+'.imageset') content_json_path = os.path.join(path_to_imageset, 'Contents.json') - imageNameOriginal = '' - darkImageNameOriginal = '' + image_name_original = '' + dark_image_name_original = '' with open(content_json_path, 'r') as openfile: json_object = json.load(openfile) for json_image in json_object["images"]: if "appearances" in json_image: # dark - darkImageNameOriginal = json_image["filename"] + dark_image_name_original = json_image["filename"] else: # light - imageNameOriginal = json_image["filename"] - hasDark = True if "dark_image_name" in image else False - imageNameImport = image["image_name"] if "image_name" in image else '' - darkImageNameImport = image["dark_image_name"] if "dark_image_name" in image else '' + image_name_original = json_image["filename"] + has_dark = True if "dark_image_name" in image else False + image_name_import = image["image_name"] if "image_name" in image else '' + dark_image_name_import = image["dark_image_name"] if "dark_image_name" in image else '' # conditions to start updating - file_path = os.path.join(path_to_imageset, imageNameOriginal) - dark_file_path = os.path.join(path_to_imageset, darkImageNameOriginal) - files_to_changes_exist = os.path.exists(file_path) and imageNameOriginal != '' # 1 - if hasDark: - files_to_changes_exist = files_to_changes_exist and os.path.exists(dark_file_path) and darkImageNameOriginal != '' - contents_json_is_good = os.path.exists(content_json_path) and imageNameOriginal != '' # 2 - if hasDark: - contents_json_is_good = contents_json_is_good and darkImageNameOriginal != '' + file_path = os.path.join(path_to_imageset, image_name_original) + dark_file_path = os.path.join(path_to_imageset, dark_image_name_original) + files_to_changes_exist = os.path.exists(file_path) and image_name_original != '' # 1 + if has_dark: + files_to_changes_exist = files_to_changes_exist and os.path.exists(dark_file_path) and dark_image_name_original != '' + contents_json_is_good = os.path.exists(content_json_path) and image_name_original != '' # 2 + if has_dark: + contents_json_is_good = contents_json_is_good and dark_image_name_original != '' path_to_imageset_exists = os.path.exists(path_to_imageset) # 3 - file_to_copy_path = os.path.join(self.assets_dir, imageNameImport) - dark_file_to_copy_path = os.path.join(self.assets_dir, darkImageNameImport) + file_to_copy_path = os.path.join(self.assets_dir, image_name_import) + dark_file_to_copy_path = os.path.join(self.assets_dir, dark_image_name_import) files_to_copy_exist = os.path.exists(file_to_copy_path) # 4 - if hasDark: + if has_dark: files_to_copy_exist = files_to_copy_exist and os.path.exists(dark_file_to_copy_path) if files_to_changes_exist and contents_json_is_good and path_to_imageset_exists and files_to_copy_exist: # Delete current file(s) os.remove(file_path) - if hasDark: + if has_dark: os.remove(dark_file_path) # Change Contents.json with open(content_json_path, 'r') as openfile: contents_string = openfile.read() - contents_string = contents_string.replace(imageNameOriginal, imageNameImport) - if hasDark: - contents_string = contents_string.replace(darkImageNameOriginal, darkImageNameImport) + contents_string = contents_string.replace(image_name_original, image_name_import) + if has_dark: + contents_string = contents_string.replace(dark_image_name_original, dark_image_name_import) with open(content_json_path, 'w') as openfile: openfile.write(contents_string) # Copy new file(s) shutil.copy(file_to_copy_path, path_to_imageset) - logging.debug(assetName+"->images->"+name+": 'light mode'/universal image was updated with "+imageNameImport) - if hasDark: + logging.debug(asset_name+"->images->"+name+": 'light mode'/universal image was updated with "+image_name_import) + if has_dark: shutil.copy(dark_file_to_copy_path, path_to_imageset) - logging.debug(assetName+"->images->"+name+": 'dark mode' image was updated with "+darkImageNameImport) + logging.debug(asset_name+"->images->"+name+": 'dark mode' image was updated with "+dark_image_name_import) else: # Handle errors if not files_to_changes_exist: - logging.error(assetName+"->images->"+name+": original file(s) doesn't exist") + logging.error(asset_name+"->images->"+name+": original file(s) doesn't exist") elif not contents_json_is_good: - logging.error(assetName+"->images->"+name+": Contents.json doesn't exist or wrong original file(s) in config") + logging.error(asset_name+"->images->"+name+": Contents.json doesn't exist or wrong original file(s) in config") elif not path_to_imageset_exists: - logging.error(assetName+"->images->"+name+": "+ path_to_imageset + " doesn't exist") + logging.error(asset_name+"->images->"+name+": "+ path_to_imageset + " doesn't exist") elif not files_to_copy_exist: - logging.error(assetName+"->images->"+name+": file(s) to copy doesn't exist") + logging.error(asset_name+"->images->"+name+": file(s) to copy doesn't exist") - def replace_colors(self, assetData): - asset = assetData[1] - assetName = assetData[0] + def replace_colors(self, asset_data): + asset = asset_data[1] + asset_name = asset_data[0] if "colors" in asset: colors_path = asset["colors_path"] if "colors_path" in asset else "" for name, color in asset["colors"].items(): @@ -173,9 +173,9 @@ def replace_colors(self, assetData): new_json = json.dumps(json_object) with open(content_json_path, 'w') as openfile: openfile.write(new_json) - logging.debug(assetName+"->colors->"+name+": color was updated with light:'"+light_color+"' dark:'"+dark_color+"'") + logging.debug(asset_name+"->colors->"+name+": color was updated with light:'"+light_color+"' dark:'"+dark_color+"'") else: - logging.error(assetName+"->colors->"+name+": " + content_json_path + " doesn't exist") + logging.error(asset_name+"->colors->"+name+": " + content_json_path + " doesn't exist") def change_color_components(self, components, color, name): color = color.replace("#", "") @@ -187,9 +187,9 @@ def change_color_components(self, components, color, name): components["blue"] = "0x"+color[4]+color[5] return components - def replace_app_icon(self, assetData): - asset = assetData[1] - assetName = assetData[0] + def replace_app_icon(self, asset_data): + asset = asset_data[1] + asset_name = asset_data[0] if "icon" in asset: icon_path = asset["icon_path"] if "icon_path" in asset else "" for name, icon in asset["icon"].items(): @@ -222,11 +222,11 @@ def replace_app_icon(self, assetData): openfile.write(contents_string) # Copy new file shutil.copy(file_to_copy_path, path_to_iconset) - logging.debug(assetName+"->icon->"+name+": 'app icon was updated with "+file_to_copy) + logging.debug(asset_name+"->icon->"+name+": 'app icon was updated with "+file_to_copy) else: - logging.error(assetName+"->icon->"+name+": " + file_to_copy_path + " doesn't exist") + logging.error(asset_name+"->icon->"+name+": " + file_to_copy_path + " doesn't exist") else: - logging.error(assetName+"->icon->"+name+": " + file_to_change_path + " doesn't exist") + logging.error(asset_name+"->icon->"+name+": " + file_to_change_path + " doesn't exist") def set_app_project_config(self): self.set_app_bundle_ids() From 4beb029a5bbfd6841c69ec17db35c963fba52de0 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Tue, 26 Dec 2023 18:11:09 +0100 Subject: [PATCH 27/31] chore: added app versions, refactor and unify code to search parameters in project file --- Documentation/Theming_implementation.md | 13 +- config_script/whitelabel.py | 169 +++++++++++++++++------- 2 files changed, 130 insertions(+), 52 deletions(-) diff --git a/Documentation/Theming_implementation.md b/Documentation/Theming_implementation.md index bd944b49c..5347189ed 100644 --- a/Documentation/Theming_implementation.md +++ b/Documentation/Theming_implementation.md @@ -25,16 +25,17 @@ This is the folder where all image assets, which should be copied into the proje import_dir: 'path/to/images/source' ``` ### Xcode Project Settings -The theming script can change the development team and app bundle ID: +The theming script can change the app name, version, development team and app bundle ID: ```yaml project_config: project_path: 'path/to/project/project.pbxproj' # path to project.pbxproj file dev_team: '1234567890' # Apple development team ID - app_bundle_id: - configurations: - config1: # Configuration name - can be any - from_id: "bundle.id.app.old" # Bundle ID to be changed - to_id: "bundle.id.app.new" # Bundle ID to be set + marketing_version: '1.0.1' # App marketing version + current_project_version: '2' # App build number + configurations: + config1: # Configuration name - can be any + app_bundle_id: "bundle.id.app.new" # Bundle ID to be set + product_name: "Mobile App Name" # App Name to be set ``` ### Assets The config `whitelabel.yaml` can contain a few Asset items (every added Xcode project can have its own Assets). diff --git a/config_script/whitelabel.py b/config_script/whitelabel.py index c89ed5799..a9669f3b3 100644 --- a/config_script/whitelabel.py +++ b/config_script/whitelabel.py @@ -39,11 +39,12 @@ class WhitelabelApp: project_config: project_path: 'path/to/project/project.pbxproj' # path to project.pbxproj file dev_team: '1234567890' # apple development team id - app_bundle_id: - configurations: - config1: # configuration name - can be any - from_id: "bundle.id.app.old" # bundle ID to be changed - to_id: "bundle.id.app.new" # bundle ID which should be set + marketing_version: '1.0.1' # app marketing version + current_project_version: '2' # app build number + configurations: + config1: # configuration name - can be any + app_bundle_id: "bundle.id.app.new" # bundle ID which should be set + product_name: "Mobile App Name" # app name which should be set """ def __init__(self, **kwargs): @@ -73,8 +74,6 @@ def copy_assets(self): else: logging.debug("Assets not found") - - def replace_images(self, asset_data): asset = asset_data[1] asset_name = asset_data[0] @@ -222,76 +221,154 @@ def replace_app_icon(self, asset_data): openfile.write(contents_string) # Copy new file shutil.copy(file_to_copy_path, path_to_iconset) - logging.debug(asset_name+"->icon->"+name+": 'app icon was updated with "+file_to_copy) + logging.debug(asset_name+"->icon->"+name+": app icon was updated with "+file_to_copy) else: logging.error(asset_name+"->icon->"+name+": " + file_to_copy_path + " doesn't exist") else: logging.error(asset_name+"->icon->"+name+": " + file_to_change_path + " doesn't exist") def set_app_project_config(self): - self.set_app_bundle_ids() - self.set_dev_team() - + self.set_build_related_params() + self.set_project_global_params() - def set_app_bundle_ids(self): - if "app_bundle_id" in self.project_config: - app_bundle_id = self.project_config["app_bundle_id"] + def set_build_related_params(self): + # check if configurations exist + if "configurations" in self.project_config: + configurations = self.project_config["configurations"] # read project file with open(self.config_project_path, 'r') as openfile: config_file_string = openfile.read() errors_texts = [] - for name, config in app_bundle_id["configurations"].items(): - # if from_id and to_id are configured - if "from_id" in config and "to_id" in config: - from_id = config["from_id"] - from_id_string = "PRODUCT_BUNDLE_IDENTIFIER = "+from_id+";" - to_id = config["to_id"] - to_id_string = "PRODUCT_BUNDLE_IDENTIFIER = "+to_id+";" - if to_id != '': - # if from_id is in project file - if from_id_string in config_file_string: - config_file_string = config_file_string.replace(from_id_string, to_id_string) - # else if to_id is not set already - elif to_id_string not in config_file_string: - errors_texts.append("app_bundle_id->configurations->"+name+": bundle id '"+from_id+"' was not found in project") - else: - errors_texts.append("app_bundle_id->configurations->"+name+": 'to_id' parameter is empty in config") - else: - errors_texts.append("app_bundle_id->configurations->"+name+": bundle ids were not found in config") + for name, config in configurations.items(): + # replace parameters for every config + config_file_string = self.replace_parameter_in_config("app_bundle_id", config_file_string, config, name, errors_texts) + config_file_string = self.replace_parameter_in_config("product_name", config_file_string, config, name, errors_texts) # write to project file with open(self.config_project_path, 'w') as openfile: openfile.write(config_file_string) # print success message or errors if are presented if len(errors_texts) == 0: - logging.debug("Bundle ids were successfully changed") + logging.debug("Project configurations parameters were successfully changed") else: for error in errors_texts: logging.error(error) else: - logging.error("Bundle ids config is not defined") + logging.error("Project configuration is not defined") + + def replace_parameter_in_config(self, parameter, config_file_string, config, config_name, errors_texts): + # if parameter is configured + if parameter in config: + parameter_value = config[parameter] + # if parameter's value is not empty + if parameter_value != '' and parameter_value is not None: + parameter_string = '' + parameter_regex = '' + # define regex rule and replacement string for every possible parameter + if parameter == "app_bundle_id": + parameter_string = "PRODUCT_BUNDLE_IDENTIFIER = "+parameter_value+";" + parameter_regex = "PRODUCT_BUNDLE_IDENTIFIER = .*;" + elif parameter == "product_name": + parameter_string = "PRODUCT_NAME = \""+parameter_value+"\";" + parameter_regex = "PRODUCT_NAME = \".*\";" + # if regex is defined + if parameter_regex != '': + # replace parameter in config file + config_file_string = self.replace_parameter_for_target(config_file_string, config_name, parameter_string, parameter_regex, errors_texts) + else: + errors_texts.append("project_config->configurations->"+config_name+": Regex rule for '"+parameter+"' is not defined in config script") + else: + errors_texts.append("project_config->configurations->"+config_name+": '"+parameter+"' parameter is empty in config") + else: + errors_texts.append("project_config->configurations->"+config_name+": '"+parameter+"' was not found in config") + return config_file_string + + def replace_parameter_for_target(self, config_file_string, config_name, new_param_string, search_param_regex, errors_texts): + # replace parameter for Debug and Relase Schemes + config_file_string = self.replace_parameter_for_scheme(config_file_string, config_name, 'Debug', new_param_string, search_param_regex, errors_texts) + config_file_string = self.replace_parameter_for_scheme(config_file_string, config_name, 'Release', new_param_string, search_param_regex, errors_texts) + return config_file_string - def set_dev_team(self): - if "dev_team" in self.project_config: - dev_team = self.project_config["dev_team"] - if dev_team != '': + def replace_parameter_for_scheme(self, config_file_string, config_name, scheme_name, new_param_string, search_param_regex, errors_texts): + # search substring for current build config only + search_string = re.search(self.regex_string_for_build_config(scheme_name+config_name), config_file_string) + # if build config is found + if search_string is not None: + # get build config as string + config_string = search_string.group() + config_string_out = config_string + # search parameter in config_string + parameter_search_string = re.search(search_param_regex, config_string) + if parameter_search_string is not None: + # get parameter_string as string + parameter_string = parameter_search_string.group() + # replace existing parameter value with new value + config_string_out = config_string.replace(parameter_string, new_param_string) + else: + errors_texts.append("project_config->configurations->"+config_name+": Check regex please. Can't find place in project file where insert '"+new_param_string+"'") + # if something found + if config_string != config_string_out: + config_file_string = config_file_string.replace(config_string, config_string_out) + else: + errors_texts.append("project_config->configurations->"+config_name+": not found in project file") + return config_file_string + + def regex_string_for_build_config(self, build_config): + # regex to search build config inside project file + return f"/\* {build_config} \*/ = {{\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = [\s|\S]*\t\t\tname = {build_config};" + + def set_project_global_params(self): + # set values for 'global' parameters + self.set_global_parameter("dev_team") + self.set_global_parameter("marketing_version") + self.set_global_parameter("current_project_version") + + def set_global_parameter(self, parameter): + # if parameter is defined in config + if parameter in self.project_config: + parameter_value = self.project_config[parameter] + # if parameter value is not empty + if parameter_value != '' and parameter_value is not None: # read project file with open(self.config_project_path, 'r') as openfile: config_file_string = openfile.read() - config_file_string_out = re.sub('DEVELOPMENT_TEAM = .{10};','DEVELOPMENT_TEAM = '+dev_team+';', config_file_string) + config_file_string_out = config_file_string + parameter_string = '' + parameter_regex = '' + # define regex rule and replacement string for every possible parameter + if parameter == "dev_team": + parameter_string = 'DEVELOPMENT_TEAM = '+parameter_value+';' + parameter_regex = 'DEVELOPMENT_TEAM = .{10};' + elif parameter == "marketing_version": + parameter_string = 'MARKETING_VERSION = '+parameter_value+';' + parameter_regex = 'MARKETING_VERSION = .*;' + elif parameter == "current_project_version": + parameter_string = 'CURRENT_PROJECT_VERSION = '+parameter_value+';' + parameter_regex = 'CURRENT_PROJECT_VERSION = .*;' + # if regex is defined + if parameter_regex != '': + # replace all regex findings with new parameters string + config_file_string_out = re.sub(parameter_regex, parameter_string, config_file_string) + else: + logging.error("Regex rule for '"+parameter+"' is not defined in config script") # if any entries were found and replaced if config_file_string_out != config_file_string: # write to project file with open(self.config_project_path, 'w') as openfile: openfile.write(config_file_string_out) - logging.debug("Dev Team was set successfuly") - else: - logging.error("No dev Team is found in project file") + logging.debug("'"+parameter+"' was set successfuly") + # if nothing was found + elif re.search(parameter_regex, config_file_string) is None: + logging.error("Check regex please. Nothing was found for '"+parameter+"' in project file") + # if parameter was found but it's replaced already + elif re.search(parameter_regex, config_file_string).group() == parameter_string and parameter_string != '': + logging.debug("Looks like '"+parameter+"' is set already") + # if parameter was not found and it's not empty + elif parameter_string != '': + logging.error("No '"+parameter+"' is found in project file") else: - logging.error("Dev Team is empty in config") + logging.error("'"+parameter+"' is empty in config") else: - logging.error("Dev Team is not defined") - - + logging.error("'"+parameter+"' is not defined") def main(): """ From 29f866a8205a6b88d53edaa0bf83fcc16d11c7e0 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Tue, 26 Dec 2023 18:16:47 +0100 Subject: [PATCH 28/31] chore: added install dependencies to documentation --- Documentation/Theming_implementation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/Theming_implementation.md b/Documentation/Theming_implementation.md index 5347189ed..78e969e84 100644 --- a/Documentation/Theming_implementation.md +++ b/Documentation/Theming_implementation.md @@ -5,6 +5,7 @@ This documentation provides instructions on how to implement Theme assets for th The `whitelabel.py` theming script requires the following Python dependencies to be installed: - `pip install coloredlogs` - `pip install pillow` +- `pip install pyyaml` ## How to Run the Script The theming script `whitelabel.py` can be ran from the OpenEdX iOS root project folder with the following command: From a20c810d62c84da0c238fd3a41dae5165a4bd36d Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Wed, 27 Dec 2023 14:59:25 +0100 Subject: [PATCH 29/31] chore: fixed according feedback, improved documentation --- Documentation/Theming_implementation.md | 32 ++++++--- config_script/whitelabel.py | 89 ++++++++++++------------- 2 files changed, 64 insertions(+), 57 deletions(-) diff --git a/Documentation/Theming_implementation.md b/Documentation/Theming_implementation.md index 78e969e84..f9fc0c892 100644 --- a/Documentation/Theming_implementation.md +++ b/Documentation/Theming_implementation.md @@ -3,25 +3,32 @@ This documentation provides instructions on how to implement Theme assets for th ## Python dependecies The `whitelabel.py` theming script requires the following Python dependencies to be installed: -- `pip install coloredlogs` -- `pip install pillow` -- `pip install pyyaml` +- `pip3 install coloredlogs` +- `pip3 install pillow` +- `pip3 install pyyaml` ## How to Run the Script -The theming script `whitelabel.py` can be ran from the OpenEdX iOS root project folder with the following command: +The theming script `whitelabel.py` can be run from the OpenEdX iOS root project folder with the following command: ```bash -python config_script/whitelabel.py --config-file=path/to/configfile/whitelabel.yaml -v +python3 config_script/whitelabel.py --config-file=path/to/configfile/whitelabel.yaml -v ``` -Where -- `config_script/whitelabel.py` is the path to the `whitelabel.py` script. +where +- `config_script/whitelabel.py` is the path to the `whitelabel.py` script - `--config-file=path/to/configfile/whitelabel.yaml` is the path to the configuration file `whitelabel.yaml` - `-v` sets the log level. +## Example of whitelabel.yaml +You can get example of `whitelabel.yaml` file by run next command: +```bash +python3 config_script/whitelabel.py --config-file=path/to/configfile/whitelabel.yaml --help-config-file +``` +Just copy script's output to your `whitelabel.yaml` file. + ## Config Options The config file `whitelabel.yaml` can be created by yourself or obtained from some config repo. This config can contain the following options: ### Folder with source assets -This is the folder where all image assets, which should be copied into the project, are placed: +This is the folder where all image assets, which should be copied into the project, are placed (can be relative or absolute): ```yaml import_dir: 'path/to/images/source' ``` @@ -34,9 +41,12 @@ project_config: marketing_version: '1.0.1' # App marketing version current_project_version: '2' # App build number configurations: - config1: # Configuration name - can be any - app_bundle_id: "bundle.id.app.new" # Bundle ID to be set - product_name: "Mobile App Name" # App Name to be set + config1: # Build Configuration name in project + app_bundle_id: "bundle.id.app.new1" # Bundle ID to be set + product_name: "Mobile App Name1" # App Name to be set + config2: # Build Configuration name in project + app_bundle_id: "bundle.id.app.new2" # Bundle ID to be set + product_name: "Mobile App Name2" # App Name to be set ``` ### Assets The config `whitelabel.yaml` can contain a few Asset items (every added Xcode project can have its own Assets). diff --git a/config_script/whitelabel.py b/config_script/whitelabel.py index a9669f3b3..ef11674dd 100644 --- a/config_script/whitelabel.py +++ b/config_script/whitelabel.py @@ -8,44 +8,47 @@ import coloredlogs from PIL import Image import re +from textwrap import dedent class WhitelabelApp: - EXAMPLE_CONFIG_FILE = """ - --- - # Notes: - # Config file can contain next optins: - import_dir: 'path/to/asset/Images' # folder where importing images are placed - assets: - AssetName: - images_path: 'Theme/Theme/Assets.xcassets' # path where images are placed in this Asset - colors_path: 'Theme/Theme/Assets.xcassets/Colors' # path where colors are placed in this Asset - icon_path: 'Theme/Assets.xcassets' # path where the app icon is placed in this Asset - images: - image1: # Asset name - image_name: 'some_image.svg' # image to replace the existing one for image1 Asset (light/universal) - image2: # Asset name - current_path: 'SomeFolder' # Path to image2.imageset inside Assets.xcassets - image_name: 'Rectangle.png' # image to replace the existing one for image2 Asset (light/universal) - dark_image_name: 'RectangleDark.png' # image to replace the existing dark appearance for image2 Asset (dark) - colors: - LoginBackground: # color asset name in Assets - current_path: '' # optional: path to color inside colors_path - light: '#FFFFFF' - dark: '#ED5C13' - icon: - AppIcon: - current_path: '' # optional: path to icon inside icon_path - image_name: 'appIcon.jpg' # image to replace the current AppIcon - png or jpg are supported - project_config: - project_path: 'path/to/project/project.pbxproj' # path to project.pbxproj file - dev_team: '1234567890' # apple development team id - marketing_version: '1.0.1' # app marketing version - current_project_version: '2' # app build number - configurations: - config1: # configuration name - can be any - app_bundle_id: "bundle.id.app.new" # bundle ID which should be set - product_name: "Mobile App Name" # app name which should be set - """ + EXAMPLE_CONFIG_FILE = dedent(""" + # Notes: + # Config file can contain next options: + import_dir: 'path/to/asset/Images' # folder where importing images are placed + assets: + AssetName: + images_path: 'Theme/Theme/Assets.xcassets' # path where images are placed in this Asset + colors_path: 'Theme/Theme/Assets.xcassets/Colors' # path where colors are placed in this Asset + icon_path: 'Theme/Assets.xcassets' # path where the app icon is placed in this Asset + images: + image1: # Asset name + image_name: 'some_image.svg' # image to replace the existing one for image1 Asset (light/universal) + image2: # Asset name + current_path: 'SomeFolder' # Path to image2.imageset inside Assets.xcassets + image_name: 'Rectangle.png' # image to replace the existing one for image2 Asset (light/universal) + dark_image_name: 'RectangleDark.png' # image to replace the existing dark appearance for image2 Asset (dark) + colors: + LoginBackground: # color asset name in Assets + current_path: '' # optional: path to color inside colors_path + light: '#FFFFFF' + dark: '#ED5C13' + icon: + AppIcon: + current_path: '' # optional: path to icon inside icon_path + image_name: 'appIcon.jpg' # image to replace the current AppIcon - png or jpg are supported + project_config: + project_path: 'path/to/project/project.pbxproj' # path to project.pbxproj file + dev_team: '1234567890' # apple development team id + marketing_version: '1.0.1' # app marketing version + current_project_version: '2' # app build number + configurations: + config1: # build configuration name in project + app_bundle_id: "bundle.id.app.new1" # bundle ID which should be set + product_name: "Mobile App Name1" # app name which should be set + config2: # build configuration name in project + app_bundle_id: "bundle.id.app.new2" # bundle ID which should be set + product_name: "Mobile App Name2" # app name which should be set + """) def __init__(self, **kwargs): self.assets_dir = kwargs.get('import_dir') @@ -273,7 +276,7 @@ def replace_parameter_in_config(self, parameter, config_file_string, config, con # if regex is defined if parameter_regex != '': # replace parameter in config file - config_file_string = self.replace_parameter_for_target(config_file_string, config_name, parameter_string, parameter_regex, errors_texts) + config_file_string = self.replace_parameter_for_build_config(config_file_string, config_name, parameter_string, parameter_regex, errors_texts) else: errors_texts.append("project_config->configurations->"+config_name+": Regex rule for '"+parameter+"' is not defined in config script") else: @@ -281,16 +284,10 @@ def replace_parameter_in_config(self, parameter, config_file_string, config, con else: errors_texts.append("project_config->configurations->"+config_name+": '"+parameter+"' was not found in config") return config_file_string - - def replace_parameter_for_target(self, config_file_string, config_name, new_param_string, search_param_regex, errors_texts): - # replace parameter for Debug and Relase Schemes - config_file_string = self.replace_parameter_for_scheme(config_file_string, config_name, 'Debug', new_param_string, search_param_regex, errors_texts) - config_file_string = self.replace_parameter_for_scheme(config_file_string, config_name, 'Release', new_param_string, search_param_regex, errors_texts) - return config_file_string - def replace_parameter_for_scheme(self, config_file_string, config_name, scheme_name, new_param_string, search_param_regex, errors_texts): + def replace_parameter_for_build_config(self, config_file_string, config_name, new_param_string, search_param_regex, errors_texts): # search substring for current build config only - search_string = re.search(self.regex_string_for_build_config(scheme_name+config_name), config_file_string) + search_string = re.search(self.regex_string_for_build_config(config_name), config_file_string) # if build config is found if search_string is not None: # get build config as string @@ -314,7 +311,7 @@ def replace_parameter_for_scheme(self, config_file_string, config_name, scheme_n def regex_string_for_build_config(self, build_config): # regex to search build config inside project file - return f"/\* {build_config} \*/ = {{\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = [\s|\S]*\t\t\tname = {build_config};" + return f"/\\* {build_config} \\*/ = {{\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = [\\s|\\S]*\t\t\tname = {build_config};" def set_project_global_params(self): # set values for 'global' parameters From 1d9ceea757aee9eda287058459e77120ab2daa56 Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Wed, 27 Dec 2023 15:03:54 +0100 Subject: [PATCH 30/31] chore: fixed how to get config example --- Documentation/Theming_implementation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Theming_implementation.md b/Documentation/Theming_implementation.md index f9fc0c892..3c3d42c23 100644 --- a/Documentation/Theming_implementation.md +++ b/Documentation/Theming_implementation.md @@ -20,7 +20,7 @@ where ## Example of whitelabel.yaml You can get example of `whitelabel.yaml` file by run next command: ```bash -python3 config_script/whitelabel.py --config-file=path/to/configfile/whitelabel.yaml --help-config-file +python3 config_script/whitelabel.py --help-config-file ``` Just copy script's output to your `whitelabel.yaml` file. From f964e81b183b31884e8b1cdb5f741eac440b5aac Mon Sep 17 00:00:00 2001 From: Anton Yarmolenko Date: Wed, 27 Dec 2023 15:11:21 +0100 Subject: [PATCH 31/31] chore: improve documentation --- Documentation/Theming_implementation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/Theming_implementation.md b/Documentation/Theming_implementation.md index 3c3d42c23..7d8ba6f61 100644 --- a/Documentation/Theming_implementation.md +++ b/Documentation/Theming_implementation.md @@ -15,7 +15,7 @@ python3 config_script/whitelabel.py --config-file=path/to/configfile/whitelabel. where - `config_script/whitelabel.py` is the path to the `whitelabel.py` script - `--config-file=path/to/configfile/whitelabel.yaml` is the path to the configuration file `whitelabel.yaml` -- `-v` sets the log level. +- `-v` sets the log level (all messages if '-v' is present and errors only if is not). ## Example of whitelabel.yaml You can get example of `whitelabel.yaml` file by run next command: