@granite-js/screen 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/GraniteScreen.podspec +25 -0
- package/LICENSE +202 -0
- package/android/CMakeLists.txt +62 -0
- package/android/build.gradle +63 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/BundleEvaluator.cpp +27 -0
- package/android/src/main/cpp/BundleEvaluator.h +17 -0
- package/android/src/main/cpp/onLoad.cpp +6 -0
- package/android/src/main/kotlin/run/granite/BundleEvaluator.kt +50 -0
- package/android/src/main/kotlin/run/granite/BundleLoader.kt +40 -0
- package/android/src/main/kotlin/run/granite/DefaultBundleLoader.kt +20 -0
- package/android/src/main/kotlin/run/granite/DefaultErrorView.kt +58 -0
- package/android/src/main/kotlin/run/granite/DefaultLoadingView.kt +51 -0
- package/android/src/main/kotlin/run/granite/GraniteReactDelegate.kt +76 -0
- package/android/src/main/kotlin/run/granite/GraniteReactDelegateImpl.kt +448 -0
- package/android/src/main/kotlin/run/granite/GraniteReactHost.kt +113 -0
- package/android/src/main/kotlin/run/granite/ReactHostFactory.kt +106 -0
- package/gradle-plugin/LICENSE +201 -0
- package/gradle-plugin/README.md +578 -0
- package/gradle-plugin/build.gradle.kts +97 -0
- package/gradle-plugin/gradle/libs.versions.toml +17 -0
- package/gradle-plugin/gradle/wrapper/gradle-wrapper.jar +0 -0
- package/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +7 -0
- package/gradle-plugin/gradle.properties +12 -0
- package/gradle-plugin/gradlew +248 -0
- package/gradle-plugin/gradlew.bat +93 -0
- package/gradle-plugin/settings.gradle.kts +1 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/GraniteExtension.kt +225 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/GranitePlugin.kt +784 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/GraniteRootExtension.kt +107 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/GraniteRootProjectPlugin.kt +290 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/BuildConfigConfigurator.kt +69 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/DependencyConfigurator.kt +232 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/DependencyCoordinates.kt +29 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/DevServerResourceConfigurator.kt +101 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/JniPackagingConfigurator.kt +160 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/NdkConfigurator.kt +135 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/RepositoryConfigurator.kt +148 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/ResourceConfigurator.kt +56 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/generators/CMakeGenerator.kt +105 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/generators/CppAutolinkingGenerator.kt +152 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/generators/EntryPointGenerator.kt +100 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/AndroidDependencyConfig.kt +23 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/AutolinkingConfig.kt +89 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/CMakeEntry.kt +47 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/NativeModule.kt +177 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/AssetPackagingTask.kt +194 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/AutolinkingTask.kt +431 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/BundleTask.kt +275 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/CodegenArtifactsTask.kt +218 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/CodegenSchemaTask.kt +186 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/AutolinkingParser.kt +128 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/ConflictDetector.kt +121 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/JdkValidator.kt +73 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/NodeExecutableFinder.kt +43 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/ReactNativeVersionReader.kt +329 -0
- package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/TaskDependencyValidator.kt +198 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/GraniteExtensionTest.kt +191 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/GranitePluginTest.kt +156 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/GraniteRootProjectPluginTest.kt +87 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/BuildConfigConfiguratorTest.kt +115 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/DependencyConfiguratorTest.kt +338 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/DevServerResourceConfiguratorTest.kt +205 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/ResourceConfiguratorTest.kt +131 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/fixtures/NativeModuleFixtures.kt +67 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/generators/CMakeGeneratorTest.kt +71 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/generators/CppAutolinkingGeneratorTest.kt +344 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/generators/EntryPointGeneratorTest.kt +40 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/models/AutolinkingConfigTest.kt +350 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/models/CMakeEntryTest.kt +200 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/models/NativeModuleTest.kt +562 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/AssetPackagingTaskTest.kt +318 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/AutolinkingTaskTest.kt +89 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/BundleTaskTest.kt +68 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/CodegenTasksTest.kt +410 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/AutolinkingParserTest.kt +335 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/ConflictDetectorTest.kt +75 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/JdkValidatorTest.kt +88 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/ReactNativeVersionReaderTest.kt +585 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/TaskDependencyValidatorTest.kt +123 -0
- package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/TaskTestUtils.kt +88 -0
- package/gradle-plugin/src/test/resources/fixtures/sample-rn-config.json +45 -0
- package/ios/BundleLoader/BundleEvaluator.h +16 -0
- package/ios/BundleLoader/BundleEvaluator.mm +76 -0
- package/ios/BundleLoader/BundleLoadable.swift +91 -0
- package/ios/GraniteBundleLoaderTypes.swift +7 -0
- package/ios/GraniteScreen.h +12 -0
- package/ios/ReactNativeHosting/DefaultViews.swift +138 -0
- package/ios/ReactNativeHosting/GraniteDefaultModuleProvider.h +24 -0
- package/ios/ReactNativeHosting/GraniteDefaultModuleProvider.mm +22 -0
- package/ios/ReactNativeHosting/GraniteHostingHelper.swift +103 -0
- package/ios/ReactNativeHosting/GraniteNativeFactory.swift +35 -0
- package/ios/ReactNativeHosting/GraniteNativeFactoryDelegateImpl.swift +30 -0
- package/ios/ReactNativeHosting/GraniteNativeFactoryImpl.swift +24 -0
- package/ios/ReactNativeHosting/GraniteReactHost.swift +39 -0
- package/ios/ReactNativeHosting/GraniteScreen-Bridging-Header.h +12 -0
- package/package.json +59 -0
- package/react-native.config.js +8 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
package run.granite.gradle.utils
|
|
2
|
+
|
|
3
|
+
import org.gradle.api.Project
|
|
4
|
+
import org.gradle.api.Task
|
|
5
|
+
import org.gradle.testfixtures.ProjectBuilder
|
|
6
|
+
import java.io.File
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Test utilities for creating and configuring Gradle tasks in tests.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates a test project with Android Library plugin applied.
|
|
14
|
+
*
|
|
15
|
+
* @param projectDir Optional project directory (uses temp dir if not specified)
|
|
16
|
+
* @return Configured Gradle project
|
|
17
|
+
*/
|
|
18
|
+
internal fun createProject(projectDir: File? = null): Project {
|
|
19
|
+
val project = ProjectBuilder.builder()
|
|
20
|
+
.apply {
|
|
21
|
+
if (projectDir != null) {
|
|
22
|
+
withProjectDir(projectDir)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
.build()
|
|
26
|
+
|
|
27
|
+
// Apply Android Library plugin for testing
|
|
28
|
+
project.plugins.apply("com.android.library")
|
|
29
|
+
|
|
30
|
+
return project
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Creates and registers a test task of the specified type.
|
|
35
|
+
*
|
|
36
|
+
* This helper allows testing of abstract Gradle tasks by using Gradle's
|
|
37
|
+
* task registration mechanism which handles property injection.
|
|
38
|
+
*
|
|
39
|
+
* @param T Task type to create
|
|
40
|
+
* @param project Project to register the task in (defaults to new test project)
|
|
41
|
+
* @param taskName Name for the task (defaults to class simple name)
|
|
42
|
+
* @param block Configuration block to setup task properties
|
|
43
|
+
* @return Configured task instance
|
|
44
|
+
*/
|
|
45
|
+
internal inline fun <reified T : Task> createTestTask(
|
|
46
|
+
project: Project = createProject(),
|
|
47
|
+
taskName: String = T::class.java.simpleName,
|
|
48
|
+
noinline block: (T) -> Unit = {},
|
|
49
|
+
): T = project.tasks.register(taskName, T::class.java, block).get()
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Creates a test file with specified content.
|
|
53
|
+
*
|
|
54
|
+
* @param parent Parent directory
|
|
55
|
+
* @param name File name
|
|
56
|
+
* @param content File content
|
|
57
|
+
* @return Created file
|
|
58
|
+
*/
|
|
59
|
+
internal fun createTestFile(parent: File, name: String, content: String): File {
|
|
60
|
+
val file = File(parent, name)
|
|
61
|
+
file.parentFile.mkdirs()
|
|
62
|
+
file.writeText(content)
|
|
63
|
+
return file
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Creates a test directory structure.
|
|
68
|
+
*
|
|
69
|
+
* @param parent Parent directory
|
|
70
|
+
* @param path Directory path (can be nested with /)
|
|
71
|
+
* @return Created directory
|
|
72
|
+
*/
|
|
73
|
+
internal fun createTestDirectory(parent: File, path: String): File {
|
|
74
|
+
val dir = File(parent, path)
|
|
75
|
+
dir.mkdirs()
|
|
76
|
+
return dir
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Reads a file and returns its content.
|
|
81
|
+
*
|
|
82
|
+
* @param file File to read
|
|
83
|
+
* @return File content as string
|
|
84
|
+
*/
|
|
85
|
+
internal fun readFileContent(file: File): String {
|
|
86
|
+
require(file.exists()) { "File does not exist: ${file.absolutePath}" }
|
|
87
|
+
return file.readText()
|
|
88
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"project": {
|
|
3
|
+
"name": "test-project",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"android": {
|
|
6
|
+
"sourceDir": "android",
|
|
7
|
+
"manifestPath": "android/AndroidManifest.xml",
|
|
8
|
+
"packageName": "com.example.testproject"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"react-native-webview": {
|
|
13
|
+
"name": "react-native-webview",
|
|
14
|
+
"root": "/path/to/node_modules/react-native-webview",
|
|
15
|
+
"platforms": {
|
|
16
|
+
"android": {
|
|
17
|
+
"sourceDir": "android",
|
|
18
|
+
"packageImportPath": "com.reactnativecommunity.webview.RNCWebViewPackage",
|
|
19
|
+
"packageInstance": "new RNCWebViewPackage()",
|
|
20
|
+
"libraryName": "RNCWebView",
|
|
21
|
+
"componentDescriptors": ["RNCWebViewComponentDescriptor"],
|
|
22
|
+
"cmakeListsPath": "android/CMakeLists.txt"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"react-native-camera": {
|
|
27
|
+
"name": "react-native-camera",
|
|
28
|
+
"root": "/path/to/node_modules/react-native-camera",
|
|
29
|
+
"platforms": {
|
|
30
|
+
"android": {
|
|
31
|
+
"sourceDir": "android",
|
|
32
|
+
"packageImportPath": "org.reactnative.camera.RNCameraPackage",
|
|
33
|
+
"packageInstance": "new RNCameraPackage()",
|
|
34
|
+
"libraryName": "RNCamera",
|
|
35
|
+
"componentDescriptors": null
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"pure-js-module": {
|
|
40
|
+
"name": "pure-js-module",
|
|
41
|
+
"root": "/path/to/node_modules/pure-js-module",
|
|
42
|
+
"platforms": {}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//
|
|
2
|
+
// BundleEvaluator.h
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by 오 진성B on 10/8/25.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#import <Foundation/Foundation.h>
|
|
9
|
+
|
|
10
|
+
@interface BundleEvaluator : NSObject
|
|
11
|
+
|
|
12
|
+
+ (void)evaluate:(NSData *)scriptData
|
|
13
|
+
url:(NSString *)url
|
|
14
|
+
bridgeProxy:(id)bridgeProxy
|
|
15
|
+
completion:(void (^)(NSError *))completion;
|
|
16
|
+
@end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
//
|
|
2
|
+
// BundleEvaluator.mm
|
|
3
|
+
// Pods
|
|
4
|
+
//
|
|
5
|
+
// Created by 오 진성B on 10/8/25.
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#import "BundleEvaluator.h"
|
|
9
|
+
#import <React/RCTBridgeProxy.h>
|
|
10
|
+
#import <React-callinvoker/ReactCommon/CallInvoker.h>
|
|
11
|
+
#import <jsi/jsi.h>
|
|
12
|
+
|
|
13
|
+
@interface RCTBridgeProxy (JSIRuntime)
|
|
14
|
+
- (void *)runtime;
|
|
15
|
+
@end
|
|
16
|
+
|
|
17
|
+
@interface RCTBridgeProxy (RCTTurboModule)
|
|
18
|
+
- (std::shared_ptr<facebook::react::CallInvoker>)jsCallInvoker;
|
|
19
|
+
@end
|
|
20
|
+
|
|
21
|
+
@implementation BundleEvaluator
|
|
22
|
+
|
|
23
|
+
+ (void)evaluate:(NSData *)scriptData
|
|
24
|
+
url:(NSString *)url
|
|
25
|
+
bridgeProxy:(id)bridgeProxy
|
|
26
|
+
completion:(void (^)(NSError *))completion {
|
|
27
|
+
|
|
28
|
+
// Cast to RCTBridgeProxy
|
|
29
|
+
RCTBridgeProxy *bridgeProxyObj = (RCTBridgeProxy *)bridgeProxy;
|
|
30
|
+
|
|
31
|
+
// Get CallInvoker
|
|
32
|
+
std::shared_ptr<facebook::react::CallInvoker> callInvoker = bridgeProxyObj.jsCallInvoker;
|
|
33
|
+
if (!callInvoker) {
|
|
34
|
+
NSError *error = [NSError errorWithDomain:@"TossBundleLoader"
|
|
35
|
+
code:500
|
|
36
|
+
userInfo:@{NSLocalizedDescriptionKey: @"CallInvoker unavailable"}];
|
|
37
|
+
completion(error);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Get runtime pointer
|
|
42
|
+
facebook::jsi::Runtime *runtime = (facebook::jsi::Runtime *)bridgeProxyObj.runtime;
|
|
43
|
+
if (!runtime) {
|
|
44
|
+
NSError *error = [NSError errorWithDomain:@"TossBundleLoader"
|
|
45
|
+
code:500
|
|
46
|
+
userInfo:@{NSLocalizedDescriptionKey: @"Runtime unavailable"}];
|
|
47
|
+
completion(error);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Prepare source
|
|
52
|
+
std::string source{static_cast<const char *>([scriptData bytes]), [scriptData length]};
|
|
53
|
+
std::string sourceUrl{[url UTF8String]};
|
|
54
|
+
|
|
55
|
+
// Execute on JS thread
|
|
56
|
+
callInvoker->invokeAsync([source = std::move(source),
|
|
57
|
+
sourceUrl = std::move(sourceUrl),
|
|
58
|
+
runtime,
|
|
59
|
+
completion]() {
|
|
60
|
+
try {
|
|
61
|
+
runtime->evaluateJavaScript(
|
|
62
|
+
std::make_unique<facebook::jsi::StringBuffer>(std::move(source)),
|
|
63
|
+
sourceUrl
|
|
64
|
+
);
|
|
65
|
+
completion(nil);
|
|
66
|
+
} catch (const std::exception &e) {
|
|
67
|
+
NSString *errorString = [NSString stringWithUTF8String:e.what()];
|
|
68
|
+
NSError *error = [NSError errorWithDomain:@"TossBundleLoader"
|
|
69
|
+
code:500
|
|
70
|
+
userInfo:@{NSLocalizedDescriptionKey: errorString}];
|
|
71
|
+
completion(error);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
//
|
|
2
|
+
// BundleLoader.swift
|
|
3
|
+
// GraniteScreen
|
|
4
|
+
//
|
|
5
|
+
// Protocol for abstracting bundle loading strategies
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
|
|
10
|
+
// MARK: - BundleLoader Protocol
|
|
11
|
+
|
|
12
|
+
/// Protocol that abstracts bundle loading strategies
|
|
13
|
+
/// Matches Android's BundleLoader interface for cross-platform consistency
|
|
14
|
+
public protocol BundleLoadable {
|
|
15
|
+
/// Loads a bundle and returns its source configuration
|
|
16
|
+
func loadBundle() async throws -> BundleSource
|
|
17
|
+
static func evaluate(scriptData: Data,
|
|
18
|
+
url: String,
|
|
19
|
+
bridgeProxy: Any) async throws
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// MARK: - BundleSource
|
|
23
|
+
|
|
24
|
+
/// Represents the source of a JavaScript bundle
|
|
25
|
+
/// Matches Android's sealed class BundleSource
|
|
26
|
+
public enum BundleSource {
|
|
27
|
+
/// Development server bundle
|
|
28
|
+
case devServer(host: String = "localhost", port: Int = 8081, moduleName: String, forBundleRoot: String)
|
|
29
|
+
|
|
30
|
+
/// Production bundle from various locations
|
|
31
|
+
case production(location: ProductionLocation, moduleName: String)
|
|
32
|
+
|
|
33
|
+
case customPath(URL, moduleName: String)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
/// Represents the location of a production bundle
|
|
38
|
+
public enum ProductionLocation {
|
|
39
|
+
/// Bundle from file system
|
|
40
|
+
case fileSystemBundle(filePath: URL)
|
|
41
|
+
|
|
42
|
+
/// Bundle from app's main bundle
|
|
43
|
+
case embeddedBundle
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// MARK: - Bundle Loading Error
|
|
47
|
+
|
|
48
|
+
/// Errors that can occur during bundle loading
|
|
49
|
+
public enum BundleLoadError: LocalizedError {
|
|
50
|
+
case networkError(Error)
|
|
51
|
+
case fileNotFound(String)
|
|
52
|
+
case invalidURL(String)
|
|
53
|
+
case downloadFailed(String)
|
|
54
|
+
case metroServerUnavailable
|
|
55
|
+
case bundleNotReady
|
|
56
|
+
|
|
57
|
+
public var errorDescription: String? {
|
|
58
|
+
switch self {
|
|
59
|
+
case .networkError(let error):
|
|
60
|
+
return "Network error: \(error.localizedDescription)"
|
|
61
|
+
case .fileNotFound(let path):
|
|
62
|
+
return "Bundle file not found at: \(path)"
|
|
63
|
+
case .invalidURL(let url):
|
|
64
|
+
return "Invalid bundle URL: \(url)"
|
|
65
|
+
case .downloadFailed(let reason):
|
|
66
|
+
return "Bundle download failed: \(reason)"
|
|
67
|
+
case .metroServerUnavailable:
|
|
68
|
+
return "Metro server is not available"
|
|
69
|
+
case .bundleNotReady:
|
|
70
|
+
return "Bundle is not ready for loading"
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
public extension BundleLoadable {
|
|
76
|
+
public static func evaluate(scriptData: Data,
|
|
77
|
+
url: String,
|
|
78
|
+
bridgeProxy: Any) async throws {
|
|
79
|
+
try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) in
|
|
80
|
+
BundleEvaluator.evaluate(scriptData,
|
|
81
|
+
url: url,
|
|
82
|
+
bridgeProxy: bridgeProxy) { nsError in
|
|
83
|
+
if let error = nsError {
|
|
84
|
+
continuation.resume(throwing: error)
|
|
85
|
+
} else {
|
|
86
|
+
continuation.resume()
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//
|
|
2
|
+
// GraniteScreen.h
|
|
3
|
+
// GraniteScreen
|
|
4
|
+
//
|
|
5
|
+
// Umbrella header for GraniteScreen framework
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
#import <Foundation/Foundation.h>
|
|
9
|
+
|
|
10
|
+
// Version information
|
|
11
|
+
FOUNDATION_EXPORT double GraniteScreenVersionNumber;
|
|
12
|
+
FOUNDATION_EXPORT const unsigned char GraniteScreenVersionString[];
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
//
|
|
2
|
+
// DefaultViews.swift
|
|
3
|
+
// GraniteScreen
|
|
4
|
+
//
|
|
5
|
+
// Default loading and error views for React Native hosting
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import UIKit
|
|
9
|
+
|
|
10
|
+
/// Default loading view shown while React Native is loading
|
|
11
|
+
public class DefaultLoadingView: UIView {
|
|
12
|
+
private let activityIndicator = UIActivityIndicatorView(style: .large)
|
|
13
|
+
private let label = UILabel()
|
|
14
|
+
|
|
15
|
+
public override init(frame: CGRect) {
|
|
16
|
+
super.init(frame: frame)
|
|
17
|
+
setupViews()
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public convenience init() {
|
|
21
|
+
self.init(frame: .zero)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
required init?(coder: NSCoder) {
|
|
25
|
+
super.init(coder: coder)
|
|
26
|
+
setupViews()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private func setupViews() {
|
|
30
|
+
backgroundColor = .systemBackground
|
|
31
|
+
|
|
32
|
+
// Setup activity indicator
|
|
33
|
+
activityIndicator.translatesAutoresizingMaskIntoConstraints = false
|
|
34
|
+
activityIndicator.startAnimating()
|
|
35
|
+
addSubview(activityIndicator)
|
|
36
|
+
|
|
37
|
+
// Setup label
|
|
38
|
+
label.text = "Loading React Native..."
|
|
39
|
+
label.textColor = .label
|
|
40
|
+
label.textAlignment = .center
|
|
41
|
+
label.translatesAutoresizingMaskIntoConstraints = false
|
|
42
|
+
addSubview(label)
|
|
43
|
+
|
|
44
|
+
// Layout constraints
|
|
45
|
+
NSLayoutConstraint.activate([
|
|
46
|
+
activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor),
|
|
47
|
+
activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor, constant: -20),
|
|
48
|
+
|
|
49
|
+
label.topAnchor.constraint(equalTo: activityIndicator.bottomAnchor, constant: 16),
|
|
50
|
+
label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
|
|
51
|
+
label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20)
|
|
52
|
+
])
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/// Default error view shown when React Native fails to load
|
|
57
|
+
public class DefaultErrorView: UIView {
|
|
58
|
+
private let errorImageView = UIImageView()
|
|
59
|
+
private let titleLabel = UILabel()
|
|
60
|
+
private let messageLabel = UILabel()
|
|
61
|
+
private let retryButton = UIButton(type: .system)
|
|
62
|
+
|
|
63
|
+
private let error: Error
|
|
64
|
+
public var onRetry: (() -> Void)?
|
|
65
|
+
|
|
66
|
+
public init(error: Error) {
|
|
67
|
+
self.error = error
|
|
68
|
+
super.init(frame: .zero)
|
|
69
|
+
setupViews()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
required init?(coder: NSCoder) {
|
|
73
|
+
fatalError("init(coder:) has not been implemented")
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private func setupViews() {
|
|
77
|
+
backgroundColor = .systemBackground
|
|
78
|
+
|
|
79
|
+
// Setup error icon
|
|
80
|
+
errorImageView.image = UIImage(systemName: "exclamationmark.triangle.fill")
|
|
81
|
+
errorImageView.tintColor = .systemRed
|
|
82
|
+
errorImageView.contentMode = .scaleAspectFit
|
|
83
|
+
errorImageView.translatesAutoresizingMaskIntoConstraints = false
|
|
84
|
+
addSubview(errorImageView)
|
|
85
|
+
|
|
86
|
+
// Setup title
|
|
87
|
+
titleLabel.text = "Failed to Load"
|
|
88
|
+
titleLabel.font = .systemFont(ofSize: 20, weight: .semibold)
|
|
89
|
+
titleLabel.textColor = .label
|
|
90
|
+
titleLabel.textAlignment = .center
|
|
91
|
+
titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
92
|
+
addSubview(titleLabel)
|
|
93
|
+
|
|
94
|
+
// Setup message
|
|
95
|
+
messageLabel.text = error.localizedDescription
|
|
96
|
+
messageLabel.font = .systemFont(ofSize: 16)
|
|
97
|
+
messageLabel.textColor = .secondaryLabel
|
|
98
|
+
messageLabel.textAlignment = .center
|
|
99
|
+
messageLabel.numberOfLines = 0
|
|
100
|
+
messageLabel.translatesAutoresizingMaskIntoConstraints = false
|
|
101
|
+
addSubview(messageLabel)
|
|
102
|
+
|
|
103
|
+
// Setup retry button
|
|
104
|
+
retryButton.setTitle("Retry", for: .normal)
|
|
105
|
+
retryButton.titleLabel?.font = .systemFont(ofSize: 17, weight: .medium)
|
|
106
|
+
retryButton.backgroundColor = .systemBlue
|
|
107
|
+
retryButton.setTitleColor(.white, for: .normal)
|
|
108
|
+
retryButton.layer.cornerRadius = 8
|
|
109
|
+
retryButton.translatesAutoresizingMaskIntoConstraints = false
|
|
110
|
+
retryButton.addTarget(self, action: #selector(retryTapped), for: .touchUpInside)
|
|
111
|
+
addSubview(retryButton)
|
|
112
|
+
|
|
113
|
+
// Layout constraints
|
|
114
|
+
NSLayoutConstraint.activate([
|
|
115
|
+
errorImageView.centerXAnchor.constraint(equalTo: centerXAnchor),
|
|
116
|
+
errorImageView.centerYAnchor.constraint(equalTo: centerYAnchor, constant: -80),
|
|
117
|
+
errorImageView.widthAnchor.constraint(equalToConstant: 60),
|
|
118
|
+
errorImageView.heightAnchor.constraint(equalToConstant: 60),
|
|
119
|
+
|
|
120
|
+
titleLabel.topAnchor.constraint(equalTo: errorImageView.bottomAnchor, constant: 20),
|
|
121
|
+
titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
|
|
122
|
+
titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
|
|
123
|
+
|
|
124
|
+
messageLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 12),
|
|
125
|
+
messageLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 40),
|
|
126
|
+
messageLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -40),
|
|
127
|
+
|
|
128
|
+
retryButton.topAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 32),
|
|
129
|
+
retryButton.centerXAnchor.constraint(equalTo: centerXAnchor),
|
|
130
|
+
retryButton.widthAnchor.constraint(equalToConstant: 120),
|
|
131
|
+
retryButton.heightAnchor.constraint(equalToConstant: 44)
|
|
132
|
+
])
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
@objc private func retryTapped() {
|
|
136
|
+
onRetry?()
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// GraniteDefaultModuleProvider.h
|
|
2
|
+
// GraniteScreen
|
|
3
|
+
//
|
|
4
|
+
// Exposes default module provisioning to Swift
|
|
5
|
+
//
|
|
6
|
+
|
|
7
|
+
#import <Foundation/Foundation.h>
|
|
8
|
+
#if __has_include(<React-RCTAppDelegate/RCTDependencyProvider.h>)
|
|
9
|
+
#import <React-RCTAppDelegate/RCTDependencyProvider.h>
|
|
10
|
+
#else
|
|
11
|
+
#import <React_RCTAppDelegate/RCTDependencyProvider.h>
|
|
12
|
+
#endif
|
|
13
|
+
|
|
14
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
15
|
+
|
|
16
|
+
@interface GraniteDefaultModuleProvider : NSObject
|
|
17
|
+
|
|
18
|
+
+ (nullable id)defaultModuleInstanceForClass:(Class)moduleClass
|
|
19
|
+
dependencyProvider:(nullable id<RCTDependencyProvider>)dependencyProvider
|
|
20
|
+
NS_SWIFT_NAME(defaultModuleInstance(for:dependencyProvider:));
|
|
21
|
+
|
|
22
|
+
@end
|
|
23
|
+
|
|
24
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#import "GraniteDefaultModuleProvider.h"
|
|
2
|
+
|
|
3
|
+
// Declared in React-RCTAppDelegate but only exposed in ObjC++ headers.
|
|
4
|
+
#ifdef __cplusplus
|
|
5
|
+
extern "C" id RCTAppSetupDefaultModuleFromClass(
|
|
6
|
+
Class moduleClass,
|
|
7
|
+
id<RCTDependencyProvider> dependencyProvider);
|
|
8
|
+
#else
|
|
9
|
+
extern id RCTAppSetupDefaultModuleFromClass(
|
|
10
|
+
Class moduleClass,
|
|
11
|
+
id<RCTDependencyProvider> dependencyProvider);
|
|
12
|
+
#endif
|
|
13
|
+
|
|
14
|
+
@implementation GraniteDefaultModuleProvider
|
|
15
|
+
|
|
16
|
+
+ (id)defaultModuleInstanceForClass:(Class)moduleClass
|
|
17
|
+
dependencyProvider:(id<RCTDependencyProvider>)dependencyProvider
|
|
18
|
+
{
|
|
19
|
+
return RCTAppSetupDefaultModuleFromClass(moduleClass, dependencyProvider);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
//
|
|
2
|
+
// GraniteHostingHelper.swift
|
|
3
|
+
// GraniteScreen
|
|
4
|
+
//
|
|
5
|
+
// Helper extension for setting up React Native hosting with GraniteNativeFactory
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import React
|
|
9
|
+
import ReactAppDependencyProvider
|
|
10
|
+
import UIKit
|
|
11
|
+
|
|
12
|
+
struct GraniteHostItem {
|
|
13
|
+
var hostURL: URL
|
|
14
|
+
var moduleName: String
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
public enum GraniteReactHostError: Error {
|
|
18
|
+
case bundleLoadError
|
|
19
|
+
case graniteItemConvertError
|
|
20
|
+
case rnHostViewFactoryError
|
|
21
|
+
case bundleURLParsingError
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private var ReactNativeHostAssociatedKey: UInt8 = 0
|
|
25
|
+
private var ReactNativeDelegateAssociatedKey: UInt8 = 0
|
|
26
|
+
// MARK: - Setup Extension for GraniteReactHost
|
|
27
|
+
/// Extension for classes implementing GraniteReactHost
|
|
28
|
+
/// Provides one-line setup matching Android's pattern
|
|
29
|
+
public extension GraniteReactHost where Self: UIViewController {
|
|
30
|
+
|
|
31
|
+
/// Setup React Native host with automatic lifecycle management
|
|
32
|
+
/// Call this in viewDidLoad() for one-line initialization like Android
|
|
33
|
+
@MainActor
|
|
34
|
+
func setupHost(bundleLoader: BundleLoadable,
|
|
35
|
+
initialProperties: [AnyHashable: Any],
|
|
36
|
+
launchOptions: [AnyHashable: Any]) async {
|
|
37
|
+
graniteSetupDidStart()
|
|
38
|
+
do {
|
|
39
|
+
let bundle = try await bundleLoader.loadBundle()
|
|
40
|
+
do {
|
|
41
|
+
let item = try await convertToGraniteItem(bundleSource: bundle)
|
|
42
|
+
let delegate = ReactNativeFactoryDelegate(url: item.hostURL)
|
|
43
|
+
delegate.dependencyProvider = RCTAppDependencyProvider()
|
|
44
|
+
// Use GraniteNativeFactoryWrapper to enable module customization
|
|
45
|
+
let factory = GraniteNativeFactoryImpl(delegate: delegate)
|
|
46
|
+
factory.reactHost = self // self is GraniteReactHost
|
|
47
|
+
let rnHostView = factory.view(withModuleName: item.moduleName,
|
|
48
|
+
initialProperties: initialProperties,
|
|
49
|
+
launchOptions: launchOptions)
|
|
50
|
+
// 진성님 돌아오고나면 의견나눠보기 objC 기반으로 메모리 등록해서 사용하는방법
|
|
51
|
+
objc_setAssociatedObject(self, &ReactNativeHostAssociatedKey, factory, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
52
|
+
objc_setAssociatedObject(self, &ReactNativeDelegateAssociatedKey, delegate, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
53
|
+
if let rnHostView {
|
|
54
|
+
setGraniteBaseView(graniteBaseView: rnHostView)
|
|
55
|
+
graniteSetupDidFinish()
|
|
56
|
+
} else {
|
|
57
|
+
graniteSetupDidError(didFailWith: GraniteReactHostError.rnHostViewFactoryError)
|
|
58
|
+
}
|
|
59
|
+
} catch {
|
|
60
|
+
graniteSetupDidError(didFailWith: GraniteReactHostError.graniteItemConvertError)
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
graniteSetupDidError(didFailWith: GraniteReactHostError.bundleLoadError)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private func setGraniteBaseView(graniteBaseView: UIView) {
|
|
68
|
+
self.graniteBaseView = graniteBaseView
|
|
69
|
+
view.addSubview(graniteBaseView)
|
|
70
|
+
graniteBaseView.translatesAutoresizingMaskIntoConstraints = false
|
|
71
|
+
NSLayoutConstraint.activate([
|
|
72
|
+
graniteBaseView.topAnchor.constraint(equalTo: view.topAnchor),
|
|
73
|
+
graniteBaseView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
|
74
|
+
graniteBaseView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
|
75
|
+
graniteBaseView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
|
|
76
|
+
])
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private func convertToGraniteItem(bundleSource: BundleSource) async throws -> GraniteHostItem {
|
|
80
|
+
switch bundleSource {
|
|
81
|
+
case .devServer(let host, let port, let moduleName, let forBundleRoot):
|
|
82
|
+
if let url = URL(string: "http://\(host):\(port)/\(forBundleRoot).bundle?platform=ios&dev=true&minify=false") {
|
|
83
|
+
return .init(hostURL: url, moduleName: moduleName)
|
|
84
|
+
} else {
|
|
85
|
+
let url = URL(string: "http://\(host):\(port)/\(forBundleRoot).bundle?platform=ios&dev=true&minify=false")
|
|
86
|
+
return .init(hostURL: url!, moduleName: moduleName)
|
|
87
|
+
}
|
|
88
|
+
case .production(let location, let moduleName):
|
|
89
|
+
switch location {
|
|
90
|
+
case .fileSystemBundle(let filePath):
|
|
91
|
+
return .init(hostURL: filePath, moduleName: moduleName)
|
|
92
|
+
case .embeddedBundle:
|
|
93
|
+
if let url = Bundle.main.url(forResource: "main", withExtension: "jsbundle") {
|
|
94
|
+
return .init(hostURL: url, moduleName: moduleName)
|
|
95
|
+
} else {
|
|
96
|
+
throw GraniteReactHostError.bundleURLParsingError
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
case .customPath(let url, let moduleName):
|
|
100
|
+
return .init(hostURL: url, moduleName: moduleName)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//
|
|
2
|
+
// GraniteNativeFactory.swift
|
|
3
|
+
// GraniteScreen
|
|
4
|
+
//
|
|
5
|
+
// Swift implementation of GraniteNativeFactory
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
import Foundation
|
|
9
|
+
import ObjectiveC
|
|
10
|
+
import React_RCTAppDelegate
|
|
11
|
+
import UIKit
|
|
12
|
+
|
|
13
|
+
@objcMembers
|
|
14
|
+
open class GraniteNativeFactory: RCTReactNativeFactory {
|
|
15
|
+
|
|
16
|
+
open func view(
|
|
17
|
+
withModuleName moduleName: String,
|
|
18
|
+
initialProperties initProps: [AnyHashable: Any]?,
|
|
19
|
+
launchOptions: [AnyHashable: Any]?
|
|
20
|
+
) -> UIView? {
|
|
21
|
+
return self.rootViewFactory.view(
|
|
22
|
+
withModuleName: moduleName,
|
|
23
|
+
initialProperties: initProps,
|
|
24
|
+
launchOptions: launchOptions
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@objc(getModuleInstanceFromClass:)
|
|
29
|
+
open func getModuleInstance(from moduleClass: AnyClass) -> Any? {
|
|
30
|
+
return GraniteDefaultModuleProvider.defaultModuleInstance(
|
|
31
|
+
for: moduleClass,
|
|
32
|
+
dependencyProvider: delegate?.dependencyProvider
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
}
|