react-native-legal 1.0.0 → 1.1.1
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/android/build.gradle +6 -7
- package/android/src/main/java/com/reactnativelegal/ReactNativeLegalActivity.kt +23 -23
- package/android/src/main/java/com/reactnativelegal/ReactNativeLegalModuleImpl.kt +5 -4
- package/android/src/main/java/com/reactnativelegal/ReactNativeLegalTurboPackage.kt +4 -5
- package/android/src/newarch/java/com/reactnativelegal/ReactNativeLegalModule.kt +6 -4
- package/android/src/oldarch/java/com/reactnativelegal/ReactNativeLegalModule.kt +6 -5
- package/ios/ReactNativeLegalDetailViewController.swift +143 -130
- package/ios/ReactNativeLegalLicenseMetadata.swift +2 -2
- package/ios/ReactNativeLegalModule.h +1 -1
- package/ios/ReactNativeLegalModule.mm +6 -2
- package/ios/ReactNativeLegalModuleImpl.swift +74 -63
- package/ios/ReactNativeLegalTableViewController.swift +63 -59
- package/package.json +2 -3
- package/plugin-utils/build/common.d.ts +1 -0
- package/plugin-utils/build/common.js +46 -19
- package/plugin-utils/src/common.ts +76 -36
- package/android/react-native-helpers.gradle +0 -45
package/android/build.gradle
CHANGED
|
@@ -12,6 +12,7 @@ buildscript {
|
|
|
12
12
|
dependencies {
|
|
13
13
|
classpath("com.android.tools.build:gradle:7.2.1")
|
|
14
14
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
|
|
15
|
+
classpath("com.diffplug.spotless:com.diffplug.spotless.gradle.plugin:7.0.2")
|
|
15
16
|
}
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -19,6 +20,10 @@ def isNewArchitectureEnabled() {
|
|
|
19
20
|
return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
if (project == rootProject) {
|
|
24
|
+
apply from: 'spotless.gradle'
|
|
25
|
+
}
|
|
26
|
+
|
|
22
27
|
apply plugin: 'com.android.library'
|
|
23
28
|
apply plugin: 'kotlin-android'
|
|
24
29
|
if (isNewArchitectureEnabled()) {
|
|
@@ -55,14 +60,8 @@ repositories {
|
|
|
55
60
|
google()
|
|
56
61
|
}
|
|
57
62
|
|
|
58
|
-
apply from: "$projectDir/react-native-helpers.gradle"
|
|
59
|
-
|
|
60
63
|
dependencies {
|
|
61
|
-
|
|
62
|
-
implementation "com.facebook.react:react-android" // Set by the React Native Gradle Plugin
|
|
63
|
-
} else {
|
|
64
|
-
implementation 'com.facebook.react:react-native:+' // From node_modules
|
|
65
|
-
}
|
|
64
|
+
implementation "com.facebook.react:react-android" // Set by the React Native Gradle Plugin
|
|
66
65
|
|
|
67
66
|
def aboutLibrariesPluginVersion = "10.8.3"
|
|
68
67
|
implementation("com.mikepenz:aboutlibraries-core-android:$aboutLibrariesPluginVersion")
|
|
@@ -12,12 +12,10 @@ import com.mikepenz.aboutlibraries.LibsBuilder.Companion.BUNDLE_TITLE
|
|
|
12
12
|
import com.mikepenz.aboutlibraries.ui.LibsSupportFragment
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Based on AboutLibraries LibsActivity,
|
|
16
|
-
*
|
|
17
|
-
* with improved back handling
|
|
18
|
-
* and hidden Toolbar/ActionBar for TV devices
|
|
15
|
+
* Based on AboutLibraries LibsActivity, but simplified (no search filter), with improved back
|
|
16
|
+
* handling and hidden Toolbar/ActionBar for TV devices
|
|
19
17
|
*/
|
|
20
|
-
class ReactNativeLegalActivity: AppCompatActivity() {
|
|
18
|
+
class ReactNativeLegalActivity : AppCompatActivity() {
|
|
21
19
|
private lateinit var fragment: LibsSupportFragment
|
|
22
20
|
|
|
23
21
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
@@ -26,9 +24,7 @@ class ReactNativeLegalActivity: AppCompatActivity() {
|
|
|
26
24
|
setContentView(R.layout.activity_licenses)
|
|
27
25
|
|
|
28
26
|
val bundle = intent.extras
|
|
29
|
-
fragment = LibsSupportFragment().apply {
|
|
30
|
-
arguments = bundle
|
|
31
|
-
}
|
|
27
|
+
fragment = LibsSupportFragment().apply { arguments = bundle }
|
|
32
28
|
|
|
33
29
|
// https://developer.android.com/training/tv/start/hardware.html#runtime-check
|
|
34
30
|
val isTVDevice = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
|
@@ -39,13 +35,16 @@ class ReactNativeLegalActivity: AppCompatActivity() {
|
|
|
39
35
|
setupToolbar(bundle)
|
|
40
36
|
}
|
|
41
37
|
|
|
42
|
-
supportFragmentManager
|
|
38
|
+
supportFragmentManager
|
|
39
|
+
.beginTransaction()
|
|
40
|
+
.replace(R.id.fragment_container, fragment)
|
|
41
|
+
.commit()
|
|
43
42
|
}
|
|
44
43
|
|
|
45
44
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
46
45
|
if (item.itemId == android.R.id.home) {
|
|
47
46
|
onBackPressedDispatcher.onBackPressed()
|
|
48
|
-
return true
|
|
47
|
+
return true
|
|
49
48
|
}
|
|
50
49
|
return super.onOptionsItemSelected(item)
|
|
51
50
|
}
|
|
@@ -57,9 +56,7 @@ class ReactNativeLegalActivity: AppCompatActivity() {
|
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
private fun setupToolbar(bundle: Bundle?) {
|
|
60
|
-
val title = bundle?.let {
|
|
61
|
-
it.getString(BUNDLE_TITLE, "")
|
|
62
|
-
} ?: ""
|
|
59
|
+
val title = bundle?.let { it.getString(BUNDLE_TITLE, "") } ?: ""
|
|
63
60
|
|
|
64
61
|
val toolbar = findViewById<Toolbar>(R.id.toolbar)
|
|
65
62
|
setSupportActionBar(toolbar)
|
|
@@ -71,12 +68,13 @@ class ReactNativeLegalActivity: AppCompatActivity() {
|
|
|
71
68
|
}
|
|
72
69
|
|
|
73
70
|
toolbar.setOnApplyWindowInsetsListener { v, insets ->
|
|
74
|
-
val systemInsets =
|
|
75
|
-
|
|
71
|
+
val systemInsets =
|
|
72
|
+
WindowInsetsCompat.toWindowInsetsCompat(insets)
|
|
73
|
+
.getInsets(WindowInsetsCompat.Type.systemBars())
|
|
76
74
|
v.updatePadding(
|
|
77
75
|
left = v.paddingLeft + systemInsets.left,
|
|
78
76
|
top = v.paddingTop + systemInsets.top,
|
|
79
|
-
right = v.paddingRight + systemInsets.right
|
|
77
|
+
right = v.paddingRight + systemInsets.right,
|
|
80
78
|
)
|
|
81
79
|
|
|
82
80
|
insets
|
|
@@ -85,14 +83,16 @@ class ReactNativeLegalActivity: AppCompatActivity() {
|
|
|
85
83
|
if (toolbar.isAttachedToWindow) {
|
|
86
84
|
toolbar.requestApplyInsets()
|
|
87
85
|
} else {
|
|
88
|
-
toolbar.addOnAttachStateChangeListener(
|
|
89
|
-
|
|
90
|
-
v
|
|
91
|
-
|
|
86
|
+
toolbar.addOnAttachStateChangeListener(
|
|
87
|
+
object : View.OnAttachStateChangeListener {
|
|
88
|
+
override fun onViewAttachedToWindow(v: View) {
|
|
89
|
+
v.removeOnAttachStateChangeListener(this)
|
|
90
|
+
v.requestApplyInsets()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
override fun onViewDetachedFromWindow(v: View) = Unit
|
|
92
94
|
}
|
|
93
|
-
|
|
94
|
-
override fun onViewDetachedFromWindow(v: View) = Unit
|
|
95
|
-
})
|
|
95
|
+
)
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -9,10 +9,11 @@ object ReactNativeLegalModuleImpl {
|
|
|
9
9
|
|
|
10
10
|
fun launchLicenseListScreen(reactContext: ReactApplicationContext, licenseHeaderText: String) {
|
|
11
11
|
val context = reactContext.currentActivity ?: return
|
|
12
|
-
val intent =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
val intent =
|
|
13
|
+
Intent(context, ReactNativeLegalActivity::class.java).apply {
|
|
14
|
+
this.putExtra("data", LibsBuilder())
|
|
15
|
+
this.putExtra(LibsBuilder.BUNDLE_TITLE, licenseHeaderText)
|
|
16
|
+
}
|
|
16
17
|
|
|
17
18
|
context.startActivity(intent)
|
|
18
19
|
}
|
|
@@ -8,7 +8,7 @@ import com.facebook.react.module.model.ReactModuleInfo
|
|
|
8
8
|
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
9
9
|
import com.facebook.react.turbomodule.core.interfaces.TurboModule
|
|
10
10
|
|
|
11
|
-
class ReactNativeLegalTurboPackage: TurboReactPackage() {
|
|
11
|
+
class ReactNativeLegalTurboPackage : TurboReactPackage() {
|
|
12
12
|
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
13
13
|
return when (name) {
|
|
14
14
|
ReactNativeLegalModule.NAME -> ReactNativeLegalModule(reactContext)
|
|
@@ -17,9 +17,8 @@ class ReactNativeLegalTurboPackage: TurboReactPackage() {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
20
|
-
val moduleList: Array<Class<out NativeModule?>> =
|
|
21
|
-
ReactNativeLegalModule::class.java
|
|
22
|
-
)
|
|
20
|
+
val moduleList: Array<Class<out NativeModule?>> =
|
|
21
|
+
arrayOf(ReactNativeLegalModule::class.java)
|
|
23
22
|
val reactModuleInfoMap: MutableMap<String, ReactModuleInfo> = HashMap()
|
|
24
23
|
for (moduleClass in moduleList) {
|
|
25
24
|
val reactModule = moduleClass.getAnnotation(ReactModule::class.java) ?: continue
|
|
@@ -30,7 +29,7 @@ class ReactNativeLegalTurboPackage: TurboReactPackage() {
|
|
|
30
29
|
true,
|
|
31
30
|
reactModule.needsEagerInit,
|
|
32
31
|
reactModule.isCxxModule,
|
|
33
|
-
TurboModule::class.java.isAssignableFrom(moduleClass)
|
|
32
|
+
TurboModule::class.java.isAssignableFrom(moduleClass),
|
|
34
33
|
)
|
|
35
34
|
}
|
|
36
35
|
return ReactModuleInfoProvider { reactModuleInfoMap }
|
|
@@ -4,11 +4,13 @@ import com.facebook.react.bridge.ReactApplicationContext
|
|
|
4
4
|
import com.facebook.react.module.annotations.ReactModule
|
|
5
5
|
|
|
6
6
|
@ReactModule(name = ReactNativeLegalModule.NAME)
|
|
7
|
-
class ReactNativeLegalModule(
|
|
8
|
-
reactContext
|
|
9
|
-
) : NativeReactNativeLegalSpec(reactContext) {
|
|
7
|
+
class ReactNativeLegalModule(reactContext: ReactApplicationContext) :
|
|
8
|
+
NativeReactNativeLegalSpec(reactContext) {
|
|
10
9
|
override fun launchLicenseListScreen(licenseHeaderText: String) {
|
|
11
|
-
ReactNativeLegalModuleImpl.launchLicenseListScreen(
|
|
10
|
+
ReactNativeLegalModuleImpl.launchLicenseListScreen(
|
|
11
|
+
reactApplicationContext,
|
|
12
|
+
licenseHeaderText,
|
|
13
|
+
)
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
companion object {
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
package com.reactnativelegal
|
|
2
2
|
|
|
3
|
-
import com.facebook.react.bridge.Promise
|
|
4
3
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
4
|
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
6
5
|
import com.facebook.react.bridge.ReactMethod
|
|
7
6
|
import com.facebook.react.module.annotations.ReactModule
|
|
8
7
|
|
|
9
8
|
@ReactModule(name = ReactNativeLegalModule.NAME)
|
|
10
|
-
class ReactNativeLegalModule(
|
|
11
|
-
|
|
12
|
-
) : ReactContextBaseJavaModule(reactContext) {
|
|
9
|
+
class ReactNativeLegalModule(private val reactContext: ReactApplicationContext) :
|
|
10
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
13
11
|
override fun getName() = NAME
|
|
14
12
|
|
|
15
13
|
@ReactMethod
|
|
16
14
|
fun launchLicenseListScreen(licenseHeaderText: String) {
|
|
17
|
-
ReactNativeLegalModuleImpl.launchLicenseListScreen(
|
|
15
|
+
ReactNativeLegalModuleImpl.launchLicenseListScreen(
|
|
16
|
+
reactApplicationContext,
|
|
17
|
+
licenseHeaderText,
|
|
18
|
+
)
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
companion object {
|
|
@@ -1,156 +1,169 @@
|
|
|
1
1
|
class ReactNativeLegalDetailViewController: UIViewController {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
#if os(tvOS)
|
|
6
|
-
let fontSize: CGFloat = 40
|
|
7
|
-
let horizontalContentInset: CGFloat = 100
|
|
8
|
-
let bottomContentInset: CGFloat = 100
|
|
9
|
-
#else
|
|
10
|
-
let fontSize: CGFloat = 13.3
|
|
11
|
-
let horizontalContentInset: CGFloat = 26
|
|
12
|
-
let bottomContentInset: CGFloat = 16
|
|
13
|
-
#endif
|
|
14
|
-
|
|
15
|
-
textView.alwaysBounceVertical = true
|
|
16
|
-
#if os(iOS)
|
|
17
|
-
textView.dataDetectorTypes = .link
|
|
18
|
-
#endif
|
|
19
|
-
textView.font = UIFont.systemFont(ofSize: fontSize)
|
|
20
|
-
#if os(iOS)
|
|
21
|
-
textView.isEditable = false
|
|
22
|
-
#endif
|
|
23
|
-
#if os(tvOS)
|
|
24
|
-
textView.isSelectable = true
|
|
25
|
-
textView.isUserInteractionEnabled = true
|
|
26
|
-
#endif
|
|
27
|
-
textView.textContainerInset = UIEdgeInsets(top: 0, left: horizontalContentInset, bottom: bottomContentInset, right: horizontalContentInset)
|
|
28
|
-
textView.translatesAutoresizingMaskIntoConstraints = false
|
|
29
|
-
|
|
30
|
-
if #available(iOS 13.0, tvOS 13.0, *) {
|
|
31
|
-
#if os(iOS)
|
|
32
|
-
textView.backgroundColor = .systemBackground
|
|
33
|
-
#endif
|
|
34
|
-
textView.textColor = .secondaryLabel
|
|
35
|
-
}
|
|
2
|
+
private var licenseContentTextView: UITextView = {
|
|
3
|
+
let textView = UITextView()
|
|
36
4
|
|
|
37
|
-
|
|
38
|
-
|
|
5
|
+
#if os(tvOS)
|
|
6
|
+
let fontSize: CGFloat = 40
|
|
7
|
+
let horizontalContentInset: CGFloat = 100
|
|
8
|
+
let bottomContentInset: CGFloat = 100
|
|
9
|
+
#else
|
|
10
|
+
let fontSize: CGFloat = 13.3
|
|
11
|
+
let horizontalContentInset: CGFloat = 26
|
|
12
|
+
let bottomContentInset: CGFloat = 16
|
|
13
|
+
#endif
|
|
39
14
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
15
|
+
textView.alwaysBounceVertical = true
|
|
16
|
+
#if os(iOS)
|
|
17
|
+
textView.dataDetectorTypes = .link
|
|
18
|
+
#endif
|
|
19
|
+
textView.font = UIFont.systemFont(ofSize: fontSize)
|
|
20
|
+
#if os(iOS)
|
|
21
|
+
textView.isEditable = false
|
|
22
|
+
#endif
|
|
23
|
+
#if os(tvOS)
|
|
24
|
+
textView.isSelectable = true
|
|
25
|
+
textView.isUserInteractionEnabled = true
|
|
26
|
+
#endif
|
|
27
|
+
textView.textContainerInset = UIEdgeInsets(
|
|
28
|
+
top: 0, left: horizontalContentInset, bottom: bottomContentInset,
|
|
29
|
+
right: horizontalContentInset)
|
|
30
|
+
textView.translatesAutoresizingMaskIntoConstraints = false
|
|
31
|
+
|
|
32
|
+
if #available(iOS 13.0, tvOS 13.0, *) {
|
|
33
|
+
#if os(iOS)
|
|
34
|
+
textView.backgroundColor = .systemBackground
|
|
35
|
+
#endif
|
|
36
|
+
textView.textColor = .secondaryLabel
|
|
50
37
|
}
|
|
51
|
-
|
|
52
|
-
|
|
38
|
+
|
|
39
|
+
return textView
|
|
40
|
+
}()
|
|
41
|
+
|
|
42
|
+
private var metadata: ReactNativeLegalLicenseMetadata
|
|
43
|
+
|
|
44
|
+
init(metadata: ReactNativeLegalLicenseMetadata) {
|
|
45
|
+
self.metadata = metadata
|
|
46
|
+
super.init(nibName: nil, bundle: nil)
|
|
47
|
+
self.title = metadata.name
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
required init?(coder: NSCoder) {
|
|
51
|
+
fatalError("init(coder:) has not been implemented")
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
#if os(iOS)
|
|
53
55
|
private var scrolledToTopOnMount = false
|
|
54
|
-
|
|
56
|
+
|
|
55
57
|
override func viewDidLayoutSubviews() {
|
|
56
|
-
|
|
58
|
+
super.viewDidLayoutSubviews()
|
|
57
59
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
if scrolledToTopOnMount {
|
|
61
|
+
return
|
|
62
|
+
}
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
licenseContentTextView.setContentOffset(CGPoint.zero, animated: false)
|
|
65
|
+
scrolledToTopOnMount = true
|
|
64
66
|
}
|
|
65
|
-
#endif
|
|
66
|
-
|
|
67
|
-
override func viewDidLoad() {
|
|
68
|
-
super.viewDidLoad()
|
|
69
|
-
|
|
70
|
-
view.addSubview(licenseContentTextView)
|
|
71
|
-
#if os(iOS)
|
|
72
|
-
if #available(iOS 13.0, *) {
|
|
73
|
-
view.backgroundColor = .systemBackground
|
|
74
|
-
}
|
|
75
|
-
#endif
|
|
67
|
+
#endif
|
|
76
68
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
licenseContentTextView.bottomAnchor.constraint(equalToSystemSpacingBelow: view.safeAreaLayoutGuide.bottomAnchor, multiplier: 1.0),
|
|
80
|
-
licenseContentTextView.leadingAnchor.constraint(equalToSystemSpacingAfter: view.safeAreaLayoutGuide.leadingAnchor, multiplier: 1.0),
|
|
81
|
-
licenseContentTextView.trailingAnchor.constraint(equalToSystemSpacingAfter: view.safeAreaLayoutGuide.trailingAnchor, multiplier: 1.0)
|
|
82
|
-
])
|
|
69
|
+
override func viewDidLoad() {
|
|
70
|
+
super.viewDidLoad()
|
|
83
71
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
|
|
91
|
-
if context.previouslyFocusedView == context.nextFocusedView {
|
|
92
|
-
return
|
|
93
|
-
}
|
|
72
|
+
view.addSubview(licenseContentTextView)
|
|
73
|
+
#if os(iOS)
|
|
74
|
+
if #available(iOS 13.0, *) {
|
|
75
|
+
view.backgroundColor = .systemBackground
|
|
76
|
+
}
|
|
77
|
+
#endif
|
|
94
78
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
79
|
+
NSLayoutConstraint.activate([
|
|
80
|
+
licenseContentTextView.topAnchor.constraint(
|
|
81
|
+
equalToSystemSpacingBelow: view.safeAreaLayoutGuide.topAnchor, multiplier: 1.0),
|
|
82
|
+
licenseContentTextView.bottomAnchor.constraint(
|
|
83
|
+
equalToSystemSpacingBelow: view.safeAreaLayoutGuide.bottomAnchor, multiplier: 1.0),
|
|
84
|
+
licenseContentTextView.leadingAnchor.constraint(
|
|
85
|
+
equalToSystemSpacingAfter: view.safeAreaLayoutGuide.leadingAnchor, multiplier: 1.0),
|
|
86
|
+
licenseContentTextView.trailingAnchor.constraint(
|
|
87
|
+
equalToSystemSpacingAfter: view.safeAreaLayoutGuide.trailingAnchor, multiplier: 1.0),
|
|
88
|
+
])
|
|
89
|
+
|
|
90
|
+
licenseContentTextView.text = metadata.content
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#if os(tvOS)
|
|
94
|
+
private var tvRemoteGestureRecognizers: [String: UISwipeGestureRecognizer] = [:]
|
|
95
|
+
|
|
96
|
+
override func didUpdateFocus(
|
|
97
|
+
in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator
|
|
98
|
+
) {
|
|
99
|
+
if context.previouslyFocusedView == context.nextFocusedView {
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if context.nextFocusedView == licenseContentTextView {
|
|
104
|
+
licenseContentTextView.becomeFirstResponder()
|
|
105
|
+
self.addSwipeGestureRecognizers()
|
|
106
|
+
} else if context.previouslyFocusedView == licenseContentTextView {
|
|
107
|
+
self.removeSwipeGestureRecognizers()
|
|
108
|
+
licenseContentTextView.resignFirstResponder()
|
|
109
|
+
}
|
|
102
110
|
}
|
|
103
|
-
|
|
111
|
+
|
|
104
112
|
private func addSwipeGestureRecognizers() {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
113
|
+
let recognizerUp = UISwipeGestureRecognizer(target: self, action: #selector(swipedUp))
|
|
114
|
+
recognizerUp.direction = .up
|
|
115
|
+
|
|
116
|
+
let recognizerDown = UISwipeGestureRecognizer(target: self, action: #selector(swipedDown))
|
|
117
|
+
recognizerDown.direction = .down
|
|
118
|
+
|
|
119
|
+
tvRemoteGestureRecognizers["swipedUp"] = recognizerUp
|
|
120
|
+
tvRemoteGestureRecognizers["swipedDown"] = recognizerDown
|
|
121
|
+
|
|
122
|
+
for recognizer in tvRemoteGestureRecognizers.values {
|
|
123
|
+
licenseContentTextView.addGestureRecognizer(recognizer)
|
|
124
|
+
}
|
|
117
125
|
}
|
|
118
|
-
|
|
126
|
+
|
|
119
127
|
private func removeSwipeGestureRecognizers() {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
128
|
+
if let recognizerUp = tvRemoteGestureRecognizers["swipedUp"] {
|
|
129
|
+
licenseContentTextView.removeGestureRecognizer(recognizerUp)
|
|
130
|
+
}
|
|
123
131
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
132
|
+
if let recognizerDown = tvRemoteGestureRecognizers["swipedDown"] {
|
|
133
|
+
licenseContentTextView.removeGestureRecognizer(recognizerDown)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
tvRemoteGestureRecognizers.removeAll()
|
|
129
137
|
}
|
|
130
|
-
|
|
138
|
+
|
|
131
139
|
@objc private func swipedUp() {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
140
|
+
let offset =
|
|
141
|
+
licenseContentTextView.contentOffset.y - licenseContentTextView.visibleSize.height / 2
|
|
142
|
+
|
|
143
|
+
self.swipeVertical(with: offset)
|
|
135
144
|
}
|
|
136
|
-
|
|
145
|
+
|
|
137
146
|
@objc private func swipedDown() {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
147
|
+
let offset =
|
|
148
|
+
licenseContentTextView.contentOffset.y + licenseContentTextView.visibleSize.height / 2
|
|
149
|
+
|
|
150
|
+
self.swipeVertical(with: offset)
|
|
141
151
|
}
|
|
142
|
-
|
|
152
|
+
|
|
143
153
|
@objc private func swipeVertical(with offset: CGFloat) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
154
|
+
DispatchQueue.main.async {
|
|
155
|
+
let newOffset = min(
|
|
156
|
+
max(offset, 0),
|
|
157
|
+
self.licenseContentTextView.contentSize.height
|
|
158
|
+
- self.licenseContentTextView.visibleSize.height)
|
|
159
|
+
|
|
160
|
+
UIView.animate(withDuration: 0.5) {
|
|
161
|
+
self.licenseContentTextView.contentOffset = CGPoint(
|
|
162
|
+
x: self.licenseContentTextView.contentOffset.x,
|
|
163
|
+
y: newOffset
|
|
164
|
+
)
|
|
153
165
|
}
|
|
166
|
+
}
|
|
154
167
|
}
|
|
155
|
-
#endif
|
|
168
|
+
#endif
|
|
156
169
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#import "ReactNativeLegalModule.h"
|
|
2
2
|
|
|
3
|
+
#if __has_include(<ReactNativeLegal/ReactNativeLegal-Swift.h>)
|
|
4
|
+
#import <ReactNativeLegal/ReactNativeLegal-Swift.h>
|
|
5
|
+
#else
|
|
3
6
|
#import "ReactNativeLegal-Swift.h"
|
|
7
|
+
#endif
|
|
4
8
|
|
|
5
9
|
#if RCT_NEW_ARCH_ENABLED
|
|
6
10
|
#import "ReactNativeLegal.h"
|
|
@@ -15,14 +19,14 @@ RCT_EXPORT_MODULE(ReactNativeLegalModule)
|
|
|
15
19
|
|
|
16
20
|
RCT_EXPORT_METHOD(launchLicenseListScreen : (NSString *)licenseHeaderText)
|
|
17
21
|
{
|
|
18
|
-
|
|
22
|
+
[ReactNativeLegalModuleImpl launchLicenseListScreenWithLicenseHeaderText:licenseHeaderText];
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
#if RCT_NEW_ARCH_ENABLED
|
|
22
26
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
23
27
|
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
24
28
|
{
|
|
25
|
-
|
|
29
|
+
return std::make_shared<facebook::react::NativeReactNativeLegalSpecJSI>(params);
|
|
26
30
|
}
|
|
27
31
|
#endif
|
|
28
32
|
|
|
@@ -1,78 +1,89 @@
|
|
|
1
1
|
import UIKit
|
|
2
2
|
|
|
3
3
|
@objc(ReactNativeLegalModuleImpl)
|
|
4
|
-
public class ReactNativeLegalModuleImpl
|
|
5
|
-
|
|
4
|
+
public class ReactNativeLegalModuleImpl: NSObject {
|
|
5
|
+
@objc public static func launchLicenseListScreen(licenseHeaderText: String) {
|
|
6
|
+
guard
|
|
7
|
+
let settingsBundleUrl = Bundle.main.url(forResource: "Settings", withExtension: "bundle"),
|
|
8
|
+
let settingsBundle = Bundle(url: settingsBundleUrl),
|
|
9
|
+
let licensePlistUrl = settingsBundle.url(
|
|
10
|
+
forResource: "com.mono0926.LicensePlist", withExtension: "plist"),
|
|
11
|
+
let dictArray = parsePlistToDictArray(fileUrl: licensePlistUrl)
|
|
12
|
+
else {
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let licenses = parseRawLicenseMetadataToArray(
|
|
17
|
+
rawLicenses: getChildPaneSpecifiers(dictArray: dictArray),
|
|
18
|
+
licensePlistUrl: licensePlistUrl
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
DispatchQueue.main.async {
|
|
22
|
+
guard let presentedViewController = RCTPresentedViewController() else {
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let tableViewController = ReactNativeLegalTableViewController(
|
|
27
|
+
title: licenseHeaderText, data: licenses)
|
|
28
|
+
let navController = UINavigationController(rootViewController: tableViewController)
|
|
29
|
+
navController.modalPresentationStyle = .fullScreen
|
|
30
|
+
presentedViewController.present(navController, animated: true)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private static func getChildPaneSpecifiers(dictArray: [[String: Any]]) -> [[String: Any]] {
|
|
35
|
+
return
|
|
36
|
+
dictArray
|
|
37
|
+
.filter { $0["Type"] as? String == "PSChildPaneSpecifier" }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private static func parseRawLicenseMetadataToArray(
|
|
41
|
+
rawLicenses: [[String: Any]], licensePlistUrl: URL
|
|
42
|
+
) -> [ReactNativeLegalLicenseMetadata] {
|
|
43
|
+
return
|
|
44
|
+
rawLicenses
|
|
45
|
+
.map { licenseObj in
|
|
6
46
|
guard
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return
|
|
47
|
+
let file = licenseObj["File"] as? String,
|
|
48
|
+
let title = licenseObj["Title"] as? String
|
|
49
|
+
else {
|
|
50
|
+
return ReactNativeLegalLicenseMetadata()
|
|
12
51
|
}
|
|
13
|
-
|
|
14
|
-
let licenses = parseRawLicenseMetadataToArray(
|
|
15
|
-
rawLicenses: getChildPaneSpecifiers(dictArray: dictArray),
|
|
16
|
-
licensePlistUrl: licensePlistUrl
|
|
17
|
-
)
|
|
18
52
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
let tableViewController = ReactNativeLegalTableViewController(title: licenseHeaderText, data: licenses)
|
|
25
|
-
let navController = UINavigationController(rootViewController: tableViewController)
|
|
26
|
-
navController.modalPresentationStyle = .fullScreen
|
|
27
|
-
presentedViewController.present(navController, animated: true)
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
private static func getChildPaneSpecifiers(dictArray: Array<Dictionary<String, Any>>) -> Array<Dictionary<String, Any>> {
|
|
32
|
-
return dictArray
|
|
33
|
-
.filter { $0["Type"] as? String == "PSChildPaneSpecifier" }
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
private static func parseRawLicenseMetadataToArray(rawLicenses: Array<Dictionary<String, Any>>, licensePlistUrl: URL) -> [ReactNativeLegalLicenseMetadata] {
|
|
37
|
-
return rawLicenses
|
|
38
|
-
.map { licenseObj in
|
|
39
|
-
guard
|
|
40
|
-
let file = licenseObj["File"] as? String,
|
|
41
|
-
let title = licenseObj["Title"] as? String else {
|
|
42
|
-
return ReactNativeLegalLicenseMetadata()
|
|
43
|
-
}
|
|
53
|
+
let fileUrl = licensePlistUrl.deletingLastPathComponent().addPathComponent(file)
|
|
54
|
+
.appendingPathExtension("plist")
|
|
44
55
|
|
|
45
|
-
let fileUrl = licensePlistUrl.deletingLastPathComponent().addPathComponent(file).appendingPathExtension("plist")
|
|
46
|
-
|
|
47
|
-
guard
|
|
48
|
-
let data = parsePlistToDictArray(fileUrl: fileUrl),
|
|
49
|
-
let item = data.first(where: { $0["FooterText"] != nil }),
|
|
50
|
-
let footerText = item["FooterText"] as? String else {
|
|
51
|
-
return ReactNativeLegalLicenseMetadata(name: title)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return ReactNativeLegalLicenseMetadata(name: title, content: footerText)
|
|
55
|
-
}
|
|
56
|
-
.filter { $0.name != nil }
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
private static func parsePlistToDictArray(fileUrl: URL) -> Array<Dictionary<String, Any>>? {
|
|
60
56
|
guard
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
57
|
+
let data = parsePlistToDictArray(fileUrl: fileUrl),
|
|
58
|
+
let item = data.first(where: { $0["FooterText"] != nil }),
|
|
59
|
+
let footerText = item["FooterText"] as? String
|
|
60
|
+
else {
|
|
61
|
+
return ReactNativeLegalLicenseMetadata(name: title)
|
|
64
62
|
}
|
|
65
|
-
|
|
66
|
-
return
|
|
63
|
+
|
|
64
|
+
return ReactNativeLegalLicenseMetadata(name: title, content: footerText)
|
|
65
|
+
}
|
|
66
|
+
.filter { $0.name != nil }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private static func parsePlistToDictArray(fileUrl: URL) -> [[String: Any]]? {
|
|
70
|
+
guard
|
|
71
|
+
let data = try? NSDictionary(contentsOf: fileUrl, error: ()),
|
|
72
|
+
let array = data["PreferenceSpecifiers"] as? [Any]
|
|
73
|
+
else {
|
|
74
|
+
return nil
|
|
67
75
|
}
|
|
76
|
+
|
|
77
|
+
return array.compactMap { $0 as? [String: Any] }
|
|
78
|
+
}
|
|
68
79
|
}
|
|
69
80
|
|
|
70
81
|
extension URL {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return self.appendingPathComponent(component)
|
|
82
|
+
func addPathComponent(_ component: String) -> URL {
|
|
83
|
+
if #available(iOS 16.0, tvOS 16.0, *) {
|
|
84
|
+
return self.appending(component: component)
|
|
77
85
|
}
|
|
86
|
+
|
|
87
|
+
return self.appendingPathComponent(component)
|
|
88
|
+
}
|
|
78
89
|
}
|
|
@@ -1,72 +1,76 @@
|
|
|
1
1
|
class ReactNativeLegalTableViewController: UITableViewController {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
2
|
+
private let cellIdentifier = "ReactNativeLegalCell"
|
|
3
|
+
|
|
4
|
+
private var data: [ReactNativeLegalLicenseMetadata] = [] {
|
|
5
|
+
didSet {
|
|
6
|
+
tableView.reloadData()
|
|
8
7
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
#else
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public convenience init(title: String, data: [ReactNativeLegalLicenseMetadata]) {
|
|
11
|
+
#if os(iOS)
|
|
12
|
+
if #available(iOS 13.0, *) {
|
|
13
|
+
self.init(style: .insetGrouped)
|
|
14
|
+
} else {
|
|
18
15
|
self.init(style: .grouped)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
}
|
|
17
|
+
#else
|
|
18
|
+
self.init(style: .grouped)
|
|
19
|
+
#endif
|
|
20
|
+
self.title = title
|
|
21
|
+
self.data = data
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#if os(iOS)
|
|
25
25
|
override func viewDidLoad() {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
#endif
|
|
31
|
-
|
|
32
|
-
@objc func dismissTableViewController(_ sender: AnyObject) {
|
|
33
|
-
self.navigationController?.dismiss(animated: true)
|
|
26
|
+
super.viewDidLoad()
|
|
27
|
+
|
|
28
|
+
setupCloseButton()
|
|
34
29
|
}
|
|
35
|
-
|
|
36
|
-
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
37
|
-
let dequeuedCell = tableView.dequeueReusableCell(withIdentifier: CELL_IDENTIFIER) ?? UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: CELL_IDENTIFIER)
|
|
30
|
+
#endif
|
|
38
31
|
|
|
39
|
-
|
|
32
|
+
@objc func dismissTableViewController(_ sender: AnyObject) {
|
|
33
|
+
self.navigationController?.dismiss(animated: true)
|
|
34
|
+
}
|
|
40
35
|
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
|
|
37
|
+
-> UITableViewCell
|
|
38
|
+
{
|
|
39
|
+
let dequeuedCell =
|
|
40
|
+
tableView.dequeueReusableCell(withIdentifier: cellIdentifier)
|
|
41
|
+
?? UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: cellIdentifier)
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
48
|
-
return data.count
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
52
|
-
let metadata = data[indexPath.row]
|
|
53
|
-
let detailViewController = ReactNativeLegalDetailViewController(metadata: metadata)
|
|
43
|
+
let metadata = data[indexPath.row]
|
|
54
44
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
45
|
+
dequeuedCell.textLabel?.text = metadata.name
|
|
46
|
+
dequeuedCell.accessoryType = UITableViewCell.AccessoryType.disclosureIndicator
|
|
47
|
+
|
|
48
|
+
return dequeuedCell
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
52
|
+
return data.count
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
56
|
+
let metadata = data[indexPath.row]
|
|
57
|
+
let detailViewController = ReactNativeLegalDetailViewController(metadata: metadata)
|
|
58
|
+
|
|
59
|
+
self.navigationController?.pushViewController(detailViewController, animated: true)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#if os(iOS)
|
|
59
63
|
private func setupCloseButton() {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
var barButtonSystemItem: UIBarButtonItem.SystemItem = .done
|
|
65
|
+
if #available(iOS 13.0, *) {
|
|
66
|
+
barButtonSystemItem = .close
|
|
67
|
+
}
|
|
64
68
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
self.navigationItem.leftBarButtonItem = UIBarButtonItem(
|
|
70
|
+
barButtonSystemItem: barButtonSystemItem,
|
|
71
|
+
target: self,
|
|
72
|
+
action: #selector(dismissTableViewController(_:))
|
|
73
|
+
)
|
|
70
74
|
}
|
|
71
|
-
#endif
|
|
75
|
+
#endif
|
|
72
76
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-legal",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Acknowledge OSS libraries used in your React Native app",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -24,14 +24,13 @@
|
|
|
24
24
|
"bugs": {
|
|
25
25
|
"url": "https://github.com/callstackincubator/react-native-legal/issues"
|
|
26
26
|
},
|
|
27
|
-
"homepage": "https://github.
|
|
27
|
+
"homepage": "https://callstackincubator.github.io/react-native-legal/",
|
|
28
28
|
"publishConfig": {
|
|
29
29
|
"registry": "https://registry.npmjs.org/"
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
32
|
"android/src",
|
|
33
33
|
"android/build.gradle",
|
|
34
|
-
"android/react-native-helpers.gradle",
|
|
35
34
|
"bare-plugin",
|
|
36
35
|
"ios",
|
|
37
36
|
"lib",
|
|
@@ -34,19 +34,23 @@ function arrayIncludesObject(array, object) {
|
|
|
34
34
|
}
|
|
35
35
|
exports.arrayIncludesObject = arrayIncludesObject;
|
|
36
36
|
/**
|
|
37
|
-
* Scans
|
|
37
|
+
* Scans a single package and its dependencies for license information
|
|
38
38
|
*/
|
|
39
|
-
function
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
39
|
+
function scanPackage(packageName, version, processedPackages, result) {
|
|
40
|
+
const packageKey = `${packageName}@${version}`;
|
|
41
|
+
// Skip if already processed to avoid circular dependencies
|
|
42
|
+
if (processedPackages.has(packageKey)) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
processedPackages.add(packageKey);
|
|
46
|
+
try {
|
|
47
|
+
const localPackageJsonPath = getPackageJsonPath(packageName);
|
|
48
|
+
if (!localPackageJsonPath) {
|
|
49
|
+
console.warn(`[react-native-legal] skipping ${packageName} could not find package.json`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const localPackageJson = require(path_1.default.resolve(localPackageJsonPath));
|
|
53
|
+
if (localPackageJson.private !== true) {
|
|
50
54
|
const licenseFiles = glob.sync('LICEN{S,C}E{.md,}', {
|
|
51
55
|
cwd: path_1.default.dirname(localPackageJsonPath),
|
|
52
56
|
absolute: true,
|
|
@@ -54,20 +58,40 @@ function scanDependencies(appPackageJsonPath) {
|
|
|
54
58
|
nodir: true,
|
|
55
59
|
ignore: '**/{__tests__,__fixtures__,__mocks__}/**',
|
|
56
60
|
});
|
|
57
|
-
|
|
61
|
+
result[packageName] = {
|
|
58
62
|
author: parseAuthorField(localPackageJson),
|
|
59
63
|
content: licenseFiles?.[0] ? fs_1.default.readFileSync(licenseFiles[0], { encoding: 'utf-8' }) : undefined,
|
|
64
|
+
file: licenseFiles?.[0] ? licenseFiles[0] : undefined,
|
|
60
65
|
description: localPackageJson.description,
|
|
61
66
|
type: parseLicenseField(localPackageJson),
|
|
62
67
|
url: parseRepositoryFieldToUrl(localPackageJson),
|
|
63
68
|
version: localPackageJson.version,
|
|
64
69
|
};
|
|
65
70
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
const dependencies = localPackageJson.dependencies || {};
|
|
72
|
+
const isWorkspacePackage = version.startsWith('workspace:');
|
|
73
|
+
if (!isWorkspacePackage)
|
|
74
|
+
return;
|
|
75
|
+
Object.entries(dependencies).forEach(([depName, depVersion]) => {
|
|
76
|
+
scanPackage(depName, depVersion, processedPackages, result);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
console.warn(`[react-native-legal] could not process package.json for ${packageName}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Scans `package.json` and searches for all packages under `dependencies` field. Supports monorepo projects.
|
|
85
|
+
*/
|
|
86
|
+
function scanDependencies(appPackageJsonPath) {
|
|
87
|
+
const appPackageJson = require(path_1.default.resolve(appPackageJsonPath));
|
|
88
|
+
const dependencies = appPackageJson.dependencies;
|
|
89
|
+
const result = {};
|
|
90
|
+
const processedPackages = new Set();
|
|
91
|
+
Object.entries(dependencies).forEach(([packageName, version]) => {
|
|
92
|
+
scanPackage(packageName, version, processedPackages, result);
|
|
93
|
+
});
|
|
94
|
+
return result;
|
|
71
95
|
}
|
|
72
96
|
exports.scanDependencies = scanDependencies;
|
|
73
97
|
function needsQuoting(value) {
|
|
@@ -148,11 +172,14 @@ function generateLicensePlistNPMOutput(licenses, iosProjectPath) {
|
|
|
148
172
|
if (dependency !== normalizedName) {
|
|
149
173
|
renames[normalizedName] = dependency;
|
|
150
174
|
}
|
|
175
|
+
const relativeLicenseFile = licenseObj.file ? path_1.default.relative(iosProjectPath, licenseObj.file) : undefined;
|
|
151
176
|
return {
|
|
152
177
|
name: normalizedName,
|
|
153
178
|
version: licenseObj.version,
|
|
154
179
|
...(licenseObj.url && { source: licenseObj.url }),
|
|
155
|
-
|
|
180
|
+
...(licenseObj.file
|
|
181
|
+
? { file: relativeLicenseFile }
|
|
182
|
+
: { body: licenseObj.content ?? licenseObj.type ?? 'UNKNOWN' }),
|
|
156
183
|
};
|
|
157
184
|
});
|
|
158
185
|
const yamlDoc = {
|
|
@@ -8,6 +8,7 @@ type LicenseObj = {
|
|
|
8
8
|
author?: string;
|
|
9
9
|
content?: string;
|
|
10
10
|
description?: string;
|
|
11
|
+
file?: string;
|
|
11
12
|
type?: string;
|
|
12
13
|
url?: string;
|
|
13
14
|
version: string;
|
|
@@ -31,10 +32,11 @@ type AboutLibrariesLicenseJsonPayload = {
|
|
|
31
32
|
};
|
|
32
33
|
|
|
33
34
|
type LicensePlistPayload = {
|
|
34
|
-
body: string;
|
|
35
35
|
name: string;
|
|
36
36
|
source?: string;
|
|
37
37
|
version: string;
|
|
38
|
+
body?: string;
|
|
39
|
+
file?: string;
|
|
38
40
|
};
|
|
39
41
|
|
|
40
42
|
function compareObjects(a: unknown, b: unknown): boolean {
|
|
@@ -70,48 +72,82 @@ export function arrayIncludesObject(array?: unknown[], object?: unknown) {
|
|
|
70
72
|
return array?.map((item) => compareObjects(item, object)).reduce((acc, curr) => acc || curr, false);
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Scans a single package and its dependencies for license information
|
|
77
|
+
*/
|
|
78
|
+
function scanPackage(
|
|
79
|
+
packageName: string,
|
|
80
|
+
version: string,
|
|
81
|
+
processedPackages: Set<string>,
|
|
82
|
+
result: Record<string, LicenseObj>,
|
|
83
|
+
) {
|
|
84
|
+
const packageKey = `${packageName}@${version}`;
|
|
85
|
+
|
|
86
|
+
// Skip if already processed to avoid circular dependencies
|
|
87
|
+
if (processedPackages.has(packageKey)) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
processedPackages.add(packageKey);
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const localPackageJsonPath = getPackageJsonPath(packageName);
|
|
95
|
+
|
|
96
|
+
if (!localPackageJsonPath) {
|
|
97
|
+
console.warn(`[react-native-legal] skipping ${packageName} could not find package.json`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const localPackageJson = require(path.resolve(localPackageJsonPath));
|
|
102
|
+
|
|
103
|
+
if (localPackageJson.private !== true) {
|
|
104
|
+
const licenseFiles = glob.sync('LICEN{S,C}E{.md,}', {
|
|
105
|
+
cwd: path.dirname(localPackageJsonPath),
|
|
106
|
+
absolute: true,
|
|
107
|
+
nocase: true,
|
|
108
|
+
nodir: true,
|
|
109
|
+
ignore: '**/{__tests__,__fixtures__,__mocks__}/**',
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
result[packageName] = {
|
|
113
|
+
author: parseAuthorField(localPackageJson),
|
|
114
|
+
content: licenseFiles?.[0] ? fs.readFileSync(licenseFiles[0], { encoding: 'utf-8' }) : undefined,
|
|
115
|
+
file: licenseFiles?.[0] ? licenseFiles[0] : undefined,
|
|
116
|
+
description: localPackageJson.description,
|
|
117
|
+
type: parseLicenseField(localPackageJson),
|
|
118
|
+
url: parseRepositoryFieldToUrl(localPackageJson),
|
|
119
|
+
version: localPackageJson.version,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const dependencies = localPackageJson.dependencies || {};
|
|
124
|
+
|
|
125
|
+
const isWorkspacePackage = version.startsWith('workspace:');
|
|
126
|
+
|
|
127
|
+
if (!isWorkspacePackage) return;
|
|
128
|
+
|
|
129
|
+
Object.entries(dependencies).forEach(([depName, depVersion]) => {
|
|
130
|
+
scanPackage(depName, depVersion as string, processedPackages, result);
|
|
131
|
+
});
|
|
132
|
+
} catch (error) {
|
|
133
|
+
console.warn(`[react-native-legal] could not process package.json for ${packageName}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
73
137
|
/**
|
|
74
138
|
* Scans `package.json` and searches for all packages under `dependencies` field. Supports monorepo projects.
|
|
75
139
|
*/
|
|
76
140
|
export function scanDependencies(appPackageJsonPath: string) {
|
|
77
141
|
const appPackageJson = require(path.resolve(appPackageJsonPath));
|
|
78
142
|
const dependencies: Record<string, string> = appPackageJson.dependencies;
|
|
143
|
+
const result: Record<string, LicenseObj> = {};
|
|
144
|
+
const processedPackages = new Set<string>();
|
|
79
145
|
|
|
80
|
-
|
|
81
|
-
(
|
|
82
|
-
|
|
83
|
-
const localPackageJsonPath = getPackageJsonPath(dependency);
|
|
84
|
-
|
|
85
|
-
if (!localPackageJsonPath) {
|
|
86
|
-
console.warn(`[react-native-legal] skipping ${dependency} could not find package.json`);
|
|
87
|
-
return acc;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const localPackageJson = require(path.resolve(localPackageJsonPath));
|
|
91
|
-
const licenseFiles = glob.sync('LICEN{S,C}E{.md,}', {
|
|
92
|
-
cwd: path.dirname(localPackageJsonPath),
|
|
93
|
-
absolute: true,
|
|
94
|
-
nocase: true,
|
|
95
|
-
nodir: true,
|
|
96
|
-
ignore: '**/{__tests__,__fixtures__,__mocks__}/**',
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
acc[dependency] = {
|
|
100
|
-
author: parseAuthorField(localPackageJson),
|
|
101
|
-
content: licenseFiles?.[0] ? fs.readFileSync(licenseFiles[0], { encoding: 'utf-8' }) : undefined,
|
|
102
|
-
description: localPackageJson.description,
|
|
103
|
-
type: parseLicenseField(localPackageJson),
|
|
104
|
-
url: parseRepositoryFieldToUrl(localPackageJson),
|
|
105
|
-
version: localPackageJson.version,
|
|
106
|
-
};
|
|
107
|
-
} catch (error) {
|
|
108
|
-
console.warn(`[react-native-legal] could not process package.json for ${dependency}`);
|
|
109
|
-
}
|
|
146
|
+
Object.entries(dependencies).forEach(([packageName, version]) => {
|
|
147
|
+
scanPackage(packageName, version, processedPackages, result);
|
|
148
|
+
});
|
|
110
149
|
|
|
111
|
-
|
|
112
|
-
},
|
|
113
|
-
{} as Record<string, LicenseObj>,
|
|
114
|
-
);
|
|
150
|
+
return result;
|
|
115
151
|
}
|
|
116
152
|
|
|
117
153
|
function needsQuoting(value: string) {
|
|
@@ -210,11 +246,15 @@ export function generateLicensePlistNPMOutput(licenses: Record<string, LicenseOb
|
|
|
210
246
|
renames[normalizedName] = dependency;
|
|
211
247
|
}
|
|
212
248
|
|
|
249
|
+
const relativeLicenseFile = licenseObj.file ? path.relative(iosProjectPath, licenseObj.file) : undefined;
|
|
250
|
+
|
|
213
251
|
return {
|
|
214
252
|
name: normalizedName,
|
|
215
253
|
version: licenseObj.version,
|
|
216
254
|
...(licenseObj.url && { source: licenseObj.url }),
|
|
217
|
-
|
|
255
|
+
...(licenseObj.file
|
|
256
|
+
? { file: relativeLicenseFile }
|
|
257
|
+
: { body: licenseObj.content ?? licenseObj.type ?? 'UNKNOWN' }),
|
|
218
258
|
} as LicensePlistPayload;
|
|
219
259
|
});
|
|
220
260
|
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
def safeAppExtGet(prop, fallback) {
|
|
2
|
-
def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
|
|
3
|
-
appProject?.ext?.has(prop) ? appProject.ext.get(prop) : fallback
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
// Let's detect react-native's directory, it will be used to determine RN's version
|
|
7
|
-
// https://github.com/software-mansion/react-native-reanimated/blob/cda4627c3337c33674f05f755b7485165c6caca9/android/build.gradle#L88
|
|
8
|
-
def resolveReactNativeDirectory() {
|
|
9
|
-
def reactNativeLocation = safeAppExtGet("REACT_NATIVE_NODE_MODULES_DIR", null)
|
|
10
|
-
if (reactNativeLocation != null) {
|
|
11
|
-
return file(reactNativeLocation)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
// monorepo workaround
|
|
15
|
-
// react-native can be hoisted or in project's own node_modules
|
|
16
|
-
def reactNativeFromNodeModules = file("${projectDir}/../../react-native")
|
|
17
|
-
if (reactNativeFromNodeModules.exists()) {
|
|
18
|
-
return reactNativeFromNodeModules
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
def reactNativeFromProjectNodeModules = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
|
|
22
|
-
if (reactNativeFromProjectNodeModules.exists()) {
|
|
23
|
-
return reactNativeFromProjectNodeModules
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
throw new GradleException(
|
|
27
|
-
"[react-native-legal] Unable to resolve react-native location in " +
|
|
28
|
-
"node_modules. You should project extension property (in app/build.gradle) " +
|
|
29
|
-
"`REACT_NATIVE_NODE_MODULES_DIR` with path to react-native."
|
|
30
|
-
)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// https://github.com/software-mansion/react-native-reanimated/blob/cda4627c3337c33674f05f755b7485165c6caca9/android/build.gradle#L199#L205
|
|
34
|
-
def reactNativeRootDir = resolveReactNativeDirectory()
|
|
35
|
-
|
|
36
|
-
def reactProperties = new Properties()
|
|
37
|
-
print("$reactNativeRootDir")
|
|
38
|
-
file("$reactNativeRootDir/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) }
|
|
39
|
-
|
|
40
|
-
def REACT_NATIVE_VERSION = reactProperties.getProperty("VERSION_NAME")
|
|
41
|
-
def REACT_NATIVE_MINOR_VERSION = REACT_NATIVE_VERSION.startsWith("0.0.0-") ? 1000 : REACT_NATIVE_VERSION.split("\\.")[1].toInteger()
|
|
42
|
-
|
|
43
|
-
project.ext.shouldConsumeReactNativeFromMavenCentral = { ->
|
|
44
|
-
return REACT_NATIVE_MINOR_VERSION >= 71
|
|
45
|
-
}
|