react-native-readium 4.0.1 → 5.0.0-rc.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/README.md +93 -9
- package/android/build.gradle +32 -29
- package/android/gradle.properties +3 -3
- package/android/src/main/java/com/reactnativereadium/ReadiumView.kt +88 -28
- package/android/src/main/java/com/reactnativereadium/ReadiumViewManager.kt +20 -19
- package/android/src/main/java/com/reactnativereadium/reader/BaseReaderFragment.kt +27 -6
- package/android/src/main/java/com/reactnativereadium/reader/EpubReaderFragment.kt +53 -69
- package/android/src/main/java/com/reactnativereadium/reader/PositionLabelManager.kt +132 -0
- package/android/src/main/java/com/reactnativereadium/reader/ReaderService.kt +64 -65
- package/android/src/main/java/com/reactnativereadium/reader/ReaderViewModel.kt +6 -85
- package/android/src/main/java/com/reactnativereadium/reader/VisualReaderFragment.kt +32 -2
- package/android/src/main/java/com/reactnativereadium/utils/FragmentFactory.kt +2 -3
- package/android/src/main/java/com/reactnativereadium/utils/JsonExtensions.kt +61 -0
- package/android/src/main/java/com/reactnativereadium/utils/MetadataNormalizer.kt +183 -0
- package/android/src/main/java/com/reactnativereadium/utils/NormalizedMetadata.kt +179 -0
- package/android/src/main/java/com/reactnativereadium/utils/extensions/InputStream.kt +0 -9
- package/ios/App/AppModule.swift +3 -9
- package/ios/Common/Toolkit/Extensions/Locator.swift +1 -1
- package/ios/Data/Bookmark.swift +1 -1
- package/ios/Reader/Common/ReaderViewController.swift +118 -21
- package/ios/Reader/EPUB/AssociatedColors.swift +1 -1
- package/ios/Reader/EPUB/EPUBHTTPServer.swift +13 -0
- package/ios/Reader/EPUB/EPUBModule.swift +1 -1
- package/ios/Reader/EPUB/EPUBViewController.swift +3 -4
- package/ios/Reader/ReaderFormatModule.swift +1 -1
- package/ios/Reader/ReaderModule.swift +1 -1
- package/ios/Reader/ReaderService.swift +70 -35
- package/ios/ReadiumView.swift +62 -25
- package/ios/ReadiumViewManager.m +1 -1
- package/lib/src/ReadiumViewNativeComponent.d.ts +19 -0
- package/lib/src/ReadiumViewNativeComponent.js +10 -0
- package/lib/src/components/BaseReadiumView.d.ts +1 -2
- package/lib/src/components/BaseReadiumView.js +3 -7
- package/lib/src/components/ReadiumView.js +15 -15
- package/lib/src/components/ReadiumView.web.js +100 -21
- package/lib/src/interfaces/BaseReadiumViewProps.d.ts +2 -1
- package/lib/src/interfaces/Preferences.d.ts +3 -2
- package/lib/src/interfaces/PublicationMetadata.d.ts +114 -0
- package/lib/src/interfaces/PublicationMetadata.js +1 -0
- package/lib/src/interfaces/PublicationReady.d.ts +15 -0
- package/lib/src/interfaces/PublicationReady.js +1 -0
- package/lib/src/interfaces/index.d.ts +2 -0
- package/lib/src/interfaces/index.js +2 -0
- package/lib/src/utils/index.d.ts +0 -1
- package/lib/src/utils/index.js +0 -1
- package/lib/web/hooks/index.d.ts +3 -2
- package/lib/web/hooks/index.js +3 -2
- package/lib/web/hooks/useLocationObserver.d.ts +2 -1
- package/lib/web/hooks/useLocationObserver.js +18 -11
- package/lib/web/hooks/useNavigator.d.ts +12 -0
- package/lib/web/hooks/useNavigator.js +87 -0
- package/lib/web/hooks/usePositionLabel.d.ts +9 -0
- package/lib/web/hooks/usePositionLabel.js +33 -0
- package/lib/web/hooks/usePreferencesObserver.d.ts +2 -0
- package/lib/web/hooks/usePreferencesObserver.js +54 -0
- package/lib/web/utils/manifestFetcher.d.ts +8 -0
- package/lib/web/utils/manifestFetcher.js +28 -0
- package/lib/web/utils/manifestNormalizer.d.ts +8 -0
- package/lib/web/utils/manifestNormalizer.js +70 -0
- package/lib/web/utils/metadataNormalizer.d.ts +53 -0
- package/lib/web/utils/metadataNormalizer.js +220 -0
- package/lib/web/utils/navigatorListeners.d.ts +6 -0
- package/lib/web/utils/navigatorListeners.js +50 -0
- package/lib/web/utils/publicationUtils.d.ts +15 -0
- package/lib/web/utils/publicationUtils.js +39 -0
- package/package.json +24 -14
- package/react-native-readium.podspec +7 -5
- package/src/ReadiumViewNativeComponent.ts +35 -0
- package/src/components/BaseReadiumView.tsx +3 -10
- package/src/components/ReadiumView.tsx +15 -15
- package/src/components/ReadiumView.web.tsx +120 -27
- package/src/interfaces/BaseReadiumViewProps.ts +2 -1
- package/src/interfaces/Preferences.ts +3 -2
- package/src/interfaces/PublicationMetadata.ts +141 -0
- package/src/interfaces/PublicationReady.ts +18 -0
- package/src/interfaces/index.ts +2 -0
- package/src/utils/index.ts +0 -1
- package/web/hooks/index.ts +3 -2
- package/web/hooks/useLocationObserver.ts +24 -11
- package/web/hooks/useNavigator.ts +146 -0
- package/web/hooks/usePositionLabel.ts +51 -0
- package/web/hooks/usePreferencesObserver.ts +69 -0
- package/web/utils/manifestFetcher.ts +38 -0
- package/web/utils/manifestNormalizer.ts +74 -0
- package/web/utils/metadataNormalizer.ts +238 -0
- package/web/utils/navigatorListeners.ts +60 -0
- package/web/utils/publicationUtils.ts +47 -0
- package/android/src/main/java/com/reactnativereadium/search/SearchFragment.kt +0 -100
- package/android/src/main/java/com/reactnativereadium/search/SearchPagingSource.kt +0 -44
- package/android/src/main/java/com/reactnativereadium/search/SearchResultAdapter.kt +0 -68
- package/android/src/main/java/com/reactnativereadium/utils/R2DispatcherActivity.kt +0 -45
- package/android/src/main/java/com/reactnativereadium/utils/SectionDecoration.kt +0 -98
- package/android/src/main/java/com/reactnativereadium/utils/SingleClickListener.kt +0 -32
- package/android/src/main/java/com/reactnativereadium/utils/extensions/Bitmap.kt +0 -23
- package/android/src/main/java/com/reactnativereadium/utils/extensions/Context.kt +0 -16
- package/android/src/main/java/com/reactnativereadium/utils/extensions/File.kt +0 -22
- package/android/src/main/java/com/reactnativereadium/utils/extensions/Link.kt +0 -6
- package/android/src/main/java/com/reactnativereadium/utils/extensions/Metadata.kt +0 -6
- package/android/src/main/java/com/reactnativereadium/utils/extensions/URL.kt +0 -29
- package/android/src/main/java/com/reactnativereadium/utils/extensions/Uri.kt +0 -17
- package/android/src/main/res/layout/fragment_search.xml +0 -39
- package/android/src/main/res/layout/item_recycle_search.xml +0 -14
- package/ios/Common/EPUBPreferences.swift +0 -8
- package/ios/Common/Paths.swift +0 -52
- package/ios/Common/Publication.swift +0 -15
- package/ios/Common/Toolkit/Extensions/HTTPClient.swift +0 -65
- package/ios/Common/Toolkit/Extensions/UIImage.swift +0 -12
- package/ios/Common/Toolkit/Extensions/UIViewController.swift +0 -19
- package/ios/Common/Toolkit/ScreenOrientation.swift +0 -13
- package/lib/src/utils/createFragment.d.ts +0 -1
- package/lib/src/utils/createFragment.js +0 -10
- package/lib/web/hooks/useReaderRef.d.ts +0 -3
- package/lib/web/hooks/useReaderRef.js +0 -85
- package/lib/web/hooks/useSettingsObserver.d.ts +0 -2
- package/lib/web/hooks/useSettingsObserver.js +0 -44
- package/src/utils/createFragment.ts +0 -15
- package/web/hooks/useReaderRef.ts +0 -109
- package/web/hooks/useSettingsObserver.ts +0 -61
|
@@ -13,14 +13,15 @@ import android.view.ViewGroup
|
|
|
13
13
|
import android.view.WindowInsets
|
|
14
14
|
import android.widget.FrameLayout
|
|
15
15
|
import androidx.fragment.app.Fragment
|
|
16
|
-
import
|
|
17
|
-
import org.readium.r2.navigator.ExperimentalDecorator
|
|
16
|
+
import androidx.lifecycle.lifecycleScope
|
|
18
17
|
import com.reactnativereadium.R
|
|
19
18
|
import com.reactnativereadium.databinding.FragmentReaderBinding
|
|
20
19
|
import com.reactnativereadium.utils.clearPadding
|
|
21
20
|
import com.reactnativereadium.utils.hideSystemUi
|
|
22
21
|
import com.reactnativereadium.utils.padSystemUi
|
|
23
22
|
import com.reactnativereadium.utils.showSystemUi
|
|
23
|
+
import kotlinx.coroutines.flow.launchIn
|
|
24
|
+
import kotlinx.coroutines.flow.onEach
|
|
24
25
|
|
|
25
26
|
/*
|
|
26
27
|
* Adds fullscreen support to the BaseReaderFragment
|
|
@@ -32,6 +33,8 @@ abstract class VisualReaderFragment : BaseReaderFragment() {
|
|
|
32
33
|
private var _binding: FragmentReaderBinding? = null
|
|
33
34
|
val binding get() = _binding!!
|
|
34
35
|
|
|
36
|
+
private var positionLabelManager: PositionLabelManager? = null
|
|
37
|
+
|
|
35
38
|
override fun onCreateView(
|
|
36
39
|
inflater: LayoutInflater,
|
|
37
40
|
container: ViewGroup?,
|
|
@@ -45,6 +48,23 @@ abstract class VisualReaderFragment : BaseReaderFragment() {
|
|
|
45
48
|
super.onViewCreated(view, savedInstanceState)
|
|
46
49
|
navigatorFragment = navigator as Fragment
|
|
47
50
|
|
|
51
|
+
// Initialize position label manager - simple overlay, matching iOS approach
|
|
52
|
+
positionLabelManager = PositionLabelManager(
|
|
53
|
+
containerView = binding.fragmentReaderContainer,
|
|
54
|
+
publication = model.publication,
|
|
55
|
+
lifecycleScope = viewLifecycleOwner.lifecycleScope
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
// Update position label when navigator location changes
|
|
59
|
+
navigator.currentLocator
|
|
60
|
+
.onEach { locator ->
|
|
61
|
+
positionLabelManager?.update(
|
|
62
|
+
position = locator.locations.position,
|
|
63
|
+
totalProgression = locator.locations.totalProgression
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
.launchIn(viewLifecycleOwner.lifecycleScope)
|
|
67
|
+
|
|
48
68
|
childFragmentManager.addOnBackStackChangedListener {
|
|
49
69
|
updateSystemUiVisibility()
|
|
50
70
|
}
|
|
@@ -55,10 +75,20 @@ abstract class VisualReaderFragment : BaseReaderFragment() {
|
|
|
55
75
|
}
|
|
56
76
|
|
|
57
77
|
override fun onDestroyView() {
|
|
78
|
+
positionLabelManager?.cleanup()
|
|
79
|
+
positionLabelManager = null
|
|
58
80
|
_binding = null
|
|
59
81
|
super.onDestroyView()
|
|
60
82
|
}
|
|
61
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Update the text color of the position label.
|
|
86
|
+
* @param color Android color integer
|
|
87
|
+
*/
|
|
88
|
+
fun setPositionLabelColor(color: Int) {
|
|
89
|
+
positionLabelManager?.setTextColor(color)
|
|
90
|
+
}
|
|
91
|
+
|
|
62
92
|
fun updateSystemUiVisibility() {
|
|
63
93
|
if (navigatorFragment.isHidden)
|
|
64
94
|
requireActivity().showSystemUi()
|
|
@@ -2,7 +2,6 @@ package com.reactnativereadium.utils
|
|
|
2
2
|
|
|
3
3
|
import androidx.fragment.app.Fragment
|
|
4
4
|
import androidx.fragment.app.FragmentFactory
|
|
5
|
-
import org.readium.r2.shared.extensions.tryOrNull
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Creates a [FragmentFactory] for a single type of [Fragment] using the result of the given
|
|
@@ -36,8 +35,8 @@ class CompositeFragmentFactory(private val factories: List<FragmentFactory>) : F
|
|
|
36
35
|
|
|
37
36
|
override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
|
|
38
37
|
for (factory in factories) {
|
|
39
|
-
|
|
40
|
-
?.let { return it }
|
|
38
|
+
runCatching { factory.instantiate(classLoader, className) }
|
|
39
|
+
.getOrNull()?.let { return it }
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
return super.instantiate(classLoader, className)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
package com.reactnativereadium.utils
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableArray
|
|
5
|
+
import com.facebook.react.bridge.WritableMap
|
|
6
|
+
import org.json.JSONArray
|
|
7
|
+
import org.json.JSONObject
|
|
8
|
+
import org.readium.r2.shared.publication.Link
|
|
9
|
+
import org.readium.r2.shared.publication.Locator
|
|
10
|
+
|
|
11
|
+
internal fun Locator.toWritableMap(): WritableMap =
|
|
12
|
+
toJSON().toWritableMap()
|
|
13
|
+
|
|
14
|
+
internal fun Link.toWritableMap(): WritableMap =
|
|
15
|
+
toJSON().toWritableMap()
|
|
16
|
+
|
|
17
|
+
internal fun List<Link>.toWritableArray(): WritableArray =
|
|
18
|
+
Arguments.createArray().apply {
|
|
19
|
+
forEach { pushMap(it.toWritableMap()) }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
internal fun JSONObject.toWritableMap(): WritableMap {
|
|
23
|
+
val map = Arguments.createMap()
|
|
24
|
+
val iterator = keys()
|
|
25
|
+
while (iterator.hasNext()) {
|
|
26
|
+
val key = iterator.next()
|
|
27
|
+
when (val value = opt(key)) {
|
|
28
|
+
JSONObject.NULL -> map.putNull(key)
|
|
29
|
+
is JSONObject -> map.putMap(key, value.toWritableMap())
|
|
30
|
+
is JSONArray -> map.putArray(key, value.toWritableArray())
|
|
31
|
+
is String -> map.putString(key, value)
|
|
32
|
+
is Boolean -> map.putBoolean(key, value)
|
|
33
|
+
is Int -> map.putInt(key, value)
|
|
34
|
+
is Long -> map.putDouble(key, value.toDouble())
|
|
35
|
+
is Double -> map.putDouble(key, value)
|
|
36
|
+
is Float -> map.putDouble(key, value.toDouble())
|
|
37
|
+
else -> map.putString(key, value?.toString() ?: "")
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return map
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
internal fun JSONArray.toWritableArray(): WritableArray {
|
|
44
|
+
val array = Arguments.createArray()
|
|
45
|
+
for (index in 0 until length()) {
|
|
46
|
+
when (val value = opt(index)) {
|
|
47
|
+
JSONObject.NULL -> array.pushNull()
|
|
48
|
+
is JSONObject -> array.pushMap(value.toWritableMap())
|
|
49
|
+
is JSONArray -> array.pushArray(value.toWritableArray())
|
|
50
|
+
is String -> array.pushString(value)
|
|
51
|
+
is Boolean -> array.pushBoolean(value)
|
|
52
|
+
is Int -> array.pushInt(value)
|
|
53
|
+
is Long -> array.pushDouble(value.toDouble())
|
|
54
|
+
is Double -> array.pushDouble(value)
|
|
55
|
+
is Float -> array.pushDouble(value.toDouble())
|
|
56
|
+
null -> array.pushNull()
|
|
57
|
+
else -> array.pushString(value.toString())
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return array
|
|
61
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
package com.reactnativereadium.utils
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.*
|
|
4
|
+
import org.readium.r2.shared.publication.Metadata
|
|
5
|
+
import org.readium.r2.shared.publication.LocalizedString
|
|
6
|
+
import org.readium.r2.shared.publication.Contributor
|
|
7
|
+
import org.readium.r2.shared.publication.Subject
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Normalizes metadata to ensure consistent structure per RWPM spec.
|
|
11
|
+
*
|
|
12
|
+
* This normalizer works directly with Readium's native Kotlin types (Metadata, LocalizedString,
|
|
13
|
+
* Contributor, Subject) and converts them to normalized, spec-compliant structures.
|
|
14
|
+
*
|
|
15
|
+
* This approach is:
|
|
16
|
+
* - More efficient (no intermediate WritableMap conversion)
|
|
17
|
+
* - More type-safe (working with actual Kotlin types)
|
|
18
|
+
* - Based on the stable RWPM specification
|
|
19
|
+
*
|
|
20
|
+
* https://readium.org/webpub-manifest/
|
|
21
|
+
*/
|
|
22
|
+
object MetadataNormalizer {
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Normalizes a Metadata object to a consistent React Native-compatible format.
|
|
26
|
+
* Works directly with Readium native types for efficiency and type safety.
|
|
27
|
+
*/
|
|
28
|
+
fun normalize(metadata: Metadata): WritableMap {
|
|
29
|
+
// Build typed NormalizedMetadata object from Readium's native types
|
|
30
|
+
val normalized = NormalizedMetadata(
|
|
31
|
+
// Required fields (provide default empty string if null)
|
|
32
|
+
title = normalizeLocalizedString(metadata.localizedTitle) ?: "",
|
|
33
|
+
|
|
34
|
+
// Optional localized string fields
|
|
35
|
+
sortAs = normalizeLocalizedString(metadata.localizedSortAs),
|
|
36
|
+
subtitle = normalizeLocalizedString(metadata.localizedSubtitle),
|
|
37
|
+
|
|
38
|
+
// Simple string fields
|
|
39
|
+
identifier = metadata.identifier,
|
|
40
|
+
description = metadata.description,
|
|
41
|
+
readingProgression = metadata.readingProgression?.name?.lowercase(),
|
|
42
|
+
|
|
43
|
+
// Date fields (convert Instant to ISO string)
|
|
44
|
+
modified = metadata.modified?.toString(),
|
|
45
|
+
published = metadata.published?.toString(),
|
|
46
|
+
|
|
47
|
+
// Layout is not directly on Metadata - would need Publication.metadata.presentation
|
|
48
|
+
// For now, pass null and extract from JSON if needed
|
|
49
|
+
layout = null,
|
|
50
|
+
|
|
51
|
+
// Array fields
|
|
52
|
+
language = if (metadata.languages.isNotEmpty()) metadata.languages else null,
|
|
53
|
+
conformsTo = metadata.conformsTo.map { it.uri }.takeIf { it.isNotEmpty() },
|
|
54
|
+
|
|
55
|
+
// Numeric fields
|
|
56
|
+
duration = metadata.duration,
|
|
57
|
+
numberOfPages = metadata.numberOfPages,
|
|
58
|
+
|
|
59
|
+
// Normalize contributors (all types)
|
|
60
|
+
author = normalizeContributors(metadata.authors),
|
|
61
|
+
translator = normalizeContributors(metadata.translators),
|
|
62
|
+
editor = normalizeContributors(metadata.editors),
|
|
63
|
+
artist = normalizeContributors(metadata.artists),
|
|
64
|
+
illustrator = normalizeContributors(metadata.illustrators),
|
|
65
|
+
letterer = normalizeContributors(metadata.letterers),
|
|
66
|
+
penciler = normalizeContributors(metadata.pencilers),
|
|
67
|
+
colorist = normalizeContributors(metadata.colorists),
|
|
68
|
+
inker = normalizeContributors(metadata.inkers),
|
|
69
|
+
narrator = normalizeContributors(metadata.narrators),
|
|
70
|
+
contributor = normalizeContributors(metadata.contributors),
|
|
71
|
+
publisher = normalizeContributors(metadata.publishers),
|
|
72
|
+
imprint = normalizeContributors(metadata.imprints),
|
|
73
|
+
|
|
74
|
+
// Normalize subjects
|
|
75
|
+
subject = normalizeSubjects(metadata.subjects),
|
|
76
|
+
|
|
77
|
+
// Complex objects - these need special handling
|
|
78
|
+
// For now, convert via JSON if they exist
|
|
79
|
+
accessibility = metadata.accessibility?.let { convertAccessibilityToWritableMap(it) },
|
|
80
|
+
belongsTo = if (metadata.belongsTo.isNotEmpty()) convertBelongsToToWritableMap(metadata.belongsTo) else null
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
// Convert to WritableMap for React Native
|
|
84
|
+
return normalized.toWritableMap()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Normalizes a LocalizedString per RWPM spec.
|
|
89
|
+
*
|
|
90
|
+
* Per RWPM spec, we extract a single string from the localized string using priority:
|
|
91
|
+
* 1. null key (undefined/undetermined language)
|
|
92
|
+
* 2. "en" (English fallback)
|
|
93
|
+
* 3. First available translation
|
|
94
|
+
*
|
|
95
|
+
* https://readium.org/webpub-manifest/contexts/default/
|
|
96
|
+
*/
|
|
97
|
+
private fun normalizeLocalizedString(value: LocalizedString?): String? {
|
|
98
|
+
if (value == null) return null
|
|
99
|
+
|
|
100
|
+
// Priority: null key (undefined) -> "en" -> first available
|
|
101
|
+
return value.translations[null]?.string
|
|
102
|
+
?: value.translations["en"]?.string
|
|
103
|
+
?: value.translations.values.firstOrNull()?.string
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Normalizes contributors per RWPM spec.
|
|
108
|
+
*
|
|
109
|
+
* Converts Readium's Contributor objects to our normalized format.
|
|
110
|
+
* https://readium.org/webpub-manifest/schema/contributor-object.schema.json
|
|
111
|
+
*/
|
|
112
|
+
private fun normalizeContributors(contributors: List<Contributor>): List<NormalizedContributor>? {
|
|
113
|
+
if (contributors.isEmpty()) return null
|
|
114
|
+
|
|
115
|
+
return contributors.mapNotNull { contributor ->
|
|
116
|
+
val name = normalizeLocalizedString(contributor.localizedName) ?: return@mapNotNull null
|
|
117
|
+
|
|
118
|
+
NormalizedContributor(
|
|
119
|
+
name = name,
|
|
120
|
+
sortAs = normalizeLocalizedString(contributor.localizedSortAs),
|
|
121
|
+
identifier = contributor.identifier,
|
|
122
|
+
role = contributor.roles.firstOrNull(),
|
|
123
|
+
position = contributor.position?.toInt()
|
|
124
|
+
)
|
|
125
|
+
}.takeIf { it.isNotEmpty() }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Normalizes subjects per RWPM spec.
|
|
130
|
+
*
|
|
131
|
+
* Converts Readium's Subject objects to our normalized format.
|
|
132
|
+
* https://readium.org/webpub-manifest/schema/subject.schema.json
|
|
133
|
+
*/
|
|
134
|
+
private fun normalizeSubjects(subjects: List<Subject>): List<NormalizedSubject>? {
|
|
135
|
+
if (subjects.isEmpty()) return null
|
|
136
|
+
|
|
137
|
+
return subjects.mapNotNull { subject ->
|
|
138
|
+
val name = normalizeLocalizedString(subject.localizedName) ?: return@mapNotNull null
|
|
139
|
+
|
|
140
|
+
NormalizedSubject(
|
|
141
|
+
name = name,
|
|
142
|
+
sortAs = normalizeLocalizedString(subject.localizedSortAs),
|
|
143
|
+
code = subject.code,
|
|
144
|
+
scheme = subject.scheme
|
|
145
|
+
)
|
|
146
|
+
}.takeIf { it.isNotEmpty() }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Converts Readium's Accessibility object to WritableMap.
|
|
151
|
+
* This is a complex nested structure that we pass through as-is.
|
|
152
|
+
*/
|
|
153
|
+
private fun convertAccessibilityToWritableMap(accessibility: org.readium.r2.shared.publication.Accessibility): WritableMap? {
|
|
154
|
+
return try {
|
|
155
|
+
// Use Readium's built-in JSON serialization
|
|
156
|
+
val json = accessibility.toJSON()
|
|
157
|
+
json.toWritableMap()
|
|
158
|
+
} catch (e: Exception) {
|
|
159
|
+
null
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Converts Readium's belongsTo map to WritableMap.
|
|
165
|
+
* This is a complex nested structure that we pass through as-is.
|
|
166
|
+
*/
|
|
167
|
+
private fun convertBelongsToToWritableMap(belongsTo: Map<String, List<org.readium.r2.shared.publication.Collection>>): WritableMap? {
|
|
168
|
+
return try {
|
|
169
|
+
Arguments.createMap().apply {
|
|
170
|
+
belongsTo.forEach { (key, collections) ->
|
|
171
|
+
val array = Arguments.createArray().apply {
|
|
172
|
+
collections.forEach { collection ->
|
|
173
|
+
collection.toJSON().toWritableMap()?.let { pushMap(it) }
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
putArray(key, array)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
} catch (e: Exception) {
|
|
180
|
+
null
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
package com.reactnativereadium.utils
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableArray
|
|
5
|
+
import com.facebook.react.bridge.WritableMap
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Normalized metadata data classes that mirror the TypeScript PublicationMetadata interface.
|
|
9
|
+
* These provide type safety and structure enforcement during metadata normalization.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Contributor information for publication metadata
|
|
14
|
+
*/
|
|
15
|
+
data class NormalizedContributor(
|
|
16
|
+
val name: String,
|
|
17
|
+
val sortAs: String? = null,
|
|
18
|
+
val identifier: String? = null,
|
|
19
|
+
val role: String? = null,
|
|
20
|
+
val position: Int? = null
|
|
21
|
+
) {
|
|
22
|
+
fun toWritableMap(): WritableMap {
|
|
23
|
+
return Arguments.createMap().apply {
|
|
24
|
+
putString("name", name)
|
|
25
|
+
sortAs?.let { putString("sortAs", it) }
|
|
26
|
+
identifier?.let { putString("identifier", it) }
|
|
27
|
+
role?.let { putString("role", it) }
|
|
28
|
+
position?.let { putInt("position", it) }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Subject/tag information
|
|
35
|
+
*/
|
|
36
|
+
data class NormalizedSubject(
|
|
37
|
+
val name: String,
|
|
38
|
+
val sortAs: String? = null,
|
|
39
|
+
val code: String? = null,
|
|
40
|
+
val scheme: String? = null
|
|
41
|
+
) {
|
|
42
|
+
fun toWritableMap(): WritableMap {
|
|
43
|
+
return Arguments.createMap().apply {
|
|
44
|
+
putString("name", name)
|
|
45
|
+
sortAs?.let { putString("sortAs", it) }
|
|
46
|
+
code?.let { putString("code", it) }
|
|
47
|
+
scheme?.let { putString("scheme", it) }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Normalized publication metadata following the RWPM spec
|
|
54
|
+
* @see https://readium.org/webpub-manifest/
|
|
55
|
+
*/
|
|
56
|
+
data class NormalizedMetadata(
|
|
57
|
+
// Required fields
|
|
58
|
+
val title: String,
|
|
59
|
+
|
|
60
|
+
// Optional localized string fields
|
|
61
|
+
val sortAs: String? = null,
|
|
62
|
+
val subtitle: String? = null,
|
|
63
|
+
|
|
64
|
+
// Simple fields
|
|
65
|
+
val identifier: String? = null,
|
|
66
|
+
val description: String? = null,
|
|
67
|
+
val readingProgression: String? = null,
|
|
68
|
+
val layout: String? = null,
|
|
69
|
+
val modified: String? = null,
|
|
70
|
+
val published: String? = null,
|
|
71
|
+
|
|
72
|
+
// Array fields
|
|
73
|
+
val language: List<String>? = null,
|
|
74
|
+
val conformsTo: List<String>? = null,
|
|
75
|
+
|
|
76
|
+
// Numeric fields
|
|
77
|
+
val duration: Double? = null,
|
|
78
|
+
val numberOfPages: Int? = null,
|
|
79
|
+
|
|
80
|
+
// Contributors (all types)
|
|
81
|
+
val author: List<NormalizedContributor>? = null,
|
|
82
|
+
val translator: List<NormalizedContributor>? = null,
|
|
83
|
+
val editor: List<NormalizedContributor>? = null,
|
|
84
|
+
val artist: List<NormalizedContributor>? = null,
|
|
85
|
+
val illustrator: List<NormalizedContributor>? = null,
|
|
86
|
+
val letterer: List<NormalizedContributor>? = null,
|
|
87
|
+
val penciler: List<NormalizedContributor>? = null,
|
|
88
|
+
val colorist: List<NormalizedContributor>? = null,
|
|
89
|
+
val inker: List<NormalizedContributor>? = null,
|
|
90
|
+
val narrator: List<NormalizedContributor>? = null,
|
|
91
|
+
val contributor: List<NormalizedContributor>? = null,
|
|
92
|
+
val publisher: List<NormalizedContributor>? = null,
|
|
93
|
+
val imprint: List<NormalizedContributor>? = null,
|
|
94
|
+
|
|
95
|
+
// Subjects
|
|
96
|
+
val subject: List<NormalizedSubject>? = null,
|
|
97
|
+
|
|
98
|
+
// Complex objects (preserved as-is from source)
|
|
99
|
+
val accessibility: WritableMap? = null,
|
|
100
|
+
val belongsTo: WritableMap? = null
|
|
101
|
+
) {
|
|
102
|
+
/**
|
|
103
|
+
* Converts this normalized metadata to a WritableMap for React Native
|
|
104
|
+
*/
|
|
105
|
+
fun toWritableMap(): WritableMap {
|
|
106
|
+
return Arguments.createMap().apply {
|
|
107
|
+
// Required fields
|
|
108
|
+
putString("title", title)
|
|
109
|
+
|
|
110
|
+
// Optional localized fields
|
|
111
|
+
sortAs?.let { putString("sortAs", it) }
|
|
112
|
+
subtitle?.let { putString("subtitle", it) }
|
|
113
|
+
|
|
114
|
+
// Simple fields
|
|
115
|
+
identifier?.let { putString("identifier", it) }
|
|
116
|
+
description?.let { putString("description", it) }
|
|
117
|
+
readingProgression?.let { putString("readingProgression", it) }
|
|
118
|
+
layout?.let { putString("layout", it) }
|
|
119
|
+
modified?.let { putString("modified", it) }
|
|
120
|
+
published?.let { putString("published", it) }
|
|
121
|
+
|
|
122
|
+
// Array fields
|
|
123
|
+
language?.let {
|
|
124
|
+
putArray("language", Arguments.createArray().apply {
|
|
125
|
+
it.forEach { lang -> pushString(lang) }
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
conformsTo?.let {
|
|
129
|
+
putArray("conformsTo", Arguments.createArray().apply {
|
|
130
|
+
it.forEach { item -> pushString(item) }
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Numeric fields
|
|
135
|
+
duration?.let { putDouble("duration", it) }
|
|
136
|
+
numberOfPages?.let { putInt("numberOfPages", it) }
|
|
137
|
+
|
|
138
|
+
// Contributors
|
|
139
|
+
author?.let { putArray("author", it.toContributorWritableArray()) }
|
|
140
|
+
translator?.let { putArray("translator", it.toContributorWritableArray()) }
|
|
141
|
+
editor?.let { putArray("editor", it.toContributorWritableArray()) }
|
|
142
|
+
artist?.let { putArray("artist", it.toContributorWritableArray()) }
|
|
143
|
+
illustrator?.let { putArray("illustrator", it.toContributorWritableArray()) }
|
|
144
|
+
letterer?.let { putArray("letterer", it.toContributorWritableArray()) }
|
|
145
|
+
penciler?.let { putArray("penciler", it.toContributorWritableArray()) }
|
|
146
|
+
colorist?.let { putArray("colorist", it.toContributorWritableArray()) }
|
|
147
|
+
inker?.let { putArray("inker", it.toContributorWritableArray()) }
|
|
148
|
+
narrator?.let { putArray("narrator", it.toContributorWritableArray()) }
|
|
149
|
+
contributor?.let { putArray("contributor", it.toContributorWritableArray()) }
|
|
150
|
+
publisher?.let { putArray("publisher", it.toContributorWritableArray()) }
|
|
151
|
+
imprint?.let { putArray("imprint", it.toContributorWritableArray()) }
|
|
152
|
+
|
|
153
|
+
// Subjects
|
|
154
|
+
subject?.let { putArray("subject", it.toSubjectWritableArray()) }
|
|
155
|
+
|
|
156
|
+
// Complex objects
|
|
157
|
+
accessibility?.let { putMap("accessibility", it) }
|
|
158
|
+
belongsTo?.let { putMap("belongsTo", it) }
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Extension function to convert a list of contributors to WritableArray
|
|
165
|
+
*/
|
|
166
|
+
private fun List<NormalizedContributor>.toContributorWritableArray(): WritableArray {
|
|
167
|
+
return Arguments.createArray().apply {
|
|
168
|
+
this@toContributorWritableArray.forEach { pushMap(it.toWritableMap()) }
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Extension function to convert a list of subjects to WritableArray
|
|
174
|
+
*/
|
|
175
|
+
private fun List<NormalizedSubject>.toSubjectWritableArray(): WritableArray {
|
|
176
|
+
return Arguments.createArray().apply {
|
|
177
|
+
this@toSubjectWritableArray.forEach { pushMap(it.toWritableMap()) }
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -2,11 +2,8 @@ package com.reactnativereadium.utils.extensions
|
|
|
2
2
|
|
|
3
3
|
import kotlinx.coroutines.Dispatchers
|
|
4
4
|
import kotlinx.coroutines.withContext
|
|
5
|
-
import org.readium.r2.shared.extensions.tryOrNull
|
|
6
5
|
import java.io.File
|
|
7
6
|
import java.io.InputStream
|
|
8
|
-
import java.util.*
|
|
9
|
-
|
|
10
7
|
|
|
11
8
|
suspend fun InputStream.toFile(path: String) {
|
|
12
9
|
withContext(Dispatchers.IO) {
|
|
@@ -15,9 +12,3 @@ suspend fun InputStream.toFile(path: String) {
|
|
|
15
12
|
}
|
|
16
13
|
}
|
|
17
14
|
}
|
|
18
|
-
|
|
19
|
-
suspend fun InputStream.copyToTempFile(dir: String): File? = tryOrNull {
|
|
20
|
-
val filename = UUID.randomUUID().toString()
|
|
21
|
-
File(dir + filename)
|
|
22
|
-
.also { toFile(it.path) }
|
|
23
|
-
}
|
package/ios/App/AppModule.swift
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import Combine
|
|
2
2
|
import Foundation
|
|
3
3
|
import UIKit
|
|
4
|
-
import
|
|
5
|
-
import R2Streamer
|
|
4
|
+
import ReadiumShared
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
/// Base module delegate, that sub-modules' delegate can extend.
|
|
@@ -22,15 +21,10 @@ final class AppModule {
|
|
|
22
21
|
var reader: ReaderModuleAPI! = nil
|
|
23
22
|
|
|
24
23
|
init() throws {
|
|
25
|
-
guard let server = PublicationServer() else {
|
|
26
|
-
/// FIXME: we should recover properly if the publication server can't start, maybe this should only forbid opening a publication?
|
|
27
|
-
fatalError("Can't start publication server")
|
|
28
|
-
}
|
|
29
|
-
|
|
30
24
|
reader = ReaderModule(delegate: self)
|
|
31
25
|
|
|
32
|
-
// Set Readium
|
|
33
|
-
|
|
26
|
+
// Set Readium logging minimum level for debugging
|
|
27
|
+
ReadiumEnableLog(withMinimumSeverityLevel: .debug)
|
|
34
28
|
}
|
|
35
29
|
}
|
|
36
30
|
|