From 8d9611bb908c4a9cefb8cf1556f759d4e23a3c65 Mon Sep 17 00:00:00 2001 From: Tommy Nguyen <4123478+tido64@users.noreply.github.com> Date: Thu, 8 Feb 2024 11:02:49 +0100 Subject: [PATCH 1/5] feat: add support for custom app fonts --- .rubocop.yml | 11 ++++-- ios/ReactTestApp.xcodeproj/project.pbxproj | 10 +++--- ios/test_app.rb | 35 ++++++++++++++++--- macos/ReactTestApp.xcodeproj/project.pbxproj | 10 +++--- .../plugins/withIosBaseMods.mjs | 2 +- test-app.gradle | 29 ++++++++++++++- 6 files changed, 79 insertions(+), 18 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index eb840c535..281154b70 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -24,10 +24,17 @@ Metrics/MethodLength: Enabled: false Metrics/CyclomaticComplexity: - AllowedMethods: [make_project!, react_native_pods, use_test_app_internal!] + AllowedMethods: + - generate_info_plist! + - make_project! + - react_native_pods + - use_test_app_internal! Metrics/PerceivedComplexity: - AllowedMethods: [make_project!, react_native_pods, use_test_app_internal!] + AllowedMethods: + - make_project! + - react_native_pods + - use_test_app_internal! Naming/FileName: Exclude: diff --git a/ios/ReactTestApp.xcodeproj/project.pbxproj b/ios/ReactTestApp.xcodeproj/project.pbxproj index 2573624b4..750b3d84e 100644 --- a/ios/ReactTestApp.xcodeproj/project.pbxproj +++ b/ios/ReactTestApp.xcodeproj/project.pbxproj @@ -59,7 +59,7 @@ 19ECD0D7232ED425003D8557 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 19ECD0D9232ED425003D8557 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 19ECD0DB232ED427003D8557 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = SOURCE_ROOT; }; - 19ECD0E3232ED427003D8557 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 19ECD0E3232ED427003D8557 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = SOURCE_ROOT; }; 19ECD0E8232ED427003D8557 /* ReactTestAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactTestAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 19ECD0EC232ED428003D8557 /* ReactTestAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactTestAppTests.swift; sourceTree = ""; }; 19ECD0EE232ED428003D8557 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -220,8 +220,8 @@ 19ECD0CA232ED425003D8557 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1341; - LastUpgradeCheck = 1341; + LastSwiftUpdateCheck = 1520; + LastUpgradeCheck = 1520; ORGANIZATIONNAME = Microsoft; TargetAttributes = { 19ECD0D1232ED425003D8557 = { @@ -379,7 +379,7 @@ DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = UBF8T346G9; ENABLE_PREVIEWS = YES; - INFOPLIST_FILE = ReactTestApp/Info.plist; + INFOPLIST_FILE = Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -403,7 +403,7 @@ DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = UBF8T346G9; ENABLE_PREVIEWS = YES; - INFOPLIST_FILE = ReactTestApp/Info.plist; + INFOPLIST_FILE = Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/ios/test_app.rb b/ios/test_app.rb index 15eaea3db..fa18a6be1 100644 --- a/ios/test_app.rb +++ b/ios/test_app.rb @@ -1,3 +1,4 @@ +require('cfpropertylist') require('json') require('pathname') @@ -121,6 +122,33 @@ def generate_assets_catalog!(project_root, target_platform, destination) end end +def generate_info_plist!(project_root, target_platform, destination) + manifest = app_manifest(project_root) + return if manifest.nil? + + infoplist_src = project_path('ReactTestApp/Info.plist', target_platform) + infoplist_dst = File.join(destination, File.basename(infoplist_src)) + + plist = CFPropertyList::List.new(file: infoplist_src) + info = CFPropertyList.native_types(plist.value) + + # Register fonts + fonts = [] + resources = resolve_resources(manifest, target_platform) + resources.each do |filename| + fonts << File.basename(filename) if filename.end_with?('.otf') || filename.end_with?('.ttf') + end + unless fonts.empty? + # https://developer.apple.com/documentation/bundleresources/information_property_list/atsapplicationfontspath + info['ATSApplicationFontsPath'] = '.' if target_platform == :macos + # https://developer.apple.com/documentation/uikit/text_display_and_fonts/adding_a_custom_font_to_your_app + info['UIAppFonts'] = fonts unless target_platform == :macos + end + + plist.value = CFPropertyList.guess(info) + plist.save(infoplist_dst, CFPropertyList::List::FORMAT_XML, { :formatted => true }) +end + def react_native_pods(version) if version.zero? || version >= v(0, 71, 0) 'use_react_native-0.71' @@ -226,6 +254,7 @@ def make_project!(xcodeproj, project_root, target_platform, options) end generate_assets_catalog!(project_root, target_platform, destination) + generate_info_plist!(project_root, target_platform, destination) # Copy localization files and replace instances of `ReactTestApp` with app display name product_name = display_name || name @@ -251,13 +280,11 @@ def make_project!(xcodeproj, project_root, target_platform, options) # Note the location of Node so we can use it later in script phases File.open(File.join(project_root, '.xcode.env'), 'w') do |f| - node_bin = `which node` - node_bin.strip! + node_bin = `which node`.strip! f.write("export NODE_BINARY=#{node_bin}\n") end File.open(File.join(destination, '.env'), 'w') do |f| - node_bin = `dirname $(which node)` - node_bin.strip! + node_bin = `dirname $(which node)`.strip! f.write("export PATH=#{node_bin}:$PATH\n") end diff --git a/macos/ReactTestApp.xcodeproj/project.pbxproj b/macos/ReactTestApp.xcodeproj/project.pbxproj index b278ba53e..3a53b1a17 100644 --- a/macos/ReactTestApp.xcodeproj/project.pbxproj +++ b/macos/ReactTestApp.xcodeproj/project.pbxproj @@ -44,7 +44,7 @@ 193EF064247A736200BE8C79 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 193EF066247A736300BE8C79 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = SOURCE_ROOT; }; 193EF069247A736300BE8C79 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 193EF06B247A736300BE8C79 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 193EF06B247A736300BE8C79 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = SOURCE_ROOT; }; 193EF06C247A736300BE8C79 /* ReactTestApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ReactTestApp.entitlements; sourceTree = ""; }; 193EF071247A736300BE8C79 /* ReactTestAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactTestAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 193EF077247A736300BE8C79 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -218,8 +218,8 @@ 193EF057247A736100BE8C79 /* Project object */ = { isa = PBXProject; attributes = { - LastSwiftUpdateCheck = 1341; - LastUpgradeCheck = 1341; + LastSwiftUpdateCheck = 1520; + LastUpgradeCheck = 1520; ORGANIZATIONNAME = Microsoft; TargetAttributes = { 193EF05E247A736100BE8C79 = { @@ -389,7 +389,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = ReactTestApp/Info.plist; + INFOPLIST_FILE = Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", @@ -410,7 +410,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - INFOPLIST_FILE = ReactTestApp/Info.plist; + INFOPLIST_FILE = Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", diff --git a/scripts/config-plugins/plugins/withIosBaseMods.mjs b/scripts/config-plugins/plugins/withIosBaseMods.mjs index 9dceca488..ce32371e1 100644 --- a/scripts/config-plugins/plugins/withIosBaseMods.mjs +++ b/scripts/config-plugins/plugins/withIosBaseMods.mjs @@ -29,7 +29,7 @@ const defaultProviders = { expoProviders.xcodeproj, "ReactTestApp.xcodeproj/project.pbxproj" ), - infoPlist: modifyFilePath(expoProviders.infoPlist, "ReactTestApp/Info.plist"), + infoPlist: modifyFilePath(expoProviders.infoPlist, "Info.plist"), entitlements: modifyFilePath( expoProviders.entitlements, "ReactTestApp/ReactTestApp.entitlements" diff --git a/test-app.gradle b/test-app.gradle index 8a6e15e92..5b39d3deb 100644 --- a/test-app.gradle +++ b/test-app.gradle @@ -14,6 +14,11 @@ private static void applyConfigPlugins(String testAppDir, File rootDir) { } } +private static boolean isFontFile(File file) { + // https://github.com/facebook/react-native/blob/3dfedbc1aec18a4255e126fde96d5dc7b1271ea7/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/assets/ReactFontManager.java#L28 + return file.name.endsWith(".ttf") || file.name.endsWith(".otf") +} + def testAppDir = buildscript.sourceFile.getParent() apply(from: "${testAppDir}/android/test-app-util.gradle") @@ -93,17 +98,39 @@ ext.applyTestAppModule = { Project project -> def generatedResDir = file("${buildDir}/generated/rncli/src/main/res/") generatedResDir.mkdirs() + // https://github.com/facebook/react-native/blob/3dfedbc1aec18a4255e126fde96d5dc7b1271ea7/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/assets/ReactFontManager.java#L25 + def generatedFontsDir = file("${generatedAssetsDir}/fonts") + generatedFontsDir.mkdirs() + def generatedRawDir = file("${generatedResDir}/raw") generatedRawDir.mkdirs() preBuild.dependsOn(tasks.register("copyAssets", Copy) { androidResourceFiles.each { - from(it) + if (!isFontFile(it)) { + from(it) + } } into(generatedAssetsDir) }) + preBuild.dependsOn(tasks.register("copyFonts", Copy) { + androidResourceFiles.each { + if (isFontFile(it)) { + from(it) { + // File-based resource names must contain only lowercase + // a-z, 0-9, or underscore + rename { + it.toLowerCase() + } + } + } + } + + into(generatedFontsDir) + }) + preBuild.dependsOn(tasks.register("copyResources", Copy) { if (androidResDir != null) { from(androidResDir) From de6b8249823896d2f89574cbc32c63976fb9d31a Mon Sep 17 00:00:00 2001 From: Tommy Nguyen <4123478+tido64@users.noreply.github.com> Date: Thu, 8 Feb 2024 14:07:24 +0100 Subject: [PATCH 2/5] fixup! feat: add support for custom app fonts --- test-app.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-app.gradle b/test-app.gradle index 5b39d3deb..1a37f8f79 100644 --- a/test-app.gradle +++ b/test-app.gradle @@ -16,7 +16,7 @@ private static void applyConfigPlugins(String testAppDir, File rootDir) { private static boolean isFontFile(File file) { // https://github.com/facebook/react-native/blob/3dfedbc1aec18a4255e126fde96d5dc7b1271ea7/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/assets/ReactFontManager.java#L28 - return file.name.endsWith(".ttf") || file.name.endsWith(".otf") + return [".otf", ".ttf"].any { file.name.endsWith(it) } } def testAppDir = buildscript.sourceFile.getParent() From fb1364568312ccc25d458883ba377eb7aa4d53f3 Mon Sep 17 00:00:00 2001 From: Tommy Nguyen <4123478+tido64@users.noreply.github.com> Date: Thu, 8 Feb 2024 14:14:22 +0100 Subject: [PATCH 3/5] copy raw resources --- test-app.gradle | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/test-app.gradle b/test-app.gradle index 1a37f8f79..147887b03 100644 --- a/test-app.gradle +++ b/test-app.gradle @@ -19,6 +19,31 @@ private static boolean isFontFile(File file) { return [".otf", ".ttf"].any { file.name.endsWith(it) } } +private static boolean isMediaFile(File file) { + // https://developer.android.com/media/platform/supported-formats + return [ + ".3gp", + ".aac", + ".amr", + ".flac", + ".imy", + ".m4a", + ".mid", + ".mkv", + ".mp3", + ".mp4", + ".mxmf", + ".ogg", + ".ota", + ".rtttl", + ".rtx", + ".ts", + ".wav", + ".webm", + ".xmf", + ].any { file.name.endsWith(it) } +} + def testAppDir = buildscript.sourceFile.getParent() apply(from: "${testAppDir}/android/test-app-util.gradle") @@ -107,7 +132,7 @@ ext.applyTestAppModule = { Project project -> preBuild.dependsOn(tasks.register("copyAssets", Copy) { androidResourceFiles.each { - if (!isFontFile(it)) { + if (!isFontFile(it) && !isMediaFile(it)) { from(it) } } @@ -131,6 +156,22 @@ ext.applyTestAppModule = { Project project -> into(generatedFontsDir) }) + preBuild.dependsOn(tasks.register("copyRawResources", Copy) { + androidResourceFiles.each { + if (isMediaFile(it)) { + from(it) { + // File-based resource names must contain only lowercase + // a-z, 0-9, or underscore + rename { + it.toLowerCase() + } + } + } + } + + into(generatedRawDir) + }) + preBuild.dependsOn(tasks.register("copyResources", Copy) { if (androidResDir != null) { from(androidResDir) From a014cf91f0e60fb5fa78069d659dd50c3f71d1d9 Mon Sep 17 00:00:00 2001 From: Tommy Nguyen <4123478+tido64@users.noreply.github.com> Date: Thu, 8 Feb 2024 15:04:28 +0100 Subject: [PATCH 4/5] fixup! feat: add support for custom app fonts --- ios/test_app.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ios/test_app.rb b/ios/test_app.rb index fa18a6be1..0a065d885 100644 --- a/ios/test_app.rb +++ b/ios/test_app.rb @@ -133,10 +133,11 @@ def generate_info_plist!(project_root, target_platform, destination) info = CFPropertyList.native_types(plist.value) # Register fonts + font_files = ['.otf', '.ttf'] fonts = [] resources = resolve_resources(manifest, target_platform) resources.each do |filename| - fonts << File.basename(filename) if filename.end_with?('.otf') || filename.end_with?('.ttf') + fonts << File.basename(filename) if font_files.include?(File.extname(filename)) end unless fonts.empty? # https://developer.apple.com/documentation/bundleresources/information_property_list/atsapplicationfontspath From 0fac473eeb9b471892e65275921ef215dab01e5d Mon Sep 17 00:00:00 2001 From: Tommy Nguyen <4123478+tido64@users.noreply.github.com> Date: Mon, 12 Feb 2024 12:44:58 +0100 Subject: [PATCH 5/5] assets/fonts doesn't require lowercase resources --- test-app.gradle | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/test-app.gradle b/test-app.gradle index 147887b03..db5f1e230 100644 --- a/test-app.gradle +++ b/test-app.gradle @@ -143,13 +143,7 @@ ext.applyTestAppModule = { Project project -> preBuild.dependsOn(tasks.register("copyFonts", Copy) { androidResourceFiles.each { if (isFontFile(it)) { - from(it) { - // File-based resource names must contain only lowercase - // a-z, 0-9, or underscore - rename { - it.toLowerCase() - } - } + from(it) } }